- Replace all occurrences of 'ss-tools' with 'superset-tools' in 104 files - Rename git bundle file ss-tools.bundle → superset-tools.bundle - Update .gitignore pattern accordingly - Preserve variable names (hasSsTools etc.) and code identifiers
29 KiB
Tasks: Maintenance Banner for Dashboards
Input: Design documents from specs/031-maintenance-banner/
Prerequisites: plan.md ✅, spec.md ✅, research.md ✅, data-model.md ✅, contracts/modules.md ✅
Tests: Included for all C3+ contracts, new API endpoints, database models, and ADR-rejected regression paths.
Organization: Tasks grouped by user story for independent implementation and verification.
Format: - [ ] T### [P?] [USx] Description with exact file path
- [P]: Can run in parallel (different files, no dependencies)
- [USx]: Which user story (US1-US5);
--for setup/foundational/polish phases - Paths relative to repository root
Phase 1: Setup (Shared Infrastructure)
Purpose: Project scaffolding — directories, package inits, Alembic migration skeleton.
- T001 Create
backend/src/api/routes/maintenance/package directory structure (__init__.py) - T002 [P] Create
backend/tests/test file stubs for maintenance feature:test_maintenance_service.py,test_sql_table_extractor.py,test_maintenance_api.py - T003 [P] Create frontend directory structure:
frontend/src/routes/maintenance/,frontend/src/lib/stores/maintenance.svelte.js,frontend/src/lib/api/maintenance.js(empty placeholder files). Note: UI components reuse existing primitives — no new component scaffolds needed. - T004 Generate Alembic migration for new tables (
maintenance_events,maintenance_dashboard_states,maintenance_settings) viaalembic revision --autogenerate -m "add maintenance banner tables"after models are defined in Phase 2
Phase 2: Foundational (Blocking Prerequisites)
Purpose: Core infrastructure that MUST be complete before ANY user story can be implemented. All models, schemas, SupersetClient extension, SQL extractor, router scaffold.
⚠️ CRITICAL: No user story work can begin until this phase is complete.
Database Models
- T005 [P] [--] Implement
MaintenanceEventSQLAlchemy model inbackend/src/models/maintenance.pywith columns (id, task_id — loose reference, environment_id — snapshot of target env, tables JSON, start_time, end_time nullable, message, status enum: pending/applying/active/partial/ending/completed/failed, timestamps) and relationship to MaintenanceDashboardState. Contract:[DEF:MaintenanceEvent:Model]C1. - T005a [P] [--] Implement
MaintenanceDashboardBannerSQLAlchemy model inbackend/src/models/maintenance.py— canonical "one chart per dashboard" entity with columns (id, environment_id, dashboard_id, chart_id, banner_text, status enum: active/removed, timestamps) and unique partial index on (environment_id, dashboard_id) WHERE status='active'. Contract:[DEF:MaintenanceDashboardBanner:Model]C1. - T006 [P] [--] Implement
MaintenanceDashboardStateSQLAlchemy model inbackend/src/models/maintenance.pywith columns (id, event_id FK, dashboard_id, banner_id FK→maintenance_dashboard_banners, status enum: pending_apply/active/removing/removed/apply_failed/removal_failed, timestamps) and composite index on (dashboard_id, status). Contract:[DEF:MaintenanceDashboardState:Model]C1. - T007 [P] [--] Implement
MaintenanceSettingsSQLAlchemy model inbackend/src/models/maintenance.py— single-row table (id="default") with CheckConstraint(id='default'), columns (target_environment_id, display_timezone default "UTC", banner_template text, dashboard_scope enum, excluded_dashboard_ids JSON, forced_dashboard_ids JSON, updated_at). Contract:[DEF:MaintenanceSettings:Model]C1.
Pydantic Schemas
- T008 [--] Implement all Pydantic request/response schemas in
backend/src/api/routes/maintenance/_schemas.py: MaintenanceStartRequest, MaintenanceSettingsUpdate, MaintenanceStartResponse, MaintenanceTaskResult, MaintenanceDashboardResult, MaintenanceFailedDashboard, MaintenanceEndResponse, MaintenanceEndTaskResult, MaintenanceEndAllTaskResult, MaintenanceSettingsResponse, MaintenanceEventItem, MaintenanceEventListResponse, MaintenanceDashboardBannerState. Contract:[DEF:MaintenanceSchemas:Module]C1.
SupersetClient Extension (C4 — REQUIRES belief runtime)
- T009 [--] Implement
SupersetDashboardsWriteMixininbackend/src/core/superset_client/_dashboards_write.pywith methods:create_markdown_chart(dashboard_id, markdown_text) → chart_id(POST /api/v1/chart/ with viz_type="markdown"),update_markdown_chart(chart_id, markdown_text)(PUT /api/v1/chart/{id} to update markdown params),update_dashboard_layout(dashboard_id, chart_id)(insert at position (0,0), full width 12 cols, shift existing components down),remove_chart_from_layout(dashboard_id, chart_id)(remove from position_json),delete_chart(chart_id)(DELETE /api/v1/chart/{id}),get_dashboard_layout(dashboard_id)(GET /api/v1/dashboard/{id} for position_json). Contract:[DEF:SupersetDashboardsWriteMixin:Class]C4. (RATIONALE: chart_id tracked in MaintenanceDashboardBanner DB table — no need to scan position_json) - T010 [--] Instrument
SupersetDashboardsWriteMixinwith belief runtime markers:belief_scopecontext manager wrapping each Superset API call,log(REASON)before POST/PUT/DELETE,log(REFLECT)on success,log(EXPLORE)on HTTP errors.
SQL Table Extractor (C2)
- T011 [--] Implement
SqlTableExtractormodule inbackend/src/services/sql_table_extractor.pywith two-phase extraction: (1) determine Jinja block spans vs SQL spans, (2) in Jinja spans — extract"schema.table"from string values, (3) in SQL spans — regex[a-zA-Z][\w]*\.[a-zA-Z][\w]*+ sqlparse filtering to reject SQL string literal/comment positions. Orchestrator:extract_tables_from_sql(raw_sql) → set[str]. Contract:[DEF:SqlTableExtractor:Module]C2. (RATIONALE: Jinja string values in {% set %} blocks must NOT be filtered as SQL strings; REJECTED: sqlparse-only approach loses Jinja-embedded tables)
Router & App Registration
- T012 [--] Implement
MaintenanceRouterinbackend/src/api/routes/maintenance/_router.py— createsAPIRouter(prefix="/api/maintenance", tags=["maintenance"]). Contract:[DEF:MaintenanceRouter:Module]C2. - T013 [--] Implement route handler stubs in
backend/src/api/routes/maintenance/_routes.py— all 7 endpoint signatures with placeholder returns and proper RBACDepends(require_role(...))guards. Contract:[DEF:MaintenanceRoutes:Module]C3 (stub phase). - T014 [--] Register
maintenance_routerin main FastAPI application (backend/src/app.py) and registerSupersetDashboardsWriteMixinin SupersetClient assembly.
Seed Data
- T015 [--] Create Alembic data migration (or startup check) to insert default
MaintenanceSettingsrow (id="default", target_environment_id=, dashboard_scope="published_only", excluded=[], forced=[]) if not exists.
Checkpoint: Foundation ready — models, schemas, SupersetClient extension, SQL extractor, router, and seed data in place. All user stories can now begin.
Phase 3: User Story 1 — Banner Placement (Priority: P1) 🎯 MVP
Goal: External tool calls POST /api/maintenance/start → superset-tools finds affected dashboards → places markdown banners.
Independent Test: Call API with a known table name, verify banner appears on affected dashboards in Superset.
Tests for US1 (Write FIRST, ensure they FAIL)
- T016 [P] [US1] Write contract test for
POST /api/maintenance/startinbackend/tests/test_maintenance_api.py— test valid request returns202, test missingstart_timereturns 422, testend_time < start_timereturns 422, test paststart_time>1h returns 422, testtables>100 entries returns 422, test optionalend_time(omitted → task result shows «уточняется»), test duplicate request returns200 {status:"already_active"}, test no-match in task result, test RBAC returns 403, test HTML injection in message returns 400. - T017 [P] [US1] Write unit test for
SqlTableExtractor.extract_tables_from_sql()inbackend/tests/test_sql_table_extractor.py— test table-based matching, virtual-SQL extraction with Jinja{% set %}, string literal false-positive rejection, case-insensitive matching. - T018 [P] [US1] Write integration test for
MaintenanceService.start_maintenance()inbackend/tests/test_maintenance_service.py— test dashboard discovery, markdown chart creation, MaintenanceEvent persistence, MaintenanceDashboardState creation, idempotency check.
Implementation for US1
- T019 [US1] Implement
find_affected_dashboards()method inbackend/src/services/maintenance_service.py— fetches all datasets from Superset, matches table-based datasets via (schema, table_name), matches virtual datasets via SqlTableExtractor, applies scope/excluded/forced filtering from MaintenanceSettings, returns deduplicated dashboard list. Contract C3 within[DEF:MaintenanceService:Class]. - T019a [US1] Implement
ensure_banner_chart()method inbackend/src/services/maintenance_service.py— idempotently gets or creates the sharedMaintenanceDashboardBannerfor a dashboard. If banner exists (ACTIVE in DB) → return it. Otherwise →create_markdown_chart()+update_dashboard_layout(), store chart_id in banner row. Contract C3. (RATIONALE: DB is source of truth for chart existence — no position_json scanning needed; REJECTED: per-event chart creation) - T020 [US1] Implement
build_banner_text()method inbackend/src/services/maintenance_service.py— readsbanner_templateanddisplay_timezonefrom MaintenanceSettings, substitutes{start_time},{end_time},{message}variables. Missing variables in template are simply omitted. Single-event: direct substitution. Multi-event: aggregates per-event start/end/message, then substitutes into template. Handles absentend_time→ «Окончание: уточняется». Contract C2. - T021 [US1] Implement
start_maintenance()method inbackend/src/services/maintenance_service.py— orchestrates full flow: (1) idempotency check by(tables, start_time, end_time)key using row-level lock (SELECT ... FOR UPDATEon maintenance_events to prevent race condition from concurrent identical /start calls), (2) create MaintenanceEvent (status=PENDING→APPLYING), (3)find_affected_dashboards(), (4) for each dashboard:ensure_banner_chart()→ create MaintenanceDashboardState (PENDING_APPLY→ACTIVE or APPLY_FAILED), rebuild aggregated banner text viabuild_banner_text()+update_markdown_chart(), (5) transition event to ACTIVE (all success) or PARTIAL (some failed). Contract C4. - T022 [US1] Instrument
start_maintenance()with belief runtime:belief_scope("start_maintenance"),log(REASON)at entry/checkpoint,log(REFLECT)on completion,log(EXPLORE)on Superset errors. - T023 [US1] Implement
POST /api/maintenance/startroute handler inbackend/src/api/routes/maintenance/_routes.py— validates MaintenanceStartRequest:start_timerequired,end_timeoptional (absent → banner text shows «Окончание: уточняется»), message field sanitized (reject raw HTML, escape special chars). Idempotency check by(tables, start_time, end_time)key. Otherwise: create MaintenanceEvent, dispatch task via TaskManager, return202 {task_id, maintenance_id, status:"pending"}. RBAC:Depends(require_role("operator")). - T024 [US1] Run backend verification:
cd backend && source .venv/bin/activate && python -m pytest backend/tests/test_maintenance_api.py backend/tests/test_maintenance_service.py backend/tests/test_sql_table_extractor.py -v
Checkpoint: POST /api/maintenance/start fully functional. Banners placed on affected dashboards. Idempotency working.
Phase 4: User Story 2 — Banner Removal (Priority: P1)
Goal: External tool calls POST /api/maintenance/{id}/end or /end-all → banners removed from dashboards.
Independent Test: After US1, call /end and verify banners disappear; call again and verify idempotent.
Tests for US2 (Write FIRST, ensure they FAIL)
- T025 [P] [US2] Write contract test for
POST /api/maintenance/{id}/endinbackend/tests/test_maintenance_api.py— test successful removal, test already-completed idempotent response, test multi-event scenario (banner stays when other active events exist, text rebuilt). - T026 [P] [US2] Write integration test for
MaintenanceService.end_maintenance()inbackend/tests/test_maintenance_service.py— test chart deletion via Superset API, test MaintenanceDashboardState transition to REMOVED, test MaintenanceEvent transition to COMPLETED, test banner rebuild when other active events remain. - T026a [P] [US2] Write integration test for
MaintenanceService.end_all_maintenance()inbackend/tests/test_maintenance_service.py— test bulk closure of all active events, verify each transitions to COMPLETED, test mixed state (some dashboards have other active events → banner stays, others → banner removed).
Implementation for US2
- T027 [US2] Implement
rebuild_banner()method inbackend/src/services/maintenance_service.py— for a givenMaintenanceDashboardBanner, queries all activeMaintenanceDashboardStaterecords linked to it, callsbuild_banner_text()for aggregated text, updates the shared markdown chart in Superset viaSupersetDashboardsWriteMixin.update_markdown_chart(). Contract C3 within[DEF:MaintenanceService:Class]. - T028 [US2] Implement
end_maintenance()method inbackend/src/services/maintenance_service.py— set event status to ENDING, then for each activeMaintenanceDashboardStatein the event: (1) check if OTHER active event-states still reference the same banner; (2) if yes → callrebuild_banner()(remove this event's info from aggregated text), transition state to REMOVED; (3) if no → transition banner to REMOVED, callremove_chart_from_layout()thendelete_chart(), transition state to REMOVING→REMOVED (or REMOVAL_FAILED on error); (4) transition event to COMPLETED. Contract C4. (RATIONALE: conditional removal prevents banner flicker; REJECTED: unconditional removal) - T029 [US2] Instrument
end_maintenance()with belief runtime markers. - T030 [US2] Implement
end_all_maintenance()method inbackend/src/services/maintenance_service.py— iterates all active events, callsend_maintenance()for each. Contract C4. - T031 [US2] Implement
POST /api/maintenance/{maintenance_id}/endandPOST /api/maintenance/end-allroute handlers inbackend/src/api/routes/maintenance/_routes.py— dispatch tasks via TaskManager, return task_id immediately. RBAC:Depends(require_role("operator")). - T032 [US2] Run backend verification:
cd backend && source .venv/bin/activate && python -m pytest backend/tests/test_maintenance_api.py backend/tests/test_maintenance_service.py -v -k "end"
Checkpoint: Banner removal fully functional. Multi-event conflict handling verified. Idempotent /end confirmed.
Phase 5: User Story 4 — Settings Configuration (Priority: P3)
Goal: Administrator can configure maintenance scope (published/draft/all, excluded dashboards, forced dashboards) via API and UI.
Note: Settings model is already in Phase 2 (T007). This phase adds API endpoints and UI panel.
Tests for US4
- T033 [P] [US4] Write API test for
GET /api/maintenance/settingsandPUT /api/maintenance/settingsinbackend/tests/test_maintenance_api.py— test read returns defaults, test update with valid payload, test RBAC (admin only for PUT), test invalid scope rejection.
Implementation for US4
- T034 [US4] Implement
GET /api/maintenance/settingsandPUT /api/maintenance/settingsroute handlers inbackend/src/api/routes/maintenance/_routes.py. GET: read MaintenanceSettings row. PUT: validate and update fields (target_environment_id, display_timezone, banner_template, dashboard_scope, excluded, forced). Return updated settings. RBAC: GET =operator+, PUT =admin. - T035 [P] [US4] Implement
MaintenanceApiClientmodule infrontend/src/lib/api/maintenance.js— typed fetch wrappers:getSettings(),updateSettings(payload),getEvents(),startMaintenance(payload),endMaintenance(id),endAllMaintenance(),getDashboardBanners(). UsesrequestApifrom existing API layer. Contract:[DEF:MaintenanceApiClient:Module]C2. - T036 [US4] Implement
MaintenanceSettingsPanelcomponent infrontend/src/lib/components/MaintenanceSettingsPanel.svelte— collapsible via<details><summary>. Form uses:<Select>from$lib/ui/Select.sveltefor target environment,<Input>from$lib/ui/Input.sveltefor timezone, native<input type="radio" bind:group>for scope (matches ScheduleConfig pattern),<SearchableMultiSelect>from$lib/components/ui/SearchableMultiSelect.sveltefor excluded/forced dashboards (options:{id, name}from dashboard list),<textarea>for banner_template with live preview,<Button isLoading={isSaving}>from$lib/ui/Button.sveltefor save. Toast viaaddToast()from$lib/toasts.js. UX states: idle/saving/saved/error. Contract C3. - T037 [US4] Run verification:
cd backend && source .venv/bin/activate && python -m pytest backend/tests/test_maintenance_api.py -v -k "settings"+cd frontend && npx vitest run tests/maintenance.test.ts -v -k "settings"
Checkpoint: Settings API operational. Settings panel renders in UI.
Phase 6: User Story 3 — Admin UI Management Page (Priority: P2)
Goal: Administrator sees active/completed events table with selective and bulk removal actions.
Independent Test: Navigate to /maintenance, view active events, click "Remove All" → banners disappear.
Tests for US3
- T038 [P] [US3] Write component test for
MaintenanceEventsTableinfrontend/tests/maintenance.test.ts— test renders active and completed events, test "Remove Banners" per event, test "Remove All" with confirmation dialog, test error state with retry. - T039 [P] [US3] Write store test for
MaintenanceStoreinfrontend/tests/maintenance-store.test.ts— test events fetching, test polling, test error handling.
Implementation for US3
- T040 [US3] Implement
GET /api/maintenance/eventsroute handler inbackend/src/api/routes/maintenance/_routes.py— returns MaintenanceEventListResponse with active and completed events, each including affected dashboard count. RBAC: authenticated. - T041 [US3] Implement
MaintenanceStoreinfrontend/src/lib/stores/maintenance.svelte.js— Svelte 5 rune store with$statefor events (active + completed), settings, dashboardBanners, isLoading, error. Uses$effect(() => subscribe(...))pattern per ADR-0007 (REJECTED: fromStore + $derived). Auto-pollsgetEvents()every 30s via$effect(WebSocket covers task progress; polling ensures event-list consistency after task completion). Contract:[DEF:MaintenanceStore:Module]C3. - T042 [US3] Implement
MaintenanceEventsTablecomponent infrontend/src/lib/components/MaintenanceEventsTable.svelte— inline<table class="min-w-full divide-y divide-gray-200">(matches health page pattern). Columns: Event ID, Tables (truncated with nativetitletooltip), Affected Dashboards, Start/End Times, Status (inline badge:rounded-full px-2.5 py-0.5 text-xs font-medium), Actions (<Button>from$lib/ui/Button.svelte). Per-event "Remove" button withisLoading. "Remove All" withconfirm()(matches health page). Skeleton:animate-pulse bg-gray-200 h-4rows. Empty:border-dashed border-gray-300 bg-gray-50 p-8 text-center. Completed events collapsible via<details><summary>. Toast viaaddToast(). Contract C3. - T043 [US3] Implement
MaintenanceBannerPageinfrontend/src/routes/maintenance/+page.svelte— composesMaintenanceSettingsPanel+MaintenanceEventsTable, passes store data. UsesaddToast()from$lib/toasts.jsfor feedback (Toast.svelte already mounted in root layout). UX states: idle/loading/action_in_progress/error/empty. Contract C3. - T044 [US3] Add navigation entry for
/maintenancein sidebar — add category to "Operations" section infrontend/src/lib/components/layout/sidebarNavigation.jswith id="maintenance", path="/maintenance", requiredPermission="maintenance:write", requiredAction="READ" (operator+ see page, admin has full access per RBAC matrix FR-015). Add translation keys tofrontend/src/lib/i18n/locales/en/nav.jsonandfrontend/src/lib/i18n/locales/ru/nav.json. - T045 [US3] Run full frontend verification:
cd frontend && npx vitest run tests/maintenance.test.ts tests/maintenance-store.test.ts -v+cd frontend && npm run build
Checkpoint: Admin management page fully functional. Settings + events tables operational.
Phase 7: User Story 5 — Dashboard Hub Badge (Priority: P3)
Goal: Dashboard Hub table shows orange "⚠️ Maintenance" badge when dashboard has active banner.
Independent Test: After US1, open Dashboard Hub → affected dashboard row shows orange badge with tooltip.
Tests for US5
- T046 [P] [US5] Write component test for
DashboardMaintenanceBadgeinfrontend/tests/maintenance.test.ts— test renders badge when maintenance active, test no badge when inactive, test tooltip content, test loading skeleton state.
Implementation for US5
- T047 [US5] Implement
GET /api/maintenance/dashboard-bannersroute handler inbackend/src/api/routes/maintenance/_routes.py— returns list of MaintenanceDashboardBannerState: {dashboard_id, active: bool, events: [...], start_time, end_time}. Queries MaintenanceDashboardState where status=ACTIVE. RBAC: authenticated. - T048 [US5] Add
getDashboardBanners()to MaintenanceStore — fetches banner states and populatesdashboardBanners$state map. - T049 [US5] Implement
DashboardMaintenanceBadgecomponent infrontend/src/lib/components/DashboardMaintenanceBadge.svelte— orange badgerounded-full px-2.5 py-0.5 text-xs font-medium bg-orange-100 text-orange-700 border border-orange-200(matches existing DashboardHub badge convention). Nativetitleattribute for tooltip (codebase-wide pattern). Renders nothing when no active banner. Loading:animate-pulse bg-gray-200 h-5 w-24 rounded-full. Contract C2. - T050 [US5] Modify
frontend/src/routes/dashboards/+page.svelte— addDashboardMaintenanceBadgeimport, insert badge column/cell in dashboard table rows, wire$derivedfrom MaintenanceStore.dashboardBanners. Ensure compliance with ADR-0007 (no fromStore + $derived pattern). Export visual verification: open Dashboard Hub page after US1 and confirm badge appears viachrome-devtoolssnapshot. - T051 [US5] Run verification:
cd frontend && npx vitest run tests/maintenance.test.ts -v -k "badge"+cd frontend && npm run build
Checkpoint: Dashboard Hub badge functional. Operators can identify maintenance-affected dashboards at a glance.
Phase 8: Polish & Cross-Cutting Verification
Purpose: Full integration verification, linting, semantic audit, ADR compliance check.
- T052 [P] Run full backend test suite:
cd backend && source .venv/bin/activate && python -m pytest backend/tests/test_maintenance_*.py backend/tests/test_sql_table_extractor.py -v --cov=src/services/maintenance_service.py --cov=src/services/sql_table_extractor.py --cov=src/api/routes/maintenance/ --cov-report=term-missing - T053 [P] Run backend lint:
cd backend && python -m ruff check src/api/routes/maintenance/ src/services/maintenance_service.py src/services/sql_table_extractor.py src/models/maintenance.py src/core/superset_client/_dashboards_write.py - T054 [P] Run frontend lint:
cd frontend && npm run lint - T055 [P] Run frontend full test suite:
cd frontend && npm run test - T056 [P] Run frontend build check:
cd frontend && npm run build - T057 Run semantic contract audit:
axiom_semantic_validation audit_contractsonbackend/src/api/routes/maintenance/,backend/src/services/maintenance_service.py,backend/src/services/sql_table_extractor.py,backend/src/core/superset_client/_dashboards_write.py,backend/src/models/maintenance.py— verify no missing C4 tags, no orphan relations, all@RATIONALE/@REJECTEDpresent. - T058 Run ADR rejected-path regression check: verify
fromStore + $derivedNOT used (ADR-0007), Svelte 5 runes syntax only (ADR-0006), no export-import cycle for chart placement (research.md R1 rejected), no per-event chart creation (one-chart-per-dashboard invariant via MaintenanceDashboardBanner unique index), noplugin:migrationpermission in maintenance code (FR-015 RBAC matrix). - T059 Run belief runtime audit:
axiom_semantic_validation audit_belief_runtimeon C4 contracts — verifybelief_scope,log(REASON),log(REFLECT),log(EXPLORE)instrumentation present instart_maintenance(),end_maintenance(), andSupersetDashboardsWriteMixin. - T060 Run quickstart.md validation: follow all steps in
specs/031-maintenance-banner/quickstart.md(backend/frontend/Docker) and confirm no failures. - T061 [P] Update
frontend/src/lib/i18n/with new translation keys for maintenance banner UI strings (English + Russian placeholders):maintenance.title,maintenance.active,maintenance.completed,maintenance.removeBanners,maintenance.removeAll,maintenance.settings,maintenance.scope,maintenance.excluded,maintenance.forced,maintenance.save,maintenance.noActiveEvents,maintenance.tooltip.
Dependencies & Execution Order
Phase Dependencies
Phase 1: Setup ──────────────────────────────────────────────┐
Phase 2: Foundational (BLOCKS ALL STORIES) ──────────────────┤
├── Phase 3: US1 (P1) Banner Placement ──────────────────┤
│ └── Phase 4: US2 (P1) Banner Removal ────────────────┤
├── Phase 5: US4 (P3) Settings API + Panel ──────────────┤
│ └── Phase 6: US3 (P2) Admin UI ──────────────────────┤
└── Phase 7: US5 (P3) Dashboard Badge ───────────────────┤
Phase 8: Polish & Cross-Cutting ─────────────────────────────┘
Within Each Phase
- Tests MUST be written and FAIL before implementation (T016-T018 before T019+, T025-T026 before T027+, etc.)
- Models (T005-T007) → Schemas (T008) → SupersetClient (T009-T010) → SQL Extractor (T011) → Router (T012-T014)
- In US1:
find_affected_dashboards()→build_banner_text()→start_maintenance()→ route handler - In US2:
rebuild_banner()→end_maintenance()→end_all_maintenance()→ route handlers - In US4: Settings API backend → API client → Settings panel
- In US3: Events API backend → Store → EventsTable → Page
Parallel Opportunities
- Phase 1: T002 and T003 can run in parallel
- Phase 2: T005, T006, T007 (models) can run in parallel; T016, T017, T018 (US1 tests) can run in parallel
- Phase 3: T016, T017, T018 (tests) can run in parallel
- Phase 4: T025, T026, T026a (tests) can run in parallel
- Phase 6: T038, T039 (tests) can run in parallel
- Phase 8: T052, T053, T054, T055, T056, T061 can run in parallel
User Story Independence
- US1 can be verified independently after Phase 2+3: call
/start, check task status, verify banner in Superset - US2 can be verified independently after Phase 2+4: call
/end, verify banner removal - US4 can be verified independently after Phase 2+5: GET/PUT /settings
- US3 can be verified independently after Phase 2+5+6: view management page, click "Remove All"
- US5 can be verified independently after Phase 2+7: open Dashboard Hub, see badge
Each story is an independently shippable increment. P1 stories (US1+US2) form the MVP.
ADR & Rejected-Path Guardrails
| ADR / Decision | Guardrail Task | Rationale |
|---|---|---|
| ADR-0001 (Module Layout) | T001, T005-T007, T019-T023 | All files in canonical paths |
| ADR-0003 (Orchestrator) | T009 | Superset REST API only, no DB manipulation |
| ADR-0005 (RBAC) | T013, T023, T031, T034, T044, FR-015 | RBAC matrix enforced; dedicated maintenance:write permission, not plugin:migration |
| ADR-0006 (Svelte 5 Runes) | T041, T049 | $state, $derived, $effect only; no legacy $: or writable |
| ADR-0007 (fromStore REJECTED) | T041 | Store uses $effect(() => subscribe(...)); task T058 verifies |
| Research R1 (Direct API) | T009 | POST /chart/ + update_markdown_chart not export-import; T058 verifies |
| Research R3 (Global + Jinja spans) | T011 | Jinja block spans preserved, not stripped; sqlparse only for SQL spans |
| Research R4 (TaskManager) | T023, T031 | TaskManager.create_task() for async execution |
| Research R5 (Dedicated Tables) | T005-T007, T005a | Not AppConfigRecord JSON; one-chart-per-dashboard via unique partial index |
| FR-015 (RBAC Matrix) | T023, T031, T034, T044 | Endpoint-by-endpoint role requirements |
Notes
- Task IDs T001-T061 are sequential, plus T005a, T019a, T026a. 64 total tasks.
- Tasks marked [P] can run in parallel within their phase.
- Backend tests use
pytest -v; frontend tests usevitest run -v. - All C4 contracts (T009-T010, T021-T022, T028-T029) MUST include belief runtime instrumentation.
- Rejected-path regression is verified by T058 (ADR audit) and T059 (belief runtime audit).
quickstart.mdvalidation (T060) is the final gate before merge.