feat(research,campaign,cli): chunked instrument-major cell walk + duplicate-instrument refusal
Two pieces of the parallel-cell-loop iteration (spec: parallel-cell-loop), still sequential — behaviour-identical results, suite green unchanged: - validate_campaign refuses a campaign document listing the same instrument twice (DocFault::DuplicateInstrument + CLI prose). A duplicate enumerates two identical cells and is the one input that could make two cells share a registry family name; with distinct instruments the per-cell family name (all four cell axes + stage ordinal) is unique by construction, so the upcoming parallel appends can never race one name's run-index assignment. - aura_campaign::execute flattens the 4-nested cell loop into a document- order-indexed cell list, groups it by instrument ordinal, and walks sequential chunks of K instrument groups; results land in index-addressed slots and the ordered outputs (outcomes, realizations, nominee groups) are rebuilt in document order. K arrives as a new NonZeroUsize parameter (default DEFAULT_PARALLEL_INSTRUMENTS = 4), exposed as --parallel-instruments on aura campaign run and threaded through the run_campaign chain; the dissolved-verb sugar paths pass the default. The chunk walk is the structural RAM bound for the parallel flip that follows: no cell of an instrument outside the current chunk can be in flight at all (chosen over a semaphore gate — blocking inside pool tasks risks worker starvation; decision log on #277). Verified: workspace suite green unchanged, clippy -D warnings clean, E2E fixtures pin the CLI prose and the document-order persistence across K. refs #277
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
//! visible to child modules without promotion.
|
||||
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
use std::num::NonZeroUsize;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{mpsc, Arc};
|
||||
|
||||
@@ -478,7 +479,11 @@ pub(crate) enum RunPresentation {
|
||||
/// is a refusal `campaign_cmd` renders as `aura: {msg}` + exit 1; an `Ok`
|
||||
/// carries the failed-cell count (#272) so the caller can exit 3 on a
|
||||
/// completed-with-failures run via `exit_on_campaign_result`.
|
||||
pub(crate) fn run_campaign(target: &str, env: &Env) -> Result<usize, String> {
|
||||
pub(crate) fn run_campaign(
|
||||
target: &str,
|
||||
env: &Env,
|
||||
parallel_instruments: NonZeroUsize,
|
||||
) -> Result<usize, String> {
|
||||
// Project gate FIRST: nothing (not even the file-sugar registration)
|
||||
// touches a store outside a project.
|
||||
if env.provenance().is_none() {
|
||||
@@ -511,7 +516,7 @@ pub(crate) fn run_campaign(target: &str, env: &Env) -> Result<usize, String> {
|
||||
));
|
||||
};
|
||||
|
||||
run_campaign_by_id(&campaign_id, env, RunPresentation::Full)
|
||||
run_campaign_by_id(&campaign_id, env, RunPresentation::Full, parallel_instruments)
|
||||
}
|
||||
|
||||
/// An executed campaign plus the context its presenter and the dissolved-verb
|
||||
@@ -540,8 +545,9 @@ pub(crate) fn run_campaign_by_id(
|
||||
campaign_id: &str,
|
||||
env: &Env,
|
||||
presentation: RunPresentation,
|
||||
parallel_instruments: NonZeroUsize,
|
||||
) -> Result<usize, String> {
|
||||
let run = run_campaign_returning(campaign_id, env)?;
|
||||
let run = run_campaign_returning(campaign_id, env, parallel_instruments)?;
|
||||
present_campaign(run, presentation, env)
|
||||
}
|
||||
|
||||
@@ -556,6 +562,7 @@ pub(crate) fn run_campaign_by_id(
|
||||
pub(crate) fn run_campaign_returning(
|
||||
campaign_id: &str,
|
||||
env: &Env,
|
||||
parallel_instruments: NonZeroUsize,
|
||||
) -> Result<CampaignRun, String> {
|
||||
let registry = env.registry();
|
||||
let campaign_text = registry
|
||||
@@ -635,6 +642,7 @@ pub(crate) fn run_campaign_returning(
|
||||
&strategies,
|
||||
&runner,
|
||||
®istry,
|
||||
parallel_instruments,
|
||||
)
|
||||
.map_err(|f| exec_fault_prose(&f))?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user