diff --git a/crates/eureka-pipeline/README.md b/crates/eureka-pipeline/README.md index 5d9c633..25fa726 100644 --- a/crates/eureka-pipeline/README.md +++ b/crates/eureka-pipeline/README.md @@ -18,13 +18,6 @@ and every encoding-relevant variant against `golden/envelopes.txt`. A diff there is a wire change; do not regenerate it to make the test pass. An additive field adds golden lines and never changes an existing one. -Opt-in, against a real store: `stored_documents_read_back` decodes, validates -and keys every stored pipeline document and asserts that it re-encodes to the -stored key, type and payload bytes. Take a copy of the store (the test copies -the file again before opening it, so the copy may be read-only), never open the -live one: - -```sh -sudo cp /var/lib/gamecult/huginn/minds/eureka/mind.redb "$SNAP/minds/eureka/mind.redb" -HUGINN_MIND_SNAPSHOT="$SNAP" cargo test -p eureka-pipeline -- --ignored --exact tests::stored_documents_read_back --nocapture -``` +The real-store gate is huginn-mind's `#[ignore]` `query::tests::stored_documents_read_back`, +which checks these stored bytes and the read path together (the leaf cannot see the read +path); the Huginn Upgrade runbook in gamecult-ops runs it. diff --git a/crates/eureka-pipeline/src/lib.rs b/crates/eureka-pipeline/src/lib.rs index d19d192..9288d4c 100644 --- a/crates/eureka-pipeline/src/lib.rs +++ b/crates/eureka-pipeline/src/lib.rs @@ -2747,79 +2747,6 @@ mod tests { assert!(!Sha("5f98228a".into()).names_same_commit(&Sha("5f98228b".into())), "equal length, differing"); } - /// stored-documents-valid and stored-bytes-reproduce, on the real mind. - /// `HUGINN_MIND_SNAPSHOT` is a copy of a state root holding - /// `minds/eureka/mind.redb`; the test copies the store file into a scratch - /// directory first (redb opens read-write), so the snapshot may be - /// read-only. Read through cultcache-rs's own store. Each pipeline - /// document must re-prepare to its stored envelope: key, type and payload - /// bytes. - #[test] - #[ignore = "needs HUGINN_MIND_SNAPSHOT, a copy of a Huginn state root; see the crate README"] - fn stored_documents_read_back() { - use cultcache_rs::{CacheBackingStore, OwnedRedbMessagePackBackingStore}; - let root = std::env::var("HUGINN_MIND_SNAPSHOT").expect("HUGINN_MIND_SNAPSHOT names the snapshot state root"); - let scratch = std::env::temp_dir().join(format!("eureka-pipeline-readback-{}", std::process::id())); - std::fs::create_dir_all(&scratch).expect("scratch directory"); - let working = scratch.join("mind.redb"); - std::fs::copy(Path::new(&root).join("minds").join("eureka").join("mind.redb"), &working).expect("the snapshot copies"); - let store = OwnedRedbMessagePackBackingStore::new(&working).expect("the snapshot opens"); - let cache = schema_cache().expect("the registrar registers"); - let envelopes = store.pull_all().expect("the snapshot reads"); - let mut stored = Vec::new(); - for envelope in &envelopes { - match PipelineDocument::decode(envelope) { - Ok(document) => { - assert_eq!(document.validate(), Ok(()), "{}: valid", envelope.key); - assert_eq!(pipeline_key(&document).as_deref(), Ok(envelope.key.as_str()), "{}: key derives byte for byte", envelope.key); - let again = document.prepare(&cache).unwrap_or_else(|error| panic!("{}: does not re-prepare: {error}", envelope.key)); - assert_eq!( - (&again.key, &again.r#type, &again.payload), - (&envelope.key, &envelope.r#type, &envelope.payload), - "{}: re-encodes to the stored bytes", - envelope.key - ); - stored.push((document.kind(), envelope.key.clone())); - } - Err(PipelineRefusal::ForeignStore { .. }) => {} - Err(error) => panic!("{}: does not decode: {error}", envelope.key), - } - } - assert!(stored.len() > 200, "{} pipeline documents read", stored.len()); - let mut subjects = 0; - let resolvable = |kind: &PipelineKind| !matches!(kind, PipelineKind::Resolution | PipelineKind::Campaign | PipelineKind::Instance); - for (kind, key) in stored.iter().filter(|(kind, _)| resolvable(kind)) { - let one = resolved_at(*kind, key, 1).unwrap_or_else(|error| panic!("{key}: no resolution keys: {error}")); - let two = resolved_at(PipelineKind::Resolution, &one, 1).unwrap_or_else(|error| panic!("{key}: no withdrawal keys: {error}")); - for derived in [&one, &two] { - pipeline_id("read_back", derived, PipelineKind::Resolution).unwrap_or_else(|error| panic!("{derived}: does not parse: {error}")); - } - subjects += 1; - } - for (_, key) in stored.iter().filter(|(kind, _)| *kind == PipelineKind::Resolution) { - resolved_at(PipelineKind::Resolution, key, 1).unwrap_or_else(|error| panic!("{key}: cannot be withdrawn: {error}")); - } - // The six findings that could not be resolved before this cut. - let stuck = [ - "cut-bifrost-retire-alarm.s2.verb-default-accepts-malformed", - "cut-ops-notice-deploy.s2.pinned-bifrost-predates-retry", - "cut-bifrost-notice-retry.s1.unknown-test-ignores-backoff", - "cut-bifrost-notice-retry.s2.closed-unknown-retry-unpinned", - "cut-bifrost-notice-retry.s2.flapping-clock-retries-every-tick", - "cut-idunn-topology-lock.s2.boot-reconcile-skipped-on-contention", - ]; - for label in stuck { - let found = stored - .iter() - .find(|(kind, key)| *kind == PipelineKind::Finding && key.ends_with(label)) - .unwrap_or_else(|| panic!("{label}: not in the snapshot")); - let one = resolved_at(PipelineKind::Finding, &found.1, 1).unwrap_or_else(|error| panic!("{label}: still stuck: {error}")); - assert!(one.split(':').nth(2).is_some_and(|local| local.len() > SUBJECT_LOCAL_MAX), "{label}: the old bound refused it"); - } - eprintln!("read back {} pipeline documents, {subjects} subjects", stored.len()); - let _ = std::fs::remove_dir_all(&scratch); - } - /// The total bound, in the one place it lives: parts that are each a legal /// label compose a local wider than 64 bytes and are refused as a whole. #[test] diff --git a/crates/huginn-mind/src/mind.rs b/crates/huginn-mind/src/mind.rs index c33ee6a..697ea51 100644 --- a/crates/huginn-mind/src/mind.rs +++ b/crates/huginn-mind/src/mind.rs @@ -786,94 +786,4 @@ mod tests { })) ); } - - /// stored-documents-valid and every-subject-resolvable, against the real - /// mind: every stored document views back unchanged; the six findings the - /// 64-byte bound left unresolvable take a resolution; and a stored - /// resolution whose withdrawal key is over 64 bytes is withdrawn. Run it - /// on a scratch copy of the state root only: it writes. - #[test] - #[ignore = "reads and writes the scratch copy of a live state root named by HUGINN_MIND_SNAPSHOT"] - fn snapshot_admits_and_reads_back() { - use crate::fixtures::{admit, r, resolution, withdrawn}; - use eureka_pipeline::{PipelineRef, ResolutionOutcome, Short, pipeline_key}; - - let root = std::env::var("HUGINN_MIND_SNAPSHOT").expect("HUGINN_MIND_SNAPSHOT names a scratch copy of the state root"); - let mut mind = Mind::open(Path::new(&root), &slug("eureka")).unwrap(); - - // The census is taken apart from the loop that checks each document. - let pipeline_envelopes = mind - .envelopes() - .iter() - .filter(|envelope| PipelineKind::ALL.iter().any(|kind| kind.type_id() == envelope.r#type)) - .count(); - assert!(pipeline_envelopes > 0, "the snapshot held no pipeline document"); - - let mut viewed = 0; - let mut resolutions = Vec::new(); - for envelope in mind.envelopes() { - let Some(kind) = PipelineKind::ALL.iter().copied().find(|kind| kind.type_id() == envelope.r#type) else { continue }; - let stored = PipelineDocument::decode(envelope).unwrap(); - stored.validate().unwrap_or_else(|refusal| panic!("{} no longer validates: {refusal:?}", envelope.key)); - let rekeyed = pipeline_key(&stored).unwrap_or_else(|refusal| panic!("{} no longer keys: {refusal:?}", envelope.key)); - assert_eq!(rekeyed, envelope.key, "stored key differs from the key the leaf computes"); - let view = mind - .view(&PipelineRef { kind, id: Short(envelope.key.clone()) }) - .unwrap() - .unwrap_or_else(|| panic!("{} does not view", envelope.key)); - assert_eq!(view.document, stored, "{}", envelope.key); - if let PipelineDocument::Resolution(held) = &stored - && !matches!(held.outcome, eureka_pipeline::ResolutionOutcome::Withdrawn { .. }) - && !envelope.key.contains(":resolution:resolution.") - { - resolutions.push(envelope.key.clone()); - } - viewed += 1; - } - assert_eq!(viewed, pipeline_envelopes, "every pipeline envelope validates, rekeys and views back"); - println!("validated, rekeyed and viewed {viewed} stored documents unchanged"); - - let stuck = [ - "cut-bifrost-retire-alarm.s2.verb-default-accepts-malformed", - "cut-ops-notice-deploy.s2.pinned-bifrost-predates-retry", - "cut-bifrost-notice-retry.s1.unknown-test-ignores-backoff", - "cut-bifrost-notice-retry.s2.closed-unknown-retry-unpinned", - "cut-bifrost-notice-retry.s2.flapping-clock-retries-every-tick", - "cut-idunn-topology-lock.s2.boot-reconcile-skipped-on-contention", - ]; - let mut resolved = 0; - for local in stuck { - let subject = r(PipelineKind::Finding, &format!("idunn-watchdog:finding:{local}")); - let outcome = admit(&mut mind, vec![resolution(subject, ResolutionOutcome::Recorded { reason: "snapshot read-back".into() })]); - match outcome { - PipelineAdmissionOutcome::Committed { writes, .. } => { - assert_eq!(writes.len(), 1, "{local}"); - resolved += 1; - } - other => panic!("stuck finding {local} was not resolved: {other:?}"), - } - } - - assert_eq!(resolved, stuck.len(), "every stuck finding resolves"); - - let mut candidates = Vec::new(); - for key in resolutions { - let withdrawal = resolution(r(PipelineKind::Resolution, &key), withdrawn()); - let withdrawal_key = pipeline_key(&withdrawal).unwrap_or_else(|refusal| panic!("withdrawal of {key} does not key: {refusal:?}")); - if withdrawal_key.rsplit(':').next().unwrap().len() > 64 { - candidates.push((key, withdrawal)); - } - } - assert!(!candidates.is_empty(), "no stored resolution had a withdrawal key over 64 bytes"); - let wanted = candidates.len(); - let mut withdrew = 0; - for (key, withdrawal) in candidates { - match admit(&mut mind, vec![withdrawal]) { - PipelineAdmissionOutcome::Committed { .. } => withdrew += 1, - other => panic!("withdrawal of {key} was not committed: {other:?}"), - } - } - assert_eq!(withdrew, wanted, "every candidate withdraws"); - println!("withdrew {withdrew} resolutions whose withdrawal keys exceed 64 bytes"); - } } diff --git a/docs/eureka-body-map.md b/docs/eureka-body-map.md index 4c6538e..f47c118 100644 --- a/docs/eureka-body-map.md +++ b/docs/eureka-body-map.md @@ -326,7 +326,7 @@ pipeline kinds are in Huginn `docs/eureka-substrate-map.md`. | Kind | Identity (what names it) | Lifecycle (what happens over time) | Authority (who decides) | | --- | --- | --- | --- | | Pipeline wire ids | `epiphany.pipeline..v2`, epoch `epiphany.pipeline.epoch.v2`, defined by the leaf, the Huginn crate `crates/eureka-pipeline` (lib `eureka_pipeline`), ids unchanged. | Renamed once to `eureka.pipeline..v3` by one `mind.redb` migration, at the next breaking leaf change or before the first hosted mind (ruling `wire-names-rename-before-hosting`). Adding the `run` kind or the `Mind` authority variant is additive (old stored documents decode; no migration), so it does not trigger the rename; a field added with the leaf's `= absent` marker is additive too (leaf-read-anchors r2: old bytes decode, empty fields encode byte-identically, `golden/envelopes.txt` gains lines and loses none); a field added any other way would. | Huginn owns the leaf. The migration is a live-mind upgrade, which is the operator's (`mind-rules-which-forks`). | -| Leaf crate and schema catalogue | Huginn `crates/eureka-pipeline`, `schemas/cultnet/` (13 pipeline schemas, `gamecult.persona_state.v0`, index, README). `epiphany.work_organ_state.v0` dies: no reader outside Epiphany (grep, F1/F3). | Landed at Huginn `afc35f9` (leaf-into-huginn). Wire pinned by `crates/eureka-pipeline/golden/envelopes.txt` and the opt-in `stored_documents_read_back`. Huginn is AGPL-3.0-only (ruling `eureka-body:ruling:leaf-relicensed-agpl`). | Huginn. Forbidden: any Cargo source resolving into GameCult/Epiphany (`nothing-runs-from-epiphany`). | +| Leaf crate and schema catalogue | Huginn `crates/eureka-pipeline`, `schemas/cultnet/` (13 pipeline schemas, `gamecult.persona_state.v0`, index, README). `epiphany.work_organ_state.v0` dies: no reader outside Epiphany (grep, F1/F3). | Landed at Huginn `afc35f9` (leaf-into-huginn). Wire pinned by `crates/eureka-pipeline/golden/envelopes.txt` and huginn-mind's opt-in `query::tests::stored_documents_read_back`. Huginn is AGPL-3.0-only (ruling `eureka-body:ruling:leaf-relicensed-agpl`). | Huginn. Forbidden: any Cargo source resolving into GameCult/Epiphany (`nothing-runs-from-epiphany`). | | `run` | `:run: