audit: shell-boundary cycle close — drift resolved in-commit

Cycle-close audit (architect: drift_found; design core clean). What
holds, architect-confirmed with the guard run and the diff read in
full: the shell boundary is real and enforced (full-workspace
c28_layering incl. completeness + shell-content checks, acceptance
grep clean — no member-run symbol left under crates/aura-cli/src);
aura-campaign keeps zero production dependency on the runner; the IC
move is verbatim against the anchor; the new library-only E2E pins C1
(two independent runners, byte-identical RunReport).

Drift items, all resolved as fixes in this commit:
- design ledger: the C28 status sentence "No crate exists yet for
  measurement" contradicted this cycle's own phase-3 done line — now
  records aura-measurement as seeded (#295), execution still unbuilt.
- the #147 registry-dispatch acceptance tests (IC deflation null,
  seeded determinism, cross-vocabulary refusal) were pure library
  properties stranded in the shell's test module — relocated to
  aura-measurement/tests/registry_dispatch.rs (engine/registry as
  dev-only, layering-exempt edges), per the cycle's own tests-move-
  with-their-module rule.
- aura-runner's direct production edge on the external data-server
  tree was structurally invisible (the direction table checks aura-*
  keys only) and unrecorded — the C28 assembly prose now names it, and
  a new c28_layering test pins the external tree's entry points to
  exactly {aura-ingest, aura-runner, aura-cli}.
- the C28 shell prose named three buckets that did not cover two real
  shell residents — it now names the op-script construction front-end
  (translation) and the `aura new` scaffolder (authoring-tooling,
  shell-resident like rendering until a second consumer wants it).

Deliberately deferred, tracker homes exist (not drift): #297
(process::exit sites in aura-runner's single-run verb paths), #294
(IC duplicate-timestamp semantics), #288-era rustdoc unresolved-link
warnings in aura-std/aura-backtest (pre-existing byte-identically at
the anchor).

No regression scripts are configured (the bench is report-only); the
architect review is the gate. Verification: cargo test --workspace
green (1474 passed, 0 failed) incl. the relocated registry-dispatch
tests and the new data-server guard; clippy --workspace --all-targets
-D warnings clean.

refs #295
This commit is contained in:
2026-07-21 06:46:13 +02:00
parent 170c6c82dc
commit 5006766579
6 changed files with 154 additions and 110 deletions
Generated
+2
View File
@@ -245,6 +245,8 @@ version = "0.1.0"
dependencies = [ dependencies = [
"aura-analysis", "aura-analysis",
"aura-core", "aura-core",
"aura-engine",
"aura-registry",
"serde", "serde",
] ]
-102
View File
@@ -84,11 +84,6 @@ use aura_runner::member::sim_optimal_manifest;
// tests that exercised them — no longer imported here. // tests that exercised them — no longer imported here.
#[cfg(test)] #[cfg(test)]
use aura_runner::member::CostLeg; use aura_runner::member::CostLeg;
// `IcMetrics` is only reached from the test module's `ic_report`/`ic_family` carves
// (the `IcReport` DTO holds the scalar directly in production), mirroring `Bias`
// below.
#[cfg(test)]
use aura_measurement::IcMetrics;
#[cfg(test)] #[cfg(test)]
use aura_backtest::{summarize, summarize_r}; use aura_backtest::{summarize, summarize_r};
#[cfg(test)] #[cfg(test)]
@@ -3860,100 +3855,3 @@ mod tests {
} }
} }
#[cfg(test)]
mod ic_tests {
use super::*;
fn ic_report(sigs: Vec<f64>, frs: Vec<f64>) -> aura_engine::RunReport<IcMetrics> {
aura_engine::RunReport {
manifest: aura_engine::RunManifest {
commit: "c".to_string(),
params: vec![("p".to_string(), aura_core::Scalar::f64(1.0))],
defaults: vec![],
window: (aura_core::Timestamp(1), aura_core::Timestamp(2)),
seed: 0,
broker: "b".to_string(),
selection: None,
instrument: None,
topology_hash: None,
project: None,
},
metrics: IcMetrics {
information_coefficient: aura_analysis::pearson_corr(&sigs, &frs),
sigs,
frs,
},
}
}
fn ic_family(members: Vec<aura_engine::RunReport<IcMetrics>>) -> aura_engine::SweepFamily<IcMetrics> {
aura_engine::SweepFamily {
space: vec![],
points: members
.into_iter()
.enumerate()
.map(|(i, report)| aura_engine::SweepPoint {
params: vec![aura_core::Cell::from_f64(i as f64)],
report,
})
.collect(),
}
}
/// #147 acceptance: the registry's deflation dispatches to the IC
/// vocabulary's OWN permutation null — a genuinely predictive family
/// deflates to a low overfit probability, a noise family to a high one.
#[test]
fn ic_family_deflation_dispatches_the_permutation_null() {
let ramp: Vec<f64> = (0..24).map(|i| i as f64).collect();
let engineered = ic_family(vec![
ic_report(ramp.clone(), ramp.clone()), // corr = 1.0: a real edge
ic_report(ramp.clone(), ramp.iter().rev().cloned().collect()), // corr = -1.0
]);
let (winner, sel) =
aura_registry::optimize_deflated(&engineered, "information_coefficient", 500, 5, 0)
.expect("ic family deflates");
assert!((winner.report.metrics.information_coefficient - 1.0).abs() < 1e-12);
let p = sel.overfit_probability.expect("resampling-null arm sets a probability");
assert!(p < 0.05, "perfectly predictive family must not look like overfit: p={p}");
// orthogonal signal — IC exactly 0.0; permuted draws beat it ~half the time.
let noise = ic_family(vec![ic_report(
vec![0.03, 0.01, 0.04, 0.02, 0.05, 0.00, 0.06, -0.01],
vec![0.01, 0.02, 0.03, 0.04, -0.04, -0.03, -0.02, -0.01],
)]);
let (_, sel) =
aura_registry::optimize_deflated(&noise, "information_coefficient", 500, 5, 0)
.expect("noise family deflates");
let p = sel.overfit_probability.expect("probability present");
assert!(p > 0.1, "a noise family must carry a high overfit probability: p={p}");
}
/// Same seed → identical selection provenance (C1 through the generic path).
#[test]
fn ic_family_deflation_is_deterministic_given_seed() {
let ramp: Vec<f64> = (0..16).map(|i| i as f64).collect();
let fam = ic_family(vec![ic_report(ramp.clone(), ramp)]);
let a = aura_registry::optimize_deflated(&fam, "information_coefficient", 300, 5, 7)
.expect("deflate a");
let b = aura_registry::optimize_deflated(&fam, "information_coefficient", 300, 5, 7)
.expect("deflate b");
assert_eq!(a.1, b.1, "same seed must yield identical FamilySelection");
}
/// C10 guard: cross-vocabulary names are refused, and the refusal names
/// the family's REAL vocabulary.
#[test]
fn cross_vocabulary_names_are_refused_both_ways() {
let ramp: Vec<f64> = (0..8).map(|i| i as f64).collect();
let fam = ic_family(vec![ic_report(ramp.clone(), ramp)]);
let err = aura_registry::optimize_deflated(&fam, "sqn", 100, 5, 0).unwrap_err();
let prose = err.to_string();
assert!(
prose.contains("unknown metric 'sqn'") && prose.contains("information_coefficient"),
"refusal must name the IC vocabulary: {prose}"
);
// and the R side still refuses the IC name with ITS roster:
assert!(aura_registry::check_r_metric("information_coefficient").is_err());
}
}
+8
View File
@@ -15,3 +15,11 @@ aura-analysis = { path = "../aura-analysis" }
# R-vocabulary metric types (aura-backtest, aura-engine); the aligned pairs # R-vocabulary metric types (aura-backtest, aura-engine); the aligned pairs
# ride #[serde(skip)] (in-memory null inputs only, never persisted). # ride #[serde(skip)] (in-memory null inputs only, never persisted).
serde = { workspace = true } serde = { workspace = true }
# The #147 registry-dispatch acceptance tests (tests/registry_dispatch.rs)
# exercise the generic deflation machinery over the IC vocabulary — pure
# library properties relocated from the shell with #295. Dev-only edges,
# C28-layering-exempt.
[dev-dependencies]
aura-engine = { path = "../aura-engine" }
aura-registry = { path = "../aura-registry" }
@@ -0,0 +1,101 @@
//! #147 acceptance at the library boundary: the registry's generic
//! deflation machinery dispatches to the IC vocabulary's OWN permutation
//! null, deterministically (C1), and the C10 wall refuses cross-vocabulary
//! metric names both ways. Relocated from the shell's test module with #295
//! (these are pure library properties — a library-only consumer must be able
//! to run them; the registry/engine edges are dev-only, layering-exempt).
use aura_measurement::IcMetrics;
fn ic_report(sigs: Vec<f64>, frs: Vec<f64>) -> aura_engine::RunReport<IcMetrics> {
aura_engine::RunReport {
manifest: aura_engine::RunManifest {
commit: "c".to_string(),
params: vec![("p".to_string(), aura_core::Scalar::f64(1.0))],
defaults: vec![],
window: (aura_core::Timestamp(1), aura_core::Timestamp(2)),
seed: 0,
broker: "b".to_string(),
selection: None,
instrument: None,
topology_hash: None,
project: None,
},
metrics: IcMetrics {
information_coefficient: aura_analysis::pearson_corr(&sigs, &frs),
sigs,
frs,
},
}
}
fn ic_family(members: Vec<aura_engine::RunReport<IcMetrics>>) -> aura_engine::SweepFamily<IcMetrics> {
aura_engine::SweepFamily {
space: vec![],
points: members
.into_iter()
.enumerate()
.map(|(i, report)| aura_engine::SweepPoint {
params: vec![aura_core::Cell::from_f64(i as f64)],
report,
})
.collect(),
}
}
/// #147 acceptance: the registry's deflation dispatches to the IC
/// vocabulary's OWN permutation null — a genuinely predictive family
/// deflates to a low overfit probability, a noise family to a high one.
#[test]
fn ic_family_deflation_dispatches_the_permutation_null() {
let ramp: Vec<f64> = (0..24).map(|i| i as f64).collect();
let engineered = ic_family(vec![
ic_report(ramp.clone(), ramp.clone()), // corr = 1.0: a real edge
ic_report(ramp.clone(), ramp.iter().rev().cloned().collect()), // corr = -1.0
]);
let (winner, sel) =
aura_registry::optimize_deflated(&engineered, "information_coefficient", 500, 5, 0)
.expect("ic family deflates");
assert!((winner.report.metrics.information_coefficient - 1.0).abs() < 1e-12);
let p = sel.overfit_probability.expect("resampling-null arm sets a probability");
assert!(p < 0.05, "perfectly predictive family must not look like overfit: p={p}");
// orthogonal signal — IC exactly 0.0; permuted draws beat it ~half the time.
let noise = ic_family(vec![ic_report(
vec![0.03, 0.01, 0.04, 0.02, 0.05, 0.00, 0.06, -0.01],
vec![0.01, 0.02, 0.03, 0.04, -0.04, -0.03, -0.02, -0.01],
)]);
let (_, sel) =
aura_registry::optimize_deflated(&noise, "information_coefficient", 500, 5, 0)
.expect("noise family deflates");
let p = sel.overfit_probability.expect("probability present");
assert!(p > 0.1, "a noise family must carry a high overfit probability: p={p}");
}
/// Same seed → identical selection provenance (C1 through the generic path).
#[test]
fn ic_family_deflation_is_deterministic_given_seed() {
let ramp: Vec<f64> = (0..16).map(|i| i as f64).collect();
let fam = ic_family(vec![ic_report(ramp.clone(), ramp)]);
let a = aura_registry::optimize_deflated(&fam, "information_coefficient", 300, 5, 7)
.expect("deflate a");
let b = aura_registry::optimize_deflated(&fam, "information_coefficient", 300, 5, 7)
.expect("deflate b");
assert_eq!(a.1, b.1, "same seed must yield identical FamilySelection");
}
/// C10 guard: cross-vocabulary names are refused, and the refusal names
/// the family's REAL vocabulary.
#[test]
fn cross_vocabulary_names_are_refused_both_ways() {
let ramp: Vec<f64> = (0..8).map(|i| i as f64).collect();
let fam = ic_family(vec![ic_report(ramp.clone(), ramp)]);
let err = aura_registry::optimize_deflated(&fam, "sqn", 100, 5, 0).unwrap_err();
let prose = err.to_string();
assert!(
prose.contains("unknown metric 'sqn'") && prose.contains("information_coefficient"),
"refusal must name the IC vocabulary: {prose}"
);
// and the R side still refuses the IC name with ITS roster:
assert!(aura_registry::check_r_metric("information_coefficient").is_err());
}
@@ -190,3 +190,29 @@ fn the_shell_defines_no_domain_modules_and_no_lib_target() {
and presentation only — a new module needs a library home" and presentation only — a new module needs a library home"
); );
} }
#[test]
fn the_external_data_server_tree_enters_at_pinned_points_only() {
// C28 (#295 audit): the external `data-server` tree is a production
// dependency of exactly the ingestion edge (External components), the
// assembly position, and the shell. Any further crate pulling it in is
// a new, undecided workspace entry point for the external tree — the
// aura-*-only direction table above cannot see it, so it is pinned here.
let permitted = ["aura-cli", "aura-ingest", "aura-runner"];
for name in workspace_crate_names() {
let manifest = workspace_root().join("crates").join(&name).join("Cargo.toml");
let text = std::fs::read_to_string(&manifest)
.unwrap_or_else(|e| panic!("read {}: {e}", manifest.display()));
let doc: toml::Value = toml::from_str(&text).expect("Cargo.toml parses as TOML");
let has = doc
.get("dependencies")
.and_then(|d| d.as_table())
.is_some_and(|deps| deps.contains_key("data-server"));
assert_eq!(
has,
permitted.contains(&name.as_str()),
"C28: `data-server` [dependencies] edge on `{name}` — the external \
tree enters the workspace only via {permitted:?}"
);
}
}
+17 -8
View File
@@ -2648,11 +2648,19 @@ composes the rungs into the canonical member-run recipe — harness assembly,
input binding (C26), the C1-load-bearing param↔config translators, and the input binding (C26), the C1-load-bearing param↔config translators, and the
shipped default `MemberRunner` — importing the ladder rungs, `aura-composites`, shipped default `MemberRunner` — importing the ladder rungs, `aura-composites`,
`aura-ingest`, and the process column, and imported only by the shell and by `aura-ingest`, and the process column, and imported only by the shell and by
downstream World programs. The **shell** (the `aura` CLI) imports everything, downstream World programs. It also carries a direct production dependency on
the external `data-server` tree (inherited from the shell with the recipe: the
runner constructs archive servers itself). The external tree therefore enters
the workspace at the ingestion edge (`aura-ingest`, External components), the
assembly position (`aura-runner`), and the shell (which imports everything by
definition) — and at no ladder or column crate beyond those; the
`c28_layering` guard pins the exact set. The **shell** (the `aura` CLI) imports everything,
is imported by nothing, exports nothing, and holds no domain logic: is imported by nothing, exports nothing, and holds no domain logic:
argv/dispatch, argv→document translation, and presentation only (rendering argv/dispatch, argv→document translation (including the op-script construction
stays in the shell until the C22 web face provides its second consumer — #295 front-end, `graph_construct`), presentation, and the `aura new` project
deferral). scaffolder (authoring-tooling template emission — shell-resident, like
rendering, until a second consumer wants it). Rendering stays in the shell
until the C22 web face provides its second consumer (#295 deferral).
**Import rule.** An outer ladder layer may import inner layers, never the **Import rule.** An outer ladder layer may import inner layers, never the
reverse; *measurement* and *strategy* are siblings (no import either way); reverse; *measurement* and *strategy* are siblings (no import either way);
@@ -2738,10 +2746,11 @@ cleanly by layer, so the layering is realized only partially:
interweave is resolved (phase 5, #291): the backtest reductions live in interweave is resolved (phase 5, #291): the backtest reductions live in
`aura-backtest::metrics` beside their `position_management` producer, and `aura-backtest::metrics` beside their `position_management` producer, and
`aura-analysis` is reduced to domain-free statistics + selection provenance `aura-analysis` is reduced to domain-free statistics + selection provenance
(`[dependencies]` = serde only). No crate exists yet for *measurement* (its run (`[dependencies]` = serde only). The *measurement* rung is seeded (#295):
verb is the additive shape dispatch, #286) or *execution* (unbuilt — the C10/C13 `aura-measurement` carries the IC vocabulary + reduction (its run verb remains
edge only). Under this model the `aura-registry → aura-research` edge is the additive shape dispatch, #286). No crate exists yet for *execution*
column-internal and legal. (unbuilt — the C10/C13 edge only). Under this model the
`aura-registry → aura-research` edge is column-internal and legal.
- Phased realization (each independently shippable; behaviour byte-identical - Phased realization (each independently shippable; behaviour byte-identical
except the purely additive shape dispatch): (1) this contract; (2) cut the except the purely additive shape dispatch): (1) this contract; (2) cut the
engine's backtest-metrics edge via `RunReport<M>`**done** (#292: engine's backtest-metrics edge via `RunReport<M>`**done** (#292: