Settle pre-dispatch model failures as terminal outcomes

VA
7h ago
+998-88

Change

Deterministic model preparation failures now settle the durable task as Outcome::Failed, so LocalHarness::close() succeeds without a provider generation request. This unblocks the oversized-skill-catalog regression in #1003.

ExecutionProgress::Failed carries a privately constructed PreDispatchFailure. The response loop and nested automatic/manual summaries share one dispatch capability. Provider generation goes through a typed attempt created only after consuming that capability, before claiming/journaling dispatch. Replays must authenticate the absence of earlier dispatch before producing a known failure. The model-command wrapper transfers responsibility to the executor before calling it; it cannot reinterpret subsequent errors.

Preparation and output-budget validation use the same classification path. The former unreachable 7-line “automatic compaction is disabled” guard is deleted: compaction now requires a threshold policy argument. For proven preparation failures, ordinary executor APIs preserve the original error; native progress retains the funded diagnostic. Existing durable Failed encoding is reused.

Guarantee

  • Class: G, deterministic preparation causes flattened into uncertain outcomes. A private failure constructor and consumed dispatch capability prevent a known-before-dispatch failure from being emitted after an attempt starts. Dispatch never matches diagnostic strings.
  • Proof: a generated-diagnostic property checks every started/delegated capability combination. A lifecycle table exercises output validation, projection planning, mandatory-source/capacity, request limits, summary output/input/missing-budget checks, accounting and policy validation; every row requires its originating diagnostic, TaskFailed, zero generation requests and successful close. A current-thread regression uses the public LocalHarness and real HTTP provider/count adapter. Existing executor recovery tests cover dispatch and replay; a post-dispatch lifecycle regression stays Indeterminate. The qualification script mutates the production Failed mapper back to the old error return and removes capability consumption; the lifecycle table, HTTP regression and capability property must all fail.
  • Entry points: model-command construction, ordinary/native stock turn execution, selected-context execution, automatic compaction and explicit summary execution. Response and summary generation share the typed attempt boundary; public model-step and standalone summary APIs retain their Result contract and use the same attempt boundary.
  • Gaps: none.

Decisions

  • The compacted mandatory-capacity rejection happens after the summary provider was called. It remains uncertain, including on replay. The mandatory-capacity check before that summary is a known Failed outcome.
  • Authority and owner preconditions run before classification, preserving rejection before journal access. Cancellation, rejection, storage/resource uncertainty and reconciliation retain their existing semantics. An unreadable dispatch history propagates its own error, preserving cancellation and native resource custody. No success-path replay scan is added.
  • The task-runtime control machine now consumes command results instead of reissuing the same committed command identity. Its native regression checks the durable Failed input, zero generation/reconciliation requests and original ledger custody.
  • The additional phase/policy borrows change the exact preparation allocation byte snapshots by 64 bytes; allocation/reallocation/deallocation counts remain unchanged. The equality guard and redundant-preparation negative control remain in force; no limits change.
  • The existing cancellation snapshots predate #949's independently owned pending-read workers. Task submission/completion pins increase by exactly one per blocked read (text +2, compaction +2, effect/goal +4); every I/O and model count remains exact and unchanged. The complete poll-sweep suite verifies coverage and its missing-cut negative controls.
  • The HTTP lifecycle tier would have caught the unclosable preparation failure. Executor-only assertions previously checked the returned error without proving terminal task settlement and close.

Validation

On bb2, filesystem-local,native-execution,test-support, test profile:

  • Harness lib build: 304 s; executor 67/67, 5.29 s; task runtime 34/34, 1.29 s.
  • Ten-site lifecycle table: 10/10 sites, 3.45 s, each Failed, zero generation requests and successful close.
  • Public lifecycle/HTTP regressions: 3/3, 0.67 s; existing output-capacity close regression 1/1, 0.66 s. The compaction cancellation fixture also passed in the expanded sweep.
  • Production mutations: build 303 s. Restoring the catch-all makes the table fail at OutputZero: Err(Indeterminate(...)); requests: 0 (0.20 s) and the real HTTP test fail with Err(Indeterminate(...)) (0.20 s). Removing dispatch-capability consumption fails the generated property, shrunk to diagnostic "a" (<0.01 s). Each negative control exited 101 as required.
  • Windows GNU cargo check --locked -p acyclic-harness --lib --tests --features filesystem-local,native-execution,test-support --target x86_64-pc-windows-gnu: passed, 344 s.
  • Staged git diff --check: passed.

Reproduce the positive suites, production negative controls and Windows GNU compile proof with powershell -NoProfile -c "& Q:/lanes/bs.ps1 bash scripts/qualify-pre-dispatch.sh".

Coordinator: add scripts/qualify-pre-dispatch.sh to the qualification/merge gate for the production mutation controls.

Live steps

The expanded existing cancellation sweep passed 13/13 in 13.03 s on the same production change, then failed one replay-prefix ordering assertion (12/13, 22.56 s) when pending-read worker submission reordered with blocking completion. All exact boundary-count checks passed. No sweep assertion or cancellation cut was removed. This broader scheduler flake remains visible to the full merge gate; the focused compaction fixture is included in this PR qualification.

Disclosure

  • No dependencies or imported source added; provenance entries are unchanged.
  • No generated descriptors or README changes.
  • No private source, protocol, credential or operational evidence included.
  • Signed commit includes the DCO sign-off.

Summary by cubic

Deterministic model preparation failures now settle the durable task as terminal Outcome::Failed, so LocalHarness::close() succeeds without a provider generation request. This unblocks the oversized-skill-catalog regression in #1003.

  • A shared DispatchPhase capability is consumed before any provider attempt starts; a known failure can only be emitted while the capability is held, and replay conversion requires an authenticated empty dispatch history.
  • The model-command wrapper delegates responsibility before building a typed provider attempt, and the task-runtime control machine consumes command results instead of reissuing the committed command identity.
  • Preparation and output-budget validation share one classification path; ordinary executor APIs keep their original error while native progress retains the funded diagnostic.
  • The unreachable "automatic compaction is disabled" guard is deleted; compaction now requires a threshold policy argument.
  • New lifecycle table covers all ten pre-dispatch sites plus real-HTTP and generated-diagnostic property regressions, and the added qualification script mutates the Failed mapping and removes capability consumption as negative controls.