Tasks: CI Test-Topology Performance

Mission: ci-test-topology-performance-01KXBJRT | Branch: feat/ci-test-topology-performance → PR to main Spec: spec.md · Plan: plan.md · Data model: data-model.md · Contracts: contracts/guard-contracts.md

Decomposition note (ownership-clean)

The spec's conceptual WP-A…K are re-grouped into 9 executable WPs with disjoint owned_files, because nearly all per-job edits target one file (.github/workflows/ci-quality.yml). That file is owned by WP06 alone; every other surface (each registry/guard file, ui-e2e.yml, sonar-project.properties, docs) is owned by exactly one WP. This keeps parallel lanes collision-free while WP06 serializes the YAML edits as subtasks.

Ownership qualifier (rationale-backed leeway, not overlap): WP06 makes small post-measurement data edits into files it does not own — the durations-driven shard-leg rebalance lands in WP01's tests/_next_shard_map.py/tests/_arch_shard_map.py (T017), and the narrowed slow-tests selection model may touch WP02's _gate_coverage.py (T015). Because WP06 now depends on WP01/WP02 (see graph), these are sequential edits to already-merged files with a one-line rationale — never a concurrent-lane collision. The disjoint-owned_files guarantee holds for scheduling; the "every surface owned by exactly one WP" claim is qualified for these shared data surfaces.

Task WPConcernSpec WP-letters / FROwns
WP01Shard substrate → N-group registryWP-B / FR-002tests/_arch_shard_map.py, tests/_next_shard_map.py, tests/conftest.py, arch+next completeness guards
WP02Coverage-preservation authority(IC-02) / FR-007tests/architectural/_gate_coverage.py, test_gate_coverage.py, baselines/nodeids
WP03Real-port family registry + serial guardWP-E(guard) / FR-004, C-002tests/_real_port_suites.py, test_serial_port_preservation.py
WP04Workflow-dist lint + marker-baseline guards(IC-05/C-005/C-007)test_workflow_dist_lint.py, test_marker_baseline.py, tests/architectural/marker_baseline.txt
WP05fast-tests-cli file splitWP-F / FR-005the split test_charter_activate_commands*.py files
WP06CI workflow topology editsWP-A/C/D/E(job)/G/H/I / FR-001,003,004,006,010,011.github/workflows/ci-quality.yml, test_job_count_ceiling.py
WP07Sonar coverage-denominator exclusionsWP-J / FR-012sonar-project.properties
WP08UI-e2e coverage feedWP-K / FR-013.github/workflows/ui-e2e.yml
WP09Timings artifact + docs + cross-cutting auditWP-G(audit) / FR-008, FR-009docs/guides/testing-parallel.md, docs/plans/testing/test-suite-acceleration-plan.md, timings artifact

Dependency graph

WP01 ─> WP02 ─┐
WP03 ─> WP04 ─┤
              ├─> WP06 ──> WP09        (WP06 depends on WP01, WP02, WP03, WP04)
WP05 (root)   WP07 (root)   WP08 (root)

Roots (parallel-startable, group 0): WP01, WP03, WP05, WP07, WP08. Group 1: WP02, WP04. Group 2: WP06. Group 3: WP09. WP05/WP07/WP08 are fully independent of the topology chain. WP06 is gated on WP02 + WP04 (not only WP01/WP03) so the coverage/marker baselines are frozen and the guards exist before WP06 changes any job selection (freeze-before-change; enforces FR-007/GC-2b).

Subtask Index

IDDescriptionWPParallel
T001Refactor _arch_shard_map.py into an N-group registry (group→roots, shard_count, marker_prefix, assignment); arch assignment byte-stableWP01
T002Update conftest.py shard hook to iterate registered groupsWP01
T003Parametrize test_arch_shard_marker_completeness.py over the registry (arch green)WP01
T004Add _next_shard_map.py — 3-leg next registration (placeholder balance, rebalanced from WP06 durations)WP01
T005Add test_next_shard_marker_completeness.pyWP01[P]
T006Extend _gate_coverage.py: register next tier in same_tier_shard_counts + cross-job disjointness checkWP02
T007Extend test_gate_coverage.py: _REQUIRED__SHARDS += next_shard_; required-selection-structures for next legsWP02
T008Freeze committed baseline node-id manifests + baseline-diff guard (GC-2b) for re-scoped jobsWP02
T009Add _real_port_suites.py (fixed-range daemon family list)WP03
T010Generalize test_serial_port_preservation.py to consume the registry (whole family)WP03
T011Add fault-injection negative: a fixed-range suite under -n auto is rejectedWP03[P]
T012Add test_workflow_dist_lint.py (GC-4: no bare load; -n autoloadfile; fixed-range never -n auto; fail-fast:false)WP04
T013Add test_marker_baseline.py + commit marker_baseline.txt (GC-5: @slow/@stress/@quarantine set doesn't grow)WP04
T014integration-tests-next: add -n auto --dist loadfile -p no:cacheprovider --durations=25, then 3-leg next_shard_N matrix (fail-fast:false)WP06
T015slow-tests: path-narrow selection (preserve --ignore set) + -n auto --dist loadfileWP06
T016orphan-sweep: extract the -n0 step into its own concurrent job; exclude it from the sync poolWP06
T017fast-tests-core-misc: rebalance the imbalanced matrix (split the specify-cli-rest leg)WP06
T018fast-tests-charter: split the two sequential -n auto steps into concurrent jobsWP06
T019Sweep -n auto --dist loadfile -p no:cacheprovider across the serial non-real-port integration-tests-* single-dir jobsWP06
T020Roster: enrol new jobs in quality-gate.needs/slow-tests.needs; bump test_job_count_ceiling.pyWP06
T021Split test_charter_activate_commands.py into balanced sibling files; re-verify collection-equivalence (ratchet)WP05
T022Add sonar.coverage.exclusions (migration/, static/, __main__.py, next/**) with rationale commentWP07[P]
T023UI-e2e Python coverage: pytest tests/ui/ --cov=… merged into the reportWP08
T024UI-e2e JS coverage: investigate Playwright/istanbul → lcov → sonar.javascript.lcov.reportPaths; else document deferral + rely on static exclusionWP08
T025Record measured per-leg durations as a committed timings artifact (_TIMINGS_BASELINE shape, run-id provenance)WP09
T026Cross-cutting audit: verify the executed-test union is unchanged across all re-topologized jobs (GC-2b)WP09
T027Update docs/guides/testing-parallel.md + docs/plans/testing/test-suite-acceleration-plan.md to the shipped topologyWP09[P]

Work Packages

WP01 — Shard substrate → N-group registry

Goal: Generalize the arch-pole shard mechanism to a registry so arch and next are data rows (FR-002; satisfies C-003/D-044). Independent test: arch completeness guard green + a next registration collectable. Deps: none. Prompt: tasks/WP01-shard-registry.md (~250 lines).

  • ✅ T001 Refactor _arch_shard_map.py into an N-group registry (WP01)
  • ✅ T002 Update conftest.py shard hook to iterate groups (WP01)
  • ✅ T003 Parametrize test_arch_shard_marker_completeness.py (WP01)
  • ✅ T004 Add _next_shard_map.py 3-leg registration (WP01)
  • ✅ T005 Add test_next_shard_marker_completeness.py (WP01)

WP02 — Coverage-preservation authority

Goal: Enforce 0-dropped/0-double-run against a committed baseline via _gate_coverage (FR-007, GC-2/GC-2b). Independent test: baseline-diff guard fails on an injected drop. Deps: WP01. Prompt: tasks/WP02-coverage-preservation.md (~250 lines).

  • ✅ T006 Extend _gate_coverage.py (next tier + cross-job disjointness) (WP02)
  • ✅ T007 Extend test_gate_coverage.py (required next legs) (WP02)
  • ✅ T008 Freeze baseline node-id manifests + GC-2b diff guard (WP02)

WP03 — Real-port family registry + serial guard

Goal: Keep the whole fixed-range daemon family serial -n0 (FR-004/C-002, GC-3). Independent test: guard rejects a family file placed under -n auto. Deps: none. Prompt: tasks/WP03-real-port-registry.md (~200 lines).

  • ✅ T009 Add _real_port_suites.py (WP03)
  • ✅ T010 Generalize test_serial_port_preservation.py (WP03)
  • ✅ T011 Fault-injection negative case (WP03)

WP04 — Workflow-dist lint + marker-baseline guards

Goal: Formalize C-001/C-002/C-006/C-007 (GC-4) and C-005 (GC-5) as committed guards. Independent test: lint guard fails on an injected bare --dist load. Deps: WP03. Prompt: tasks/WP04-workflow-guards.md (~220 lines).

  • ✅ T012 Add test_workflow_dist_lint.py (WP04)
  • ✅ T013 Add test_marker_baseline.py + baseline (WP04)

WP05 — fast-tests-cli file split

Goal: Break the --dist loadfile single-worker tail by splitting test_charter_activate_commands.py (FR-005). Independent test: sibling files collect the same tests; ratchet re-verified. Deps: none. Prompt: tasks/WP05-cli-file-split.md (~180 lines).

  • ✅ T021 Split into balanced sibling files + ratchet re-verify (WP05)

WP06 — CI workflow topology edits (ci-quality.yml)

Goal: Apply every per-job topology change + roster (FR-001/003/004-job/006/010/011). Independent test: each changed job green (budgets recorded, not gated per the flakiness policy); _gate_coverage + WP02/WP04 guards green against the edited file. Deps: WP01 (registry), WP02 (coverage baseline+guard frozen first), WP03 (real-port guard), WP04 (dist-lint/marker guards). Prompt: tasks/WP06-ci-workflow-topology.md (~600 lines, 7 subtasks — upper bound, single-file YAML).

  • ✅ T014 integration-tests-next flags + next matrix (WP06)
  • ✅ T015 slow-tests narrow + parallel (WP06)
  • ✅ T016 orphan-sweep → own concurrent job (WP06)
  • ✅ T017 fast-tests-core-misc rebalance (WP06)
  • ✅ T018 fast-tests-charter step split (WP06)
  • ✅ T019 serial integration-tests-* sweep (WP06)
  • ✅ T020 roster + ceiling (WP06)

WP07 — Sonar coverage-denominator exclusions

Goal: Remove glue/non-Python from the coverage denominator (FR-012, GC-6). Independent test: exclusion globs match only the intended glue. Deps: none. Prompt: tasks/WP07-sonar-coverage-exclusions.md (~120 lines).

  • ✅ T022 Add sonar.coverage.exclusions with rationale (WP07)

WP08 — UI-e2e coverage feed

Goal: Feed Playwright/UI-e2e coverage into Sonar (FR-013). Independent test: coverage.xml includes dashboard server-side lines exercised by tests/ui/. Deps: none. Prompt: tasks/WP08-uie2e-coverage-feed.md (~180 lines).

  • ✅ T023 UI-e2e Python coverage merged into the report (WP08)
  • ✅ T024 JS coverage → lcov (or documented deferral) (WP08)

WP09 — Timings artifact + docs + cross-cutting audit

Goal: Record measured budgets, audit coverage-union across all jobs, update docs (FR-008/009). Independent test: timings artifact present with run-id; docs match shipped topology. Deps: WP06. Prompt: tasks/WP09-timings-docs-audit.md (~200 lines).

  • □ T025 Committed timings artifact (WP09)
  • □ T026 Cross-cutting coverage-union audit (WP09)
  • □ T027 Update testing docs (WP09)

MVP scope

WP06 delivers the headline win (integration-tests-next 69.2→~7 min), but its safety net must land first. Correct order: WP01 → WP03 → {WP02, WP04} → WP06 → WP09 — WP02/WP04 freeze the coverage + marker baselines and land the guards before WP06 changes any selection (freeze-before-change). The true critical path to the headline win is 4 hops (max(WP01,WP03) → {WP02,WP04} → WP06), not 2. WP05/WP07/WP08 are independent value-adds runnable in parallel from the start.