Settle pre-dispatch model failures as terminal outcomes
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 withErr(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
DispatchPhasecapability 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
Failedmapping and removes capability consumption as negative controls.