Phase 0 Research: Single Mission-Surface Resolver
Decision 1 — Canonical owner = coordination/surface_resolver.resolve_status_surface_with_anchor
- Decision: Make
resolve_status_surface_with_anchor the sole surface-selection authority; all others become thin adapters / topology-blind-by-design / retired. - Rationale: It already holds the richest topology logic (coord→primary cascade, materialization handling,
_coord_mid8, CoordinationBranchDeleted #1848, the #1772 nesting guard). The other resolvers re-implement subsets of it. Picking the superset as owner makes the collapse a re-point, not a rewrite. - Alternatives:
_read_path_resolver.resolve_mission_read_path (also rich, but the surface_resolver already delegates to its candidate_*); a brand-new resolver (rejected — C-002 forbids a new shadow path / #1993).
- Decision: The unified
primary_feature_dir_for_mission composes <slug>[-mid8] via the single _compose_mission_dir grammar (the feature_dir_resolver.py form), NOT the raw-slug form. - Rationale: The mid8-composing form correctly locates backfilled /
<slug>-<mid8> directories; the raw-slug form silently mislocates them when a handle carries a mid8. "Topology-blind primary read" must still resolve the RIGHT primary dir. Pin with a per-caller-class regression test (bare-slug, <slug>-<mid8>, backfilled). - Alternatives: raw-slug form (rejected — mislocates mid8 handles, the divergence we're removing); keep both (rejected — it IS the bug).
- Note: this is the FR-009 behavior decision; recorded here so the unification is deliberate, not a blind merge.
Decision 3 — Coord-empty hard-fail (Q1→B), with an actionable two-path message
- Decision: A materialized-but-empty coordination worktree → hard-fail
STATUS_READ_PATH_NOT_FOUND; the message instructs the operator to either collapse/flatten (drop coordination_branch) OR recreate/populate the coordination branch. No silent primary fallback on a divergent surface. - Rationale: A silent fallback is exactly the desync (#1716 root cause) — it hides which surface is authoritative. Hard-fail surfaces the divergence and the two legitimate recoveries. Recorded as an ADR (IC-08), bound to the resolver.
- Boundary: the no-coord create→first-write window (no coordination branch at all) is NOT this case — primary is the sole authoritative surface there. The resolver must distinguish "no coord" (primary authoritative) from "coord exists but empty" (hard-fail).
Decision 4 — Typed-error pass-through first (FR-005, #2010 bug #15) — cheapest slice
- Decision: Preserve
STATUS_READ_PATH_NOT_FOUND / MISSION_AMBIGUOUS_SELECTOR through next/mission_runtime instead of flattening to MISSION_NOT_FOUND. Land independently of the resolver collapse. - Rationale: Highest-blast-radius desync symptom, no resolver change, error types already exist — the cheapest first behavioral win that de-risks the rest.
Decision 5 — Equivalence test BEFORE deletion (C-004 safety gate)
- Decision: The FR-002 differential test (same (slug, mid8, topology) → same dir or same typed error) MUST be green before any duplicate resolver is deleted.
- Rationale: #2010's unification closed without proving behavior-equivalence across input classes — that's why it's reopening here. The equivalence test is the gate that makes deletion safe; deleting first repeats #2010's mistake.
Decision 6 — T6 import migration = scoped bulk-edit, NOT global change_mode
- Decision: Only the T6 shim-retirement WP (migrate 30+
missions.feature_dir_resolver import sites) is a bulk edit; produce a scoped occurrence_map.yaml (import_paths) for THAT WP at its implement time (after IC-02's audit fixes the site set). Do not set change_mode: bulk_edit globally. - Rationale: Globally marking the mission bulk-edit would wrongly gate the non-bulk WPs on an occurrence map. The bulk slice is bounded and post-audit.
Deprecation check (brownfield)
missions/feature_dir_resolver.py — C-004 strangler shim, due for retirement (T6/IC-06). Confirmed still imported by 30+ sites.mission_read_path.py — compatibility re-export shim, only runtime_bridge.py:2442 + 1 test import it (T7, opportunistic).- No other strangler shim in the resolver surface is past a removal milestone that this mission must additionally drain (the legacy-compose C-002 allowlist already drained per #1900's analysis; only the coord-predicate entry remains, handled by IC-06).
Out-of-scope confirmations (from the boy-scout squad)
WorktreeTopology/classify_worktree_topology/read_worktree_registry — correct git-registry authority, reused; NOT a selection duplicate._mid8_from_primary_meta/resolve_declared_mid8 mid8 cascade — separate seam (#1918).