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

9.6 KiB
Raw Blame History

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 C3C5 contracts in contracts/modules.md, hierarchical LoadTesting.* IDs and cross-stack edges
II. Decision Memory Spec @RATIONALE/@REJECTED; research R1R10; 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 C3C5 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). Задачи T001T074 покрывают модули и API, но runtime-исполнение не замкнуто.

Closure tasks (tasks.md Phase 9, T075T079):

  • 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 шаги 37 не выполняются против реального (или фикстурного, но через 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 110.
  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