# 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 |
