Adds the canonical cross-role handoff schema and its fail-closed validator, live-state recovery, role-limited continuation, mandatory durable posting, the merged-awaiting-controller boundary, controller accept/reject continuation, and workflow-failure escalation with duplicate handling. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
115 lines
4.8 KiB
Markdown
115 lines
4.8 KiB
Markdown
# 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.
|