Implementation Plan: Write-Side Context-Factory Adoption (Mission B)
Branch: feat/write-side-context-factory-adoption (stacked on feat/read-path-error-fidelity / Mission A, PR #2015) | Date: 2026-06-17 | Spec: kitty-specs/write-side-context-factory-adoption-01KV9W0X/spec.md Input: Feature specification from /kitty-specs/write-side-context-factory-adoption-01KV9W0X/spec.md
Summary
Adopt the write execution path onto Mission A's frozen build_execution_context factory + its projected fragments — completing the read/write symmetry. Phase 0 (research/write-site-inventory.md + research/reduction-census.md, verified on HEAD efb28158f) established the pivotal fact: the write-half fragment fields already EXIST and RESOLVE to the correct authority — workspace.primary_root (the .parent.parent killer), status_surface.status_write_dir (resolves to the coord/status authority via resolve_status_surface, fail-closed, NOT primary_root — C-007 correct), and branch_ref.destination_ref. So Mission B is PURE CONSUMER-ROUTING: flip the hand-rolled write-side re-derivations to read these fragments. C-001 holds — the factory authority modules are NOT modified (the only mission_runtime/ touch is FR-006 deletion of dead scaffolding). The four adoption-target fragments have 0 live readers today; adoption flips them load-bearing (verification-by-deletion is the proof).
Technical Context
Language/Version: Python 3.11+ Primary Dependencies: typer, rich, ruamel.yaml, subprocess/pygit2 git, pytest, mypy, ruff (existing spec-kitty stack) Storage: filesystem — three artifact families (meta/primary, status/coord, lanes/coord); git worktrees/coord branches Testing: pytest; function-over-form behavioral + verification-by-deletion; topology-true fixtures (real 26-char ULID + real coord-worktree + real submodule); TDD-first; idempotency-preserving Target Platform: Linux/macOS developer + CI (the spec-kitty CLI) Project Type: single (CLI tool — src/specify_cli/ + src/mission_runtime/) Performance Goals: N/A (correctness/symmetry mission); resolution stays O(existing) Constraints: read/write behavioral equivalence (NFR-001); idempotency-preserving — no on-disk worktree/coord churn (NFR-004); three-artifact-family preservation (C-007); bounded conflict surface (named write seam files only); ruff+mypy clean, complexity ≤15, no suppressions (NFR-005) Scale/Scope: 8 write sites across 6 owning files; ~90–130 LOC (root-walk ~40–55, placement ~15–25, surface consumption ~20–30, FR-006 deletion ~10–15); 7 implementation concerns; no schema/data migration
Charter Check
GATE: software-dev-default charter (compact). DIR-001..DIR-013. Re-checked post-design — clean.
- DIR-001 (one owning module per concern): satisfied — each write site is its own module;
coordination/status_transition.py(R5+S1) is solely WP-COORD. - DIR-003 (decisions documented): D-1..D-5 below; the #1716 defer reuses Mission A's decision
01KV8Q49WEG9RRKCEZ3XYN5DWP. - DIR-031 (bounded-context translation): routing to factory projections is pure consumption — no new authority (C-001);
branch_namingstays a collaborator. - No charter conflicts.
Decisions (locked)
→ workspace.primary_root), P1 (core/worktree.py placement join ×2 → factory placement projection), S1 surface + target consumption (status_transition.py status write → status_surface.status_write_dir coord authority + write-target → branch_ref.destination_ref, D-2), FR-008 lanes/coord adoption (D-6), FR-006 retirement, and the NFR-006 simple-case test (D-7). ~130–200 LOC (the bounded ~90–130 + ~40–70 for the write-target + lanes). DEFER only the S2 write-surface-SELECTION ladder (_read_contract_from_transaction_target, the #1716 ~2094-LOC topology authority root) — it computes the same value the factory already does (cleanly deferrable; reduction-not-symmetry).
write/merge-target is the core of the Lane-based branch-target context object, so it finalizes here: the S1 inline coord_branch or _current_branch selector is replaced by branch_ref.destination_ref. randy's divergence (flattened arm destination_ref=target_branch CWD-invariant vs _current_branch=git HEAD) is the latent bug the adoption fixes — guarded by two binding tests: (a) the simple-case "all-targets-base → flat" test (NFR-006, D-7) and (b) a before/after on-disk-target idempotency test (NFR-004). The earlier defer is reversed because NFR-006 is exactly the missing guard that makes the write-target safe to flip. The S2 write-surface-SELECTION ladder stays deferred (#1716, D-1).
concern already fixed by Mission A WP09 (d4f0cf581, commands.py:282-311); not reachable via Mission B's write fragments. Cross-ref + verify-don't-redo; folding a closed read concern into a write mission violates C-003/C-007.
(work_package_lifecycle.py) are now byte-identical topology-aware lock-root resolvers — routing both to workspace.primary_root is the consolidation (the duplication collapses naturally; no shared-owner WP needed).
read==write resolution for root + surface + target.
(lanes.json, coord authority per C-LANES-1) through the context's coord surface — resolve_lanes_dir(<coord feature dir from status_surface>). Prefer deriving from the existing status_surface fragment + the resolve_lanes_dir seam (both already exist) over adding a raw factory field — keeps it consumer-routing / minimal factory touch (C-001). The resolve_lanes_dir pure seam is already done (Mission A); this is the consumer adoption + (if needed) a thin lanes projection.
real single-branch repo (full ULID, no coord, no lanes): every fragment resolves to base, zero coord/lane paths read or written, behavior byte-identical to the historical pre-lane flat path. This is the guard that makes the write-target (D-2) safe and proves the branch-target object degrades cleanly.
ratchet flags zero offenders) and #2016 is fixed by Mission A WP09 — both cross-ref/verify-already-fixed, close on #2015 merge; NOT new WPs. (The stale ratchet doctor.py allow-list drift found during this check was boy-scout-fixed on this branch.)
made mechanical by two pre-WPs landed BEFORE the adoption ICs: IC-CHARNET (the topology-true characterization net — 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 divergence has ZERO witnessing test; add tests that drive WITHOUT explicit repo_root on real coord/submodule topologies — the gate that makes every later deletion provable) and IC-DEDUP (PR-1: extract the byte-identical emit::_feature_status_lock_root ≡ wpl::_repo_root_for_lock into one shared helper; lands before WP-EMIT/WP-WPL fan out — the only cross-file ownership hazard). FR-006 dead-code (PR-4) + the tests that ENCODE its deletion targets as contracts (paula S-2/S-3) retire atomically in IC-RETIRE; the private-helper lock-root tests (paula S-4/S-9) get a public behavioral invariant added FIRST, then retire. PR-3 (store tidy) + PR-5 (placement helper) are in-WP boy-scout. SKIP (#1716 ~2094-LOC topology authority, out of scope): PR-7 _identity_for_request tidy + PR-8 the S2 selection ladder. RISKY/defer to IC-COORD (not a behavior-preserving pre-refactor): the _repo_root_for_feature→coord-target unification (it's the FR-004 adoption itself, behind the NFR-006/idempotency guards). Net pre-refactor ~−86 LOC, behavior-preserving.
DEFAULT — adjacent failing tests / lint / type issues an implementer hits are fixed in the same change, not litigated pre-existing-vs-introduced or left under "minimal diff." Every WP prompt carries this.
case, demystifying lane behaviour (IC-DOCS); inventoried for docs-freshness.
adoption does NOT thread an ExecutionContext into the write sites and does NOT consume the composite fragment object (the write-site files don't hold a context, and resolve_action_context requires an action token / fails closed — not a write-path entry). Instead each write site routes to the existing public pure resolver that already sources the fragment value (verified on HEAD eba2448d8): primary_root → specify_cli.core.paths.resolve_canonical_root / get_main_repo_root; status_write_dir → coordination.surface_resolver.resolve_status_surface (fail-closed, coord authority); destination_ref / placement → mission_runtime.resolution.resolve_placement_only (already exported; its docstring states it returns the same destination_ref CommitTarget the builder computes); lanes → lanes.persistence.resolve_lanes_dir. These resolvers already exist and are public — adoption is an import + call, NOT a new authority and NOT an edit to build_execution_context/resolution.py (C-001 fully holds; the only resolution.py touch remains WP07's FR-006 deletion). Symmetry proof is reframed accordingly: read and write call the same pure resolver (the single sourced path), which is what makes them equivalent — see the SC-002 reframe.
- D-1 (C-003 scope — finalize the branch-target context object): R1–R5 (
.parent.parentroot walks - D-2 (FR-004 write-target IN-SCOPE — operator decision 2026-06-17, REVERSES the prior defer): the
- D-3 (#2016 — CROSS-REF, do not fold): the orchestrator coord-read identity bootstrap is a READ-path
- D-4 (W9/W10 duplication collapse): R1 (
emit.py::_feature_status_lock_root) and R2 - D-5 (NFR-001 equivalence gate): a topology-true parameterized test (primary/coord/submodule) proving
- D-6 (#1993 lanes/coord adoption — FR-008, folded in 2026-06-17): route the lanes-dir write
- D-7 (simple-case test — NFR-006, KEYSTONE, BINDING): a dedicated "all-targets-base → flat" test on a
- D-8 (#2000 / #2016 — verified-already-fixed): #2000's composes are already routed (naming-rider #2012;
- D-9 (clean-before-touch pre-refactors — squad
research/pre-refactor/, land FIRST): the adoption is - D-10 (Fix, don't litigate — C-008 / #1970 / DIRECTIVE_025, BINDING stance): campsite-cleaning is the
- D-11 (user-doc — FR-009): an Explanation page presenting the branch-target routing table + the simple
- D-12 (adoption MECHANISM — route to the existing PUBLIC pure resolver, post-tasks squad alphonso B-1/B-2):
Project Structure
src/specify_cli/
├── status/ # WP-EMIT emit.py · WP-WPL work_package_lifecycle.py · WP-LE lifecycle_events.py · WP-STORE store.py · WP-RETIRE aggregate.py
├── coordination/ # WP-COORD status_transition.py
├── core/ # WP-WT worktree.py
src/mission_runtime/ # WP-RETIRE resolution.py + context.py (FR-006 deletion ONLY — factory NOT modified, C-001)
tests/ # behavioral + topology-true fixtures; FR-005 architectural-guard extension in tests/architectural/
Structure Decision: Single-project CLI. No new packages. The factory (Mission A's, on the stacked branch) is consumed, not modified.
Implementation Concern Map
> Concerns are NOT work packages. Seven concerns, zero owned_files overlap. Pure consumer-routing onto > the existing factory — no precondition concern (the factory is already merged), so all are independently > parallelizable. branch_naming + the factory authority modules are OUT (C-001).
IC-EMIT — status/emit.py lock-root adoption (R1)
- Purpose: Replace the
_feature_status_lock_root.parent.parentwalk withworkspace.primary_root(meta/primary read). Collapses the W9/W10 duplication (D-4). - Relevant requirements: FR-001 · Owns:
src/specify_cli/status/emit.py· Family: meta/primary · Depends: none · Risk: low.
IC-WPL — status/work_package_lifecycle.py root adoption (R2)
- Purpose: Replace the 3×
.parent.parentroot walk withworkspace.primary_root. - FR-001 · Owns:
src/specify_cli/status/work_package_lifecycle.py· meta/primary · none · low.
IC-LE — status/lifecycle_events.py root adoption (R3)
- Purpose: Replace the
.parent.parent/.parent.parent.parentwalks withworkspace.primary_root. - FR-001 · Owns:
src/specify_cli/status/lifecycle_events.py· meta/primary · none · low.
IC-STORE — status/store.py ancestor-scan adoption (R4)
- Purpose: Replace the
KITTY_SPECS_DIRancestor scan withworkspace.primary_root. - FR-001 · Owns:
src/specify_cli/status/store.py· meta/primary · none · low.
IC-WT — core/worktree.py placement adoption (P1)
- Purpose: Replace the two
feature_dir = worktree_path / KITTY_SPECS_DIR / branch_nameplacement joins (:384reuse arm,:396create arm) with the factory placement projection (CommitTarget/ArtifactPlacementFragment); naming stays via themission_dir_nameseam. - FR-002 · Owns:
src/specify_cli/core/worktree.py· meta/primary · none · low-med (placement wiring).
IC-COORD — coordination/status_transition.py root + surface + write-target adoption (R5 + S1)
- Purpose: Replace
_repo_root_for_feature's.parent.parentwalk (R5) withworkspace.primary_root; route the status write surface tostatus_surface.status_write_dir(the coord authority — C-007); replace the inlinecoord_branch or _current_branchwrite-target withbranch_ref.destination_ref(FR-004/D-2). DEFER only the S2 selection ladder (#1716). Reduces the second parallel factory (_identity_for_request) to consume the projection. - FR-003, FR-004, FR-007 · Owns:
src/specify_cli/coordination/status_transition.py· status/coord + meta/primary · none · med-high (write-surface + write-target — topology-true fixtures mandatory NFR-002; status writes stay on the coord authority, neverprimary_root; the write-target flip carries the idempotency before/after test).
IC-LANES — lanes/coord adoption (FR-008, #1993 deeper grain)
- Purpose: Route the lanes-dir write (
lanes.json, coord authority per C-LANES-1/#1991) through the context's coord surface —resolve_lanes_dir(<coord feature dir fromstatus_surface>). Prefer deriving from the existingstatus_surface+ theresolve_lanes_dirseam (D-6) over a raw factory field (C-001). Completes the third artifact family. - FR-008 · Owns: the lanes-dir write callsite(s) — pinned at Phase-0/tasks (
cli/commands/implement.py_lanes_feature_dirC-LANES-1 region + any peer); + a thin lanes projection inlanes/persistence.pyif needed · lanes/coord · none · low-med.
IC-SIMPLECASE — the "simple case still works" test (NFR-006, KEYSTONE)
- Purpose: The dedicated binding test (D-7): all-targets-base → flat. On a real single-branch repo (full ULID, no coord, no lanes), assert every adopted fragment resolves to base, zero
.worktrees//coord paths read or written, behavior byte-identical to the historical pre-lane flat path. Guards the write-target (D-2) + proves clean degradation. - NFR-006, SC-007 · Owns: a new
tests/module (e.g.tests/specify_cli/coordination/test_simple_case_flat_topology.py) — test-only, no src overlap.
IC-RETIRE — fragment-scaffolding retirement (FR-006)
- Purpose: Delete the genuinely-dead
prompt_sourcefragment (resolution.py:761-778,context.py:177-181) and the deadStatusSurfaceFragment.surface=read-param wiring (aggregate.py:199,266,309+ theif surface is not Nonebranch). Deletion only — no authority change (C-001); the twoMissionStatus.load()callers never passsurface=. - FR-006 · Owns:
src/mission_runtime/resolution.py,src/mission_runtime/context.py,src/specify_cli/status/aggregate.py· n/a · none · low.
IC-CHARNET — characterization net (clean-before-touch, FIRST — D-9)
- Purpose: Add the topology-true characterization tests that make every later deletion provable, fixing paula's live-evidence trap (the suites pass
repo_root=so they're blind to the swap; the FR-004 divergence has no witnessing test). Tests MUST drive without explicitrepo_rooton real coord-worktree + submodule topologies (full ULID): the FR-004 before/after divergence (S-1, RED-on-HEAD), a coord-topology parity fixture (S-5), a submodule test (S-6),store.py::_find_mission_specs_rootcoverage (S-7), real-coord lanes placement (S-8). - NFR-001/002, SC-002 enabler · Owns: new test modules under
tests/(test-only, no src overlap) · Sequence: FIRST.
IC-DEDUP — byte-identical lock-root de-dup (clean-before-touch — D-9, PR-1)
- Purpose: Extract the byte-identical
status/emit.py::_feature_status_lock_root≡status/work_package_lifecycle.py::_repo_root_for_lockinto ONE shared helper (e.g.workspace/root_resolver.py); both import it. Behavior-preserving (~−28 LOC). Add the public lock-root behavioral invariant (paula S-4/S-9) before retiring the private-helper-by-name tests. - (pre-FR-001 enabler) · Owns:
src/specify_cli/workspace/root_resolver.py(the new shared helper) + the two extraction edits · Sequence: before WP-EMIT/WP-WPL fan out (the only cross-file ownership hazard — land it first so each adoption lane edits only its own callsite).
IC-DOCS — branch-target user documentation (FR-009 — D-11)
- Purpose: 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. Edit SOURCE
docs/; add the docs-freshness page-inventory row. - FR-009, SC-009 · Owns: a new
docs/explanation/page + thedocs/development/3-2-page-inventory.yamlrow · Sequence: any time (independent; describes the finalized behaviour — land late).
Cross-cutting: FR-005 (boundary-contract enforcement) — verified by deletion across IC-EMIT/WPL/LE/STORE/WT/COORD (no write site re-derives mission_id/mid8/primary_root after adoption); an optional ratchet extension in tests/architectural/ flags write-side re-derivation. SC-002 (0→load-bearing) is proven once the consumers land. C-008 Fix-don't-litigate is in every WP prompt.
Sequencing (clean-before-touch first)
IC-CHARNET (characterization net — makes deletions provable) ─┐ land FIRST
IC-DEDUP (lock-root shared helper — before EMIT/WPL fan out) ─┘
└─> IC-EMIT / IC-WPL (now a one-line swap to the shared helper / primary_root)
IC-LE / IC-STORE (root walks) ┐
IC-WT (placement) ├─ parallel (disjoint files, consume the factory)
IC-COORD (root + surface + write-target, med-high — D-5 equivalence + idempotency tests)
IC-LANES (lanes/coord) │
IC-RETIRE (FR-006 deletion + retire S-2/S-3 tests atomically) ┘
IC-SIMPLECASE (the keystone flat test) + IC-DOCS — any time.
IC-CHARNET + IC-DEDUP are the two clean-before-touch pre-WPs (D-9). The adoption ICs then each consume the already-merged factory via its public resolver (D-12 — an import + call, not a shared edit). IC-COORD is highest-risk (write-surface + write-target).
IC → WP collapse (12 ICs → 9 WPs in tasks.md): IC-CHARNET → WP01; IC-DEDUP + IC-EMIT + IC-WPL → WP02 (one WP because all three touch emit.py/work_package_lifecycle.py — separate WPs would overlap ownership); IC-LE + IC-STORE → WP03 (two tiny same-concern status sites); IC-WT → WP04; IC-COORD → WP05; IC-LANES → WP06; IC-RETIRE → WP07; IC-SIMPLECASE (+ the FR-005 ratchet) → WP08; IC-DOCS → WP09.
Post-planning brownfield check
Run before tasks (standing rule). Outcome (2026-06-17):
matrix — #1716/#1878/#1619 (in-mission/deferred) and #2016 (cross-ref) are the complete set. No net-new fold.
second parallel write factory (status_transition.py::_identity_for_request + CoordinationWorkspace) is the documented duplication; ~90–130 LOC bounded, owning files pinned, no new split introduced.
(prompt_source_dir context.py:181; the surface= read-param aggregate.py:199,309). One ORTHOGONAL DeprecationWarning at core/worktree.py:304 (IC-WT's file) is unrelated to placement adoption — IC-WT may boy-scout it if due, but it is NOT a Mission B concern.
- Foldable issues: gh sweep for OPEN write-side/status/coord/topology tickets returned none beyond the
- Split-brain / LOC: the inventory (
research/write-site-inventory.md) IS the split-brain map — the - Deprecations: the FR-006 targets are confirmed due deprecations retired by IC-RETIRE
- Path drift: none — all owned files exist; line drift corrected in
research/write-site-inventory.md§6.
Complexity Tracking
No Charter Check violations — table intentionally empty.
Out of Scope
root (~2094 LOC); #1878 finalize/merge ff-advance bookkeeping. (FR-004 write-target is now IN scope, D-2.)
- The S2 write-surface-SELECTION ladder (
_read_contract_from_transaction_target) / #1716 topology authority - Any change to
build_execution_context/branch_namingbeyond consuming the projection + FR-006 deletion (C-001). - #2016 (read-path, fixed by Mission A WP09); patch-version assignment (C-004).