use std::collections::BTreeMap;
use this_me::kernel::{
ExplainOrigin, Kernel, KernelError, Memory, RecomputeMode, SecretMaterialMode,
SecretMaterialPurpose, Value,
};
fn hex(bytes: impl AsRef<[u8]>) -> String {
bytes
.as_ref()
.iter()
.map(|byte| format!("{byte:02x}"))
.collect::<String>()
}
fn path_parts(value: &str) -> Vec<String> {
if value.is_empty() {
return Vec::new();
}
value.split('.').map(str::to_string).collect()
}
fn object(entries: impl IntoIterator<Item = (&'static str, Value)>) -> Value {
Value::Object(
entries
.into_iter()
.map(|(key, value)| (key.to_string(), value))
.collect::<BTreeMap<_, _>>(),
)
}
fn replay_record(
path_expr: &str,
operator: Option<&str>,
expression: Option<&str>,
value: Value,
) -> Memory {
Memory {
path: path_parts(path_expr),
operator: operator.map(str::to_string),
expression: expression.map(Value::from),
value,
prev_hash: Some("ignored-prev".to_string()),
hash: "ignored-hash".to_string(),
}
}
#[test]
fn public_write_read_round_trips() {
let mut kernel = Kernel::new();
kernel
.postulate("apps.demo.title", "Demo Space")
.expect("public write should succeed");
assert_eq!(
kernel.read("apps.demo.title"),
Some(&Value::from("Demo Space"))
);
}
#[test]
fn children_lists_immediate_public_descendants_for_plural_shape() {
let mut kernel = Kernel::new();
kernel
.postulate("apps.demo.notes[alpha].title", "Alpha")
.unwrap();
kernel
.postulate("apps.demo.notes[beta].title", "Beta")
.unwrap();
kernel
.postulate("apps.demo.notes[beta].tags.primary", "work")
.unwrap();
assert_eq!(
kernel.children("apps.demo.notes[]").unwrap(),
vec!["alpha".to_string(), "beta".to_string()]
);
}
#[test]
fn public_subtree_expands_plural_members() {
let mut kernel = Kernel::new();
kernel
.postulate("apps.demo.notes[alpha].title", "Alpha")
.unwrap();
kernel
.postulate("apps.demo.notes[beta].title", "Beta")
.unwrap();
kernel
.postulate("apps.demo.notes[beta].tags.primary", "work")
.unwrap();
assert_eq!(
kernel.read_public_subtree("apps.demo.notes[]").unwrap(),
Some(object([
("alpha", object([("title", Value::from("Alpha"))])),
(
"beta",
object([
("tags", object([("primary", Value::from("work"))])),
("title", Value::from("Beta")),
]),
),
]))
);
}
#[test]
fn public_subtree_exact_value_wins_over_descendants() {
let mut kernel = Kernel::new();
kernel.postulate("apps.demo.status", "online").unwrap();
kernel
.postulate("apps.demo.status.detail", "warming")
.unwrap();
assert_eq!(
kernel.read_public_subtree("apps.demo.status").unwrap(),
Some(Value::from("online"))
);
}
#[test]
fn public_subtree_excludes_secret_branches() {
let mut kernel = Kernel::new();
kernel
.postulate("apps.demo.public.title", "Public")
.unwrap();
kernel.secret("apps.demo.private", "vault").unwrap();
kernel
.postulate("apps.demo.private.note", "Hidden")
.unwrap();
assert_eq!(
kernel.children("apps.demo").unwrap(),
vec!["public".to_string()]
);
assert_eq!(
kernel.read_public_subtree("apps.demo").unwrap(),
Some(object([(
"public",
object([("title", Value::from("Public"))]),
)]))
);
}
#[test]
fn memory_log_is_history_index_is_latest_projection() {
let mut kernel = Kernel::new();
kernel.postulate("apps.demo.count", 1_u64).unwrap();
kernel.postulate("apps.demo.count", 2_u64).unwrap();
assert_eq!(kernel.memories().len(), 2);
assert_eq!(kernel.read("apps.demo.count"), Some(&Value::from(2_u64)));
assert_eq!(kernel.memories()[0].value, Value::from(1_u64));
assert_eq!(
kernel.memories()[1].prev_hash,
Some(kernel.memories()[0].hash.clone())
);
}
#[test]
fn memory_hash_uses_portable_fnv1a_chain() {
let mut kernel = Kernel::new();
kernel.postulate("apps.demo.count", 1_u64).unwrap();
kernel.postulate("apps.demo.count", 2_u64).unwrap();
assert_eq!(kernel.memories()[0].hash, "650e695c");
assert_eq!(kernel.memories()[0].prev_hash, None);
assert_eq!(kernel.memories()[1].hash, "cb8489bb");
assert_eq!(kernel.memories()[1].prev_hash, Some("650e695c".to_string()));
}
#[test]
fn snapshot_hydrates_equivalent_kernel() {
let mut kernel = Kernel::new();
kernel.postulate(["apps", "demo", "title"], "Demo").unwrap();
kernel.postulate(["apps", "demo", "count"], 3_u64).unwrap();
let restored = Kernel::hydrate(kernel.export_snapshot()).expect("snapshot should hydrate");
assert_eq!(restored.memories(), kernel.memories());
assert_eq!(restored.read("apps.demo.title"), Some(&Value::from("Demo")));
assert_eq!(restored.read("apps.demo.count"), Some(&Value::from(3_u64)));
}
#[test]
fn learn_replays_memory_semantically_on_current_chain() {
let mut source = Kernel::new();
let original = source
.postulate("apps.demo.title", "Demo Space")
.unwrap()
.clone();
let mut target = Kernel::new();
target.postulate("receiver.note", "local").unwrap();
let previous_hash = target.memories()[0].hash.clone();
let learned = target.learn(&original).unwrap().clone();
assert_eq!(
target.read("apps.demo.title"),
Some(&Value::from("Demo Space"))
);
assert_eq!(learned.path, original.path);
assert_eq!(learned.value, original.value);
assert_eq!(learned.prev_hash, Some(previous_hash));
assert_ne!(learned.hash, original.hash);
}
#[test]
fn learn_replays_structural_operators() {
let mut kernel = Kernel::new();
kernel
.learn(&replay_record(
"",
Some("@"),
None,
Value::Identity("jabellae".to_string()),
))
.unwrap();
kernel
.learn(&replay_record(
"wallet",
Some("_"),
Some("vault-key"),
Value::from("***"),
))
.unwrap();
kernel
.learn(&replay_record(
"wallet.balance",
None,
None,
Value::from(100_u64),
))
.unwrap();
kernel
.learn(&replay_record(
"profile.wallet",
Some("__"),
None,
Value::Pointer(path_parts("wallet")),
))
.unwrap();
kernel
.learn(&replay_record(
"profile.legacy",
None,
None,
Value::from("remove-me"),
))
.unwrap();
kernel
.learn(&replay_record(
"profile.legacy",
Some("-"),
None,
Value::from("ignored"),
))
.unwrap();
assert_eq!(kernel.active_identity(), Some("jabellae"));
assert!(kernel.is_secret_scope("wallet"));
assert_eq!(kernel.read("wallet.balance"), Some(&Value::from(100_u64)));
assert_eq!(kernel.read_public("wallet.balance"), None);
assert_eq!(
kernel.read("profile.wallet.balance"),
Some(&Value::from(100_u64))
);
assert_eq!(kernel.read("profile.legacy"), None);
}
#[test]
fn learn_preserves_custom_operator_aliases() {
let mut kernel = Kernel::new();
kernel.define_operator("hide", "secret").unwrap();
kernel.define_operator("drop", "remove").unwrap();
let learned_secret = kernel
.learn(&replay_record(
"vault",
Some("hide"),
Some("alpha"),
Value::from("***"),
))
.unwrap()
.clone();
kernel.postulate("vault.note", "private").unwrap();
let learned_remove = kernel
.learn(&replay_record(
"vault",
Some("drop"),
None,
Value::from("-"),
))
.unwrap()
.clone();
assert_eq!(learned_secret.operator.as_deref(), Some("hide"));
assert_eq!(learned_remove.operator.as_deref(), Some("drop"));
assert_eq!(kernel.read("vault.note"), None);
assert_eq!(kernel.operator_kind("hide"), Some("secret"));
assert_eq!(kernel.operator_kind("drop"), Some("remove"));
}
#[test]
fn learned_derivation_remains_live() {
let mut kernel = Kernel::new();
kernel
.learn(&replay_record(
"order.price",
None,
None,
Value::from(10_u64),
))
.unwrap();
kernel
.learn(&replay_record(
"order.quantity",
None,
None,
Value::from(3_u64),
))
.unwrap();
kernel
.learn(&replay_record(
"order.total",
Some("="),
Some("price * quantity"),
Value::from(30_f64),
))
.unwrap();
assert_eq!(kernel.read("order.total"), Some(&Value::from(30_f64)));
kernel.postulate("order.quantity", 4_u64).unwrap();
assert_eq!(kernel.read("order.total"), Some(&Value::from(40_f64)));
}
#[test]
fn replay_memories_resets_state_but_preserves_runtime_configuration() {
let mut kernel = Kernel::new();
kernel.define_operator("drop", "remove").unwrap();
kernel.set_recompute_mode(RecomputeMode::Lazy);
kernel.postulate("old.value", "gone").unwrap();
kernel
.replay_memories(vec![
replay_record("apps.demo.title", None, None, Value::from("Demo")),
replay_record("apps.demo.count", None, None, Value::from(3_u64)),
])
.unwrap();
assert_eq!(kernel.read("old.value"), None);
assert_eq!(kernel.read("apps.demo.title"), Some(&Value::from("Demo")));
assert_eq!(kernel.read("apps.demo.count"), Some(&Value::from(3_u64)));
assert_eq!(kernel.memories().len(), 2);
assert_eq!(kernel.operator_kind("drop"), Some("remove"));
assert_eq!(kernel.recompute_mode(), RecomputeMode::Lazy);
}
#[test]
fn replay_memories_is_atomic_when_learning_fails() {
let mut kernel = Kernel::new();
kernel.postulate("stable.value", "keep").unwrap();
let original_memories = kernel.memories().to_vec();
let error = kernel
.replay_memories(vec![
replay_record("new.value", None, None, Value::from("pending")),
replay_record("", None, None, Value::from("invalid")),
])
.expect_err("invalid replay should fail");
assert_eq!(error, KernelError::EmptyPath);
assert_eq!(kernel.memories(), original_memories.as_slice());
assert_eq!(kernel.read("stable.value"), Some(&Value::from("keep")));
assert_eq!(kernel.read("new.value"), None);
}
#[test]
fn hydration_rejects_tampered_memory() {
let mut kernel = Kernel::new();
kernel.postulate("apps.demo.title", "Demo").unwrap();
let mut snapshot = kernel.export_snapshot();
snapshot.memories[0].value = Value::from("Tampered");
let error = Kernel::hydrate(snapshot).expect_err("tampering must be detected");
assert!(matches!(
error,
KernelError::HydrationHashMismatch {
path,
expected: _,
actual: _
} if path == vec!["apps".to_string(), "demo".to_string(), "title".to_string()]
));
}
#[test]
fn hydration_rejects_broken_memory_chain() {
let mut kernel = Kernel::new();
kernel.postulate("apps.demo.title", "Demo").unwrap();
kernel.postulate("apps.demo.count", 1_u64).unwrap();
let mut snapshot = kernel.export_snapshot();
snapshot.memories[1].prev_hash = None;
let error = Kernel::hydrate(snapshot).expect_err("broken chain must be detected");
assert!(matches!(
error,
KernelError::HydrationChainMismatch {
index: 1,
expected_prev_hash: Some(_),
actual_prev_hash: None
}
));
}
#[test]
fn empty_paths_are_rejected() {
let mut kernel = Kernel::new();
let error = kernel
.postulate("", "nope")
.expect_err("empty path cannot be written");
assert_eq!(error, KernelError::EmptyPath);
}
#[test]
fn default_operator_registry_matches_typescript_kernel() {
let kernel = Kernel::new();
assert_eq!(kernel.operator_kind("_"), Some("secret"));
assert_eq!(kernel.operator_kind("~"), Some("noise"));
assert_eq!(kernel.operator_kind("__"), Some("pointer"));
assert_eq!(kernel.operator_kind("->"), Some("pointer"));
assert_eq!(kernel.operator_kind("@"), Some("identity"));
assert_eq!(kernel.operator_kind("="), Some("eval"));
assert_eq!(kernel.operator_kind("?"), Some("query"));
assert_eq!(kernel.operator_kind("-"), Some("remove"));
}
#[test]
fn define_operator_updates_registry_and_snapshot_hydration() {
let mut kernel = Kernel::new();
kernel.define_operator("drop", "remove").unwrap();
assert_eq!(kernel.operator_kind("drop"), Some("remove"));
let restored = Kernel::hydrate(kernel.export_snapshot()).unwrap();
assert_eq!(restored.operator_kind("drop"), Some("remove"));
}
#[test]
fn define_operator_rejects_empty_kind_and_reserved_define_token() {
let mut kernel = Kernel::new();
assert_eq!(
kernel.define_operator(" ", "remove"),
Err(KernelError::EmptyOperator)
);
assert_eq!(
kernel.define_operator("drop", " "),
Err(KernelError::EmptyOperatorKind)
);
assert_eq!(
kernel.define_operator("+", "remove"),
Err(KernelError::ReservedOperator("+".to_string()))
);
}
#[test]
fn custom_operator_kind_applies_to_memory_replay() {
let mut kernel = Kernel::new();
kernel.define_operator("drop", "remove").unwrap();
kernel.postulate("apps.demo.title", "Demo").unwrap();
kernel
.postulate_with_operator("apps.demo", Some("drop".to_string()), Value::from("-"))
.unwrap();
assert_eq!(kernel.read("apps.demo.title"), None);
let restored = Kernel::hydrate(kernel.export_snapshot()).unwrap();
assert_eq!(restored.operator_kind("drop"), Some("remove"));
assert_eq!(restored.read("apps.demo.title"), None);
}
#[test]
fn remove_deletes_exact_path_and_descendants_from_index() {
let mut kernel = Kernel::new();
kernel.postulate("apps.demo.title", "Demo").unwrap();
kernel.postulate("apps.demo.count", 2_u64).unwrap();
kernel.postulate("apps.other.title", "Other").unwrap();
let memory = kernel.remove("apps.demo").unwrap();
assert_eq!(memory.operator.as_deref(), Some("-"));
assert_eq!(memory.path, vec!["apps".to_string(), "demo".to_string()]);
assert_eq!(memory.value, Value::from("-"));
assert_eq!(kernel.read("apps.demo.title"), None);
assert_eq!(kernel.read("apps.demo.count"), None);
assert_eq!(kernel.read("apps.other.title"), Some(&Value::from("Other")));
}
#[test]
fn remove_exact_leaf_does_not_delete_siblings() {
let mut kernel = Kernel::new();
kernel.postulate("apps.demo.title", "Demo").unwrap();
kernel.postulate("apps.demo.count", 2_u64).unwrap();
kernel.remove("apps.demo.title").unwrap();
assert_eq!(kernel.read("apps.demo.title"), None);
assert_eq!(kernel.read("apps.demo.count"), Some(&Value::from(2_u64)));
}
#[test]
fn remove_replays_through_snapshot_hydration() {
let mut kernel = Kernel::new();
kernel.postulate("apps.demo.title", "Demo").unwrap();
kernel.postulate("apps.demo.count", 2_u64).unwrap();
kernel.remove("apps.demo").unwrap();
let restored = Kernel::hydrate(kernel.export_snapshot()).unwrap();
assert_eq!(restored.memories(), kernel.memories());
assert_eq!(restored.read("apps.demo.title"), None);
assert_eq!(restored.read("apps.demo.count"), None);
}
#[test]
fn pointer_redirects_exact_path_reads() {
let mut kernel = Kernel::new();
kernel.postulate("apps.demo.title", "Demo").unwrap();
let memory = kernel
.pointer("apps.alias.title", "apps.demo.title")
.unwrap();
assert_eq!(memory.operator.as_deref(), Some("__"));
assert_eq!(
memory.value,
Value::Pointer(vec![
"apps".to_string(),
"demo".to_string(),
"title".to_string(),
])
);
assert_eq!(kernel.read("apps.alias.title"), Some(&Value::from("Demo")));
}
#[test]
fn pointer_redirects_prefix_reads() {
let mut kernel = Kernel::new();
kernel.postulate("apps.demo.title", "Demo").unwrap();
kernel.postulate("apps.demo.count", 2_u64).unwrap();
kernel.pointer("apps.alias", "apps.demo").unwrap();
assert_eq!(kernel.read("apps.alias.title"), Some(&Value::from("Demo")));
assert_eq!(kernel.read("apps.alias.count"), Some(&Value::from(2_u64)));
}
#[test]
fn pointer_reads_target_live_value_after_overwrite() {
let mut kernel = Kernel::new();
kernel.postulate("apps.demo.title", "Before").unwrap();
kernel
.pointer("apps.alias.title", "apps.demo.title")
.unwrap();
kernel.postulate("apps.demo.title", "After").unwrap();
assert_eq!(kernel.read("apps.alias.title"), Some(&Value::from("After")));
}
#[test]
fn pointer_cycles_fail_closed() {
let mut kernel = Kernel::new();
kernel.pointer("apps.a", "apps.b").unwrap();
kernel.pointer("apps.b", "apps.a").unwrap();
assert_eq!(kernel.read("apps.a"), None);
assert_eq!(kernel.read("apps.b"), None);
}
#[test]
fn pointer_replays_through_snapshot_hydration() {
let mut kernel = Kernel::new();
kernel.postulate("apps.demo.title", "Demo").unwrap();
kernel.pointer("apps.alias", "apps.demo").unwrap();
let restored = Kernel::hydrate(kernel.export_snapshot()).unwrap();
assert_eq!(restored.memories(), kernel.memories());
assert_eq!(
restored.read("apps.alias.title"),
Some(&Value::from("Demo"))
);
}
#[test]
fn root_identity_claim_sets_active_identity() {
let mut kernel = Kernel::new();
let memory = kernel.claim_identity(" Jabellae ").unwrap().clone();
assert_eq!(kernel.active_identity(), Some("jabellae"));
assert_eq!(memory.path, Vec::<String>::new());
assert_eq!(memory.operator.as_deref(), Some("@"));
assert_eq!(memory.value, Value::Identity("jabellae".to_string()));
assert_eq!(
kernel.read(""),
Some(&Value::Identity("jabellae".to_string()))
);
}
#[test]
fn scoped_identity_writes_marker_without_replacing_active_identity() {
let mut kernel = Kernel::new();
kernel.claim_identity("jabellae").unwrap();
let memory = kernel
.identity("profile.owner", "Worker-01")
.unwrap()
.clone();
assert_eq!(kernel.active_identity(), Some("jabellae"));
assert_eq!(
memory.path,
vec!["profile".to_string(), "owner".to_string(),]
);
assert_eq!(memory.operator.as_deref(), Some("@"));
assert_eq!(memory.value, Value::Identity("worker-01".to_string()));
assert_eq!(
kernel.read("profile.owner"),
Some(&Value::Identity("worker-01".to_string()))
);
}
#[test]
fn identity_claim_rejects_invalid_labels() {
let mut kernel = Kernel::new();
assert!(matches!(
kernel.claim_identity("ab"),
Err(KernelError::InvalidIdentity(_))
));
assert!(matches!(
kernel.claim_identity("bad.name"),
Err(KernelError::InvalidIdentity(_))
));
assert!(matches!(
kernel.claim_identity("-bad"),
Err(KernelError::InvalidIdentity(_))
));
assert_eq!(kernel.active_identity(), None);
assert_eq!(kernel.memories().len(), 0);
}
#[test]
fn root_identity_replays_through_snapshot_hydration() {
let mut kernel = Kernel::new();
kernel.claim_identity("jabellae").unwrap();
kernel.identity("profile.owner", "worker").unwrap();
let restored = Kernel::hydrate(kernel.export_snapshot()).unwrap();
assert_eq!(restored.memories(), kernel.memories());
assert_eq!(restored.active_identity(), Some("jabellae"));
assert_eq!(
restored.read("profile.owner"),
Some(&Value::Identity("worker".to_string()))
);
}
#[test]
fn inspect_reports_public_kernel_shape() {
let mut kernel = Kernel::new();
kernel.postulate("profile.name", "Abella").unwrap();
kernel.noise("wallet", "noise-A").unwrap();
kernel.postulate("order.price", 10_u64).unwrap();
kernel.postulate("order.quantity", 3_u64).unwrap();
kernel.derive("order", "total", "price * quantity").unwrap();
let inspect = kernel.inspect();
assert_eq!(inspect.memories.len(), kernel.memories().len());
assert_eq!(
inspect
.index
.get(&vec!["profile".to_string(), "name".to_string()]),
Some(&Value::from("Abella"))
);
assert_eq!(inspect.noise_scopes, vec![vec!["wallet".to_string()]]);
assert_eq!(
inspect.derivations,
vec![vec!["order".to_string(), "total".to_string()]]
);
}
#[test]
fn inspect_last_returns_tail_memories_only() {
let mut kernel = Kernel::new();
kernel.postulate("a", 1_u64).unwrap();
kernel.postulate("b", 2_u64).unwrap();
kernel.postulate("c", 3_u64).unwrap();
let inspect = kernel.inspect_last(2);
assert_eq!(inspect.memories.len(), 2);
assert_eq!(inspect.memories[0].path, vec!["b".to_string()]);
assert_eq!(inspect.memories[1].path, vec!["c".to_string()]);
}
#[test]
fn inspect_redacts_memories_under_secret_scope() {
let mut kernel = Kernel::new();
kernel.postulate("wallet.balance", 100_u64).unwrap();
kernel.secret("wallet", "alpha").unwrap();
kernel.postulate("wallet.note", "private").unwrap();
let inspect = kernel.inspect();
let balance = inspect
.memories
.iter()
.find(|memory| memory.path == vec!["wallet".to_string(), "balance".to_string()])
.expect("balance memory should remain observable");
let note = inspect
.memories
.iter()
.find(|memory| memory.path == vec!["wallet".to_string(), "note".to_string()])
.expect("note memory should remain observable");
assert_eq!(inspect.secret_scopes, vec![vec!["wallet".to_string()]]);
assert_eq!(balance.value, Value::from("****"));
assert_eq!(note.value, Value::from("****"));
assert_eq!(
inspect
.index
.get(&vec!["wallet".to_string(), "balance".to_string()]),
None
);
}
#[test]
fn owner_snapshot_keeps_encrypted_secret_blob_not_public_redaction() {
let mut kernel = Kernel::new();
kernel.secret("wallet", "alpha").unwrap();
kernel.postulate("wallet.note", "private").unwrap();
let snapshot = kernel.export_snapshot();
let inspect = kernel.inspect();
let restored = Kernel::hydrate(snapshot.clone()).unwrap();
assert!(matches!(
&snapshot.memories[1].value,
Value::String(blob) if blob.starts_with("b64u:")
));
assert_eq!(inspect.memories[1].value, Value::from("****"));
assert_eq!(restored.read("wallet.note"), Some(&Value::from("private")));
}
#[test]
fn explain_plain_path_reports_value_without_derivation() {
let mut kernel = Kernel::new();
kernel.postulate("profile.name", "Abella").unwrap();
let explanation = kernel.explain("profile.name").unwrap();
assert_eq!(
explanation.path,
vec!["profile".to_string(), "name".to_string()]
);
assert_eq!(explanation.value, Some(Value::from("Abella")));
assert_eq!(explanation.expr, None);
assert_eq!(explanation.derivation, None);
assert_eq!(explanation.meta.depends_on, Vec::<Vec<String>>::new());
assert_eq!(
explanation.meta.resolved_path,
vec!["profile".to_string(), "name".to_string()]
);
assert_eq!(explanation.meta.pointer_chain, Vec::<Vec<String>>::new());
assert!(!explanation.meta.secret);
}
#[test]
fn explain_derivation_reports_expression_inputs_and_dependencies() {
let mut kernel = Kernel::new();
kernel.postulate("order.price", 10_u64).unwrap();
kernel.postulate("order.quantity", 3_u64).unwrap();
kernel.derive("order", "total", "price * quantity").unwrap();
let explanation = kernel.explain("order.total").unwrap();
let derivation = explanation.derivation.unwrap();
assert_eq!(explanation.value, Some(Value::from(30_f64)));
assert_eq!(explanation.expr.as_deref(), Some("price * quantity"));
assert_eq!(derivation.expression, "price * quantity");
assert_eq!(
explanation.meta.depends_on,
vec![
vec!["order".to_string(), "price".to_string()],
vec!["order".to_string(), "quantity".to_string()],
]
);
assert_eq!(derivation.inputs.len(), 2);
assert_eq!(derivation.inputs[0].label, "price");
assert_eq!(
derivation.inputs[0].path,
vec!["order".to_string(), "price".to_string()]
);
assert_eq!(derivation.inputs[0].value, Some(Value::from(10_u64)));
assert_eq!(derivation.inputs[0].origin, ExplainOrigin::Public);
assert!(!derivation.inputs[0].masked);
}
#[test]
fn explain_pointer_reports_resolved_path_and_chain() {
let mut kernel = Kernel::new();
kernel.postulate("wallet.balance", 100_u64).unwrap();
kernel.pointer("profile.card", "wallet").unwrap();
let explanation = kernel.explain("profile.card.balance").unwrap();
assert_eq!(explanation.value, Some(Value::from(100_u64)));
assert_eq!(
explanation.meta.resolved_path,
vec!["wallet".to_string(), "balance".to_string()]
);
assert_eq!(
explanation.meta.pointer_chain,
vec![vec!["profile".to_string(), "card".to_string()]]
);
}
#[test]
fn explain_masks_secret_derivation_inputs() {
let mut kernel = Kernel::new();
kernel.postulate("pub.base", 10_u64).unwrap();
kernel.secret("secure", "alpha").unwrap();
kernel.postulate("secure.rate", 2_u64).unwrap();
kernel.derive("pub", "score", "base * secure.rate").unwrap();
let explanation = kernel.explain("pub.score").unwrap();
let derivation = explanation.derivation.unwrap();
let secret_input = derivation
.inputs
.iter()
.find(|input| input.path == vec!["secure".to_string(), "rate".to_string()])
.expect("secret input should be reported");
assert_eq!(explanation.value, Some(Value::from(20_f64)));
assert_eq!(secret_input.label, "secure.rate");
assert_eq!(secret_input.value, Some(Value::from("****")));
assert_eq!(secret_input.origin, ExplainOrigin::Secret);
assert!(secret_input.masked);
}
#[test]
fn explain_secret_path_marks_result_secret() {
let mut kernel = Kernel::new();
kernel.secret("wallet", "alpha").unwrap();
kernel.postulate("wallet.balance", 100_u64).unwrap();
let explanation = kernel.explain("wallet.balance").unwrap();
assert_eq!(explanation.value, Some(Value::from(100_u64)));
assert!(explanation.meta.secret);
}
#[test]
fn noise_scope_records_redacted_boundary_memory() {
let mut kernel = Kernel::new();
let memory = kernel.noise("wallet", "noise-A").unwrap().clone();
assert_eq!(memory.operator.as_deref(), Some("~"));
assert_eq!(memory.path, vec!["wallet".to_string()]);
assert_eq!(memory.value, Value::from("***"));
assert!(kernel.is_noise_scope("wallet"));
assert_eq!(kernel.read("wallet"), None);
assert_eq!(kernel.read_public("wallet"), None);
}
#[test]
fn noise_scope_does_not_hide_public_descendants_by_itself() {
let mut kernel = Kernel::new();
kernel.postulate("profile.name", "Abella").unwrap();
kernel.noise("profile", "noise-A").unwrap();
kernel.postulate("profile.city", "Veracruz").unwrap();
assert!(kernel.is_noise_scope("profile"));
assert_eq!(kernel.read("profile.name"), Some(&Value::from("Abella")));
assert_eq!(
kernel.read_public("profile.city"),
Some(&Value::from("Veracruz"))
);
}
#[test]
fn noise_under_secret_scope_keeps_public_view_closed() {
let mut kernel = Kernel::new();
kernel.secret("profile", "alpha").unwrap();
kernel.noise("profile", "noise-A").unwrap();
kernel.postulate("profile.name", "Abella").unwrap();
assert!(kernel.is_secret_scope("profile"));
assert!(kernel.is_noise_scope("profile"));
assert_eq!(kernel.read("profile.name"), Some(&Value::from("Abella")));
assert_eq!(kernel.read_public("profile.name"), None);
}
#[test]
fn noise_boundary_restarts_seed_then_applies_allowed_secrets() {
let mut kernel = Kernel::new();
kernel.secret("wallet", "alpha").unwrap();
kernel
.postulate("wallet.hidden.notes", "alpha-note")
.unwrap();
kernel.noise("wallet", "noise-A").unwrap();
kernel.secret("wallet.hidden", "beta").unwrap();
kernel.postulate("wallet.hidden.seed", "beta-seed").unwrap();
assert_eq!(
kernel.effective_secret("wallet.hidden.seed").unwrap(),
"36788adc"
);
}
#[test]
fn secret_material_v3_includes_active_noise_boundary() {
let mut kernel = Kernel::new();
kernel.secret("wallet", "alpha").unwrap();
kernel
.postulate("wallet.hidden.notes", "alpha-note")
.unwrap();
kernel.noise("wallet", "noise-A").unwrap();
kernel.secret("wallet.hidden", "beta").unwrap();
kernel.postulate("wallet.hidden.seed", "beta-seed").unwrap();
let material = kernel
.secret_material_v3(
"wallet.hidden.seed",
SecretMaterialMode::Value,
SecretMaterialPurpose::Value,
)
.unwrap();
assert_eq!(
hex(material),
"6d1fee023186ae05ce3797b69ae0a65f0ba933ed9c52755312306539768cb684"
);
}
#[test]
fn remove_noise_scope_clears_boundary() {
let mut kernel = Kernel::new();
kernel.noise("profile", "noise-A").unwrap();
kernel.remove("profile").unwrap();
assert!(!kernel.is_noise_scope("profile"));
kernel.postulate("profile.name", "Abella").unwrap();
assert_eq!(
kernel.read_public("profile.name"),
Some(&Value::from("Abella"))
);
}
#[test]
fn noise_scope_replays_through_snapshot_hydration() {
let mut kernel = Kernel::new();
kernel.noise("wallet", "noise-A").unwrap();
kernel.postulate("wallet.balance", 100_u64).unwrap();
let restored = Kernel::hydrate(kernel.export_snapshot()).unwrap();
assert_eq!(restored.memories(), kernel.memories());
assert!(restored.is_noise_scope("wallet"));
assert_eq!(
restored.read_public("wallet.balance"),
Some(&Value::from(100_u64))
);
}
#[test]
fn empty_noise_is_rejected() {
let mut kernel = Kernel::new();
let error = kernel
.noise("wallet", " ")
.expect_err("empty noise should be rejected");
assert_eq!(error, KernelError::EmptyNoise);
assert!(!kernel.is_noise_scope("wallet"));
assert_eq!(kernel.memories().len(), 0);
}
#[test]
fn collect_returns_values_without_committing_memory() {
let mut kernel = Kernel::new();
kernel.postulate("profile.name", "Abella").unwrap();
kernel.postulate("profile.city", "Veracruz").unwrap();
let values = kernel
.collect(["profile.name", "profile.city"])
.expect("collect should read values");
assert_eq!(
values,
Value::Array(vec![Value::from("Abella"), Value::from("Veracruz")])
);
assert_eq!(kernel.memories().len(), 2);
}
#[test]
fn query_commits_collect_memory_at_target_path() {
let mut kernel = Kernel::new();
kernel.postulate("profile.name", "Abella").unwrap();
kernel.postulate("profile.city", "Veracruz").unwrap();
let memory = kernel
.query("profile.summary", ["profile.name", "profile.city"])
.unwrap()
.clone();
assert_eq!(memory.operator.as_deref(), Some("?"));
assert_eq!(
memory.value,
Value::Array(vec![Value::from("Abella"), Value::from("Veracruz")])
);
assert_eq!(
kernel.read("profile.summary"),
Some(&Value::Array(vec![
Value::from("Abella"),
Value::from("Veracruz")
]))
);
}
#[test]
fn query_single_segment_paths_are_relative_to_target_scope() {
let mut kernel = Kernel::new();
kernel.postulate("profile.name", "Abella").unwrap();
kernel.postulate("profile.city", "Veracruz").unwrap();
kernel.query("profile", ["name", "city"]).unwrap();
assert_eq!(
kernel.read("profile"),
Some(&Value::Array(vec![
Value::from("Abella"),
Value::from("Veracruz")
]))
);
}
#[test]
fn query_missing_paths_are_null() {
let mut kernel = Kernel::new();
kernel.postulate("profile.name", "Abella").unwrap();
kernel
.query("profile.summary", ["profile.name", "profile.age"])
.unwrap();
assert_eq!(
kernel.read("profile.summary"),
Some(&Value::Array(vec![Value::from("Abella"), Value::Null]))
);
}
#[test]
fn query_under_secret_scope_stays_out_of_public_index() {
let mut kernel = Kernel::new();
kernel.secret("profile", "alpha").unwrap();
kernel.postulate("profile.name", "Abella").unwrap();
kernel.postulate("profile.city", "Veracruz").unwrap();
let memory = kernel.query("profile", ["name", "city"]).unwrap().clone();
assert_eq!(memory.operator.as_deref(), Some("?"));
assert_eq!(
kernel.read("profile"),
Some(&Value::Array(vec![
Value::from("Abella"),
Value::from("Veracruz")
]))
);
assert_eq!(kernel.read_public("profile"), None);
assert_eq!(kernel.read_public("profile.name"), None);
}
#[test]
fn query_replays_through_snapshot_hydration() {
let mut kernel = Kernel::new();
kernel.postulate("profile.name", "Abella").unwrap();
kernel.postulate("profile.city", "Veracruz").unwrap();
kernel
.query("profile.summary", ["profile.name", "profile.city"])
.unwrap();
let restored = Kernel::hydrate(kernel.export_snapshot()).unwrap();
assert_eq!(restored.memories(), kernel.memories());
assert_eq!(
restored.read("profile.summary"),
Some(&Value::Array(vec![
Value::from("Abella"),
Value::from("Veracruz")
]))
);
}
#[test]
fn empty_query_is_rejected() {
let mut kernel = Kernel::new();
let error = kernel
.query("profile.summary", Vec::<&str>::new())
.expect_err("empty query should be rejected");
assert_eq!(error, KernelError::EmptyQuery);
assert_eq!(kernel.memories().len(), 0);
}
#[test]
fn derivation_computes_from_relative_scope() {
let mut kernel = Kernel::new();
kernel.postulate("order.price", 10_u64).unwrap();
kernel.postulate("order.quantity", 3_u64).unwrap();
let memory = kernel
.derive("order", "total", "price * quantity")
.unwrap()
.clone();
assert_eq!(memory.operator.as_deref(), Some("="));
assert_eq!(memory.expression, Some(Value::from("price * quantity")));
assert_eq!(memory.path, vec!["order".to_string(), "total".to_string()]);
assert_eq!(memory.value, Value::from(30_f64));
assert_eq!(kernel.read("order.total"), Some(&Value::from(30_f64)));
}
#[test]
fn derivation_recomputes_when_dependency_changes() {
let mut kernel = Kernel::new();
kernel.postulate("order.price", 10_u64).unwrap();
kernel.postulate("order.quantity", 3_u64).unwrap();
kernel.derive("order", "total", "price * quantity").unwrap();
kernel.postulate("order.price", 12_u64).unwrap();
assert_eq!(kernel.read("order.total"), Some(&Value::from(36_f64)));
assert_eq!(
kernel.memories().last().unwrap().operator.as_deref(),
Some("=")
);
assert_eq!(
kernel.memories().last().unwrap().expression,
Some(Value::from("price * quantity"))
);
}
#[test]
fn recompute_mode_defaults_to_eager_and_can_switch_to_lazy() {
let mut kernel = Kernel::new();
assert_eq!(kernel.recompute_mode(), RecomputeMode::Eager);
kernel.set_recompute_mode(RecomputeMode::Lazy);
assert_eq!(kernel.recompute_mode(), RecomputeMode::Lazy);
}
#[test]
fn lazy_mode_defers_derivation_until_fresh_read() {
let mut kernel = Kernel::new();
kernel.set_recompute_mode(RecomputeMode::Lazy);
kernel.postulate("order.price", 10_u64).unwrap();
kernel.postulate("order.quantity", 3_u64).unwrap();
kernel.derive("order", "total", "price * quantity").unwrap();
kernel.postulate("order.price", 12_u64).unwrap();
assert_eq!(kernel.read("order.total"), Some(&Value::from(30_f64)));
assert_eq!(kernel.read_fresh("order.total"), Some(Value::from(36_f64)));
assert_eq!(kernel.read("order.total"), Some(&Value::from(36_f64)));
}
#[test]
fn explain_reports_last_recompute_wave_for_direct_dependency() {
let mut kernel = Kernel::new();
kernel.postulate("order.price", 10_u64).unwrap();
kernel.postulate("order.quantity", 3_u64).unwrap();
kernel.derive("order", "total", "price * quantity").unwrap();
kernel.postulate("order.price", 12_u64).unwrap();
let explanation = kernel.explain("order.total").unwrap();
assert_eq!(explanation.meta.k, 1);
assert_eq!(
explanation.meta.recomputed,
vec![vec!["order".to_string(), "total".to_string()]]
);
assert_eq!(
explanation.meta.source_path,
Some(vec!["order".to_string(), "price".to_string()])
);
}
#[test]
fn derivation_subscribes_to_absolute_fallback_refs() {
let mut kernel = Kernel::new();
kernel.postulate("factor", 2_u64).unwrap();
kernel.postulate("items[1].value", 13_u64).unwrap();
kernel
.derive("items[1]", "score", "value * factor")
.unwrap();
assert_eq!(kernel.read("items[1].score"), Some(&Value::from(26_f64)));
kernel.postulate("factor", 3_u64).unwrap();
let explanation = kernel.explain("items[1].score").unwrap();
assert_eq!(kernel.read("items[1].score"), Some(&Value::from(39_f64)));
assert_eq!(explanation.meta.k, 1);
assert_eq!(
explanation.meta.recomputed,
vec![vec![
"items".to_string(),
"1".to_string(),
"score".to_string(),
]]
);
assert_eq!(
explanation.meta.source_path,
Some(vec!["factor".to_string()])
);
}
#[test]
fn lazy_derivation_fresh_read_uses_absolute_fallback_refs() {
let mut kernel = Kernel::new();
kernel.set_recompute_mode(RecomputeMode::Lazy);
kernel.postulate("factor", 2_u64).unwrap();
kernel.postulate("items[1].value", 13_u64).unwrap();
kernel
.derive("items[1]", "score", "value * factor")
.unwrap();
kernel.postulate("factor", 3_u64).unwrap();
assert_eq!(kernel.read("items[1].score"), Some(&Value::from(26_f64)));
assert_eq!(
kernel.read_fresh("items[1].score"),
Some(Value::from(39_f64))
);
assert_eq!(kernel.read("items[1].score"), Some(&Value::from(39_f64)));
}
#[test]
fn recompute_wave_ignores_irrelevant_public_nodes() {
let mut kernel = Kernel::new();
for index in 0..1_000 {
kernel
.postulate(format!("bench.irrelevant[{index}].value"), index as u64)
.unwrap();
}
kernel.postulate("order.price", 10_u64).unwrap();
kernel.postulate("order.quantity", 3_u64).unwrap();
kernel.derive("order", "total", "price * quantity").unwrap();
kernel.postulate("order.price", 12_u64).unwrap();
let explanation = kernel.explain("order.total").unwrap();
assert_eq!(kernel.read("order.total"), Some(&Value::from(36_f64)));
assert_eq!(explanation.meta.k, 1);
assert_eq!(
explanation.meta.recomputed,
vec![vec!["order".to_string(), "total".to_string()]]
);
assert_eq!(
explanation.meta.source_path,
Some(vec!["order".to_string(), "price".to_string()])
);
}
#[test]
fn derivations_cascade_through_derived_refs() {
let mut kernel = Kernel::new();
kernel.postulate("price", 10_u64).unwrap();
kernel.derive("", "cost_a", "price * 2").unwrap();
kernel.derive("", "total", "cost_a + 5").unwrap();
assert_eq!(kernel.read("total"), Some(&Value::from(25_f64)));
kernel.postulate("price", 20_u64).unwrap();
assert_eq!(kernel.read("cost_a"), Some(&Value::from(40_f64)));
assert_eq!(kernel.read("total"), Some(&Value::from(45_f64)));
}
#[test]
fn explain_reports_cascaded_recompute_wave() {
let mut kernel = Kernel::new();
kernel.postulate("price", 10_u64).unwrap();
kernel.derive("", "cost_a", "price * 2").unwrap();
kernel.derive("", "total", "cost_a + 5").unwrap();
kernel.postulate("price", 20_u64).unwrap();
let explanation = kernel.explain("total").unwrap();
assert_eq!(explanation.meta.k, 2);
assert_eq!(
explanation.meta.recomputed,
vec![vec!["cost_a".to_string()], vec!["total".to_string()]]
);
assert_eq!(
explanation.meta.source_path,
Some(vec!["price".to_string()])
);
}
#[test]
fn lazy_mode_fresh_read_recomputes_cascade_and_reports_wave() {
let mut kernel = Kernel::new();
kernel.set_recompute_mode(RecomputeMode::Lazy);
kernel.postulate("price", 10_u64).unwrap();
kernel.derive("", "cost_a", "price * 2").unwrap();
kernel.derive("", "total", "cost_a + 5").unwrap();
kernel.postulate("price", 20_u64).unwrap();
assert_eq!(kernel.read("cost_a"), Some(&Value::from(20_f64)));
assert_eq!(kernel.read("total"), Some(&Value::from(25_f64)));
assert_eq!(kernel.read_fresh("total"), Some(Value::from(45_f64)));
let explanation = kernel.explain("total").unwrap();
assert_eq!(explanation.meta.k, 2);
assert_eq!(
explanation.meta.recomputed,
vec![vec!["cost_a".to_string()], vec!["total".to_string()]]
);
assert_eq!(
explanation.meta.source_path,
Some(vec!["total".to_string()])
);
}
#[test]
fn unresolved_derivation_stores_expression_until_inputs_exist() {
let mut kernel = Kernel::new();
kernel.derive("order", "total", "price * quantity").unwrap();
assert_eq!(
kernel.read("order.total"),
Some(&Value::from("price * quantity"))
);
kernel.postulate("order.price", 10_u64).unwrap();
assert_eq!(
kernel.read("order.total"),
Some(&Value::from("price * quantity"))
);
kernel.postulate("order.quantity", 2_u64).unwrap();
assert_eq!(kernel.read("order.total"), Some(&Value::from(20_f64)));
}
#[test]
fn derivation_supports_boolean_expressions() {
let mut kernel = Kernel::new();
kernel.postulate("district.currentLoad", 90_u64).unwrap();
kernel.postulate("district.capacity", 100_u64).unwrap();
kernel
.derive(
"district",
"needsRedirection",
"currentLoad / capacity * 100 > 85",
)
.unwrap();
assert_eq!(
kernel.read("district.needsRedirection"),
Some(&Value::from(true))
);
kernel.postulate("district.currentLoad", 70_u64).unwrap();
assert_eq!(
kernel.read("district.needsRedirection"),
Some(&Value::from(false))
);
}
#[test]
fn derivation_replays_through_snapshot_hydration() {
let mut kernel = Kernel::new();
kernel.postulate("order.price", 10_u64).unwrap();
kernel.postulate("order.quantity", 3_u64).unwrap();
kernel.derive("order", "total", "price * quantity").unwrap();
let mut restored = Kernel::hydrate(kernel.export_snapshot()).unwrap();
assert_eq!(restored.memories(), kernel.memories());
assert_eq!(restored.read("order.total"), Some(&Value::from(30_f64)));
restored.postulate("order.quantity", 4_u64).unwrap();
assert_eq!(restored.read("order.total"), Some(&Value::from(40_f64)));
}
#[test]
fn hydration_rejects_tampered_derivation_expression() {
let mut kernel = Kernel::new();
kernel.postulate("order.price", 10_u64).unwrap();
kernel.postulate("order.quantity", 3_u64).unwrap();
kernel.derive("order", "total", "price * quantity").unwrap();
let mut snapshot = kernel.export_snapshot();
snapshot.memories[2].expression = Some(Value::from("price + quantity"));
let error = Kernel::hydrate(snapshot).expect_err("tampering must be detected");
assert!(matches!(
error,
KernelError::HydrationHashMismatch {
path,
expected: _,
actual: _
} if path == vec!["order".to_string(), "total".to_string()]
));
}
#[test]
fn secret_scope_hides_existing_branch_from_public_index() {
let mut kernel = Kernel::new();
kernel.postulate("wallet.balance", 100_u64).unwrap();
let memory = kernel.secret("wallet", "vault-key").unwrap().clone();
assert_eq!(memory.operator.as_deref(), Some("_"));
assert_eq!(memory.path, vec!["wallet".to_string()]);
assert_eq!(memory.value, Value::from("***"));
assert!(kernel.is_secret_scope("wallet"));
assert_eq!(kernel.read("wallet.balance"), Some(&Value::from(100_u64)));
assert_eq!(kernel.read_public("wallet.balance"), None);
}
#[test]
fn effective_secret_follows_secret_lineage() {
let mut kernel = Kernel::new();
kernel.secret("wallet", "alpha").unwrap();
kernel.postulate("wallet.balance", 100_u64).unwrap();
kernel.secret("wallet.hidden", "beta").unwrap();
kernel.postulate("wallet.hidden.seed", "beta-seed").unwrap();
assert_eq!(
kernel.effective_secret("wallet.balance").unwrap(),
"9d3ce45b"
);
assert_eq!(
kernel.effective_secret("wallet.hidden.seed").unwrap(),
"f11aeb12"
);
assert_eq!(kernel.memories()[0].hash, "d995d624");
assert!(matches!(
&kernel.memories()[1].value,
Value::String(blob) if blob.starts_with("b64u:")
));
}
#[test]
fn secret_material_v3_matches_typescript_branch_and_value_fixtures() {
let mut kernel = Kernel::new();
kernel.secret("wallet", "steel-door").unwrap();
kernel.postulate("wallet.balance", 100_u64).unwrap();
kernel.postulate("wallet.note", "private").unwrap();
let branch = kernel
.secret_material_v3(
"wallet.balance",
SecretMaterialMode::Branch,
SecretMaterialPurpose::Branch,
)
.unwrap();
let value_balance = kernel
.secret_material_v3(
"wallet.balance",
SecretMaterialMode::Value,
SecretMaterialPurpose::Value,
)
.unwrap();
let value_note = kernel
.secret_material_v3(
"wallet.note",
SecretMaterialMode::Value,
SecretMaterialPurpose::Value,
)
.unwrap();
assert_eq!(
hex(branch),
"61a15d8d2c2b154bb965c9691876896b87a4e371b7fbdfb33be87a58bc56a25f"
);
assert_eq!(
hex(value_balance),
"c46935fc5b433940203756a537b56e88dc51bb711fceb21b3490343f95cdd2cb"
);
assert_eq!(
hex(value_note),
"0ba0895e0c51bd759ebc78552855137ebf7c0e608e5494942793f2dc445756a9"
);
assert_ne!(value_balance, value_note);
}
#[test]
fn blob_v3_keys_match_typescript_fixtures() {
let mut kernel = Kernel::new();
kernel.secret("wallet", "steel-door").unwrap();
kernel.postulate("wallet.balance", 100_u64).unwrap();
let branch_keys = kernel
.secret_blob_keys_v3("wallet.balance", SecretMaterialMode::Branch)
.unwrap();
let value_keys = kernel
.secret_blob_keys_v3("wallet.balance", SecretMaterialMode::Value)
.unwrap();
assert_eq!(
hex(&branch_keys.path_context),
"77616c6c65742e62616c616e6365"
);
assert_eq!(
hex(branch_keys.enc_key),
"d6c083de386973b96784c94708996945e4b6ce49d2c2f4c24b51be96b6c13f71"
);
assert_eq!(
hex(branch_keys.mac_key),
"925244047426e98be206dd29dec81cd05bb98e9a4d4d660b8c52c5f16d3f8f4f"
);
assert_eq!(
hex(&value_keys.path_context),
"77616c6c65742e62616c616e6365"
);
assert_eq!(
hex(value_keys.enc_key),
"80938b1df755f88cd88ce4170856658bea69d8ce8e611f2fdf26d4e22b0b57ab"
);
assert_eq!(
hex(value_keys.mac_key),
"966dde6a725fbec96a16e324003b60af6ce14da2a8677c8cd91e40a93c09957c"
);
}
#[test]
fn encrypt_blob_v3_matches_typescript_fixture_and_decrypts() {
let mut kernel = Kernel::new();
let nonce = [
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f,
];
kernel.secret("wallet", "steel-door").unwrap();
kernel.postulate("wallet.balance", 100_u64).unwrap();
let blob = kernel
.encrypt_secret_value_v3("wallet.balance", 100_u64, nonce)
.unwrap();
assert_eq!(
blob,
"b64u:_m1lAwABAgMEBQYHCAkKCwwNDg96n3ivJyFIHd924OaJDs29P14O"
);
assert_eq!(
kernel
.decrypt_secret_value_v3("wallet.balance", &blob)
.unwrap(),
Some(Value::from(100_u64))
);
}
#[test]
fn decrypt_blob_v3_tampering_fails_closed() {
let mut kernel = Kernel::new();
let nonce = [
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f,
];
kernel.secret("wallet", "steel-door").unwrap();
let mut blob = kernel
.encrypt_secret_value_v3("wallet.balance", 100_u64, nonce)
.unwrap();
blob.pop();
blob.push('A');
assert_eq!(
kernel
.decrypt_secret_value_v3("wallet.balance", &blob)
.unwrap(),
None
);
}
#[test]
fn writes_under_secret_scope_stay_out_of_public_index() {
let mut kernel = Kernel::new();
kernel.secret("wallet", "vault-key").unwrap();
kernel.postulate("wallet.balance", 100_u64).unwrap();
kernel.postulate("profile.name", "Jabellae").unwrap();
assert_eq!(kernel.read("wallet.balance"), Some(&Value::from(100_u64)));
assert_eq!(kernel.read_public("wallet.balance"), None);
assert_eq!(
kernel.read_public("profile.name"),
Some(&Value::from("Jabellae"))
);
}
#[test]
fn writes_under_secret_scope_store_encrypted_blobs_in_memory() {
let mut kernel = Kernel::new();
kernel.secret("wallet", "vault-key").unwrap();
kernel.postulate("wallet.balance", 100_u64).unwrap();
let memory = &kernel.memories()[1];
assert_eq!(
memory.path,
vec!["wallet".to_string(), "balance".to_string()]
);
assert!(matches!(
&memory.value,
Value::String(blob) if blob.starts_with("b64u:") && blob != "100"
));
assert_eq!(kernel.read("wallet.balance"), Some(&Value::from(100_u64)));
assert_eq!(kernel.read_public("wallet.balance"), None);
}
#[test]
fn secret_scope_does_not_hide_siblings() {
let mut kernel = Kernel::new();
kernel.secret("wallet", "vault-key").unwrap();
kernel.postulate("wallet.balance", 100_u64).unwrap();
kernel.postulate("wallets.balance", 200_u64).unwrap();
assert_eq!(kernel.read_public("wallet.balance"), None);
assert_eq!(
kernel.read_public("wallets.balance"),
Some(&Value::from(200_u64))
);
}
#[test]
fn remove_secret_scope_clears_private_data_and_scope() {
let mut kernel = Kernel::new();
kernel.secret("wallet", "vault-key").unwrap();
kernel.postulate("wallet.balance", 100_u64).unwrap();
kernel.remove("wallet").unwrap();
assert_eq!(kernel.read("wallet.balance"), None);
assert_eq!(kernel.read_public("wallet.balance"), None);
assert!(!kernel.is_secret_scope("wallet"));
kernel.postulate("wallet.balance", 200_u64).unwrap();
assert_eq!(
kernel.read_public("wallet.balance"),
Some(&Value::from(200_u64))
);
}
#[test]
fn secret_scope_replays_through_snapshot_hydration() {
let mut kernel = Kernel::new();
kernel.secret("wallet", "vault-key").unwrap();
kernel.postulate("wallet.balance", 100_u64).unwrap();
kernel.postulate("profile.name", "Jabellae").unwrap();
let restored = Kernel::hydrate(kernel.export_snapshot()).unwrap();
assert_eq!(restored.memories(), kernel.memories());
assert!(restored.is_secret_scope("wallet"));
assert_eq!(restored.read("wallet.balance"), Some(&Value::from(100_u64)));
assert_eq!(restored.read_public("wallet.balance"), None);
assert_eq!(
restored.read_public("profile.name"),
Some(&Value::from("Jabellae"))
);
}
#[test]
fn owner_snapshot_preserves_secret_material_for_hydration() {
let mut kernel = Kernel::new();
kernel.secret("wallet", "alpha").unwrap();
kernel.noise("wallet.hidden", "noise-A").unwrap();
kernel.secret("wallet.hidden", "beta").unwrap();
kernel.postulate("wallet.hidden.seed", "beta-seed").unwrap();
let restored = Kernel::hydrate(kernel.export_snapshot()).unwrap();
assert_eq!(
restored.effective_secret("wallet.hidden.seed").unwrap(),
kernel.effective_secret("wallet.hidden.seed").unwrap()
);
assert_eq!(
restored.read("wallet.hidden.seed"),
Some(&Value::from("beta-seed"))
);
}
#[test]
fn hydration_rejects_tampered_secret_material() {
let mut kernel = Kernel::new();
kernel.secret("wallet", "alpha").unwrap();
kernel.postulate("wallet.balance", 100_u64).unwrap();
let mut snapshot = kernel.export_snapshot();
snapshot
.local_secrets
.insert(vec!["wallet".to_string()], "gamma".to_string());
let error = Kernel::hydrate(snapshot).expect_err("secret material tampering must fail");
assert!(matches!(
error,
KernelError::HydrationHashMismatch {
path,
expected: _,
actual: _
} if path == vec!["wallet".to_string()]
));
}
#[test]
fn empty_secret_is_rejected() {
let mut kernel = Kernel::new();
let error = kernel
.secret("wallet", " ")
.expect_err("empty secret should be rejected");
assert_eq!(error, KernelError::EmptySecret);
assert!(!kernel.is_secret_scope("wallet"));
assert_eq!(kernel.memories().len(), 0);
}