# Canonical self-propagating handoff schema (#626)

**Applies to:** every workflow actor — author, reviewer, merger, controller,
operator, reconciler.

`#494`–`#507` defined the ledger, the canonical state comments, and the
Canonical Thread Handoff shape. This schema owns the *chain*: each actor
consumes exactly one canonical handoff, performs exactly one authorized role,
records the result durably in Gitea, and emits the next complete handoff —
until the controller records final closure.

Implemented and enforced by `self_propagating_handoff.py`; wired into
`final_report_validator.py` as rule `shared.self_propagating_handoff`.

## The block

Post this block into the Gitea issue or PR thread, and include it verbatim in
the final report. It is not an optional prose section.

```md
<!-- sph:v1 -->
## Canonical Handoff

```text
REPOSITORY: <org>/<repo>
ISSUE: <number>
PR: <number or none>
WORKFLOW_STATE: <one of the workflow states below>
HEAD_SHA: <current head, or none before a branch exists>
BASE_BRANCH: <base branch>
BASE_OR_MERGE_SHA: <base SHA, or merge commit SHA after merge>
ACTING_ROLE: <author|reviewer|merger|controller|operator|reconciler>
ACTING_IDENTITY: <username (profile)>
COMPLETED_ACTIONS: <what this actor actually did>
VALIDATION_EVIDENCE: <commands run and their results>
MUTATION_LEDGER: <every durable mutation performed>
BLOCKERS: <active blockers, or none>
NEXT_ACTOR: <role authorized by WORKFLOW_STATE, or none when complete>
NEXT_ACTION: <exact next action, or none when complete>
PROHIBITED_ACTIONS: <what the next actor must not do>
NEXT_PROMPT: <complete ready-to-run prompt, or none when complete>
WORKFLOW_FAILURE_ISSUES: <durable issue refs for tooling defects, or none>
LAST_UPDATED: <UTC timestamp>
```
```

## Workflow states and the single authorized actor

| `WORKFLOW_STATE`            | `NEXT_ACTOR` |
| --------------------------- | ------------ |
| `needs-author`              | `author`     |
| `needs-review`              | `reviewer`   |
| `approved-awaiting-merge`   | `merger`     |
| `merged-awaiting-controller`| `controller` |
| `blocked`                   | `operator`   |
| `complete`                  | `none`       |

A merged PR is **not** accepted work: merge transitions to
`merged-awaiting-controller` unless the configured workflow explicitly
authorizes automatic acceptance.

## Fail-closed rules

* Every field is required. Only `PR`, `HEAD_SHA`, `BASE_OR_MERGE_SHA`,
  `BLOCKERS`, `WORKFLOW_FAILURE_ISSUES`, `NEXT_ACTION`, and `NEXT_PROMPT` may
  carry `none`, and `PR`/`HEAD_SHA` only in `needs-author` or `blocked`.
* `NEXT_ACTOR` must equal the actor the declared state authorizes.
* `blocked` requires a concrete `BLOCKERS` entry.
* A non-terminal handoff requires a concrete `NEXT_ACTION` and a
  `NEXT_PROMPT` long enough to be ready to run.
* `complete` must carry no `NEXT_ACTION` and no `NEXT_PROMPT`: a finished
  workflow terminates instead of manufacturing more work.
* `NEXT_PROMPT` must name the repository and the issue, and must not depend on
  outside chat history. The issue or PR thread, workflow documentation, and
  live repository state must be sufficient to recover the task.
* The handoff must be posted to Gitea. A chat-only report is not durable
  workflow state.

## Live-state recovery before acting

The receiving actor re-derives truth from live state instead of trusting the
inherited handoff. `assess_handoff_live_state` detects and fails closed on:
`changed_pr_head`, `stale_approval`, `issue_closed`, `issue_reopened`,
`pr_merged`, `pr_closed_unmerged`, `stale_lease`, `foreign_lease`,
`missing_worktree`, `dirty_worktree`, `namespace_mismatch`, `stale_runtime`,
`changed_base`, and `conflicting_canonical_comments`.

A changed head invalidates any inherited review or merge handoff; the chain
recovers to `needs-review`.

## Controller closure

`accept` is only honored with all four closure proofs recorded:
`acceptance_criteria_satisfied`, `cleanup_complete`,
`canonical_final_state_posted`, `issue_closed_through_workflow`. Otherwise the
work item stays at `merged-awaiting-controller`.

`request_tests`, `request_proof`, `request_corrections`, and `reopen` return
the work to the author; `return_to_actor` returns it to a named earlier actor.

## Workflow-failure escalation

Tooling or workflow defects found while working an item never get folded into
the active feature issue. Each distinct failure carries `classification`,
`linked_issue`, `temporary_impact`, `next_valid_actor`, and `recovery_prompt`.
A failure whose signature already has a durable issue must reuse that issue
instead of filing a duplicate.

## Applicability

Enforcement is applicability-gated exactly like the #495 canonical-state gate:
once a report carries the `sph:v1` marker, the `Canonical Handoff` heading, or
a `WORKFLOW_STATE:` line, the full schema is enforced and incomplete handoffs
are rejected. Reports written before the protocol existed are unaffected.
