Fact-check the dashboard-testing spec packages against actual code and amend the full speckit document set (spec, plan, research, tasks, traceability, quickstart) so documented status matches reality: - 037: discrete metric tools work, but deploy hooks do not create VerificationRun and GET read-API endpoints are missing (T080-T081) - 038: compiler/validator work; VLM _default_submit and capture dispatch remain runtime stubs that never call LLMClient/ScreenshotService (T057-T059) - 039: UI components exist, but api/dashboard-testing.ts is unbound and pipeline views are not wired to pages; depends on 037 read-API (T054-T058) - 040: run_load_run never invokes RunnerPool, so load runs execute zero Superset requests; must wire 037 executor (T075-T079) - 041: backend index works, but no /lineage frontend and lineage_index stays opt-in (T045-T048) - 036: confirmed operational, relations to reused plugin modules fixed 19 open closure tasks total; region pairs balanced.
9.6 KiB
Implementation Plan: Dashboard Load Testing
Branch: 040-dashboard-load-testing | Date: 2026-07-22 | Spec: spec.md
Input: Clarified feature specification from /specs/040-dashboard-load-testing/spec.md
Summary
Build a dashboard load-testing surface around bounded async workers, not raw HTTP connection count. A LoadRun is one TaskManager task with a per-run execution queue; an environment capacity registry and semaphore coordinate all active runs. The runner uses the 037 Superset-native chart-data path, preserves Superset cache metadata from the JSON response, computes queue/resource/upstream timings separately, uses bounded result processing to avoid GIL distortion, and stores a pinned 041 blast-radius snapshot for configuration and PROD approval. Load runs are strictly isolated from baseline mutation.
Technical Context
Language/Version: Python 3.13+ backend; TypeScript frontend with Svelte 5 runes-only
Primary Dependencies: FastAPI 0.126, SQLAlchemy 2.0.45, APScheduler 3.11.2 for scheduled entry boundaries, httpx via existing AsyncAPIClient, Pydantic 2.x; SvelteKit 2.49, Svelte 5.56, Vite, Tailwind; no new runtime dependency
Storage: PostgreSQL 16; new load profile/run/execution/aggregate/finding tables plus existing TaskManager persistence; no baseline catalog writes
Testing: pytest unit/contract/integration tests; Vitest L1 Screen Model tests and L2 UX tests; Playwright for worker monitor/profile flow; ruff and frontend lint
Target Platform: Linux Docker deployment, modern desktop browsers
Project Type: FastAPI REST/WebSocket backend + SvelteKit SPA frontend
Frontend Architecture: .svelte.ts Screen Models, $state/$derived/$effect, typed DTOs, components bind to models; ordinary AgentChat and 036 AgentRun remain separate
Performance Goals: upstream p95/p99 measured independently from queue/resource wait; progress events ≤4 Hz; result flush ≤50 rows or 1s; no more than effective worker cap in flight; profile/matrix preview <200ms after metadata available
Constraints: PROD default cap=5, DEV/PREPROD=10, absolute ceiling=25, reserve 5 shared pool slots; direct SQL/raw query context forbidden; bounded sample 100 rows/256 KiB; 041 fingerprint required for blast-radius binding; RBAC default-deny; no implementation phase in this planning request
Scale/Scope: 100 dashboards/environment, 300 charts, 25 maximum load workers per environment, 500 selected variation combinations per profile by default
Constitution Check
GATE: Must pass before Phase 0 research. Re-check after Phase 1 design. — PASS with one explicit implementation prerequisite
| Principle | Status | Evidence / Guardrail |
|---|---|---|
| I. Semantic Contract First | ✅ | C3–C5 contracts in contracts/modules.md, hierarchical LoadTesting.* IDs and cross-stack edges |
| II. Decision Memory | ✅ | Spec @RATIONALE/@REJECTED; research R1–R10; HTTP pool, per-execution tasks, latency inference, full normalization rejected |
| III. External Orchestrator | ✅ | 037 Superset-native adapter remains sole execution boundary; no Superset plugin or SQL path |
| IV. Module Discipline | ✅ | Runner, capacity, cache, bounded processing, breaker separated; target each module <400 LOC and CC≤10 |
| V. RBAC Enforcement | ✅ | dashboard:loadtest:execute and dashboard:loadtest:prod; gate hash includes profile/cap/fingerprint |
| VI. Svelte 5 Runes Only | ✅ | LoadProfileModel/LoadRunModel .svelte.ts, no legacy stores in new surface |
| VII. Test-Driven C3+ | ✅ | Quickstart is falsifiable; C4/C5 contracts require rejection-path tests and GIL/cache/cap edge tests |
| VIII. Attention Optimization | ✅ | Anchor density, dotted IDs, shared load-testing semantic tags, bounded contract blocks |
| Repository prerequisite | ⚠️ MUST FIX DURING IMPLEMENTATION | client_registry.get_client() creates per-env semaphore but does not pass it to AsyncAPIClient; without this, connection_pool_size is declarative only. This is a foundational task, not a silent assumption. |
Post-Phase-1 re-check: PASS — the prerequisite is explicitly represented in traceability and must be verified before load execution.
Project Structure
Documentation
specs/040-dashboard-load-testing/
├── spec.md
├── ux_reference.md
├── checklists/requirements.md
├── plan.md
├── research.md
├── data-model.md
├── contracts/modules.md
├── quickstart.md
└── traceability.md
Source Code (implementation target; not changed in this planning phase)
backend/src/
├── models/load_testing.py
├── schemas/load_testing.py
├── services/load_testing/
│ ├── capacity.py
│ ├── profile.py
│ ├── matrix.py
│ ├── runner_pool.py
│ ├── timing.py
│ ├── cache_metadata.py
│ ├── bounded_result.py
│ ├── breaker.py
│ ├── aggregates.py
│ └── comparison.py
├── api/routes/load_testing.py
├── core/utils/client_registry.py # pass existing semaphore into AsyncAPIClient
└── plugins/load_testing.py # one TaskManager task per LoadRun
frontend/src/
├── lib/types/load-testing.ts
├── lib/api/load-testing.ts
├── lib/models/Dashboards.LoadProfileModel.svelte.ts
├── lib/models/Dashboards.LoadRunModel.svelte.ts
├── lib/components/dashboards/load-testing/
└── routes/load-runs/[id]/
Structure Decision: Backend domain under services/load_testing/, separate from 037 correctness engine. core/utils/client_registry.py gets only the semaphore wiring prerequisite; no shared-client replacement because auth/CSRF session continuity is an existing invariant. Frontend is a dedicated workspace/monitor, not Agent Workspace.
Phase 0 Outputs
| Artifact | Decision |
|---|---|
research.md R1 |
Async worker queue + env capacity registry; one TaskManager task per run |
| R2 | Effective cap derives from stage, override, pool size minus 5 reserved slots, ceiling 25; existing semaphore wiring prerequisite |
| R3 | Separate queue/resource/upstream timing; upstream percentiles only |
| R4 | Superset response-body cache metadata authoritative |
| R5 | Raw digest + bounded sample/count; no full 037 normalization in load path |
| R6 | Batched persistence and ≤4 Hz progress |
| R7 | Minimum-20 rolling breaker window; error/p99 thresholds |
| R8 | Deterministic matrix and seeded sampling |
| R9 | Pinned 041 blast-radius snapshot |
| R10 | Stage/RBAC/approval gate hash semantics |
Phase 1 Outputs
| Artifact | Coverage |
|---|---|
data-model.md |
Profile, variation, run, execution, aggregates, findings, breaker, blast radius, invariants |
contracts/modules.md |
Backend C3–C5 contracts and frontend Screen Models with UX states |
quickstart.md |
10-step falsifiable verification sequence |
traceability.md |
LOAD-FR-001..019 to contracts and tests; cross-spec dependencies |
Complexity Tracking
No constitution violations. The repository semaphore wiring is an explicit prerequisite, not a justification for bypassing the constitution.
MVP Runtime Closure (audit 2026-08-07)
Status correction: Факт-чекинг кода показал, что run_load_run в backend/src/plugins/load_testing.py не вызывает RunnerPool — фазы RAMP→STEADY→DRAIN→COMPLETED проходят без единого Superset-запроса; execute_chart_data из 037 в load-пути не используется (нарушение LOAD-FR-001). Задачи T001–T074 покрывают модули и API, но runtime-исполнение не замкнуто.
Closure tasks (tasks.md Phase 9, T075–T079):
- T075 — Wire
RunnerPoolintorun_load_run(executor + env semaphore + breaker + on_result persist). - T076 — 037-native executor adapter
services/load_testing/executor.py(delegates toSupersetClient.ChartData.Execute, bounded normalization). - T077 — Share per-env capacity semaphore with
client_registryadmission. - T078 — Circuit breaker evaluation between executions →
circuit_breaker_abortwith preserved partials. - T079 — Persist per-execution LoadExecution so
/statusand/comparereturn real data.
Exit rule: 040 не объявляется operational, пока quickstart шаги 3–7 не выполняются против реального (или фикстурного, но через executor) Superset-потока, а не hold/sleep-перехода.
ADR Continuity
- ADR-0001:
services/load_testing/,models/load_testing.py,api/routes/load_testing.pyfollow module layout. - ADR-0003: all Superset interaction remains in existing client boundary.
- ADR-0005: new permissions are explicit and default-deny; PROD uses 036 gate.
- ADR-0006: new UI uses Svelte 5 Runes and model-first components.
- ADR-0011: async worker execution stays on FastAPI event loop; no
asyncio.run()and no blockingAsyncJobRunner.run()from the event-loop thread. - 036/037/041 continuity: one TaskManager run, 037 chart-data semantics, 041 fingerprint pinning; no baseline mutation.
Verification Gates (implementation phase only)
- Backend load-testing unit/contract tests and existing client-registry regressions.
python -m ruff check ..- Frontend Vitest/L2 tests, lint, build, and Playwright profile/run monitor flow.
- Quickstart steps 1–10.
- Semantic contract audit and runtime instrumentation audit for C4/C5 runner/breaker flows.
- MVP gate (new): run lifecycle test asserts executor invoked per queued item against real chart-data context; breaker abort preserves partials.
#endregion DashboardLoadTesting.Plan