audit: #331 cycle close — every authored intake gates the root name, cli_fixed_cost re-pinned
Architect drift review (drift_found) resolved fix-path on all three items. [high] The sweep/walkforward/mc family builders and validate_and_register_axes read authored envelope files and put_blueprint'd them ungated — falsifying the C29 exemption's premise that the store is populated through gated intakes; all four sites now gate the root name before the family/axis build (placing the gate after the build produced a misleading unknown-axis error instead of the gate refusal — RED-verified), with an e2e pin on the synthetic sweep route. The decision was gate-the-routes, not weaken-the-prose: the exemption's premise is the invariant, not a wording choice. [medium] C24 now states the gate rule as the class it is (the op intake plus every CLI intake reading an authored envelope from a file; store read-back exempt, C29). [low] C23 reconciles names-as-debug-symbols with the render name's one operational role (trace-directory key — never compilation, identity, or execution semantics). Ratify: aura-bench cli_fixed_cost baseline re-pinned (run_line_fnv 6bb0d796f760d140 -> 9bdbc3acf7b2926a). The moved metric was caused by the #328 tidy (4474814), which switched single-run manifest defaults from wrapped to raw axis names after that cycle's bench rerun had already passed;bbac29dand this cycle's HEAD produce fingerprint-identical record lines (verified with both binaries on one scratch project), so this cycle only inherits the stale pin. All five surfaces fingerprint OK after the re-pin. refs #331, #328
This commit is contained in:
@@ -893,19 +893,31 @@ pub(crate) fn composite_from_any(text: &str, env: &aura_runner::project::Env) ->
|
||||
/// function — C29's "registered artifacts are never retroactively
|
||||
/// invalidated" stays intact.
|
||||
///
|
||||
/// **One deliberate exception:** `aura run <blueprint.json>`'s loaded-blueprint
|
||||
/// branch (`dispatch_run`, main.rs) also reads a fresh FILE and reaches the
|
||||
/// same unsanitized `traces/<name>/` seam (via `run_signal_r`/`run_measurement`
|
||||
/// -> `bind_tap_plan` -> `TraceStore::begin_run`), so it too must gate the root
|
||||
/// name — but it does NOT route through this wrapper, because its grammar is
|
||||
/// deliberately narrower than `composite_from_any`'s (envelope-only, no
|
||||
/// op-script array fallback; a bare `blueprint_from_json` call with its own
|
||||
/// load-error prose/hint convention). Routing it through this wrapper would
|
||||
/// silently loosen that grammar to also accept op-scripts. It instead calls
|
||||
/// [`gate_authored_root_name`] directly, sharing the identical `name_gate` +
|
||||
/// `name_gate_fault_prose` primitives this wrapper uses — so the refusal wording
|
||||
/// is byte-identical across all five authored-file-intake routes even though
|
||||
/// the shape-discrimination step is not shared by `dispatch_run`.
|
||||
/// **Deliberate exceptions — direct `gate_authored_root_name` callers
|
||||
/// (main.rs).** A handful of fresh-FILE intakes reach the same unsanitized
|
||||
/// `traces/<name>/` seam (or `put_blueprint` the loaded envelope straight
|
||||
/// into the registry) without going through this wrapper, because each
|
||||
/// already parses the document its own way and only needs the shared root-
|
||||
/// name gate bolted on, not the shape-discrimination `composite_from_any`
|
||||
/// does:
|
||||
/// - `aura run <blueprint.json>` (`dispatch_run`): envelope-only grammar (no
|
||||
/// op-script fallback), feeds `signal.name()` into
|
||||
/// `run_signal_r`/`run_measurement` -> `bind_tap_plan` ->
|
||||
/// `TraceStore::begin_run`.
|
||||
/// - `validate_and_register_axes` (shared by `generalize`,
|
||||
/// `sweep --real`, `walkforward --real`, `mc --real`): `put_blueprint`s
|
||||
/// the loaded envelope by topology hash before any of those four verbs
|
||||
/// touches an archive.
|
||||
/// - `run_blueprint_sweep` / `run_blueprint_walkforward` / `run_blueprint_mc`
|
||||
/// (the synthetic, no-`--real` family builders): same `put_blueprint`
|
||||
/// reason, on the routes that bypass `validate_and_register_axes`
|
||||
/// entirely (#331 cycle-close — these used to plant an ungated envelope
|
||||
/// in the store; see each fn's own comment).
|
||||
///
|
||||
/// All of these share the identical `name_gate` + `name_gate_fault_prose`
|
||||
/// primitives this wrapper uses, so the refusal wording is byte-identical
|
||||
/// across every authored-file-intake route even though the shape-
|
||||
/// discrimination step is not shared by them.
|
||||
pub(crate) fn composite_from_authored_text(
|
||||
text: &str,
|
||||
env: &aura_runner::project::Env,
|
||||
|
||||
+60
-23
@@ -901,6 +901,18 @@ fn run_blueprint_sweep(
|
||||
env: &aura_runner::project::Env,
|
||||
) {
|
||||
let _ = persist; // reserved for the deferred per-member trace path; the family record below is unconditional
|
||||
// #331 cycle-close: this fn `put_blueprint`s the loaded fresh-FILE
|
||||
// envelope into the registry below — gate the root name up front,
|
||||
// before the family ever builds (a shape-violating name can otherwise
|
||||
// confuse the wrapped/raw axis resolution the family build performs,
|
||||
// producing a misleading unrelated error instead of this refusal; see
|
||||
// `gate_authored_root_name`'s doc comment for the full route list).
|
||||
let blueprint = blueprint_from_json(doc, &|t| env.resolve(t))
|
||||
.expect("doc parse-validated at the dispatch boundary");
|
||||
if let Err(msg) = graph_construct::gate_authored_root_name(blueprint.name()) {
|
||||
eprintln!("aura: {msg}");
|
||||
std::process::exit(2);
|
||||
}
|
||||
let family = blueprint_sweep_family(doc, axes, &data, env).unwrap_or_else(|e| {
|
||||
eprintln!("aura: {e}");
|
||||
std::process::exit(2);
|
||||
@@ -915,11 +927,7 @@ fn run_blueprint_sweep(
|
||||
.topology_hash
|
||||
.clone()
|
||||
.expect("a blueprint sweep stamps every member's topology_hash");
|
||||
let canonical = blueprint_to_json(
|
||||
&blueprint_from_json(doc, &|t| env.resolve(t))
|
||||
.expect("doc parse-validated at the dispatch boundary"),
|
||||
)
|
||||
.expect("a loaded blueprint re-serializes");
|
||||
let canonical = blueprint_to_json(&blueprint).expect("a loaded blueprint re-serializes");
|
||||
reg.put_blueprint(&topo, &canonical).unwrap_or_else(|e| {
|
||||
eprintln!("aura: {e}");
|
||||
std::process::exit(1);
|
||||
@@ -947,6 +955,16 @@ fn run_blueprint_walkforward(
|
||||
doc: &str, axes: &[(String, Vec<Scalar>)], name: &str, data: DataSource, select: Selection,
|
||||
env: &aura_runner::project::Env,
|
||||
) {
|
||||
// #331 cycle-close: this fn `put_blueprint`s the loaded fresh-FILE
|
||||
// envelope into the registry below — gate the root name up front,
|
||||
// before the family ever builds (see `run_blueprint_sweep`'s identical
|
||||
// comment on why this must precede the family build, not just the
|
||||
// registry write).
|
||||
let blueprint = blueprint_from_json(doc, &|t| env.resolve(t)).expect("doc parse-validated at the dispatch boundary");
|
||||
if let Err(msg) = graph_construct::gate_authored_root_name(blueprint.name()) {
|
||||
eprintln!("aura: {msg}");
|
||||
std::process::exit(2);
|
||||
}
|
||||
let result = blueprint_walkforward_family(doc, axes, &data, select, env);
|
||||
let reg = env.registry();
|
||||
let topo = result.windows[0]
|
||||
@@ -956,10 +974,7 @@ fn run_blueprint_walkforward(
|
||||
.topology_hash
|
||||
.clone()
|
||||
.expect("a blueprint walk-forward stamps every member's topology_hash");
|
||||
let canonical = blueprint_to_json(
|
||||
&blueprint_from_json(doc, &|t| env.resolve(t)).expect("doc parse-validated at the dispatch boundary"),
|
||||
)
|
||||
.expect("a loaded blueprint re-serializes");
|
||||
let canonical = blueprint_to_json(&blueprint).expect("a loaded blueprint re-serializes");
|
||||
reg.put_blueprint(&topo, &canonical).unwrap_or_else(|e| { eprintln!("aura: {e}"); std::process::exit(1); });
|
||||
let id = match reg.append_family(name, FamilyKind::WalkForward, &walkforward_member_reports(&result)) {
|
||||
Ok(id) => id,
|
||||
@@ -983,6 +998,17 @@ fn run_blueprint_walkforward(
|
||||
/// `FamilyKind::MonteCarlo` family (C18/C21 lineage), and print each draw's member line
|
||||
/// (carrying the seed) plus the aggregate — mirroring `run_blueprint_sweep`.
|
||||
fn run_blueprint_mc(doc: &str, n_seeds: u64, name: &str, data: DataSource, env: &aura_runner::project::Env) {
|
||||
// #331 cycle-close: this fn `put_blueprint`s the loaded fresh-FILE
|
||||
// envelope into the registry below — gate the root name up front,
|
||||
// before the family ever builds (see `run_blueprint_sweep`'s identical
|
||||
// comment on why this must precede the family build, not just the
|
||||
// registry write).
|
||||
let blueprint = blueprint_from_json(doc, &|t| env.resolve(t))
|
||||
.expect("doc parse-validated at the dispatch boundary");
|
||||
if let Err(msg) = graph_construct::gate_authored_root_name(blueprint.name()) {
|
||||
eprintln!("aura: {msg}");
|
||||
std::process::exit(2);
|
||||
}
|
||||
let family = blueprint_mc_family(doc, n_seeds, &data, env).unwrap_or_else(|e| {
|
||||
eprintln!("aura: {e}");
|
||||
std::process::exit(2);
|
||||
@@ -995,11 +1021,7 @@ fn run_blueprint_mc(doc: &str, n_seeds: u64, name: &str, data: DataSource, env:
|
||||
.topology_hash
|
||||
.clone()
|
||||
.expect("a blueprint mc stamps every member's topology_hash");
|
||||
let canonical = blueprint_to_json(
|
||||
&blueprint_from_json(doc, &|t| env.resolve(t))
|
||||
.expect("doc parse-validated at the dispatch boundary"),
|
||||
)
|
||||
.expect("a loaded blueprint re-serializes");
|
||||
let canonical = blueprint_to_json(&blueprint).expect("a loaded blueprint re-serializes");
|
||||
reg.put_blueprint(&topo, &canonical).unwrap_or_else(|e| {
|
||||
eprintln!("aura: {e}");
|
||||
std::process::exit(1);
|
||||
@@ -1699,13 +1721,19 @@ fn parse_axes(
|
||||
Ok(axes)
|
||||
}
|
||||
|
||||
/// The three distinguishable ways `validate_and_register_axes` can fail: an
|
||||
/// unknown axis name is a usage error (exit 2, echoed before the archive is
|
||||
/// touched); a registry write failure is a runtime error (exit 1); running
|
||||
/// outside a project (no `Aura.toml` found up from cwd, #218's gate) is also
|
||||
/// a runtime error (exit 1) — the strategy document cannot resolve against a
|
||||
/// project store/vocabulary that doesn't exist.
|
||||
/// The four distinguishable ways `validate_and_register_axes` can fail: a
|
||||
/// shape-violating root name is a usage error (exit 2, echoed before the
|
||||
/// blueprint ever reaches the registry — #331 cycle-close: this fn
|
||||
/// `put_blueprint`s a fresh-FILE envelope and so must gate it exactly like
|
||||
/// `register`/`run`/the other authored-file intakes, see
|
||||
/// `gate_authored_root_name`'s doc comment); an unknown axis name is a usage
|
||||
/// error (exit 2, echoed before the archive is touched); a registry write
|
||||
/// failure is a runtime error (exit 1); running outside a project (no
|
||||
/// `Aura.toml` found up from cwd, #218's gate) is also a runtime error —
|
||||
/// exit 1, since the strategy document cannot resolve against a project
|
||||
/// store/vocabulary that doesn't exist.
|
||||
enum AxisRegisterError {
|
||||
BadName(String),
|
||||
UnknownAxis(String),
|
||||
Registry(String),
|
||||
NoProject(String),
|
||||
@@ -1738,6 +1766,10 @@ fn validate_and_register_axes(
|
||||
let wrapped_open = blueprint_axis_probe(doc, env).param_space();
|
||||
let blueprint = blueprint_from_json(doc, &|t| env.resolve(t))
|
||||
.expect("doc parse-validated at the dispatch boundary");
|
||||
// #331 cycle-close: this fn `put_blueprint`s a fresh-FILE envelope below —
|
||||
// gate the root name before it lands in the registry (shared choke point
|
||||
// with every other authored-file intake).
|
||||
graph_construct::gate_authored_root_name(blueprint.name()).map_err(AxisRegisterError::BadName)?;
|
||||
let raw_bound: HashSet<String> =
|
||||
blueprint.bound_param_space().into_iter().map(|b| b.name).collect();
|
||||
for (n, _) in axes {
|
||||
@@ -1774,11 +1806,16 @@ fn validate_and_register_axes(
|
||||
|
||||
/// Exit-map for a `validate_and_register_axes` failure — the stderr line and
|
||||
/// exit code every campaign dispatcher (sweep/generalize/walkforward/mc)
|
||||
/// preserves: an unknown axis is a usage error (exit 2, echoed before the
|
||||
/// archive is touched); a registry write failure or a missing project (no
|
||||
/// `Aura.toml` found up from cwd, #218's gate) is a runtime error (exit 1).
|
||||
/// preserves: a shape-violating root name (#331) or an unknown axis is a
|
||||
/// usage error (exit 2, echoed before the archive is touched); a registry
|
||||
/// write failure or a missing project (no `Aura.toml` found up from cwd,
|
||||
/// #218's gate) is a runtime error (exit 1).
|
||||
fn exit_axis_register_error(e: AxisRegisterError) -> ! {
|
||||
match e {
|
||||
AxisRegisterError::BadName(m) => {
|
||||
eprintln!("aura: {m}");
|
||||
std::process::exit(2)
|
||||
}
|
||||
AxisRegisterError::UnknownAxis(m) => {
|
||||
eprintln!("aura: {m}");
|
||||
std::process::exit(2)
|
||||
|
||||
Reference in New Issue
Block a user