Phase 0 Research — Read-Surface SSOT Completion & #1716 Closeout
Decisions consolidated from the pre-spec 3-scout scoping squad + the post-spec 3-lens squad (Planner-Priti / Architect-Alphonso / Python-Pedro, 2026-07-08). Each decision records what was chosen, why, and the alternative rejected.
C-007 plan-gate results (ground truth, re-measured at plan time)
- coord_authority live split (via the gate's own
scan_coord_authority_call_sites): 32 sites total = 7 WRITE + 25 READ. Matches spec FR-001/FR-002. The 7 write = the allow-list; 2 permanent by-design (decisions/emit.py:71,widen/state.py:63), 5 drainable. (The "~71" in #2453's body is stale.) - #1716 open children: exactly {#2088, #2100} (enumerated via
sub_issues). Closing both closes the epic (SC-004). No other open children; the epic's original items 1–4 were rejected and items 5–6 delivered by #2462 (superseded-body note).
D1 — #2404 fix locus: the SEAM, not per-caller (BINDING, C-006)
- Decision: Fix the wrong-kind-commit defect class by making
commit_for_missionper-file partition-aware (group a batch by partition, commit each group to its own ref) — or guard-reject a mixed-partition batch. - Rationale: The root cause is
commit_for_missionresolving placement from a single caller-suppliedkindfor the entire multi-file batch (commit_router.py:152). This closesspec_commit_cmd.py(kind=SPEC) andmission_finalize.py:1320(kind=TASKS_INDEX) by construction (Directive-043 — close the class, not the instances). - Alternative rejected: Patch the callers individually (spec's original 2-bug scope). Rejected — the next mixed-partition caller reintroduces the class; Pedro/Alphonso found a third instance (
mission_finalize) already, proving the pattern.
D2 — #2404 is NOT a partition flip (BINDING, C-002)
- Decision: Keep
ACCEPTANCE_MATRIX/ANALYSIS_REPORTon the coordination partition; do not re-kind them. - Rationale: The flip reverses the operator-confirmed partition locked by #2462 and fails the pinned
test_write_surface_placement_guard.py(three independent tests drive the real resolver against a real coord fixture). The read side is already correct (INV-5 symmetry: read_surface == write_surface == PLACEMENT); the matrix is stale only because writes misroute. - Alternative rejected: The issue-body "swappable-locus flip". Rejected as unsound (would also require re-adding the kinds to
auto_rebase._AUTO_REBASE_MANAGED_LAYOUT_KINDS).
D3 — accept dirty-detection surface reconciliation (M2)
- Decision: In
accept.py, detect dirty coordination artifacts on the coord worktree (wherewrite_acceptance_matrix(feature_dir=…)writes under coord topology), not only on the primarygit status, before routing them through the partition-aware seam. - Rationale:
_spec_artifact_dirty_pathsrunsgit_status_lines(repo_root=primary); the accept-time matrix write-back lands on the coord worktree, so a primary-only scan never sees the real edits — routing alone would still miss them. - Alternative rejected: Route the primary-detected set only. Rejected — leaves the actual coord-worktree edit uncommitted (the bug persists).
D4 — #2088 is close-only (0 code WPs)
- Decision: Verify
69dd1fa46on base and close #2088; no code work. - Rationale:
validate_no_overlapalready computes transitive dependency reachability and exempts sequential same-lane pairs; commit is an ancestor of base; 16 targeted tests pass. This fix is load-bearing for THIS mission's decomposition — it is what lets the 9 cross-thread collision files be same-lane sequential WPs sharingowned_files(D11). - Alternative rejected: Re-implement / treat as open work. Rejected — already fixed.
D5 — coord_authority per-site read/write adjudication (manual, not the gate flag)
- Decision: Drain the 5 sites by the manual read/write split, never the gate's function-level
is_write(which marks all 5 as writes).implement.py@1468/1663/1169 →read_dir;workflow.py:2747(mkdir +review-cycle-N.md) →write_target.reviewhas TWO sites:@2710read +@2747write. The allow-list locator2670is stale → live is2747. - Rationale: Routing a genuine write to
read_dirreproduces the #2404 failure mode. - Alternative rejected: Trust the gate's
is_writeflag. Rejected — function-level heuristic, not use-site accurate.
D6 — status-write false-negatives: reclassify, NEVER route (FR-003)
- Decision:
lanes/recovery.py:755andagent_tasks_ports.py:322are STATUS-WRITE legs mis-scanned as reads → reclassify as writes via_COORD_WRITE_BY_DESIGN/ widen the write-indicator predicate. Do not route them toread_dir. - Rationale:
recovery.py:755feedsemit_status_transition_transactionaland carries an explicit "MUST stay coord-aware — never route it" directive. - Ordering (BINDING): FR-003 (predicate widen) precedes FR-002 (floor re-pin) — widening changes the live write count, so the 7→2 floor must be re-measured after.
D7 — the new meta-read ratchet must be non-vacuous (FR-006/NFR-002)
- Decision: Establish the gate as a dedicated concern (IC-06) mirroring
test_resolution_authority_gates.py: integer floor + margin + a routed-count floor (anti-mass-allow-list) + composite-key allow-list with stale-entry detection + per-entry rationale. Every deferred site is an explicit allow-list entry with rationale and a filed follow-up issue. - Rationale: A ratchet that exempts deferred sites via a prose note (or a fixed
== N) is vacuous — the allow-list could silently swallow the census. - Alternative rejected: Fold the gate into a routing WP / defer sites via a note. Rejected (Priti F3).
D8 — migration deferral narrowed to m_0_13_* only
- Decision: Defer only the historical-fixture-sensitive
upgrade/migrations/m_0_13_.pymeta reads.migration/backfill_,migration/mission_state.py,migration/rebuild_state.pyare in-scope (route, or per-site allow-list with rationale + issue). - Rationale: A wholesale
migration/path-exclude silently drops sites #2100 itself scopes in (Priti F4).
D9 — transaction.py half-in/half-out (C-004)
- Decision: Route
transaction.py's meta reads that sit outside the 751-771 legacy block; add a byte-unchanged regression guarding 751-771. - Rationale: 751-771 is intentional legacy-mission HEAD-override (#1878); the file's other reads are routable.
D10 — runtime/next shared-package-boundary gating
- Decision: Route
src/runtime/next/meta reads ontospecify_cli.mission_metadata.load_metaonly where aspecify_cliimport is already sanctioned in that module; otherwise defer or stand up a runtime-local authority (a/tasks-time per-site call). - Rationale: Avoid a shared-package-boundary violation (
test_shared_package_boundary.py).
D11 — cross-thread linearization (9 collision files)
- Decision: The 9 files touched by both IC-04 (feature_dir) and IC-05 (meta) —
implement.py,orchestrator_api/commands.py,context/resolver.py,decisions/service.py,doctrine_synthesizer/apply.py,lanes/recovery.py,_identity_audit.py,plan_interview.py,specify_interview.py— must have their A-edit and B-edit co-owned in one WP or sequenced same-lane.acceptance/files stay file-granular (never dir-levelowned_files). - Rationale:
owned_filescannot be A/B-disjoint on these; #2088's dependency-aware overlap validator (D4) is exactly what makes same-lane sequential co-ownership legal.
D12 — single mission, not split
- Decision: Keep all four threads in one mission.
- Rationale: The #2462 dependency asymmetry is weak (the seam is already on this base at build time; #2462 is a rebase-before-land constraint). Splitting Thread B out would force by-file carving of the 9 collision files anyway. Priti verdict: keep-as-one, floor at 15–17 WPs.