feat(campaign,registry,cli): per-cell fault isolation — a failed cell is recorded, never a global abort
closes #272 A member fault (no-data, bind, run, or a caught panic) is now a recorded per-cell outcome instead of aborting the whole campaign and discarding every already-computed cell. The incident that motivated this (a 22-instrument campaign lost ~36 healthy cells ~6.7 min in because Copper had an archive gap) now completes: the healthy cells persist, the gap cell is recorded as failed, and the run exits 3. Direction (owner decision 2026-07-14): run to completion and report compromised results; no coverage preflight, no window synthesis. Containment granularity: - The CELL for a sweep-stage member fault (a grid hole structurally compromises winner selection, so the whole cell fails). - The FOLD for a walk_forward member fault (independent time windows): the surviving folds pool into the family, failed folds are recorded as StageRealization.window_faults, and the summary names the ratio. - aura-registry: additive CellFault / CellFaultKind (closed: no_data|bind|run|panic|window) / WindowFault / CellCoverage, plus fault/coverage fields on CellRealization and window_faults on StageRealization — all serde-default-skipped, so pre-#272 campaign_runs lines parse and round-trip byte-identical. - aura-campaign: run_cell returns a fault-annotated CellRealization instead of Err (execute's accumulate-then-append-once tail is unchanged and now persists every healthy cell + the one run record); a `contain` split keeps ExecFault::Registry and doc-shape preflight faults global while Member/Window become per-cell/per-fold. Member panics are caught with catch_unwind(AssertUnwindSafe) at all three member-run sites (sweep IS/OOS) and recorded as MemberFault::Panic — a member panic no longer aborts the process. The wf stage partitions Registry faults (global) from Member/Window (per-fold) and filters faulted-fold placeholders (the "faulted-member-placeholder" broker sentinel) out of the persisted family. - aura-cli: exec_fault_prose gains the Panic arm; CliMemberRunner::window_coverage derives effective bounds + interior gap months from the #264 archive primitives; present_campaign prints per-cell failure notes + a completion summary and threads the failed-cell count; a run with >=1 failed cell exits 3 ("completed with failed cells") uniformly across `aura campaign run` and the dissolved sweep/walkforward/mc/generalize verbs (exit_on_campaign_result). Usage stays 2, refused-before-running stays 1, clean stays 0. Tests: the global-abort pins flip to containment (execute + the two wf fault tests → fold-containment + all-folds-fail-the-cell); new panic-containment tests on both the sweep path (PanicRunner) and the wf path (this commit adds the wf mirror the loop left uncovered); a new gapped-archive e2e (one covered cell + one gap cell → exit 3); the ~14 CLI exit-1 pins move to the exit-3 register; a pre-#272-line byte-identical round-trip guard. Suite: cargo test --workspace green (1309 tests, 0 failed); clippy clean. Decision log: #272 comments (fork rationale, the fold Registry/Member split, the placeholder sentinel, uniform exit-3). Follow-up (minor, not blocking): the plan under-scoped Task 1 to aura-registry though the additive fields also touch aura-campaign's exec.rs literals — the loop absorbed it mechanically; a future plan for a cross-crate additive-field change should scope every crate's construction sites in the first task.
This commit is contained in:
@@ -604,3 +604,9 @@ family charted the same way, by the handle the run prints (members keyed
|
||||
uniquely names one recorded run (`aura chart <NAME>` resolves it against the
|
||||
stored campaign documents; a name reused across runs refuses rather than
|
||||
guessing which one you mean).
|
||||
|
||||
Exit codes: a clean run exits 0; a usage error exits 2; a refusal before any
|
||||
cell runs (invalid document, missing project, unresolvable strategy) exits 1;
|
||||
a run that **completes with one or more failed cells** exits 3 — the run record
|
||||
and every healthy cell persist, and the failed cells are named on stderr
|
||||
(#272).
|
||||
|
||||
@@ -1605,6 +1605,21 @@ the retired deferral: per-cell no-nominee skip, per-run unproducible-tap skip
|
||||
pure name derivation). Noted debt: `aura chart` over the campaign family ROOT (cells
|
||||
spanning instruments) is untested/semantically undefined — only per-cell read-back is
|
||||
pinned.
|
||||
**Realization (#272, 2026-07-14 — per-cell fault isolation).** A member
|
||||
fault (no-data, bind, run, or a caught panic) is a recorded per-cell outcome,
|
||||
never a global abort: `run_cell` returns a fault-annotated `CellRealization`
|
||||
(`fault: Option<CellFault>`, closed `CellFaultKind`) instead of `Err`, so
|
||||
`execute`'s existing accumulate-then-append-once tail persists every healthy
|
||||
cell and the one run record. Containment granularity is the cell for a sweep
|
||||
stage (a grid hole compromises selection) and the fold for walk_forward
|
||||
(surviving folds pool; failed folds recorded as `StageRealization.
|
||||
window_faults`, the summary naming the ratio). `ExecFault::Registry` and
|
||||
doc-shape preflight faults stay global. The CLI declares holes (per-cell
|
||||
notes + a completion summary) and a run with ≥1 failed cell exits **3**
|
||||
("completed with failed cells" — distinct from 0/1/2). A partially-covered
|
||||
window carries a `CellCoverage` annotation (effective bounds + interior gap
|
||||
months, from the #264 archive primitives). Generalize already treats a
|
||||
no-nominee cell as `missing`, so a failed cell surfaces there unchanged.
|
||||
**Guarantee.** Construction is a distinct phase, recursive at every level. Each
|
||||
node type has a **factory** `params → sized concrete node` (e.g. `SMA(length)`
|
||||
sizes its ring buffer). A **blueprint** is the param-generic, input-role-generic
|
||||
|
||||
Reference in New Issue
Block a user