Contracts
resolve_handle_to_read_path.md
Contract — resolve_handle_to_read_path (the single read-side seam)
Module: src/specify_cli/missions/_read_path_resolver.py Lifted from: orchestrator_api/commands.py:_resolve_mission_dir + _read_primary_meta (the working prototype).
def resolve_handle_to_read_path(repo_root: Path, handle: str) -> Path
Behavioral contract
1. assert_safe_path_segment(handle) — reject traversal BEFORE any join (FR-004). 2. (meta, declares_coordination) = _read_primary_meta(repo_root, handle) — primary-anchored read. 3. mid8 = resolve_declared_mid8(meta, handle) — meta.mid8 → mission_id[:8] → mid8_from_slug. 4. Fail-closed: if not mid8 and declares_coordination: raise <typed refusal>. 5. Return resolve_mission_read_path(repo_root, handle, mid8) (worktree-existence-gated). NEVER resolve_status_surface_with_anchor.
Equivalence guarantees (the proofs)
<slug>-<mid8>/ full-id handles: resolved dir UNCHANGED vs pre-mission (NFR-002).- bare-slug × coord-fresh/coord-behind: resolves the COORD dir (SC-001 cells flip; SC-002 per-CLI e2e).
- declared-but-unmaterialized coord (any handle): resolves PRIMARY (SC-005, #1718).
- traversal
handle: rejected at step 1 (FR-004). - ambiguous handle:
MISSION_AMBIGUOUS_SELECTORpropagated (no silent pick).
Selection-authority guard (FR-006)
After adoption, every read path reaches the resolver THROUGH this seam. A new direct resolve_mission_read_path call or bespoke resolve_mid8 cascade outside the seam allowlist fails the AST ratchet; an empty-mid8-against-declared-coord fails the runtime gate.