test(registry): RED pin — twin-store identity index must converge, not grow
Executable spec for the audit follow-up on the identity-index cycle: once one full repair walk has run over a same-identity-twin store (two blueprints sharing one identity id, distinct content ids), a further scan-triggering lookup must leave blueprint_identity_index.jsonl's line count unchanged while the twin identity still resolves. RED against the current per-entry snapshot comparison (appends one alternating line per scan-triggering lookup while twins coexist). refs #191
This commit is contained in:
@@ -2071,6 +2071,59 @@ mod tests {
|
||||
assert_eq!(found.as_deref(), Some(blueprint_json.as_str()));
|
||||
}
|
||||
|
||||
/// Property (#191 audit): the identity-index sidecar CONVERGES for a
|
||||
/// same-identity-twin store — two blueprints sharing one identity id but
|
||||
/// with distinct content ids (they differ only in identity-blind debug
|
||||
/// symbols). Once one full repair walk has run, a further scan-triggering
|
||||
/// lookup appends nothing (the file does not grow) while resolution
|
||||
/// answers stay unchanged. The pre-fix repair pass compares each walked
|
||||
/// entry against a mid-walk-stale snapshot, so with twins the snapshot can
|
||||
/// never equal both entries and every full-scan lookup appends one more
|
||||
/// (alternating) line — unbounded growth.
|
||||
#[test]
|
||||
fn identity_index_twin_store_converges_and_stops_growing() {
|
||||
let reg = Registry::open(temp_family_dir("identity_twin_converges"));
|
||||
let resolve = |t: &str| aura_std::std_vocabulary(t);
|
||||
|
||||
// Two same-identity twins: the one-node Bias composite, built with
|
||||
// different debug names. Debug symbols are identity-blind, so the two
|
||||
// share one identity id but hash to distinct content ids.
|
||||
let (_json_a, content_a, identity) = seed_blueprint(®, &bias_fixture());
|
||||
let twin = aura_engine::Composite::new(
|
||||
"fixture_twin",
|
||||
vec![aura_std::Bias::builder().named("b_twin").into()],
|
||||
vec![],
|
||||
vec![],
|
||||
vec![],
|
||||
);
|
||||
let (_json_b, content_b, identity_twin) = seed_blueprint(®, &twin);
|
||||
assert_eq!(identity, identity_twin, "twins must share one identity id");
|
||||
assert_ne!(content_a, content_b, "twins must have distinct content ids");
|
||||
|
||||
// One converging walk: an absent identity always scans the whole
|
||||
// store (only a scan proves absence), repairing the index.
|
||||
assert_eq!(reg.find_blueprint_by_identity("0000", &resolve).expect("io ok"), None);
|
||||
let converged_lines = fs::read_to_string(reg.identity_index_path())
|
||||
.map(|t| t.lines().count())
|
||||
.unwrap_or(0);
|
||||
|
||||
// A further scan-triggering lookup over the CONVERGED index must not
|
||||
// append another line (pre-fix: it appends one alternating twin line).
|
||||
assert_eq!(reg.find_blueprint_by_identity("0000", &resolve).expect("io ok"), None);
|
||||
let after_lines = fs::read_to_string(reg.identity_index_path())
|
||||
.map(|t| t.lines().count())
|
||||
.unwrap_or(0);
|
||||
assert_eq!(
|
||||
after_lines, converged_lines,
|
||||
"a converged twin index must not grow on a further scan-triggering lookup",
|
||||
);
|
||||
|
||||
// Resolution answers stay unchanged: the twin identity still resolves
|
||||
// to a valid stored blueprint for that identity (preservation pin).
|
||||
let found = reg.find_blueprint_by_identity(&identity, &resolve).expect("io ok");
|
||||
assert!(found.is_some(), "the twin identity still resolves to a stored blueprint");
|
||||
}
|
||||
|
||||
/// Property (#246): a campaign axis naming a BOUND param (not an open
|
||||
/// one) is checked exactly like an open-param axis — a kind-correct axis
|
||||
/// over the bound path is fault-free, and a kind-mismatched one over that
|
||||
|
||||
Reference in New Issue
Block a user