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.yamlcoord_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 primitivesbranch_naming.py coord_ composition, CoordinationWorkspace internals, mission_runtime/ seam internals: allow-listed (they are the sanctioned grammar).
  • Legacy/migrationupgrade/migrations/, migration/, upgrade/autocommit.py, invocation/executor.py (op-record self-bookkeeping on the operator's branch by design): allow-listed.

Baseline (NFR-001)

KnobCurrentThis mission
Write-side line allow-list seed1 (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 set6 (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_baseline7NOT 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 inline coord_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 an UNMATERIALIZED coord
  • Consolidation: the four duplicate _planning_read_dir wrappers 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_target and read_dir are thin

Consumer contract (what strangled sites must do)

SiteBefore (bypass)After (routed)
mission_creation.py:176CommitTarget(ref=current_branch) for SPECseam.write_target(SPEC)
implement.py:885coord_branch if coord_branch else planning_branchseam.write_target(kind)
implement.py:1462_get_cur_branch() or planning_branchseam.write_target(kind)
workflow.py:487/503/549/1694if coord_branch … else target_branchseam.write_target(kind)
tasks.py move-task/mark-statusinline lane/coord selectionseam.write_target(STATUS_STATE) (reconcile with #2438 gate)
kind-blind reads via resolve_feature_dir_for_missionkind-blind dirseam.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 mid8 fails 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).