Files
ss-tools/specs/031-maintenance-banner/tasks.md
busya ec6421de35 rename ss-tools to superset-tools across the entire project
- 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
2026-06-16 11:15:19 +03:00

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) via alembic 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 MaintenanceEvent SQLAlchemy model in backend/src/models/maintenance.py with 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 MaintenanceDashboardBanner SQLAlchemy model in backend/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 MaintenanceDashboardState SQLAlchemy model in backend/src/models/maintenance.py with 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 MaintenanceSettings SQLAlchemy model in backend/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 SupersetDashboardsWriteMixin in backend/src/core/superset_client/_dashboards_write.py with 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 SupersetDashboardsWriteMixin with belief runtime markers: belief_scope context 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 SqlTableExtractor module in backend/src/services/sql_table_extractor.py with 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 MaintenanceRouter in backend/src/api/routes/maintenance/_router.py — creates APIRouter(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 RBAC Depends(require_role(...)) guards. Contract: [DEF:MaintenanceRoutes:Module] C3 (stub phase).
  • T014 [--] Register maintenance_router in main FastAPI application (backend/src/app.py) and register SupersetDashboardsWriteMixin in SupersetClient assembly.

Seed Data

  • T015 [--] Create Alembic data migration (or startup check) to insert default MaintenanceSettings row (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/start in backend/tests/test_maintenance_api.py — test valid request returns 202, test missing start_time returns 422, test end_time < start_time returns 422, test past start_time >1h returns 422, test tables >100 entries returns 422, test optional end_time (omitted → task result shows «уточняется»), test duplicate request returns 200 {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() in backend/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() in backend/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 in backend/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 in backend/src/services/maintenance_service.py — idempotently gets or creates the shared MaintenanceDashboardBanner for 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 in backend/src/services/maintenance_service.py — reads banner_template and display_timezone from 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 absent end_time → «Окончание: уточняется». Contract C2.
  • T021 [US1] Implement start_maintenance() method in backend/src/services/maintenance_service.py — orchestrates full flow: (1) idempotency check by (tables, start_time, end_time) key using row-level lock (SELECT ... FOR UPDATE on 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 via build_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/start route handler in backend/src/api/routes/maintenance/_routes.py — validates MaintenanceStartRequest: start_time required, end_time optional (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, return 202 {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}/end in backend/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() in backend/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() in backend/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 in backend/src/services/maintenance_service.py — for a given MaintenanceDashboardBanner, queries all active MaintenanceDashboardState records linked to it, calls build_banner_text() for aggregated text, updates the shared markdown chart in Superset via SupersetDashboardsWriteMixin.update_markdown_chart(). Contract C3 within [DEF:MaintenanceService:Class].
  • T028 [US2] Implement end_maintenance() method in backend/src/services/maintenance_service.py — set event status to ENDING, then for each active MaintenanceDashboardState in the event: (1) check if OTHER active event-states still reference the same banner; (2) if yes → call rebuild_banner() (remove this event's info from aggregated text), transition state to REMOVED; (3) if no → transition banner to REMOVED, call remove_chart_from_layout() then delete_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 in backend/src/services/maintenance_service.py — iterates all active events, calls end_maintenance() for each. Contract C4.
  • T031 [US2] Implement POST /api/maintenance/{maintenance_id}/end and POST /api/maintenance/end-all route handlers in backend/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/settings and PUT /api/maintenance/settings in backend/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/settings and PUT /api/maintenance/settings route handlers in backend/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 MaintenanceApiClient module in frontend/src/lib/api/maintenance.js — typed fetch wrappers: getSettings(), updateSettings(payload), getEvents(), startMaintenance(payload), endMaintenance(id), endAllMaintenance(), getDashboardBanners(). Uses requestApi from existing API layer. Contract: [DEF:MaintenanceApiClient:Module] C2.
  • T036 [US4] Implement MaintenanceSettingsPanel component in frontend/src/lib/components/MaintenanceSettingsPanel.svelte — collapsible via <details><summary>. Form uses: <Select> from $lib/ui/Select.svelte for target environment, <Input> from $lib/ui/Input.svelte for timezone, native <input type="radio" bind:group> for scope (matches ScheduleConfig pattern), <SearchableMultiSelect> from $lib/components/ui/SearchableMultiSelect.svelte for excluded/forced dashboards (options: {id, name} from dashboard list), <textarea> for banner_template with live preview, <Button isLoading={isSaving}> from $lib/ui/Button.svelte for save. Toast via addToast() 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 MaintenanceEventsTable in frontend/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 MaintenanceStore in frontend/tests/maintenance-store.test.ts — test events fetching, test polling, test error handling.

Implementation for US3

  • T040 [US3] Implement GET /api/maintenance/events route handler in backend/src/api/routes/maintenance/_routes.py — returns MaintenanceEventListResponse with active and completed events, each including affected dashboard count. RBAC: authenticated.
  • T041 [US3] Implement MaintenanceStore in frontend/src/lib/stores/maintenance.svelte.js — Svelte 5 rune store with $state for events (active + completed), settings, dashboardBanners, isLoading, error. Uses $effect(() => subscribe(...)) pattern per ADR-0007 (REJECTED: fromStore + $derived). Auto-polls getEvents() every 30s via $effect (WebSocket covers task progress; polling ensures event-list consistency after task completion). Contract: [DEF:MaintenanceStore:Module] C3.
  • T042 [US3] Implement MaintenanceEventsTable component in frontend/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 native title tooltip), 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 with isLoading. "Remove All" with confirm() (matches health page). Skeleton: animate-pulse bg-gray-200 h-4 rows. Empty: border-dashed border-gray-300 bg-gray-50 p-8 text-center. Completed events collapsible via <details><summary>. Toast via addToast(). Contract C3.
  • T043 [US3] Implement MaintenanceBannerPage in frontend/src/routes/maintenance/+page.svelte — composes MaintenanceSettingsPanel + MaintenanceEventsTable, passes store data. Uses addToast() from $lib/toasts.js for 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 /maintenance in sidebar — add category to "Operations" section in frontend/src/lib/components/layout/sidebarNavigation.js with id="maintenance", path="/maintenance", requiredPermission="maintenance:write", requiredAction="READ" (operator+ see page, admin has full access per RBAC matrix FR-015). Add translation keys to frontend/src/lib/i18n/locales/en/nav.json and frontend/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 DashboardMaintenanceBadge in frontend/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-banners route handler in backend/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 populates dashboardBanners $state map.
  • T049 [US5] Implement DashboardMaintenanceBadge component in frontend/src/lib/components/DashboardMaintenanceBadge.svelte — orange badge rounded-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). Native title attribute 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 — add DashboardMaintenanceBadge import, insert badge column/cell in dashboard table rows, wire $derived from MaintenanceStore.dashboardBanners. Ensure compliance with ADR-0007 (no fromStore + $derived pattern). Export visual verification: open Dashboard Hub page after US1 and confirm badge appears via chrome-devtools snapshot.
  • 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_contracts on backend/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/@REJECTED present.
  • T058 Run ADR rejected-path regression check: verify fromStore + $derived NOT 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), no plugin:migration permission in maintenance code (FR-015 RBAC matrix).
  • T059 Run belief runtime audit: axiom_semantic_validation audit_belief_runtime on C4 contracts — verify belief_scope, log(REASON), log(REFLECT), log(EXPLORE) instrumentation present in start_maintenance(), end_maintenance(), and SupersetDashboardsWriteMixin.
  • 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 use vitest 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.md validation (T060) is the final gate before merge.