Tasks: Write-Side Context-Factory Adoption (Mission B)

Branch: feat/write-side-context-factory-adoption (stacked on feat/read-path-error-fidelity / Mission A) · Merge target: same · Date: 2026-06-17

Pure consumer-routing of the WRITE execution path onto Mission A's frozen build_execution_context factory + its projected fragments (C-001 — no new authority). Discipline (binding, every WP): function-over-form + verification-by-deletion, topology-true fixtures (full 26-char ULID + REAL coord-worktree + real submodule — no fabricated short ids), TDD-first, idempotency-preserving (NFR-004 — no on-disk worktree/coord churn), ruff+mypy clean ≤15 no suppressions, C-008 Fix-don't-litigate (fix adjacent breakage in the same change, don't litigate pre-existing-vs-introduced).

Clean-before-touch sequencing (D-9): WP01 (characterization net) + WP02's internal de-dup land the groundwork that makes every later deletion provable; adoption WPs then each consume the already-merged factory (an import, not a shared edit); WP08 (keystone + ratchet) lands last.

Subtask Index

IDDescriptionWPParallel
T001Build shared topology-true fixture module (full ULID + real coord-worktree + real submodule)WP01
T002FR-004 before/after write-target divergence characterization (RED-on-HEAD, drives WITHOUT explicit repo_root)WP01[P]
T003Coord-topology root/surface parity characterization for all 5 root-walk sitesWP01[P]
T004Submodule-topology characterizationWP01[P]
T005store.py::_find_mission_specs_root + real-coord lanes-placement characterizationWP01[P]
T006Public lock-root behavioral invariant (replaces paula S-4/S-9 private-by-name coverage)WP01[P]
T007Extract byte-identical lock-root resolver (emit ≡ wpl) into one workspace/root_resolver.py helperWP02
T008Route emit.py::_feature_status_lock_root to consume workspace.primary_root via the helper; delete the .parent.parent walkWP02
T009Route work_package_lifecycle.py 3× root walk to workspace.primary_root via the helper; delete the walkWP02
T010Retire the private-helper by-name tests in test_emit.py / test_work_package_lifecycle.py (public invariant now in the net)WP02
T011Verification-by-deletion: net + status/lifecycle suite greenWP02
T012ruff/mypy clean, complexity ≤15 (WP02 surface)WP02
T013Route lifecycle_events.py .parent.parent/.parent.parent.parent walks to workspace.primary_root; deleteWP03[P]
T014Route store.py KITTY_SPECS_DIR ancestor scan to workspace.primary_root; delete (PR-3 early-return tidy in-WP boy-scout)WP03[P]
T015Verification-by-deletion: net + store/lifecycle suite greenWP03
T016ruff/mypy clean (WP03 surface)WP03
T017PR-5 pre-refactor: extract the placement-compose helper (collapse the :384 reuse + :396 create joins)WP04[P]
T018Route the placement join to the factory placement projection (CommitTarget/resolve_placement_only); naming stays via mission_dir_nameWP04
T019Idempotency: before/after on-disk placement path identical (no worktree churn)WP04
T020ruff/mypy clean (WP04 surface); optional boy-scout the orthogonal :304 DeprecationWarning if dueWP04
T021Route _repo_root_for_feature (R5) .parent.parent walk to workspace.primary_rootWP05
T022Route the status write surface to status_surface.status_write_dir (coord authority — C-007; never primary_root)WP05
T023Route the write-target (coord_branch or _current_branch) to branch_ref.destination_ref (FR-004)WP05
T024Reduce the _identity_for_request second-factory body to consume the projection (FR-007); defer the S2 selection ladder (#1716)WP05
T025Idempotency before/after on-disk-target test + D-5 read==write equivalence (primary/coord/submodule)WP05
T026ruff/mypy clean ≤15 (WP05 surface — the highest-risk file)WP05
T027Route the lanes-dir write (_lanes_feature_dir C-LANES-1 region) through resolve_lanes_dir(<coord feature dir from status_surface>) (FR-008)WP06[P]
T028Thin lanes projection in lanes/persistence.py only if needed (prefer deriving from status_surface + the existing seam — C-001)WP06
T029Verify lanes.json still lands on the coord authority under coord topology; flat under no-coord (net S-8)WP06
T030ruff/mypy clean (WP06 surface)WP06
T031Delete the prompt_source fragment (resolution.py:761-778,908,929 + context.py:181,246,254)WP07[P]
T032Delete the dead StatusSurfaceFragment.surface= read-param wiring (aggregate.py:199 + the if surface is not None branch)WP07
T033Atomically retire the S-2/S-3 tests that encode the FR-006 deletion targets as contractsWP07
T034Verification-by-deletion: suite green after deletion; ruff/mypy cleanWP07
T035KEYSTONE simple-case test: all-targets-base on a real single-branch repo (full ULID, no coord, no lanes)WP08
T036Assert every adopted fragment == base; ZERO .worktrees//coord paths read or written; byte-identical to pre-lane flatWP08
T037FR-005 boundary ratchet (optional): extend tests/architectural/ to flag write-side re-derivation in the adopted modulesWP08
T038Confirm SC-007/SC-008 green on the merged adoption treeWP08
T039Author the user Explanation page: branch-target routing table + the simple case (SOURCE docs/)WP09[P]
T040Add the docs-freshness page-inventory row; docs-freshness greenWP09
T041Cross-link from the lanes/coordination explanation surface; verify Divio "Explanation" classificationWP09

WP01 — Characterization net (clean-before-touch, FIRST)

Goal: Build the topology-true characterization net that makes every later deletion provable, fixing paula's live-evidence trap — the strongest write-path suite passes repo_root= everywhere so it is BLIND to the swap, and the FR-004 write-target divergence has zero witnessing test today. Priority: P0 (gate). Independent test: the net is RED where the latent FR-004 divergence lives and GREEN elsewhere on HEAD; it drives without explicit repo_root on real coord + submodule topologies. Dependencies: none. Sequence: FIRST — all adoption WPs depend on it.

  • ✅ T001 Build shared topology-true fixture module (WP01)
  • ✅ T002 FR-004 before/after write-target divergence characterization, RED-on-HEAD (WP01)
  • ✅ T003 Coord-topology root/surface parity for all 5 root-walk sites (WP01)
  • ✅ T004 Submodule-topology characterization (WP01)
  • ✅ T005 store._find_mission_specs_root + real-coord lanes-placement characterization (WP01)
  • ✅ T006 Public lock-root behavioral invariant (WP01)

Owns the new tests/specify_cli/write_side/ net + fixtures only — no src, no existing per-site test files.

WP02 — Lock-root consolidation + primary_root adoption (IC-DEDUP + IC-EMIT + IC-WPL)

Goal: Collapse the byte-identical emit::_feature_status_lock_rootwpl::_repo_root_for_lock into one shared helper (D-4/D-9 PR-1), then route it to workspace.primary_root — deleting both .parent.parent walks. Merged into ONE WP because both edits touch emit.py + work_package_lifecycle.py (separate WPs would overlap ownership). Priority: P1. Independent test: the net (WP01) stays green after the helper extraction (behavior-preserving) and after the walk deletion (adoption). Dependencies: WP01.

  • ✅ T007 Extract the shared lock-root helper into workspace/root_resolver.py (WP02)
  • ✅ T008 Route emit.py to workspace.primary_root via the helper; delete the walk (WP02)
  • ✅ T009 Route work_package_lifecycle.py to workspace.primary_root; delete the walk (WP02)
  • ✅ T010 Retire the private-helper by-name tests (public invariant now in the net) (WP02)
  • ✅ T011 Verification-by-deletion: net + status/lifecycle suite green (WP02)
  • ✅ T012 ruff/mypy clean ≤15 (WP02)

WP03 — Status root-walk adoption (IC-LE + IC-STORE)

Goal: Route the lifecycle_events.py + store.py root walks/ancestor-scan to workspace.primary_root, deleting them. Merged (two tiny same-concern status sites). Priority: P1. Independent test: net green after deletion. Dependencies: WP01.

  • ✅ T013 Route lifecycle_events.py walks to workspace.primary_root; delete (WP03)
  • ✅ T014 Route store.py ancestor scan to workspace.primary_root; delete (+ PR-3 early-return tidy) (WP03)
  • ✅ T015 Verification-by-deletion: net + store/lifecycle suite green (WP03)
  • ✅ T016 ruff/mypy clean (WP03)

WP04 — Placement adoption (IC-WT)

Goal: Replace the two core/worktree.py placement joins with the factory placement projection; naming stays via the mission_dir_name seam. Priority: P1. Independent test: before/after on-disk placement path identical (idempotency). Dependencies: WP01.

  • ✅ T017 PR-5 pre-refactor: extract the placement-compose helper (WP04)
  • ✅ T018 Route the join to the factory placement projection (WP04)
  • ✅ T019 Idempotency: before/after on-disk placement path identical (WP04)
  • ✅ T020 ruff/mypy clean; optional boy-scout the orthogonal :304 DeprecationWarning if due (WP04)

WP05 — Coordination root + surface + write-target (IC-COORD) [highest risk]

Goal: In coordination/status_transition.py: route R5 root → workspace.primary_root; the status write surfacestatus_surface.status_write_dir (coord authority, C-007 — never primary_root); the write-targetbranch_ref.destination_ref (FR-004); reduce the _identity_for_request second factory to consume the projection (FR-007). DEFER only the S2 selection ladder (#1716). Priority: P1 (med-high). Independent test: D-5 read==write equivalence across primary/coord/submodule + before/after on-disk-target idempotency. Dependencies: WP01.

  • ✅ T021 Route _repo_root_for_feature (R5) to workspace.primary_root (WP05)
  • ✅ T022 Route the status write surface to status_surface.status_write_dir (coord authority) (WP05)
  • ✅ T023 Route the write-target to branch_ref.destination_ref (FR-004) (WP05)
  • ✅ T024 Reduce _identity_for_request to consume the projection (FR-007); defer S2 (WP05)
  • ✅ T025 Idempotency before/after on-disk-target + D-5 equivalence test (WP05)
  • ✅ T026 ruff/mypy clean ≤15 (WP05)

WP06 — Lanes/coord adoption (IC-LANES, FR-008)

Goal: Route the lanes-dir write (_lanes_feature_dir C-LANES-1 region in cli/commands/implement.py) through resolve_lanes_dir(<coord feature dir from status_surface>) so lanes.json resolves from the coord authority (C-LANES-1/#1991). Prefer deriving from the existing status_surface + the resolve_lanes_dir seam over a raw factory field (C-001). Priority: P2. Independent test: lanes.json lands on coord under coord topology, flat under no-coord (net S-8). Dependencies: WP01.

  • ✅ T027 Route the lanes-dir write through resolve_lanes_dir(<coord feature dir>) (WP06)
  • ✅ T028 Thin lanes projection in persistence.py only if needed (WP06)
  • ✅ T029 Verify lanes.json coord-vs-flat placement (WP06)
  • ✅ T030 ruff/mypy clean (WP06)

WP07 — Fragment-scaffolding retirement (IC-RETIRE, FR-006)

Goal: Delete the genuinely-dead prompt_source fragment + the dead StatusSurfaceFragment.surface= read-param wiring (0 readers, grep-proved). Retire the S-2/S-3 tests that encode these deletion targets as contracts atomically in the same change. Priority: P2. Independent test: suite green after deletion (deletion is its own proof). Dependencies: WP01.

  • ✅ T031 Delete the prompt_source fragment (resolution.py + context.py) (WP07)
  • ✅ T032 Delete the surface= read-param wiring (aggregate.py) (WP07)
  • ✅ T033 Atomically retire the S-2/S-3 contract-encoding tests (WP07)
  • ✅ T034 Verification-by-deletion: suite green; ruff/mypy clean (WP07)

WP08 — Keystone simple-case test + boundary ratchet (IC-SIMPLECASE, FR-005)

Goal: The binding "all-targets-base → flat" keystone (NFR-006/SC-007) + the optional FR-005 boundary ratchet (lands last so it doesn't flag not-yet-adopted sites). On a real single-branch repo (full ULID, no coord, no lanes): every adopted fragment resolves to base, ZERO .worktrees//coord paths read or written, byte-identical to pre-lane flat. Priority: P1 (final guard). Independent test: the keystone itself. Dependencies: WP02, WP03, WP04, WP05, WP06 (it integration-tests the adoptions).

  • ✅ T035 KEYSTONE simple-case test: all-targets-base on a real single-branch repo (WP08)
  • ✅ T036 Assert every fragment == base; ZERO coord/lane paths; byte-identical to flat (WP08)
  • ✅ T037 FR-005 boundary ratchet (optional): flag write-side re-derivation in adopted modules (WP08)
  • ✅ T038 Confirm SC-007/SC-008 green on the merged adoption tree (WP08)

WP09 — Branch-target user documentation (IC-DOCS, FR-009)

Goal: Author the user-facing Explanation page (Divio "Explanation") presenting the branch-target routing table ("this is where everything goes") + the simple case, demystifying lane behaviour. SOURCE docs/; docs-freshness page-inventory row. Priority: P2. Independent test: docs-freshness green; the page classifies as Explanation. Dependencies: none (describes the finalized behaviour from the spec; land any time).

  • ✅ T039 Author the Explanation page: routing table + simple case (WP09)
  • ✅ T040 Add the docs-freshness page-inventory row; docs-freshness green (WP09)
  • ✅ T041 Cross-link from the lanes/coordination explanation surface (WP09)

Dependencies & Parallelization

WP01 (net, FIRST) ──┬─> WP02 (lock-root)   ─┐
                    ├─> WP03 (le+store)     │
                    ├─> WP04 (placement)    ├─> WP08 (keystone + ratchet, LAST)
                    ├─> WP05 (coord) ───────┘
                    ├─> WP06 (lanes) ───────┘
                    └─> WP07 (retire)
WP09 (docs) ── independent, any time

root/surface/target) prove read/write symmetry end-to-end.

  • After WP01: WP02–WP07 run in parallel (disjoint owned files); WP09 anytime.
  • WP08 lands last (integration keystone + the ratchet that must post-date adoption).
  • MVP: WP01 + WP02 + WP05 (the net + the lock-root family + the highest-risk coordination

Requirement Coverage

WPFRs
WP01(NFR-001/002 enabler)
WP02FR-001
WP03FR-001
WP04FR-002
WP05FR-001, FR-003, FR-004, FR-007
WP06FR-008
WP07FR-006
WP08FR-005
WP09FR-009

Cross-cutting NFRs (no single owning WP — embedded in every WP's DoD + the acceptance-matrix negative invariants NI-1..5): NFR-003 (function-over-form / verification-by-deletion — the method of every adoption WP), NFR-004 (idempotency — WP04/WP05/WP06 before/after assertions), NFR-005 (ruff/mypy ≤15, no suppressions — every WP). NFR-001/002 (symmetry / topology-true fixtures) are owned by WP01.