<!-- normative -->

# Control Plane

Concurrent work, clocks, external APIs, humans, operating systems, networks, and reasoning models may produce different
outputs or timing across runs.

The architecture therefore promises deterministic governance, not deterministic end-to-end execution.

## Authoritative event flow

```text
External occurrence or executor result
                |
         Untrusted observation
                |
      Schema and authority validation
                |
     Normalisation and deduplication
                |
        Event ordering and admission
                |
        Authoritative event log
                |
       Deterministic state reducer
                |
          New graph-state version
                |
         Newly permitted actions
```

## Event and proposal requirements

Authoritative events are admitted facts and do not carry an expected graph-state version.

Every authoritative event must record:

- event identifier;
- globally increasing sequence number;
- event type;
- source;
- source authority;
- causation identifier;
- correlation identifier;
- idempotency key;
- affected node, attempt, action, artefact, budget, or resource identifiers;
- event time, if supplied;
- ingestion time;
- causal predecessors;
- input versions;
- artefact references;
- schema version;
- policy version;
- payload hash;
- validation outcome.

Every proposal that requests an authoritative mutation must additionally record:

- expected graph-state version;
- expected versions of every node, relation, contract, input artefact, policy and budget record the proposal references;
- the expected absence of any relation or identifier whose absence is material to the proposal;
- proposal identifier;
- proposing authority;
- delegated scope;
- proposed mutation;
- proposal schema version;
- proposal policy version.

Every authoritative node and relation has a monotonically increasing entity version. An admission transaction increments
the versions of only the entities it creates or changes. The transaction also advances the global event sequence and
graph version.

A proposal is not an event. A proposal becomes authoritative only when the control plane validates it and admits an
event describing the accepted mutation.

## MVP ordering, concurrency, and transactions

The single-machine MVP uses one global monotonically increasing graph version and one global authoritative event
sequence.

For every admitted event:

1. validate schema, source authority, idempotency, and referential integrity;
2. validate the proposal's expected graph version when the event originates from a proposal;
3. assign the next global event sequence;
4. apply the event through the deterministic reducer;
5. increment the global graph version exactly once if authoritative state changes;
6. persist the event, resulting graph version, state changes, and ledger changes in one atomic transaction.

Read-only observations that cause no authoritative mutation retain their global event sequence but do not increment the
graph version.

Concurrent proposals may be received, but mutation admission is serialised. A proposal is revalidated against its
referenced entity-version set at admission time rather than rejected solely because the global graph version advanced. A
proposal remains eligible when every referenced entity is unchanged and every expected-absence condition still holds,
even if unrelated admitted events advanced the global graph version. The controller emits `VERSION_CONFLICT_RECORDED`
only when a referenced entity changed, an expected-absence condition no longer holds, or the proposed mutation now
violates an invariant. Unrelated cost settlement, scheduling, timer, attempt or graph events do not invalidate the
proposal. Rejected stale proposals retain their model cost, but the MVP must not reject a proposal solely because the
global graph version advanced.

Duplicate events are detected by idempotency key and payload hash. Re-admitting an exact duplicate has no effect.
Reusing an idempotency key with a different payload is rejected as a policy violation.

Late observations are classified by purpose.

A late result observation is retained as non-authoritative evidence when the attempt is no longer result-eligible.

A late cost-only observation remains eligible while the attempt accounting state is `OPEN` or `PROVISIONALLY_SETTLED`.
It may authoritatively settle cost without admitting outputs or changing the terminal execution state.

Partitioned ordering may be considered after the reference implementation has established reducer, recovery, and replay
correctness. It is not part of the MVP.

## Replay and external inputs

Replay means applying the recorded authoritative event sequence in ascending sequence order. It does not reinvoke
executors, models, clocks, schedulers, or external services.

Executor availability, capacity changes, scheduling decisions, action dispatches, cancellations, timer firings, command
results, model results, and other external observations must enter the system as authoritative events before they affect
state.

The MVP records scheduling decisions as events. During replay, the reducer reapplies recorded scheduling events rather
than recomputing them from live executor capacity or wall-clock state.

Timeouts are driven by `TIMER_FIRED` events carrying the timer identifier, attempt identifier, configured deadline,
observed firing time, and policy version. Reading the current clock directly inside the reducer is prohibited.

Given the same initial snapshot, event sequence, schemas, and policies, replay must reproduce:

- graph state and graph version;
- node, attempt, and artefact states;
- admitted scheduling and cancellation decisions;
- budget reservations, commitments, actuals, and reclamations;
- acceptance and publication decisions;
- invalidations;
- terminal run outcome.

Reinvoking an executor creates a new attempt and new events.

## State Machines

Node lifecycle, execution-attempt lifecycle, artefact lifecycle, and result freshness are separate state dimensions. No
state name may be transferred implicitly between them. State names, event names and policy-outcome names occupy separate
namespaces. No identifier may be reused across these dimensions. Events use completed or requested verb forms.

Unlisted transitions are invalid and are rejected by the reducer.

### Node lifecycle

Node states:

<!-- gen:node-states -->

- `PROPOSED`
- `BLOCKED`
- `READY`
- `VALIDATING`
- `VALIDATED`
- `REPLAN_REQUIRED`
- `CONFLICTED`
- `ACCEPTED`
- `INFEASIBLE`
- `REJECTED`
- `SUPERSEDED`
- `CANCELLED`
- `BUDGET_EXHAUSTED`

<!-- /gen:node-states -->

Terminal node states:

<!-- gen:node-terminal -->

- `ACCEPTED`
- `INFEASIBLE`
- `REJECTED`
- `SUPERSEDED`
- `CANCELLED`
- `BUDGET_EXHAUSTED`

<!-- /gen:node-terminal -->

<!-- gen:node-transitions -->

| Current state | Event | Guard | Next state |
|---|---|---|---|
| None | `NODE_PROPOSED` | A node is created with a contract, authority, and a cause of ROOT, DECOMPOSITION, REPLAN, or ROOT_REPLACEMENT | `PROPOSED` |
| `PROPOSED` | `NODE_ADMITTED` | Contract and authority valid | `BLOCKED` |
| `PROPOSED` | `NODE_REJECTED` | Admission check failed | `REJECTED` |
| `BLOCKED` | `READINESS_ESTABLISHED` | Logical prerequisites satisfied and an action is permitted | `READY` |
| `READY` | `READINESS_REVOKED` | A prerequisite, input, budget, or policy condition changed | `BLOCKED` |
| `READY` | `DECOMPOSITION_ADMITTED` | The decomposition proposal passes authority, scope, coverage, depth, fan-out, budget and version checks | `BLOCKED` |
| `READY` | `CONFLICT_RECORDED` | Conflict policy blocks the action from proceeding | `CONFLICTED` |
| `READY` | `VALIDATION_STARTED` | Required result exists and validation commitment is active | `VALIDATING` |
| `VALIDATING` | `VALIDATION_PASSED` | Required evidence passes but acceptance has not yet been authorised | `VALIDATED` |
| `VALIDATING` | `VALIDATION_FAILED` | Failure evidence is admitted and retry policy permits another attempt for the action lineage | `READY` |
| `VALIDATING` | `VALIDATION_FAILED` | Failure evidence is admitted and retry policy prohibits another attempt for the action lineage | `REPLAN_REQUIRED` |
| `VALIDATED` | `ACCEPTANCE_AUTHORISED` | All [Parent acceptance](validation.md#parent-acceptance) conditions and acceptance bindings hold | `ACCEPTED` |
| `VALIDATED` | `ACCEPTANCE_DEFERRED` | Required approval or authority is not yet available | `BLOCKED` |
| `VALIDATED` | `VALIDATION_INVALIDATED` | A bound input, result, validator, policy, or environment version changed | `BLOCKED` |
| `VALIDATING` | `VALIDATION_INVALIDATED` | A bound input, result, validator, policy, or environment version changed | `BLOCKED` |
| `VALIDATING` | `CONFLICT_RECORDED` | Conflict policy blocks validation or acceptance | `CONFLICTED` |
| `VALIDATED` | `CONFLICT_RECORDED` | Conflict policy blocks acceptance | `CONFLICTED` |
| `CONFLICTED` | `CONFLICT_RESOLVED` | No blocking conflict remains | `BLOCKED` |
| `READY` | `REPLAN_REQUESTED` | Current plan cannot proceed under policy | `REPLAN_REQUIRED` |
| `BLOCKED` | `REPLAN_REQUESTED` | Quiescence or repeated failure requires replanning | `REPLAN_REQUIRED` |
| `VALIDATING` | `REPLAN_REQUESTED` | Repeated failure without new evidence requires replanning | `REPLAN_REQUIRED` |
| `REPLAN_REQUIRED` | `PLAN_ADMITTED` | New plan is valid and has new evidence or a materially changed contract | `BLOCKED` |
| Any non-terminal | `NODE_MARKED_INFEASIBLE` | Deterministic infeasibility policy passes | `INFEASIBLE` |
| Any non-terminal | `NODE_CANCELLED` | Cancellation authority and policy pass | `CANCELLED` |
| Any non-terminal | `BUDGET_EXHAUSTION_RECORDED` | No permitted action can obtain required commitment | `BUDGET_EXHAUSTED` |
| Any non-terminal non-root | `NODE_SUPERSEDED` | Valid replacement relation and authority exist | `SUPERSEDED` |
| Any non-terminal root | `ROOT_REPLACED` | Valid replacement relation and authority exist | `SUPERSEDED` |
| Any non-terminal | `NODE_REJECTED` | Policy permits rejection | `REJECTED` |

<!-- /gen:node-transitions -->

A node enters `PROPOSED` only through `NODE_PROPOSED`, whose `cause` is `ROOT` for the initial root, `DECOMPOSITION` for each child admitted by `DECOMPOSITION_ADMITTED`, `REPLAN` for each node introduced by `PLAN_ADMITTED`, or `ROOT_REPLACEMENT` for a replacement root admitted by `ROOT_REPLACED`.

A transition from `VALIDATING` to `READY` does not reopen the failed attempt. The failed attempt remains terminal. The
controller may create a new attempt identifier only after admitting `ATTEMPT_PLANNED` with cause `RETRY` and obtaining a
new budget commitment.

Retry eligibility is evaluated from the terminal attempt history, `retries_per_action_lineage`, repeated-failure policy,
remaining budget, cancellation state and current bindings. Validation failure does not require replanning while an
ordinary retry remains permitted.

An `ACCEPTED` node remains historically accepted under its recorded bindings. If an input later changes, the associated
result freshness changes independently to `REVALIDATION_REQUIRED`, `STALE`, or `INVALID`. New dependency satisfaction is
then prohibited until revalidation or replacement.

### Root replacement

A root may be superseded only by an admitted `ROOT_REPLACED` event naming one non-terminal replacement root.

`ROOT_REPLACED` atomically:

1. marks the previous root `SUPERSEDED`;
2. records the `SUPERSEDES` relation;
3. updates the run's active-root identifier;
4. transfers only explicitly declared remaining budget and obligations;
5. preserves the previous root and its evidence for audit.

A superseded root without a valid replacement is a terminal run outcome.

### Attempt lifecycle

Attempt states:

<!-- gen:attempt-states -->

- `PLANNED`
- `RESERVED`
- `DISPATCHED`
- `RUNNING`
- `OBSERVATION_RECEIVED`
- `RESULT_PRODUCED`
- `FAILED`
- `TIMED_OUT`
- `CANCELLED`

<!-- /gen:attempt-states -->

Terminal attempt states:

<!-- gen:attempt-terminal -->

- `RESULT_PRODUCED`
- `FAILED`
- `TIMED_OUT`
- `CANCELLED`

<!-- /gen:attempt-terminal -->

<!-- gen:attempt-transitions -->

| Current state | Event | Guard | Next state |
|---|---|---|---|
| None | `ATTEMPT_PLANNED` | An attempt is needed; a new attempt identifier and commitment are assigned; `cause` is `INITIAL` for a first attempt or `RETRY` for a retry | `PLANNED` |
| `PLANNED` | `ACTION_RESERVED` | Permission, capacity, scope, and budget checks pass | `RESERVED` |
| `PLANNED` | `CANCELLATION_CONFIRMED` | Node cancelled before reservation | `CANCELLED` |
| `RESERVED` | `ACTION_DISPATCHED` | Disposable sandbox exists and commitment remains valid | `DISPATCHED` |
| `DISPATCHED` | `DISPATCH_ACKNOWLEDGED` | Matching executor and attempt token | `RUNNING` |
| `DISPATCHED` | `DISPATCH_FAILED` | Dispatch did not start executor work | `FAILED` |
| `DISPATCHED` | `TIMER_FIRED` | The timer is the recorded dispatch-acknowledgement timer, no matching acknowledgement was admitted, and no earlier terminal event exists | `TIMED_OUT` |
| `RUNNING` | `EXECUTOR_OBSERVATION_ADMITTED` | Matching attempt token, schema, and provenance | `OBSERVATION_RECEIVED` |
| `OBSERVATION_RECEIVED` | `RESULT_ADMITTED` | Outputs are typed, hashed and in scope; accounting is `FINALLY_SETTLED`; no `CANCELLATION_CONFIRMED` has been recorded for the attempt; and the result remains eligible under current bindings | `RESULT_PRODUCED` |
| `OBSERVATION_RECEIVED` | `RESULT_REJECTED` | Result admission check failed | `FAILED` |
| `RUNNING` | `TIMER_FIRED` | Timer belongs to attempt and no earlier terminal event exists | `TIMED_OUT` |
| `RESERVED` | `CANCELLATION_CONFIRMED` | Dispatch has not occurred and cancellation authority and policy pass | `CANCELLED` |
| `DISPATCHED` | `CANCELLATION_CONFIRMED` | Executor or recovery policy confirms termination | `CANCELLED` |
| `RUNNING` | `CANCELLATION_CONFIRMED` | Executor or recovery policy confirms termination | `CANCELLED` |
| `OBSERVATION_RECEIVED` | `CANCELLATION_CONFIRMED` | Cancellation preceded result admission and policy rejects result eligibility | `CANCELLED` |
| `RUNNING` | `EXECUTOR_FAILED` | Authoritative failure observation admitted | `FAILED` |
| `DISPATCHED` | `ATTEMPT_FAILED_ON_RECOVERY` | Recovery finds no terminal observation | `FAILED` |
| `RUNNING` | `ATTEMPT_FAILED_ON_RECOVERY` | Recovery finds no terminal observation | `FAILED` |
| `OBSERVATION_RECEIVED` | `ATTEMPT_FAILED_ON_RECOVERY` | Recovery finds no terminal observation | `FAILED` |
| Any non-terminal attempt state | `SANDBOX_VIOLATION_RECORDED` | Authoritative sandbox enforcement or forensic observation establishes a violation | `FAILED` |

<!-- /gen:attempt-transitions -->

`RETRYABLE` is not an attempt state. Retry eligibility is a deterministic policy decision over a terminal failed,
timed-out, or cancelled attempt. A retry creates a new attempt identifier and a new commitment.

### Result and accounting eligibility

Execution state determines result eligibility. Accounting state determines cost-observation eligibility.

- `RESULT_PRODUCED` may admit no additional result.
- `FAILED`, `TIMED_OUT`, and `CANCELLED` may admit cost-only observations while accounting remains `OPEN` or
  `PROVISIONALLY_SETTLED`.
- No terminal execution state may return to `RUNNING`.
- Settlement events update the ledger and accounting state without changing the execution state.

`CANCELLATION_REQUESTED` is an orthogonal recorded cancellation flag, not proof that execution terminated.
`CANCELLATION_CONFIRMED`, timeout, sandbox violation or recovery moves the execution lifecycle to its terminal state.
Once `CANCELLATION_CONFIRMED` is recorded for an attempt, no subsequent observation may admit a result or publish an
output from that attempt. Cost-only observations remain eligible until accounting becomes `FINALLY_SETTLED`. This rule
applies to cancellation caused by invalidation, budget decisions, alternative loss, run termination or explicit policy.

On the successful result path, cost reconciliation occurs before result admission. Consequently, `RESULT_ADMITTED`
requires accounting state `FINALLY_SETTLED`.

### Accounting lifecycle

Accounting states are independent from attempt execution states:

<!-- gen:accounting-states -->

- `OPEN`
- `PROVISIONALLY_SETTLED`
- `FINALLY_SETTLED`

<!-- /gen:accounting-states -->

<!-- gen:accounting-transitions -->

| Current accounting state | Event | Guard | Next accounting state |
|---|---|---|---|
| None | `ATTEMPT_COMMITMENT_OPENED` | A valid commitment is atomically assigned to a newly authorised attempt | `OPEN` |
| `OPEN` | `ATTEMPT_EXECUTION_ENDED_UNRECONCILED` | Execution is terminal but authoritative actual usage is not yet available | `PROVISIONALLY_SETTLED` |
| `OPEN` | `ATTEMPT_COST_RECONCILED` | Authoritative actual usage is admitted and commitment reclamation is computed atomically | `FINALLY_SETTLED` |
| `PROVISIONALLY_SETTLED` | `ATTEMPT_COST_RECONCILED` | Authoritative actual usage is admitted and commitment reclamation is computed atomically | `FINALLY_SETTLED` |
| `OPEN` | `ATTEMPT_COST_SETTLED_CONSERVATIVELY` | The settlement deadline passed without authoritative usage | `FINALLY_SETTLED` |
| `PROVISIONALLY_SETTLED` | `ATTEMPT_COST_SETTLED_CONSERVATIVELY` | The settlement deadline passed without authoritative usage | `FINALLY_SETTLED` |

<!-- /gen:accounting-transitions -->

`ATTEMPT_COST_RECONCILED` atomically records actual consumption, reclaims the unused commitment and sets accounting to
`FINALLY_SETTLED`.

Accounting settlement never changes the terminal execution state and never restores result eligibility.

### Result freshness

Result freshness is a property of a versioned result or acceptance record and is independent from node lifecycle. It is tracked by a dedicated freshness state machine.

Freshness states:

<!-- gen:freshness-states -->

- `CURRENT`: all bound versions remain current;
- `COMPATIBLE`: a bound input changed, but a deterministic compatibility rule proves continued validity;
- `REVALIDATION_REQUIRED`: continued validity is possible but requires fresh validation evidence;
- `STALE`: the result may no longer satisfy dependant obligations;
- `INVALID`: the result is known not to satisfy its binding.

<!-- /gen:freshness-states -->

Only `CURRENT` or `COMPATIBLE` accepted results may satisfy dependencies.

<!-- gen:freshness-transitions -->

| Current state | Event | Guard | Next state |
|---|---|---|---|
| None | `RESULT_ADMITTED` | A new result or acceptance record is admitted, bound to exact versions | `CURRENT` |
| None | `ACCEPTANCE_AUTHORISED` | A new acceptance record is admitted, bound to exact versions | `CURRENT` |
| `CURRENT` | `RESULT_MARKED_STALE` | A bound input, result, or policy changed making the result stale | `STALE` |
| `CURRENT` | `RESULT_MARKED_INVALID` | A bound input, result, or policy changed making the result invalid | `INVALID` |
| `CURRENT` | `RESULT_MARKED_REVALIDATION_REQUIRED` | A bound input changed, continued validity possible but requires revalidation | `REVALIDATION_REQUIRED` |
| `CURRENT` | `RESULT_MARKED_COMPATIBLE` | A bound input changed but a deterministic compatibility rule proves continued validity | `COMPATIBLE` |
| `COMPATIBLE` | `RESULT_MARKED_STALE` | A bound input, result, or policy changed making the result stale | `STALE` |
| `COMPATIBLE` | `RESULT_MARKED_INVALID` | A bound input, result, or policy changed making the result invalid | `INVALID` |
| `COMPATIBLE` | `RESULT_MARKED_REVALIDATION_REQUIRED` | A bound input changed, continued validity possible but requires revalidation | `REVALIDATION_REQUIRED` |
| `REVALIDATION_REQUIRED` | `RESULT_MARKED_COMPATIBLE` | A deterministic compatibility rule later proves continued validity without fresh validation evidence | `COMPATIBLE` |
| `REVALIDATION_REQUIRED` | `RESULT_REVALIDATED` | Revalidation succeeded and created a new acceptance record | `STALE` |
| `REVALIDATION_REQUIRED` | `RESULT_MARKED_STALE` | Revalidation failed or timed out, or a bound input changed making the result stale | `STALE` |
| `REVALIDATION_REQUIRED` | `RESULT_MARKED_INVALID` | Revalidation failed definitively, or a bound input changed making the result invalid | `INVALID` |
| `STALE` | `RESULT_MARKED_INVALID` | Further evidence confirms invalidity | `INVALID` |

<!-- /gen:freshness-transitions -->

A successful revalidation does not return the same record to `CURRENT`. It creates a new acceptance
record, admitted by `ACCEPTANCE_AUTHORISED`, which begins at `CURRENT`; the revalidated record becomes
`STALE`. This is consistent with immutable records bound to versions: each acceptance record is tied to
a specific binding set and is never mutated in place.

Terminal freshness states:

<!-- gen:freshness-terminal -->

- `INVALID`

<!-- /gen:freshness-terminal -->

`STALE` is absorbing: it can only transition to `INVALID` via `RESULT_MARKED_INVALID`.

`VALIDATION_INVALIDATED` emits `RESULT_MARKED_REVALIDATION_REQUIRED` or `RESULT_MARKED_STALE` depending on the invalidation type.

When an accepted predecessor becomes `STALE` or `INVALID`:

- dependant attempts in `RESERVED` are cancelled and their commitments are reclaimed;
- dependant attempts in `RUNNING` receive a recorded cancellation request;
- an already dispatched executor may finish, but its returned result is recorded with freshness `STALE`;
- stale returned results may be retained as evidence but cannot enter validation, publication, or acceptance;
- accepted or published dependant results bound to the invalidated predecessor become `REVALIDATION_REQUIRED` or `STALE` according to the declared compatibility policy;
- the invalidation is propagated transitively through exact version bindings.

These rules are mandatory for the MVP and are not left to executor discretion.

### Artefact lifecycle

Artefact states:

<!-- gen:artefact-states -->

- `CANDIDATE`
- `INTEGRATED`
- `VALIDATED`
- `PUBLISHED`
- `REJECTED`
- `SUPERSEDED`

<!-- /gen:artefact-states -->

Terminal artefact-version states:

<!-- gen:artefact-terminal -->

- `PUBLISHED`
- `REJECTED`
- `SUPERSEDED`

<!-- /gen:artefact-terminal -->

<!-- gen:artefact-transitions -->

| Current state | Event | Guard | Next state |
|---|---|---|---|
| None | `ARTEFACT_ADMITTED` | Type, hash, provenance, scope, and producer authority valid | `CANDIDATE` |
| `CANDIDATE` | `ARTEFACT_INTEGRATED` | Integration owner, merge, and conflict rules pass | `INTEGRATED` |
| `INTEGRATED` | `ARTEFACT_VALIDATED` | Required validator evidence passes | `VALIDATED` |
| `VALIDATED` | `ARTEFACT_PUBLISHED` | Publishing authority and immutable version checks pass | `PUBLISHED` |
| `CANDIDATE` | `ARTEFACT_REJECTED` | Admission or integration policy rejects candidate | `REJECTED` |
| `INTEGRATED` | `ARTEFACT_REJECTED` | Validation or policy rejects integrated candidate | `REJECTED` |
| `VALIDATED` | `ARTEFACT_REJECTED` | Publication policy rejects candidate | `REJECTED` |
| `PUBLISHED` | `ARTEFACT_SUPERSEDED` | Valid replacement version is published | `SUPERSEDED` |

<!-- /gen:artefact-transitions -->

Publication creates or designates an immutable authoritative version. Published content is never mutated in place.

### Action-to-machine mapping

- `PROPOSE_DECOMPOSITION` emits graph proposal admission or rejection events. An admitted decomposition emits
  `DECOMPOSITION_ADMITTED` and moves a `READY` parent to `BLOCKED`.
- `EXECUTE` creates and advances an attempt.
- `VALIDATE` advances the node to `VALIDATING` and admits evidence.
- `INTEGRATE` advances candidate artefacts to `INTEGRATED`.
- `PUBLISH` advances validated artefacts to `PUBLISHED`.
- `REVIEW` emits evidence and does not directly change node acceptance.
- `RESOLVE_CONFLICT` emits conflict-resolution events.
- `SELECT_ALTERNATIVE` emits selection and losing-branch cancellation events.
- `REPLAN` emits a plan proposal and, if admitted, `PLAN_ADMITTED`.
- `CANCEL` emits cancellation requests and confirmations.

### Failure outcome mapping

Policy outcomes in [Failure and Replanning](#failure-and-replanning) are decisions that emit state-machine events:

- `RETRY` creates a new attempt;
- `SELECT_ALTERNATIVE` changes alternative selection and may cancel attempts;
- `REPLAN` emits `REPLAN_REQUESTED`;
- `BLOCK` emits `READINESS_REVOKED`;
- `MARK_INFEASIBLE` emits `NODE_MARKED_INFEASIBLE`;
- `CANCEL` emits node or attempt cancellation events;
- `MARK_STALE` emits `RESULT_MARKED_STALE`;
- `SUPERSEDE` emits node or artefact supersession;
- `REJECT` emits node or artefact rejection;
- `MARK_CONFLICTED` emits `CONFLICT_RECORDED`;
- `MARK_BUDGET_EXHAUSTED` emits `BUDGET_EXHAUSTION_RECORDED`.

### Policy transition completeness

Every policy outcome must resolve to a legal event path. The reducer rejects a policy registry at startup if any
configured outcome lacks a legal transition from an applicable source state.

<!-- gen:policy-completeness -->

| Condition | Applicable source states | Required event path |
|---|---|---|
| Attempt needed | none or terminal failed attempt | `ATTEMPT_PLANNED` |
| Retry prohibited | Node `VALIDATING`, `READY`, or `BLOCKED` | `REPLAN_REQUESTED` |
| Replan admitted | Node `REPLAN_REQUIRED` | `PLAN_ADMITTED` to `BLOCKED` |
| Replan produces no material change | Node `REPLAN_REQUIRED` | `NODE_MARKED_INFEASIBLE` or `BUDGET_EXHAUSTION_RECORDED`, according to the applicable deterministic condition |
| Blocking conflict found | Node `READY`, `VALIDATING`, or `VALIDATED` | `CONFLICT_RECORDED` |
| Input invalidated | Node `READY`, `VALIDATING`, or `VALIDATED` | `READINESS_REVOKED` or `VALIDATION_INVALIDATED` |
| Controller recovery loses attempt | Attempt `DISPATCHED`, `RUNNING`, or `OBSERVATION_RECEIVED` | `ATTEMPT_FAILED_ON_RECOVERY` |
| `PLANNED` attempt cancelled before reservation | `PLANNED` | `CANCELLATION_CONFIRMED` to `CANCELLED` |
| Decomposition admitted from READY | Node `READY` | `DECOMPOSITION_ADMITTED` to `BLOCKED` |

<!-- /gen:policy-completeness -->

### Failure and Replanning

Failure is evidence, not merely an exception. Preserve:

- attempted action;
- executor;
- input versions;
- outputs;
- diagnostics;
- actual cost;
- validation results;
- introduced assumptions;
- conflicts;
- retry history.

Possible policy outcomes:

- RETRY
- SELECT_ALTERNATIVE
- REPLAN
- BLOCK
- MARK_INFEASIBLE
- CANCEL
- MARK_STALE
- SUPERSEDE
- REJECT
- MARK_CONFLICTED
- MARK_BUDGET_EXHAUSTED

Repeated failure without new evidence should trigger replanning or termination, not unlimited retries or superficial
decomposition.

### Repeated-failure rule

Every failed attempt records a diagnostic fingerprint:

```text
SHA-256(
    action type
    + contract version
    + input version set
    + executor profile
    + controller-assigned resolution strategy identifier
    + normalised exit code
    + normalised diagnostic category
    + normalised primary diagnostic text
)
```

Paths, timestamps, generated identifiers, memory addresses, and nondeterministic ordering are removed during
normalisation.

A failure provides new evidence if at least one of the following differs from every earlier failed attempt for the same
action lineage:

- diagnostic fingerprint;
- contract version;
- input version set;
- executor profile or tool version;
- controller-assigned resolution strategy identifier;
- admitted evidence set.

A model-supplied strategy label, identifier, free-text description or randomly varying value is not new evidence.

The controller assigns `resolution_strategy_id` by matching the admitted action plan to a deterministic, versioned
strategy registry. A strategy counts as changed only when the assigned registry entry differs and the new entry requires
a materially different operation, tool, algorithm, input transformation or diagnostic hypothesis.

Additional wording, reordered steps, fresh generated identifiers or an unrecognised strategy do not establish new
evidence. An unrecognised model-supplied strategy is recorded for audit but is treated as the same strategy for retry
eligibility.

After two consecutive failures without new evidence, the action is no longer retryable under the default policy. The
reducer emits `REPLAN_REQUESTED`. If replanning produces no materially changed contract, inputs, strategy, or evidence,
the affected node becomes `INFEASIBLE` or `BUDGET_EXHAUSTED` according to the applicable deterministic
condition.

### READY-node action selection

The MVP does not spend a separate model call to choose between `EXECUTE` and `PROPOSE_DECOMPOSITION`.

For every `READY` node, the controller deterministically evaluates action eligibility in this order:

1. choose `EXECUTE` when the node contract is directly executable within one owned scope, its required inputs and
   acceptance procedure are resolved, and no deterministic decomposition trigger applies;
2. choose `PROPOSE_DECOMPOSITION` when execution cannot be authorised because the contract contains multiple mandatory
   obligations requiring distinct owned scopes, an unresolved integration dependency, or an estimated context or action
   requirement above the frozen per-attempt limit;
3. choose `EXECUTE` when both actions are eligible and decomposition's deterministic expected-benefit score does not
   exceed its frozen coordination-cost threshold;
4. choose `PROPOSE_DECOMPOSITION` when both are eligible and that threshold is exceeded;
5. if neither action is eligible, emit the applicable readiness-revocation, replan, infeasibility or budget event.

The score inputs, thresholds and tie-breaking rule are deterministic, versioned and frozen in the experiment manifest.
They use contract metadata and recorded execution evidence, not an additional model generation.

`FIXED_ONE_LEVEL` follows its frozen decomposition. `DIRECT` always selects `EXECUTE` for its undecomposed root. The
deterministic selector applies to recursive arms.

### Run termination and outcomes

A run terminates when one of the following outcomes is admitted for the active root:

<!-- gen:run-outcomes -->

| Root condition | Run outcome |
|---|---|
| `ACCEPTED` | `ACCEPTANCE_CLAIMED` |
| `INFEASIBLE` | `INFEASIBLE` |
| `REJECTED` | `REJECTED` |
| `CANCELLED` | `CANCELLED` |
| `BUDGET_EXHAUSTED` | `BUDGET_EXHAUSTED` |
| `SUPERSEDED` without an admitted replacement root | `SUPERSEDED_WITHOUT_REPLACEMENT` |
| Unrecoverable controller or oracle-independent infrastructure failure | `INFRASTRUCTURE_FAILURE` |
| Wall-clock budget expires | `BUDGET_EXHAUSTED` with dimension `WALL_CLOCK` |

<!-- /gen:run-outcomes -->

`SUPERSEDED` does not terminate the run when the same atomic transaction admits a valid replacement root. The run
continues using that replacement as its active root.

A run is quiescent only when all of the following hold simultaneously:

1. no attempt is in `PLANNED`, `RESERVED`, `DISPATCHED`, `RUNNING` or `OBSERVATION_RECEIVED`;
2. no live timer can emit a future transition for the run;
3. no executor observation, cost observation, graph proposal, result proposal, integration proposal or publication
   proposal is awaiting admission;
4. no node is `READY` with an action permitted by authority, dependency, scope, capacity, retry, budget and policy
   checks;
5. no deterministic reducer or scheduler event is currently derivable from authoritative state.

A node waiting only for unresolved mandatory children is not sufficient to establish quiescence if any child has a
permitted action.

Quiescence is evaluated only after the event queue has been drained in global event-sequence order. It is a
deterministic projection of recorded state and must not depend on a direct wall-clock read or live executor query.

Quiescence is evaluated only when the active root is non-terminal.

On quiescence:

- a requirement for authority unavailable to the autonomous experiment is an admitted infeasibility condition and emits
  `NODE_MARKED_INFEASIBLE`;
- exhausted cost or token capacity emits `BUDGET_EXHAUSTION_RECORDED`;
- proven impossibility emits `NODE_MARKED_INFEASIBLE`;
- otherwise the active root enters `REPLAN_REQUIRED`;
- if no materially changed replan is admissible, the reducer emits `NODE_MARKED_INFEASIBLE`.

The reducer never attempts to move a terminal root to `REPLAN_REQUIRED`.

### Crash recovery

The controller must record `ACTION_DISPATCHED` atomically before sending a request to an executor.

Each attempt uses a unique, single-use attempt token. Executor observations are idempotent by attempt identifier, token,
and payload hash.

After controller restart:

1. replay the complete authoritative event log;
2. reconstruct graph, machine, ledger, timer, and scheduling state;
3. inspect attempts in `DISPATCHED`, `RUNNING`, or `OBSERVATION_RECEIVED`;
4. admit `ATTEMPT_FAILED_ON_RECOVERY` for every such attempt lacking a terminal observation;
5. transition those attempts to `FAILED`;
6. reconcile known actual costs or conservatively charge the full outstanding commitment when actual cost cannot be
   established;
7. apply the normal retry and repeated-failure policy;
8. delete or quarantine disposable worktrees after recording their tree and diff hashes.

Recovery also inspects terminal attempts whose accounting state is not `FINALLY_SETTLED`. It either admits an available
idempotent cost observation or conservatively settles the full outstanding commitment when the settlement deadline has
passed.

The MVP does not reconnect to an in-flight executor after a controller crash. It fails the attempt deterministically and
retries only under normal policy.

Because writing side effects are confined to disposable worktrees, abandoning an attempt cannot mutate the authoritative
repository tree. Publication and integration remain event-backed atomic operations.
