Contracts
behavioral-contracts.md
Phase 1 Contracts: Implement-Loop Friction Quick-Wins II
These are behavioral contracts (not HTTP APIs — this is an internal CLI mission). Each names the observable input→output the fix must satisfy and the regression that pins it.
C-A1 — Allocator uncommitted-artifact check (FR-001)
placement ref is {shell_pid, shell_pid_created_at, base_branch, base_commit, planning_base_branch} Then that file is dropped from the "uncommitted planning artifact" set (no block).
N-lane allocation test asserting 0 inter-allocation commits.
- Given
resolve_planning_artifact_stagingsees aWP##.mdwhose only porcelain diff vs the - Given the same file also has any other line changed Then it still counts (block preserved).
- Pin:
tests/specify_cli/cli/commands/test_implement_vcs_lock_claim.pysibling + a sequential
C-A2 — Analysis-report freshness (FR-002)
Then check_analysis_report_current returns current (no stale_analysis_report).
- Given a pipe-table
tasks.mdwhose only change is a status cell[ ]↔[D]/[P]/[x] - Given a substantive row-text change Then it returns stale.
- Pin: pipe-table sibling of
test_analysis_report_survives_subtask_checkbox_churn.
C-B1 — Pre-review gate interpreter + verdict (FR-003/004, C-004)
Then run_scoped_tests_at_head runs via uv run and returns a real pass/fail (not no_coverage).
from the 300s subprocess timeout (a lock-wait must never be charged against the run timeout).
a concurrent-invocation test; a lock-wait-does-not-trip-run-timeout test. (Update the two existing real-subprocess tests that currently mask this.)
- Given
sys.executable -m pytestfails (No module named pytest) butuv run pytestis green - Given
uvabsent Then falls back tosys.executablewithout crashing. - Given concurrent scoped runs Then they serialize (advisory lock); no false timeout
no_coverage. - Given the lock is contended Then lock-acquire uses its OWN timeout + fallback-to-run, decoupled
- Pin: a regression under a pytest-lacking interpreter (monkeypatch
sys.executable+ fakeuv);
C-B2 — Sub-agent long-gate contract (FR-005)
skip, it uses --skip-pre-review-gate or the honored disable env; the orchestrator must not assume a silent hand-back. Documented in the implement-review skill/doctrine.
- Contract: a dispatched implement/review sub-agent polls the synchronous gate to completion; to
- Pin: skill/doc assertion (no product-code test).
C-C1 — Manifest output_path portability (FR-006/NFR-004)
(no leading /, no home dir).
reconstructed live output_path still .exists()-resolves (no migration).
unaffected); only the on-disk JSON is relative.
test (relative-store round-trip does not break get_hash) in tests/specify_cli/tool_surface/profiles/test_manifest.py.
- Given an in-tree profile entry serialized Then the on-disk JSON
output_pathis repo-relative - Given a legacy absolute-path manifest loaded under a different
project_rootThen the - Given an out-of-tree entry Then it serializes absolute (fallback), like
source_path. - Invariant: in-memory
output_pathand the internal manifest key stay ABSOLUTE (get_hash/.exists() - Pin: in-tree-relative-serialization test + absolute→relative tolerance test + a keying-invariant
C-C2 — Issue-matrix schema-drift error (FR-007)
with the schema-drift detail naming the offending/normalized column, and does not list all referenced issues as "Missing rows".
- Given a matrix whose mandatory column header is non-canonical Then the approval blocker leads
- Pin: malformed-column test in
test_tasks_parsing_validation.py.
C-C3 — Bulk-edit inference (FR-008)
triggered is False.
the true-positive at risk from dropping the low-weight +1 at INFERENCE_THRESHOLD=4; if it cannot pass at the current threshold, adopt the HIGH/scale co-occurrence fallback instead of lowering detection.
- Given a spec with
refactor+update+change+singlerenameon a non-bulk change Then - Given "rename all occurrences …" / "rename … across the codebase" Then
triggered is True. - Given a single-HIGH-phrase bulk spec (score 3, no MEDIUM) Then
triggered is True— this is - Pin: all three cases in
tests/specify_cli/bulk_edit/test_inference.py.
C-D1 — setup-plan scaffold result (FR-009)
non-error state (scaffolded/awaiting_content), not blocked (via a net-new pristine predicate).
state — source prompts + the next engine result-switch (engine.py:287-292) — must not fall through on scaffolded.
confirm source prompt templates + agent-copy regeneration.
- Given a freshly-scaffolded plan with no populated content Then
resultis the new - Given a populated-but-insufficient plan Then
result == blocked. - Given a substantive committed plan Then
result == successand it advances. - Invariant: the specify side is already
scaffold_only: success(no change); consumers of the new - Pin:
test_mission_setup_plan_phases.py; anext-engine test that the new state is handled;
C-E1 — move-task coord-lane staging (FR-010/C-002)
for_review Then staging resolves via the authority path (commit_router/resolve_planning_artifact_staging) with no manual git restore dance and the lane branch never commits kitty-specs/`.
(b) zero kitty-specs/ entries committed on the lane branch. NO commit_guard.block_mission_specs exemption; do NOT touch _mt_resolve_status_placement_ref/_collect_status_artifacts/_primary_bundle_status_artifacts.
IC-07 diff does not touch the three status-bundling symbols.
- Given untracked planning artifacts on primary + a coord-topology lane When `move-task --to
- Invariant (DUAL pin): (a) STATUS_STATE ref/event byte-identical pre/post vs merged #168, AND
- Pin: a coord-topology move-task regression asserting BOTH (a) and (b); an arch/unit guard that the