audit: cycle-close tidy for #277 — preflight duplicate refusal, zero-bound pin, C1 realization note

Resolutions for the architect's four drift items (all fix/document, none
ratified away):

- [medium] execute enforced family-name uniqueness only via the CLI's
  validate tier: preflight now refuses duplicate campaign instruments
  itself (defense in depth for direct callers), RED-first
  (execute_refuses_duplicate_instruments).
- [medium] the parallel cell loop's C1 relationship lived only in the
  git-ignored spec: C1 gains a realization note (docs/design/INDEX.md)
  recording the chunked instrument-major schedule, the structural
  residency bound, and the two scheduling-dependent fatal-path carve-outs
  (fault attribution among completed cells; already-written family lines)
  — both inert and outside the success-path bit-identity.
- [low] the fatal-path orphan-line honesty is part of that note.
- [low] the --parallel-instruments zero-reject acceptance criterion had no
  protecting test: campaign_run_rejects_a_zero_parallel_instruments_bound
  pins clap's NonZeroUsize usage error (exit 2).

Gates re-verified: workspace suite green, clippy -D warnings clean.

refs #277
This commit is contained in:
2026-07-16 15:19:24 +02:00
parent cf94377f30
commit 69bb2fc978
4 changed files with 65 additions and 0 deletions
+15
View File
@@ -237,6 +237,21 @@ pub fn preflight(process: &ProcessDoc, campaign: &CampaignDoc) -> Result<(), Exe
StageBlock::Generalize { .. } => "std::generalize",
}
}
// Duplicate instruments enumerate identical cells whose registry family
// names collide (the name embeds the raw instrument string), racing one
// name's run-index assignment under the parallel cell loop — refused here
// as defense in depth beside the CLI's validate tier (#277 audit tidy).
let mut seen_instruments = std::collections::BTreeSet::new();
for instrument in &campaign.data.instruments {
if !seen_instruments.insert(instrument.as_str()) {
return Err(ExecFault::PipelineShape {
detail: format!(
"campaign.data.instruments lists \"{instrument}\" more than once — \
duplicate instruments collide on registry family names"
),
});
}
}
// shape: `std::sweep (std::gate)* (std::walk_forward)? (std::monte_carlo)?
// (std::generalize)?` — a monotone rank walk over adjacent pairs captures
// every violation: a rank drop is an out-of-order stage (an annotator
+22
View File
@@ -1177,3 +1177,25 @@ fn member_faults_stay_contained_in_the_parallel_loop() {
assert!(out.record.cells.iter().all(|c| c.fault.is_some()));
assert_eq!(reg.load_campaign_runs().expect("load runs").len(), 1);
}
/// Defense in depth (audit tidy, #277): `execute` itself refuses duplicate
/// instruments via `preflight`, independent of the CLI's validate tier — a
/// direct caller skipping `validate_campaign` must not reach the cell loop
/// with colliding registry family names.
#[test]
fn execute_refuses_duplicate_instruments() {
let reg = temp_registry("duplicate_instruments");
let doc = campaign(&["AAA", "AAA"]);
let proc_doc = process(vec![sweep_stage(false)]);
let err = execute(
CAMPAIGN_ID,
&doc,
&proc_doc,
&strategies(),
&FakeRunner::clean(),
&reg,
DEFAULT_PARALLEL_INSTRUMENTS,
)
.expect_err("duplicate instruments collide on family names");
assert!(matches!(err, ExecFault::PipelineShape { .. }));
}
+9
View File
@@ -4075,3 +4075,12 @@ fn campaign_over_a_gapped_archive_records_the_uncovered_cell_and_continues() {
"the campaign-run summary names the one failed cell: {out}"
);
}
/// Acceptance pin (#277): `--parallel-instruments` rejects 0 at parse time —
/// the NonZeroUsize carrier makes a zero residency bound unrepresentable, so
/// clap refuses it as a usage error (exit 2) before any target resolution.
#[test]
fn campaign_run_rejects_a_zero_parallel_instruments_bound() {
let (out, code) = run_code(&["campaign", "run", "unused", "--parallel-instruments", "0"]);
assert_eq!(code, Some(2), "clap usage error expected, got: {out}");
}