Tasks: Write-Surface Coherence
Mission: write-surface-coherence-01KVTVZS Branch: feat/write-surface-coherence Input: spec.md · plan.md · data-model.md · contracts/placement-bifurcation.md · research.md
Make the write side of the planning lifecycle kind-aware (the read side already is via MissionArtifactKind / artifact_home_for), and re-partition the planning + identity kinds onto the primary target_branch for every topology shape. Status/bookkeeping kinds stay on the coordination branch for coord topology. The frozenset partition (_PRIMARY_ARTIFACT_KINDS / _PLACEMENT_ARTIFACT_KINDS in src/mission_runtime/artifacts.py) is the single swappable locus (NFR-004). Forward-only (C-003); behavior-neutral for flattened missions (NFR-001).
Subtask Index
| ID | Description | WP | Parallel |
|---|---|---|---|
| T001 | Add _PRIMARY_ARTIFACT_KINDS frozenset; move planning+identity kinds out of _PLACEMENT_ARTIFACT_KINDS | WP01 | — |
| T002 | Make artifact_home_for route primary kinds → primary write/commit | WP01 | — |
| T003 | Make resolve_placement_only kind-aware (accept MissionArtifactKind, internal seam) | WP01 | — |
| T004 | Confirm partition membership of LANE_STATE/ACCEPTANCE_MATRIX/ANALYSIS_REPORT | WP01 | — |
| T005 | Red-first partition unit tests (SPEC→target_branch, STATUS_STATE→coord; flat unchanged) | WP01 | — |
| T006 | Thread REQUIRED kind through commit_for_mission (no default — DECISION 1) | WP02 | — |
| T007 | spec_commit_cmd + _commit_artifact_to_branch callers pass kind | WP02 | — |
| T008 | mission.py inline commit tails (~2345/2392/3825) + record-analysis (~1919) pass kind | WP02 | — |
| T009 | tasks.py map-requirements caller passes kind; acceptance meta commit passes PRIMARY_METADATA | WP02 | — |
| T010 | Remove the planning→coord route in the router use_coord arm | WP02 | — |
| T011 | Red-first caller-convergence test (planning caller → primary on coord fixture) | WP02 | — |
| T031 | Thread explicit kind at all 7 resolve_placement_only sites (status/base reads stay coord) | WP02 | — |
| T032 | Converge direct-safe_commit planning writers (tasks.py:2438/3076) + retire _skip_target_commit 2nd router | WP02 | — |
| T012 | _resolve_mission_aware_target (safe-commit) consults the kind authority | WP03 | — |
| T013 | append-history (commands.py:1283/1340) consults the kind authority | WP03 | — |
| T014 | Converge _planning_commit_worktree (mission.py:775) onto the partition | WP03 | — |
| T015 | FR-008: refuse a primary-kind commit to a protected target_branch | WP03 | — |
| T016 | Red-first bypass-writer + protected-primary tests | WP03 | — |
| T017 | Stop the read path consulting the coord husk for primary-partition kinds | WP04 | ∥ WP02/03 |
| T018 | Preserve C-005 KEEP transients (#1718 create-window, #1848 coord-deleted) | WP04 | ∥ WP02/03 |
| T019 | Red-first stale-coord-shadow test (#2062 class) for a planning artifact | WP04 | ∥ WP02/03 |
| T020 | Govern _materialise_coord_worktree staging + _try_advance_ref for status-only coord writes | WP05 | — |
| T021 | Remove dead ff-advance on the planning path; no orphaned target_branch param | WP05 | — |
| T022 | Confirm residue dirty-filter (is_coordination_artifact_residue_path) stays correct | WP05 | — |
| T023 | Red-first helper-governance test (status coord write still ff-advances; planning does not transit) | WP05 | — |
| T024 | Route the ~3 inline json.loads(meta…read_text()) reads in mission.py through canonical load_meta | WP06 | — |
| T025 | Name/reconcile the duplicate load_meta at task_utils/support.py:363 | WP06 | — |
| T026 | SC-004 contract-pinned test (canonical load_meta is the import; zero inline reads in touched modules) | WP06 | — |
| T027 | Behavioral two-ref guard across commit_for_mission + a bypass writer + _planning_commit_worktree | WP07 | — |
| T028 | Flattened-regression proof (NFR-001) | WP07 | — |
| T029 | FR-007 end-to-end coord-topology mission maps 100% requirements | WP07 | — |
| T030 | FR-008 protected-primary refusal end-to-end | WP07 | — |
WP01 — Kind-aware placement authority + re-partition
Prompt: tasks/WP01-kind-aware-placement-authority.md
Summary
- Goal: Make the write-side placement consult the existing
MissionArtifactKindmodel and re-partition the planning + identity kinds onto primary by introducing_PRIMARY_ARTIFACT_KINDS. The frozenset partition is the single swappable locus (NFR-004). - Priority: P0 — foundation; every other WP depends on it.
- Independent test: Unit test on
artifact_home_for/resolve_placement_onlyover a coord-topology fixture:SPEC→target_branch,STATUS_STATE→ coordination, flat unchanged.
Tracking
- ✅ T001 Add
_PRIMARY_ARTIFACT_KINDSfrozenset; move planning+identity kinds out of_PLACEMENT_ARTIFACT_KINDS(WP01) - ✅ T002 Make
artifact_home_forroute primary kinds → primary write/commit (WP01) - ✅ T003 Make
resolve_placement_onlykind-aware (acceptMissionArtifactKind, internal seam) (WP01) - ✅ T004 Confirm partition membership of
LANE_STATE/ACCEPTANCE_MATRIX/ANALYSIS_REPORT(WP01) - ✅ T005 Red-first partition unit tests (SPEC→target_branch, STATUS_STATE→coord; flat unchanged) (WP01)
Implementation sketch
src/mission_runtime/artifacts.py: add_PRIMARY_ARTIFACT_KINDS = frozenset({SPEC, DATA_MODEL, RESEARCH, CHECKLIST, FINALIZED_EXECUTION_PLAN, TASKS_INDEX, WORK_PACKAGE_TASK, LANE_STATE, PRIMARY_METADATA}); remove those members from_PLACEMENT_ARTIFACT_KINDS(lines 78-93);artifact_home_for(115-138) returns a primary home (write_surface="primary",commit_target=None/target ref) for primary kinds before the placement branch.src/mission_runtime/resolution.py:resolve_placement_only(1013-1106) gains a REQUIRED keywordkind: MissionArtifactKind(DECISION 1 — NO default; a default would silently flip un-threaded callers coord→primary); consults the partition to decide primary vs placement. Internal seam only (NFR-003). A no-kind call raisesTypeError(pinned by T005).- Negative scope: NO migration logic (FR-010 / C-003).
Dependencies: none. Estimated prompt size: ~260 lines.
WP02 — Converge commit_for_mission write path
Prompt: tasks/WP02-converge-commit-for-mission.md
Summary
- Goal: Thread the kind through
commit_for_missionand all its planning callers; remove the planning→coordination route in the router arm so planning artifacts commit to the primarytarget_branch. - Priority: P0.
- Independent test: A planning caller (e.g.
spec-commit) on a coord-topology fixture lands its commit ontarget_branch, not the coordination branch.
Tracking
- ✅ T006 Thread REQUIRED
kindthroughcommit_for_mission(no default — DECISION 1) (WP02) - ✅ T007
spec_commit_cmd+_commit_artifact_to_branchcallers pass kind (WP02) - ✅ T008 mission.py inline commit tails (~2345/2392/3825) + record-analysis (~1919) pass kind (WP02)
- ✅ T009 tasks.py
map-requirementscaller passes kind; acceptance meta commit passesPRIMARY_METADATA(WP02) - ✅ T010 Remove the planning→coord route in the router
use_coordarm (WP02) - ✅ T011 Red-first caller-convergence test (planning caller → primary on coord fixture) (WP02)
- ✅ T031 Thread explicit
kindat all 7resolve_placement_onlysites (status/base reads stay coord) (WP02) - ✅ T032 Converge direct-
safe_commitplanning writers (tasks.py:2438/3076) + retire_skip_target_commit2nd router (WP02)
Implementation sketch
commit_router.py:82-206:commit_for_missionaccepts a REQUIREDkind(DECISION 1 — no default), forwards toresolve_placement_only(repo_root, mission_slug, kind=kind); theuse_coordarm (124) becomes kind-aware (primary kinds never route through coordination). The protected-ref refusal message is left wired (WP03 T015 rewrites its text).spec_commit_cmd.py:155and mission.py_commit_artifact_to_branch(~1092), tails (2345/2392/3825), record-analysis (1919): pass the artifact kind (no silentSPECfallback).tasks.py:3870map-requirements: pass kind.acceptance/__init__.py_commit_acceptance_meta_via_router(~1398): passPRIMARY_METADATA.- T031 (DECISION 2): all 7
resolve_placement_onlysites threadkind. Status/base reads stay coord:status_transition.py:332(STATUS_STATE),tasks.py:359(review-currency base),commands.py:796(lane-base gate)._resolve_write_targetcoord test added. - T032 (DECISION 3): the hardcoded
safe_commit(target=CommitTarget(ref=target_branch))planning writes attasks.py:2438(move-task:WORK_PACKAGE_TASK) andtasks.py:3076(tasks.md:TASKS_INDEX) route through the kind authority;_skip_target_commit(tasks.py:2425) is retired/narrowed so it is not a 2nd routing authority (G-1).
Owned files note: WP02 adds src/specify_cli/coordination/status_transition.py (the _resolve_write_target status caller, T031). It also touches the direct-safe_commit planning writers in agent/tasks.py (already owned).
Dependencies: WP01. Estimated prompt size: ~302 lines (was ~300; +T031/T032 added, under the <700 ceiling).
WP03 — Converge bypass writers + 2nd routing authority + FR-008
Prompt: tasks/WP03-converge-bypass-writers-fr008.md
Summary
- Goal: Converge the writers that bypass
commit_for_mission(safe-commit,append-history) and the second routing authority (_planning_commit_worktree) onto the partition; enforce FR-008 protected-primary refusal. - Priority: P0.
- Independent test:
safe-commitof aspec.mdon a coord-topology fixture lands ontarget_branch; a primary-kind commit to a protectedtarget_branchis refused.
Tracking
- ✅ T012
_resolve_mission_aware_target(safe-commit) consults the kind authority (WP03) - ✅ T013
append-history(commands.py:1283/1340) consults the kind authority (WP03) - ✅ T014 Converge
_planning_commit_worktree(mission.py:775) onto the partition (WP03) - ✅ T015 FR-008: refuse a primary-kind commit to a protected
target_branch(WP03) - ✅ T016 Red-first bypass-writer + protected-primary tests (WP03)
Implementation sketch
safe_commit_cmd.py:192-209_resolve_mission_aware_target: resolve with the planning kind; the result is primary for planning artifacts under coord topology.orchestrator_api/commands.py:1260-1303_resolve_history_commit_args: WP prompt files areWORK_PACKAGE_TASK(a primary kind) → primary placement; retire theroutes_through_coordinationarm for that kind.mission.py:775_planning_commit_worktree: consult the partition (retire the independentroutes_through_coordinationdecision for primary kinds → return(repo_root, paths)).- FR-008 guard: a primary-kind commit whose resolved
target_branchis protected is refused (thesafe_commitstep-6ProtectedBranchRefusedpath), with feature-branch guidance. - T015 (DECISION 5 — MANDATORY message rewrite): REPLACE the coord-transit guidance in BOTH
commit_router.py:126-137(router refusal) ANDcommit_helpers.py:285(ProtectedBranchRefused) withmission create --start-branch <feature-branch>guidance. Test asserts each message contains "feature branch" and NOT "coordination worktree".
Owned files note: WP03 adds src/specify_cli/coordination/commit_router.py and src/specify_cli/git/commit_helpers.py (the two FR-008 message sites, T015). commit_router.py is shared with WP02/WP05 (serialized).
Dependencies: WP02. Estimated prompt size: ~224 lines.
WP04 — Planning read-path residue
Prompt: tasks/WP04-planning-read-path-residue.md
Summary
- Goal: Stop the planning read path consulting the coordination husk for primary-partition kinds once writes are primary-always; preserve the C-005 KEEP transients.
- Priority: P1 — runs in PARALLEL with WP02/WP03 (depends only on WP01).
- Independent test: Red-first stale-coord-shadow test (#2062 class) — a stale
-coordplanning copy must not shadow primary truth.
Tracking
- ✅ T017 Stop the read path consulting the coord husk for primary-partition kinds (WP04)
- ✅ T018 Preserve C-005 KEEP transients (#1718 create-window, #1848 coord-deleted) (WP04)
- ✅ T019 Red-first stale-coord-shadow test (#2062 class) for a planning artifact (WP04)
Implementation sketch
- REAL per-kind read split (DECISION 4 — not a no-op): planning-artifact reads route to the PRIMARY feature dir (
primary_feature_dir_for_mission, the sanctioned pattern atmission.py:830/1226/1273/1903); status reads keep the topology-aware seam (resolve_handle_to_read_path:843). Identify the planning-read callers going through the topology seam and route them to primary; clean the husk residue (_read_path_resolver.py:961-963consults_coord_husk, thetopology is Nonearms;surface_resolver.py:473/503-528). - Preserve C-005 KEEP transients (#1718 create-window, #1848 coord-deleted) — only PLANNING-kind reads change.
- DoD: a line-cited deliverable naming the exact resolver line(s) changed (or, per-caller, the line proving already-primary). NO whole-WP green-pin / "no change needed"; the coord-topology stale-husk T019 variant is red-first.
Dependencies: WP01 (parallel with WP02/WP03). Estimated prompt size: ~205 lines.
WP05 — Shared coord-worktree helper governance
Prompt: tasks/WP05-coord-worktree-helper-governance.md
Summary
- Goal: Make the coord-worktree helpers correct once planning no longer transits coord — staging,
_try_advance_refff-advance (#1878), residue filter apply to status-only coord writes; remove dead ff-advance on the planning path. - Priority: P1.
- Independent test: A status-only coord write still ff-advances; a planning commit goes direct to
target_branchwith no coord transit and no orphanedtarget_branchparam.
Tracking
- ✅ T020 Govern
_materialise_coord_worktreestaging +_try_advance_reffor status-only coord writes (WP05) - ✅ T021 Remove dead ff-advance on the planning path; no orphaned
target_branchparam (WP05) - ✅ T022 Confirm residue dirty-filter (
is_coordination_artifact_residue_path) stays correct (WP05) - ✅ T023 Red-first helper-governance test (status coord write ff-advances; planning does not transit) (WP05)
Implementation sketch
commit_router.py:_materialise_coord_worktree(214-263) and_try_advance_ref(358-392) now only fire for COORD-partition writes; theuse_coord and target_branchff-advance gate (199) must no longer be reachable on a planning commit. T020 (DECISION 8): add a RUNTIME guard (assert kind not in _PRIMARY_ARTIFACT_KINDSor a typed raise) at the staging entry — not a comment — covered by a T023 test.mission.py:775_planning_commit_worktree: after WP03 it returns(repo_root, paths)for primary kinds — verify no dead coord staging path or unusedtarget_branchplumbing remains.is_coordination_artifact_residue_path(artifacts.py:141): SPEC/DATA_MODEL/RESEARCH are now primary kinds — confirm the residue filter still classifies only COORD-partition files as residue (the partition moved, so these are no longer coord residue).
Dependencies: WP03. Estimated prompt size: ~250 lines.
WP06 — Meta-reader sweep (in-mission)
Prompt: tasks/WP06-meta-reader-sweep.md
Summary
- Goal: Route the ~3 inline
json.loads(meta…read_text())reads in mission.py through canonicalload_meta; name/reconcile the duplicateload_metaattask_utils/support.py:363. - Priority: P2.
- Independent test: SC-004 contract-pinned test — the canonical
load_metais the import in the touched modules; zero inlinemeta.jsonreads remain there.
Tracking
- ✅ T024 Route the ~3 inline
json.loads(meta…read_text())reads in mission.py through canonicalload_meta(WP06) - ✅ T025 Name/reconcile the duplicate
load_metaattask_utils/support.py:363(WP06) - ✅ T026 SC-004 contract-pinned test (canonical
load_metais the import; zero inline reads in touched modules) (WP06)
Implementation sketch
mission.py:442,:1647,:3487: replacejson.loads(meta_*.read_text(...))withload_meta(...)fromspecify_cli.mission_metadata(the canonical authority).task_utils/support.py:363already delegates to_load_meta_canonical(mission_metadata.load_meta) but exposes a path-taking shim — namemission_metadata.load_metaas canonical; do NOT fork. Document the shim's distinct signature (path vs dir).- Keep to in-mission sites; the ~53-site #2100 backlog stays deferred.
- T026 (DECISION 7): BEHAVIORAL test only — feed a malformed
meta.jsonthrough each of the 3 converted sites via their pre-existing entry points; assertload_meta-contract degradation (not rawJSONDecodeError). DROP the source-grep / module-count option.
Dependencies: WP05. Estimated prompt size: ~190 lines.
WP07 — Behavioral verification
Prompt: tasks/WP07-behavioral-verification.md
Summary
- Goal: Prove the bifurcation behaviorally (two-ref guard) across every converged write site, guard the flattened regression, and verify FR-007 end-to-end + FR-008 refusal.
- Priority: P0 (final gate).
- Independent test: Behavioral two-ref guard passes — planning-kind →
target_branchAND status-kind → coordination — exercised acrosscommit_for_mission, a bypass writer, and_planning_commit_worktree.
Tracking
- ✅ T027 Behavioral two-ref guard across
commit_for_mission+ a bypass writer +_planning_commit_worktree(WP07) - ✅ T028 Flattened-regression proof (NFR-001) (WP07)
- ✅ T029 FR-007 end-to-end coord-topology mission maps 100% requirements (WP07)
- ✅ T030 FR-008 protected-primary refusal end-to-end (WP07)
Implementation sketch
tests/architectural/test_write_surface_placement_guard.py: the behavioral two-ref guard (NFR-002) — for a coord fixture assert primary-kind→target_branchAND status-kind→coordination, exercised across the three write paths, driving the REAL resolver (noresolve_topology/resolve_placement_onlystubs — DECISION 7). T027 includes a MANDATORY anti-mutant negative test that forces the pre-fix partition (SPEC back in_PLACEMENT_ARTIFACT_KINDS) and asserts the planning-ref assertion goes red.tests/missions/test_write_surface_coherence.py: FR-007 fresh coord-topology mission specify→plan→tasks→finalize maps 100% requirements with zero manual coord steps; NFR-001 flattened regression green; FR-008 protected-primary refusal. T030 (DECISION 6) asserts BOTH refusal shapes: the router returnsCommitRouterResult(status="no_op_wrong_surface");safe_commitRAISESProtectedBranchRefused— each against the path that produces it.- Realistic data: real-length ULID/mid8, never short fake slugs.
Dependencies: WP01, WP02, WP03, WP04, WP05, WP06. Estimated prompt size: ~270 lines.