# Implementation Plan: Maintenance Banner for Dashboards **Branch**: `031-maintenance-banner` | **Date**: 2026-05-21 | **Spec**: [spec.md](./spec.md) **Input**: Feature specification from `/specs/031-maintenance-banner/spec.md` ## Summary Service for automatically adding/removing maintenance warning banners on Superset dashboards during ETL table updates. External tools call async API endpoints to start/end maintenance events; ss-tools discovers affected dashboards (by matching table names against datasets), creates markdown charts at the top of each dashboard via Superset REST API, and provides a management UI for manual control and configuration. All operations are RBAC-gated (operator/admin roles) and traceable via TaskManager. ## Technical Context **Language/Version**: Python 3.13+ (backend), JavaScript/TypeScript (frontend Svelte 5 runes) **Primary Dependencies**: FastAPI 0.126, SQLAlchemy 2.0, APScheduler 3.11 (backend); SvelteKit 2.x, Svelte 5.43, Vite 7.x, Tailwind CSS 3.x (frontend) **Storage**: PostgreSQL 16 (dedicated ss-tools DB — not Superset metadata DB per ADR-0003) **Testing**: pytest (backend), vitest + @testing-library/svelte (frontend) **Target Platform**: Linux server (Docker), modern browsers **Project Type**: web application (FastAPI REST + WebSocket backend, SvelteKit SPA frontend) **Performance Goals**: /start returns task_id within 1s; banner placement within 60s for up to 50 dashboards; /end within 30s for up to 50 dashboards **Constraints**: RBAC enforcement (operator/admin per ADR-0005), Svelte 5 runes only (per ADR-0006), no fromStore + $derived (per ADR-0007), offline-capable Docker bundle **Scale/Scope**: 1 maintenance event typically affects 5-50 dashboards; up to 5 concurrent active events; table lists up to 100 entries ## Constitution Check *GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* | Principle | Status | Evidence | |-----------|--------|----------| | ADR-0001 (Module Layout) | ✅ PASS | All planned files follow canonical layout: `backend/src/api/routes/maintenance/`, `backend/src/services/maintenance_service.py`, `backend/src/models/maintenance.py`, `frontend/src/routes/maintenance/`, `frontend/src/lib/components/` | | ADR-0002 (Semantic Protocol) | ✅ PASS | All C3+ contracts carry `@PURPOSE`, `@RELATION`; C4 contracts add `@PRE`/`@POST`/`@SIDE_EFFECT` + belief runtime | | ADR-0003 (Orchestrator Pattern) | ✅ PASS | Feature operates as external orchestrator, not integrated into Superset. Uses Superset REST API only. | | ADR-0004 (Plugin Architecture) | ✅ PASS | Maintenance is a core feature in `services/` (stateless, in-process), not a subprocess-isolated plugin per ADR-0004. Justified in research.md R2. | | ADR-0005 (Auth RBAC) | ✅ PASS | FR-014: operator/admin roles for mutations; admin for settings | | ADR-0006 (Frontend Architecture) | ✅ PASS | Svelte 5 runes, SPA mode (adapter-static), Tailwind CSS, requestApi wrapper | | ADR-0007 (fromStore REJECTED) | ✅ PASS | Maintenance store uses `$effect(() => subscribe(...))` pattern, not fromStore + $derived | | Module <400 LOC | ✅ PASS | Each planned file: routes ~150 LOC, service ~250 LOC, components ~150 LOC each | | CC ≤ 10 per contract | ✅ PASS | All contract functions are small, focused operations | **Verdict**: ✅ No blocking conflicts. Proceed to Phase 0/1. ## Project Structure ### Documentation (this feature) ```text specs/031-maintenance-banner/ ├── plan.md # This file ├── research.md # Phase 0: 7 resolved decisions ├── data-model.md # Phase 1: SQLAlchemy models, Pydantic schemas, state transitions ├── quickstart.md # Phase 1: Verification commands ├── contracts/ │ └── modules.md # Phase 1: 14 semantic contracts (C1-C4) ├── spec.md # Feature specification ├── ux_reference.md # UX interaction reference ├── checklists/ │ └── requirements.md # Requirements validation checklist └── tasks.md # Phase 2 output (NOT created by /speckit.plan) ``` ### Source Code (repository root) ```text backend/ ├── src/ │ ├── api/routes/maintenance/ # NEW: package-based route module │ │ ├── __init__.py │ │ ├── _router.py # APIRouter assembly │ │ ├── _routes.py # Route handlers │ │ └── _schemas.py # Pydantic schemas │ ├── core/superset_client/ │ │ └── _dashboards_write.py # NEW: SupersetClient write mixin │ ├── models/ │ │ └── maintenance.py # NEW: SQLAlchemy models │ └── services/ │ ├── maintenance_service.py # NEW: Core business logic │ └── sql_table_extractor.py # NEW: SQL table name parser ├── tests/ │ ├── test_maintenance_service.py # Unit/integration tests │ ├── test_sql_table_extractor.py # SQL parsing tests │ └── test_maintenance_api.py # API endpoint tests frontend/ ├── src/ │ ├── routes/maintenance/ │ │ └── +page.svelte # NEW: Management page │ ├── lib/ │ │ ├── components/ │ │ │ ├── MaintenanceSettingsPanel.svelte # NEW │ │ │ ├── MaintenanceEventsTable.svelte # NEW │ │ │ └── DashboardMaintenanceBadge.svelte # NEW │ │ ├── stores/ │ │ │ └── maintenance.svelte.js # NEW │ │ └── api/ │ │ └── maintenance.js # NEW │ └── routes/dashboards/ │ └── +page.svelte # MODIFIED: add maintenance badge column └── tests/ ├── maintenance.test.ts # Component tests └── maintenance-store.test.ts # Store tests ``` **Structure Decision**: Package-based route for `maintenance/` mirrors existing `dashboards/` pattern. Backend service in `services/` (stateless, request-scoped) per ADR-0001 boundary rule #3. Superset client extension in `core/superset_client/` as singleton mixin. ## Semantic Contract Guidance All 15 contracts defined in `contracts/modules.md`: - **Complexity 1** (4): MaintenanceEvent, MaintenanceDashboardBanner, MaintenanceDashboardState, MaintenanceSettings models, Pydantic schemas - **Complexity 2** (3): SqlTableExtractor, MaintenanceRouter, MaintenanceApiClient, DashboardMaintenanceBadge - **Complexity 3** (5): MaintenanceRoutes, MaintenanceStore, MaintenanceBannerPage, MaintenanceSettingsPanel, MaintenanceEventsTable - **Complexity 4** (3): SupersetDashboardsWriteMixin, MaintenanceService (start/end/end_all methods) C4 contracts require `@PRE`, `@POST`, `@SIDE_EFFECT` plus belief runtime instrumentation per semantics-python skill. ## Complexity Tracking *No constitutional violations to justify. All module placements and complexity levels follow ADR-0001 guidelines.* | Aspect | Rationale | |--------|-----------| | Package-based route (`maintenance/`) | Mirrors `dashboards/` pattern; 3 files <200 LOC each | | Service in `services/` not `core/` | Stateless, request-scoped per ADR-0001 rule #3 | | SupersetClient mixin in `core/` | Singleton, app-scoped per ADR-0001 rule #2 | | Not a plugin | Maintenance is core infrastructure, not user-configurable | ## Phase 0 Outputs See [research.md](./research.md) for 7 resolved decisions: 1. Chart placement via direct API (not export-import) 2. Module placement per ADR-0001 3. SQL parsing via sqlparse with regex fallback 4. Async orchestration via existing TaskManager 5. Dedicated DB tables (not AppConfigRecord JSON) 6. Svelte 5 runes with ADR-0007 compliance 7. Existing RBAC reuse (operator + admin roles) ## Phase 1 Outputs | Artifact | Path | Description | |----------|------|-------------| | Data Model | [data-model.md](./data-model.md) | 3 SQLAlchemy models, 10 Pydantic schemas, ER diagram, state transitions | | Contracts | [contracts/modules.md](./contracts/modules.md) | 14 semantic contracts (C1-C4) with relations, pre/post, rationale | | Quickstart | [quickstart.md](./quickstart.md) | Verification commands for backend/frontend/lint |