# Canonical Turn Contract (orchestration-envelope owner)

## Turn contract — verify before ending the turn

```
✓ 1. Branch verified FIRST (`git branch --show-current` RUN, output pasted; default branch → new branch created before edits). Never work on main/master.
✓ 2. All pre-edit gates (phase-lint, architectural invariants, dependency) RUN, plus frozen acceptance verified, with commands/exit codes pasted. Any FAIL without an allowed override → STOP.
✓ 3. `git add`, `git commit -m "<type>(<scope>): <summary>"` EXECUTED; SHA pasted. Commits you did not run do not count.
✓ 4. Unit finished (single-pass/--fix/final phase)? `git push`, `gh pr create` EXECUTED; PR URL printed in chat (with --body-file, real Markdown, Closes #N). Roadmap/fix-index updated to `done · [#<pr>](<pr-url>)` in follow-up commit. For mid-phase push rules when a PR is already open, see per-consumer supplement (§ Push policy supplement).
✓ 5. Clean-tree check LAST (`git status --porcelain` RUN, output pasted). No uncommitted changes (code or docs). PR branch not ahead of remote.
✓ 6. Artifact language: explicit user > project docs > English.
✓ 7. Descope guard applied to every issue created this turn (dated ## Amendments in SPEC before issue).
✓ 8. Out-of-scope findings classified per Opportunistic finding policy, recorded in decisions.md; proposals created no issue without explicit user triage.
✓ 9. Closing `→ Next:` block printed as ABSOLUTE last output.
✓ 10. Machine result emitted if driver requested (package output profile).
✓ 11. No reconstruction from memory — missing reference → STOP.
```

**A box that runs a command discloses what the command needs.** When a box
invokes a script with a placeholder (`bun scripts/x.mjs <unit-dir>`; node fallback
if bun is not installed), the same
skill's text must name the artifact that placeholder requires — the script
declares it (`PREREQUISITES` in `scripts/needs-input.mjs`) and
`scripts/promise-coherence.mjs` checks the disclosure. A box that instructs a
run the skill never equips reproduces the failure it was written to prevent
(fix #258 F45/F53: `missing-unit-doc`).

**Single owner:** orchestration-envelope/references/TURN_CONTRACT.md. All skills load this + skill-specific additions only. Duplication forbidden. Missing reference = STOP.

**Push policy supplement:** Box 4 covers the end-of-unit push. Some consumers (e.g. `execute-phase/references/PREFLIGHT.md`) define additional push rules for mid-phase commits when a PR is already open — those per-consumer supplements extend this contract and must be loaded alongside it by the consumer skill.

## Machine-check profile (boxes 1–5)

Boxes 1–5 are mechanical. Demonstrate them by running the verifier — the
scaffold shim `template/.agentic-workflow/hooks/turn-contract.sh` (bash + git +
gh; presence-only box2 — the conditional phase-lint clause is engine-only, so
a shim receipt leaves that clause unverified, ED-52-3) or the producer-crate
engine `packages/agentic-workflow/bin/turn-contract.mjs` — and pasting its
one-line receipt beside the git/gh evidence. When the receipt is pasted,
reciting boxes 1–5 in prose is **not** required. Verifier unavailable (no bash,
or no shim/engine in the project)? Recite boxes 1–5 exactly as before — the
profile never weakens a box, and a turn with neither receipt nor recitation is
unverified. Boxes 6–11 are unchanged: they stay agent-attested judgment calls
no script may speak for. The closing `→ Next:` block may be echoed from the
`workflow-status` envelope instead of hand-authored: `next.recommended`
supplies the recommended line and `next.alternatives` the `·` sub-bullets,
preserving the fixed block shape.

```text
turn-contract-receipt@1
ok-line:    TURN-CONTRACT ok
fail-line:  TURN-CONTRACT fail box<N>: <code>
codes:      branch-default | not-a-repo | no-commits | unit-doc-missing |
            phase-lint-failed | pr-not-open | pr-head-mismatch |
            pr-unreachable | dirty-tree | ahead-of-remote
exit:       0 ok | 1 contract fail | 2 usage error
order:      boxes 1→5, first failure wins; within box5, dirty-tree precedes
            ahead-of-remote
stdout:     exactly one line; diagnostics never (git/gh evidence stays in the
            agent's transcript)
```

## Hand-off grammar (versioned — read by `scripts/normative-drift.test.mjs`)

Box 9's closing hand-off and box 10's machine result are ordered here, so the
tokens they may use are declared as grammar rather than prose. Every `from`/`to`
pair below must exist in the schema package's `WORKFLOW_TRANSITION_TABLE`, and
every field row must be a key that package's envelope validator declares: a pair
or a key no machine surface defines is a defect in this file, never in the table.

```text
hand-off-transitions@1
from | to
workflow-intent:review-spec | workflow-intent:plan-feature
workflow-intent:plan-feature | workflow-intent:review-plan
workflow-intent:review-plan | workflow-intent:execute-phase
workflow-intent:plan-fix | workflow-intent:review-plan
workflow-intent:review-plan | workflow-intent:design-feature
workflow-intent:execute-phase | workflow-intent:review-change
workflow-intent:review-change | workflow-intent:audit-pr
workflow-intent:audit-pr | workflow-intent:merge
workflow-intent:triage-issue | workflow-intent:execute-phase
workflow-intent:status | workflow-intent:design-feature
```

```text
hand-off-fields@1
# machine: envelope
object | field
next | recommended
next | alternatives
next | tier
next | suggested
next | continuation
```

## Open questions (the needs-input halt)

An input a step cannot resolve is never inferred: the step stops and hands the
questions back. The block below is that hand-off — printed by
`scripts/needs-input.mjs`, never hand-authored — so a weaker model copies it
instead of paraphrasing the questions away. Interactive, the block is the pause
point and the run waits for a user decision; unattended, it is a hard fail
carrying the same block.

```text
open-questions@1
# produced-by: scripts/needs-input.mjs <unit-dir> [--json]
# input:       <unit-dir>/SPEC.md + <unit-dir>/decisions.md (the ledger's own grammar is
#              published by the template agent guide, template/AGENTS.md)
# machine:     continuation-refusal-type (schema-export:CONTINUATION_REFUSALS)
# exit:        0 well-formed artifact (pending questions or none) | 1 fail-closed | 2 usage error
header:      open-questions@1
fields:      Unit: <unit-folder-name>
             Open questions: <count>
             - <question-id> · <route> · <the question>
               Blocks: <why nothing proceeds without it>
               Searched: <where the repository was searched: path:line, greps, prior units>
               Options:
                 - <one researched option with its trade-off>
             Pending: none                    (only when the count is 0)
             Route back: <design | unit-lane> · … | none
             Continuation: no-decision-available | none
             Decision: open | none
fail-block:  NEEDS-INPUT FAIL — needs-input@1
             Unit: <unit-folder-name>
             Reason: <missing-unit-doc | malformed-ledger-header | malformed-entry |
                      missing-search-receipt | missing-options | unknown-route |
                      unknown-question | invalid-authority | duplicate-id>
             Where: <path[:line]>
             Continuation: no-decision-available
route:       design | unit-lane
```

`route` is the stage that owns the fact and therefore receives it back —
`design` for scope/architecture questions, `unit-lane` for plan-owned facts.
Execution never interviews: it halts with the questions. `Continuation` reuses
the schema's existing `no-decision-available` (`decisionAvailable === false`),
so the halt needs no new envelope field.

## Path-protection checkpoint (feature 60)

The Tier 1 path gate is versioned so the checkpoint invocation, the closed
reason vocabulary, and the two record blocks cannot drift from the crate module
that produces them. The preflight runs the checkpoint over the just-closed
phase's committed range (`execute-phase`'s preflight owns the step text):

```text
path-protection@1
# checkpoint: path-guard --unit <unit-dir> --phase <P<n>> --base <P<n> base ref>
# reasons: schema-export:PATH_GUARD_REASONS (packages/agentic-workflow/src/path-policy.mjs)
reason | kind
clean | pass
justified | pass
approved | pass
protected-modification | fail
approval-required | fail
undeclared-test | fail
unmatched-record | fail
malformed-declaration | fail
missing-config | degraded
malformed-config | degraded
```

The gate prints one fixed block (`PATH-GUARD <pass|fail> — <reason>`, then an
`offenders:` line, then `phase: … freeze-after: … checked:`, then any `DEGRADED`
line) and exits 0 on pass, 1 on fail, 2 on usage. The plan declares the protected
set it will author:

```text
path-protection-plan@1
freeze-after: <P<n>|none>
kind | path | justification
created | <repo-relative path or glob> | <one-line justification>
not-created | <test name> | <one-line justification>
ignored | <test name> | <one-line justification>
```

Every escape is an append-only record row in the unit's `decisions.md`:

```text
path-protection-records@1
kind | paths | phase | date | authority | justification
justification | <path or glob>[,<path or glob>] | <P<n>> | <YYYY-MM-DD> | execute-phase | <one-line justification>
approval | <path or glob>[,<path or glob>] | <P<n>> | <YYYY-MM-DD> | human-owner | <one-line justification>
```

A `justification` row satisfies a justification requirement; an `approval` row
satisfies an approval requirement and additionally requires a matching
justification row. The authority column is validated (`execute-phase` for
justification, `human-owner` for approval), so no auto-approval authority exists.

