use ccf_core::context::{
apply_active_context_update, reject_cross_context_normalization_leakage,
reject_global_shared_timestamp, ContextAccumulator, ContextUpdateTick,
PerContextAccumulatorStore, CONTEXT_ISOLATION_JOURNEY_ID, CONTEXT_ISOLATION_STORY_ID,
CROSS_CONTEXT_NORMALIZATION_OLD_WRONG_PATH, GLOBAL_SHARED_TIMESTAMP_OLD_WRONG_PATH,
};
use ccf_core::qac::Matrix3;
const TEST_SOURCE: &str = include_str!("per_context_isolation.rs");
const CONTRACT_SOURCE: &str =
include_str!("../../../docs2/contracts/ccf-core-v1/journey_context_isolation.yml");
const ACTIVE_CONTEXT: &str = "K1";
const INACTIVE_CONTEXT: &str = "K2";
const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
const EXPECTED_K1_UPDATE_COUNT_BEFORE: u64 = 3;
const EXPECTED_K1_UPDATE_COUNT_AFTER: u64 = 4;
const EXPECTED_K2_UPDATE_COUNT: u64 = 11;
const EXPECTED_K1_TIMESTAMP_BEFORE: u64 = 1_781_755_200_000;
const EXPECTED_K1_TIMESTAMP_AFTER: u64 = 1_781_758_800_000;
const EXPECTED_K2_TIMESTAMP: u64 = 1_781_751_600_000;
const EXPECTED_K1_BEFORE_HASH: u64 = 0x1d6a_07df_fdce_bb25;
const EXPECTED_K1_AFTER_HASH: u64 = 0x8e39_d71b_4685_80d4;
const EXPECTED_K2_HASH: u64 = 0xe6ad_e95f_9142_b7b5;
fn k1_state() -> Matrix3 {
Matrix3::new([[0.82, 1.15, 0.64], [1.04, 0.91, 1.33], [0.77, 1.28, 1.09]])
}
fn k1_reference() -> Matrix3 {
Matrix3::new([[1.21, 0.73, 1.08], [0.88, 1.42, 0.96], [1.31, 0.69, 1.17]])
}
fn k2_state() -> Matrix3 {
Matrix3::new([[1.32, 0.86, 1.11], [0.93, 1.27, 0.72], [1.08, 0.99, 1.44]])
}
fn fixture_store() -> PerContextAccumulatorStore {
PerContextAccumulatorStore {
first: ContextAccumulator {
context_id: ACTIVE_CONTEXT,
state: k1_state(),
update_count: EXPECTED_K1_UPDATE_COUNT_BEFORE,
last_update_unix_ms: EXPECTED_K1_TIMESTAMP_BEFORE,
},
second: ContextAccumulator {
context_id: INACTIVE_CONTEXT,
state: k2_state(),
update_count: EXPECTED_K2_UPDATE_COUNT,
last_update_unix_ms: EXPECTED_K2_TIMESTAMP,
},
}
}
fn active_tick() -> ContextUpdateTick {
ContextUpdateTick {
context_id: ACTIVE_CONTEXT,
reference_state: k1_reference(),
left_normalization: [1.10, 0.90, 1.05],
right_normalization: [0.95, 1.20, 0.85],
alpha_t: 0.35,
wall_clock_unix_ms: EXPECTED_K1_TIMESTAMP_AFTER,
}
}
fn matrix_hash(matrix: Matrix3) -> u64 {
let mut hash = FNV_OFFSET;
for row in matrix.entries {
for value in row {
for byte in value.to_bits().to_le_bytes() {
hash ^= byte as u64;
hash = hash.wrapping_mul(FNV_PRIME);
}
}
}
hash
}
#[test]
fn active_context_update_changes_only_k1() {
eprintln!("journey={CONTEXT_ISOLATION_JOURNEY_ID} story={CONTEXT_ISOLATION_STORY_ID}");
let mut store = fixture_store();
let inactive_baseline = store.second;
let active_hash_before = matrix_hash(store.first.state);
let inactive_hash_before = matrix_hash(inactive_baseline.state);
assert_eq!(active_hash_before, EXPECTED_K1_BEFORE_HASH);
assert_eq!(inactive_hash_before, EXPECTED_K2_HASH);
assert_eq!(store.first.update_count, EXPECTED_K1_UPDATE_COUNT_BEFORE);
assert_eq!(
store.first.last_update_unix_ms,
EXPECTED_K1_TIMESTAMP_BEFORE
);
assert_eq!(store.second.update_count, EXPECTED_K2_UPDATE_COUNT);
assert_eq!(store.second.last_update_unix_ms, EXPECTED_K2_TIMESTAMP);
let report = apply_active_context_update(&mut store, &active_tick())
.expect("per-context isolation implementation is required");
assert_eq!(report.active_context, ACTIVE_CONTEXT);
assert_eq!(report.inactive_context, INACTIVE_CONTEXT);
assert_eq!(report.active_before.state_hash, EXPECTED_K1_BEFORE_HASH);
assert_eq!(
report.active_before.update_count,
EXPECTED_K1_UPDATE_COUNT_BEFORE
);
assert_eq!(
report.active_before.last_update_unix_ms,
EXPECTED_K1_TIMESTAMP_BEFORE
);
assert_eq!(report.active_after.state_hash, EXPECTED_K1_AFTER_HASH);
assert_eq!(
report.active_after.update_count,
EXPECTED_K1_UPDATE_COUNT_AFTER
);
assert_eq!(
report.active_after.last_update_unix_ms,
EXPECTED_K1_TIMESTAMP_AFTER
);
assert_eq!(report.inactive_before.state_hash, EXPECTED_K2_HASH);
assert_eq!(report.inactive_after.state_hash, EXPECTED_K2_HASH);
assert_eq!(report.inactive_after.state, inactive_baseline.state);
assert_eq!(report.inactive_after.update_count, EXPECTED_K2_UPDATE_COUNT);
assert_eq!(
report.inactive_after.last_update_unix_ms,
EXPECTED_K2_TIMESTAMP
);
assert_eq!(store.second, inactive_baseline);
eprintln!(
"active_context={} inactive_context={} k1_state_hash_before=0x{:016x} k1_state_hash_after=0x{:016x} k2_state_hash_before=0x{:016x} k2_state_hash_after=0x{:016x} k1_update_count_changed=PASS k1_timestamp_changed=PASS k1_state_hash_changed=PASS k2_state_byte_equal=PASS k2_timestamp_unchanged=PASS k2_update_count_unchanged=PASS active_context_only=PASS no_global_normalization=PASS",
report.active_context,
report.inactive_context,
report.active_before.state_hash,
report.active_after.state_hash,
report.inactive_before.state_hash,
report.inactive_after.state_hash
);
}
#[test]
fn global_shared_timestamp_old_wrong_path_is_rejected() {
eprintln!("journey={CONTEXT_ISOLATION_JOURNEY_ID} story={CONTEXT_ISOLATION_STORY_ID}");
let report = reject_global_shared_timestamp()
.expect("global/shared timestamp negative guard is required");
assert_eq!(
report.old_wrong_path,
GLOBAL_SHARED_TIMESTAMP_OLD_WRONG_PATH
);
assert!(report.rejected);
eprintln!(
"global_shared_timestamp_negative=PASS old_wrong_path={}",
report.old_wrong_path
);
}
#[test]
fn cross_context_normalization_old_wrong_path_is_rejected() {
eprintln!("journey={CONTEXT_ISOLATION_JOURNEY_ID} story={CONTEXT_ISOLATION_STORY_ID}");
let report = reject_cross_context_normalization_leakage()
.expect("cross-context normalization negative guard is required");
assert_eq!(
report.old_wrong_path,
CROSS_CONTEXT_NORMALIZATION_OLD_WRONG_PATH
);
assert!(report.rejected);
eprintln!(
"cross_context_normalization_negative=PASS old_wrong_path={}",
report.old_wrong_path
);
}
#[test]
fn context_isolation_gate_has_no_silent_skip_marker() {
eprintln!("journey={CONTEXT_ISOLATION_JOURNEY_ID} story={CONTEXT_ISOLATION_STORY_ID}");
for required_token in [
"cargo test -p ccf-core --test per_context_isolation -- --nocapture",
"active_context=K1",
"inactive_context=K2",
"k1_update_count_changed=PASS",
"k2_state_byte_equal=PASS",
"global_shared_timestamp_negative=PASS",
"cross_context_normalization_negative=PASS",
] {
assert!(
CONTRACT_SOURCE.contains(required_token),
"context isolation contract missing token {required_token}"
);
}
let skip_needles = [
concat!("test", ".skip"),
concat!("describe", ".skip"),
concat!("it", ".skip"),
concat!("#[", "ignore]"),
concat!("status:", " skipped"),
];
for source in [TEST_SOURCE, CONTRACT_SOURCE] {
for needle in skip_needles {
assert!(
!source.contains(needle),
"context isolation gate must not contain silent skip marker {needle}"
);
}
}
eprintln!("context_isolation_skip_scan=PASS");
}