Files
ss-tools/specs/040-dashboard-load-testing/plan.md
busya ac95beb1a0 docs(specs): record 036-041 MVP runtime gaps as open closure phases
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.
2026-08-07 12:56:40 +07:00

150 lines
9.6 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Implementation Plan: Dashboard Load Testing
**Branch**: `040-dashboard-load-testing` | **Date**: 2026-07-22 | **Spec**: [spec.md](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
```text
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)
```text
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 `RunnerPool` into `run_load_run` (executor + env semaphore + breaker + on_result persist).
- T076 — 037-native executor adapter `services/load_testing/executor.py` (delegates to `SupersetClient.ChartData.Execute`, bounded normalization).
- T077 — Share per-env capacity semaphore with `client_registry` admission.
- T078 — Circuit breaker evaluation between executions → `circuit_breaker_abort` with preserved partials.
- T079 — Persist per-execution LoadExecution so `/status` and `/compare` return 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.py` follow 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 blocking `AsyncJobRunner.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)
1. Backend load-testing unit/contract tests and existing client-registry regressions.
2. `python -m ruff check .`.
3. Frontend Vitest/L2 tests, lint, build, and Playwright profile/run monitor flow.
4. Quickstart steps 1–10.
5. Semantic contract audit and runtime instrumentation audit for C4/C5 runner/breaker flows.
6. **MVP gate (new)**: run lifecycle test asserts executor invoked per queued item against real chart-data context; breaker abort preserves partials.
#endregion DashboardLoadTesting.Plan