Contracts
ratchet-contract.md
Contract: Architectural Ratchet Extension
The ratchet is the enforcement that keeps the strangle strangled (SC-005). This mission extends the existing ratchet — it does not build a new one (a parallel gate would be a shadow authority, violating C-001/Directive-044).
Target files (existing)
tests/architectural/test_no_write_side_rederivation.py— AST scanner over write sites.tests/architectural/resolution_gate_allowlist.yaml—coord_authority_baseline+ entries.tests/architectural/test_resolution_authority_gates.py— integer floor guards.
New grammar (FR-011)
The scanner today matches three grammars: parent.parent root-walks, mission_id[:8] recompute, and coord_branch or _current_branch. It does not catch the construction where the bypass sites actually live:
CommitTarget(ref=<current-checkout expression>)
# e.g. CommitTarget(ref=current_branch), CommitTarget(ref=_get_cur_branch() or planning_branch)
Contract: extend the scanner to flag CommitTarget(...) (and safe_commit(...) destination) whose ref/destination_ref argument is derived from a current-checkout expression rather than a seam.write_target(...) call, in any module not on the allow-list.
Detection boundary (must not false-positive)
- Sanctioned coord primitives —
branch_naming.pycoord_composition,CoordinationWorkspaceinternals,mission_runtime/seam internals: allow-listed (they are the sanctioned grammar). - Legacy/migration —
upgrade/migrations/,migration/,upgrade/autocommit.py,invocation/executor.py(op-record self-bookkeeping on the operator's branch by design): allow-listed.
Baseline (NFR-001)
| Knob | Current | This mission |
|---|---|---|
| Write-side line allow-list seed | 1 (coordination/status_transition.py:347, deferred #1716 HEAD selector — re-anchored 343→347 by the #1842 tombstone hook) | shrink-only toward permanent-fixture floor; must not grow |
| Adopted-module set | 6 (status/emit.py, status/work_package_lifecycle.py, status/lifecycle_events.py, status/store.py, coordination/status_transition.py, core/worktree.py) | expand to include each strangled surface (core/mission_creation.py, then implement.py, workflow.py, tasks_move_task.py, mission_record_analysis.py) as its route lands |
coord_authority_baseline | 7 | NOT drained here — its 5 drainable entries are kind-blind resolve_feature_dir_for_mission reads (disjoint from the routed writes); the 7→2 drain is deferred to #2453. This mission keeps it at 7. |
| VISIBLE-but-tracked (new-grammar allow-list) | — | allow-list retrospective/writer.py (sanctioned #2119 RETROSPECTIVE authority), and the residual checkout-derived write fallbacks orchestrator_api/commands.py:1451 + coordination/transaction.py legacy override + tasks_map_requirements.py:177, each with a tracked: #2453 rationale — flagged, not silent |
Sequencing rule (contract): a module is added to the adopted set (and its baseline decremented) in the same WP that routes it — never before (the ratchet would go red without a landed route) and never after (a routed-but-unadopted module can silently regress).
Pass/fail
is at or below baseline; the new grammar finds zero un-allow-listed offenders.
routed module missing from the adopted set.
- PASS: every lifecycle/planning write/read resolves through the seam; the allow-list
- FAIL: any un-allow-listed
CommitTarget(ref=<checkout>), any allow-list growth, or a
seam-api.md
Contract: Placement Seam API
The seam is the single access point for "where do I write/read artifact kind K under my mission's topology?" It is the public face of the existing resolve_action_context root — a thin authority the leaf resolvers delegate to (FR-001, C-001). This is an internal Python contract, not an HTTP API.
Authority
One authority object per mission operation, exposing two kind-aware projections plus the classifier already in artifacts.py.
write_target(kind: MissionArtifactKind) -> CommitTarget
- Given the mission's stored topology and
kind. - Returns the
CommitTarget(branch ref + worktree root) the write must commit to. - Rules:
- Coordination-partition kind AND
routes_through_coordination(stored_topology)→ coord surface / coordination branch. - Otherwise → primary surface / target branch.
- The result is derived from stored topology +
artifact_home_for; never from the current checkout. - Forbidden for callers:
CommitTarget(ref=<current_checkout>),safe_commit(destination_ref=current_branch), or inlinecoord_branch if coord_branch else planning_branch.
read_dir(kind: MissionArtifactKind) -> Path | ResolvedSurface
branch resolve via the branch ref (not a worktree requirement); a flattened mission reads all kinds from primary (T-2).
shared helper that delegates here (FR-001).
- Given the mission's stored topology and
kind. - Returns the directory/surface to read this kind from.
- Rules: symmetric to
write_target. Coordination reads on anUNMATERIALIZEDcoord - Consolidation: the four duplicate
_planning_read_dirwrappers collapse to one
artifact_home_for(kind, placement_ref) -> MissionArtifactHome (existing classifier)
projections over it. Call sites consume the home; they do not assemble placement (H-1).
- Sole constructor of a
MissionArtifactHome.write_targetandread_dirare thin
Consumer contract (what strangled sites must do)
| Site | Before (bypass) | After (routed) |
|---|---|---|
mission_creation.py:176 | CommitTarget(ref=current_branch) for SPEC | seam.write_target(SPEC) |
implement.py:885 | coord_branch if coord_branch else planning_branch | seam.write_target(kind) |
implement.py:1462 | _get_cur_branch() or planning_branch | seam.write_target(kind) |
workflow.py:487/503/549/1694 | if coord_branch … else target_branch | seam.write_target(kind) |
tasks.py move-task/mark-status | inline lane/coord selection | seam.write_target(STATUS_STATE) (reconcile with #2438 gate) |
kind-blind reads via resolve_feature_dir_for_mission | kind-blind dir | seam.read_dir(kind) |
Invariants (contract-level)
- I-1 (C-001): no lifecycle/planning site computes placement outside the seam — including when the seam returns primary.
- I-2 (T-1): coord-routing =
routes_through_coordination(stored_topology); no inline== COORD/coordination_branch is not None. - I-3 (M-1): composition with empty
mid8fails loudly (FR-007). - I-4: no behavior change to which surface a kind resolves to — this is a routing/consolidation change, not a partition change (C-002).