#![cfg(feature = "std")]
use std::vec::Vec;
#[cfg(test)]
use crate::bank::BankMotif;
use crate::bank::{bank_hash, collapse as bank_collapse, Episode};
use crate::candidate::{prepare_with_detectors, CandidateConfig, CandidateInterval};
use crate::consensus::{form as consensus_form, ConsensusCell};
use crate::contract::{chain, Contract};
use crate::detector::{evaluate as detector_evaluate, DetectorCell, DetectorThresholds};
use crate::fixed::Q16;
use crate::grammar::{GrammarState, ReasonCode};
use crate::hash::sha256;
use crate::motif::registry_hash as motif_registry_hash;
use crate::residual::{compute as residual_compute, Baseline, ResidualCell};
use crate::serialize::{write_fixture, write_key, write_str, write_u64};
use crate::sign::{compute as sign_compute, SignCell};
use crate::verdict::FinalVerdict;
use crate::window::{compute_features, WindowFeature};
use crate::TraceEvent;
#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)]
pub enum EmissionMode {
#[default]
Audit,
Throughput,
}
impl EmissionMode {
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::Audit => "audit",
Self::Throughput => "throughput",
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub struct IntermediateHashes {
pub input_catalog: [u8; 32],
pub contract: [u8; 32],
pub bank: [u8; 32],
pub detector_registry: [u8; 32],
pub kernel_sequence: [u8; 32],
pub window_feature: [u8; 32],
pub residual_field: [u8; 32],
pub sign_field: [u8; 32],
pub detector_cell: [u8; 32],
pub consensus_grid: [u8; 32],
pub candidate_interval: [u8; 32],
pub episode: [u8; 32],
}
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct CaseFile {
pub version: &'static str,
pub backend: &'static str,
pub mode: EmissionMode,
pub hashes: IntermediateHashes,
pub episodes: Vec<Episode>,
pub final_case_file_hash: [u8; 32],
pub final_verdict: FinalVerdict,
}
const CASE_FILE_VERSION: &str = "DSFB-GPU-DEBUG-CASE-0.1";
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct CompactCaseSummary {
pub backend: &'static str,
pub mode: EmissionMode,
pub hashes: IntermediateHashes,
pub candidates: Vec<CandidateInterval>,
pub breach_flags: u32,
}
pub mod breach_flags {
pub const BANK_HASH_MISMATCH: u32 = 1 << 0;
pub const DETECTOR_REGISTRY_MISMATCH: u32 = 1 << 1;
pub const GRAPH_PLAN_MISMATCH: u32 = 1 << 2;
}
#[must_use]
#[allow(clippy::too_many_lines)]
pub fn build_cpu(events: &[TraceEvent], contract: &Contract) -> CaseFile {
let features = compute_features(
events,
contract.n_windows,
contract.n_entities,
u64::from(contract.window_size_ms) * 1_000_000,
);
let residuals = residual_compute(&features, &Baseline::CANONICAL);
let signs = sign_compute(
&residuals,
Q16::from_raw(contract.ewma_alpha_q16_raw),
contract.n_windows,
contract.n_entities,
);
let detectors = detector_evaluate(
&residuals,
&signs,
&DetectorThresholds::CANONICAL,
contract.n_windows,
contract.n_entities,
);
let consensus = consensus_form(&signs, &detectors, contract.n_windows, contract.n_entities);
let masks: Vec<u32> = detectors.iter().map(|d| d.detector_mask).collect();
let candidates = prepare_with_detectors(
&consensus,
&masks,
contract.n_windows,
contract.n_entities,
&CandidateConfig::CANONICAL,
);
build_from_artifacts_with_mode(
events,
contract,
"cpu",
EmissionMode::Audit,
&features,
&residuals,
&signs,
&detectors,
&consensus,
&candidates,
)
}
#[must_use]
#[allow(clippy::too_many_lines)]
pub fn build_cpu_throughput(events: &[TraceEvent], contract: &Contract) -> CaseFile {
let features = compute_features(
events,
contract.n_windows,
contract.n_entities,
u64::from(contract.window_size_ms) * 1_000_000,
);
let residuals = residual_compute(&features, &Baseline::CANONICAL);
let signs = sign_compute(
&residuals,
Q16::from_raw(contract.ewma_alpha_q16_raw),
contract.n_windows,
contract.n_entities,
);
let detectors = detector_evaluate(
&residuals,
&signs,
&DetectorThresholds::CANONICAL,
contract.n_windows,
contract.n_entities,
);
let consensus = consensus_form(&signs, &detectors, contract.n_windows, contract.n_entities);
let masks: Vec<u32> = detectors.iter().map(|d| d.detector_mask).collect();
let candidates = prepare_with_detectors(
&consensus,
&masks,
contract.n_windows,
contract.n_entities,
&CandidateConfig::CANONICAL,
);
build_from_artifacts_with_mode(
events,
contract,
"cpu",
EmissionMode::Throughput,
&features,
&residuals,
&signs,
&detectors,
&consensus,
&candidates,
)
}
#[must_use]
#[allow(clippy::too_many_arguments)]
pub fn build_from_artifacts(
events: &[TraceEvent],
contract: &Contract,
backend: &'static str,
features: &[WindowFeature],
residuals: &[ResidualCell],
signs: &[SignCell],
detectors: &[DetectorCell],
consensus: &[ConsensusCell],
candidates: &[CandidateInterval],
) -> CaseFile {
build_from_artifacts_with_mode(
events,
contract,
backend,
EmissionMode::Audit,
features,
residuals,
signs,
detectors,
consensus,
candidates,
)
}
#[must_use]
#[allow(clippy::too_many_arguments)]
#[allow(clippy::too_many_lines)]
pub fn build_from_artifacts_with_mode(
events: &[TraceEvent],
contract: &Contract,
backend: &'static str,
mode: EmissionMode,
features: &[WindowFeature],
residuals: &[ResidualCell],
signs: &[SignCell],
detectors: &[DetectorCell],
consensus: &[ConsensusCell],
candidates: &[CandidateInterval],
) -> CaseFile {
let episodes = bank_collapse(
candidates,
consensus,
contract.n_windows,
contract.n_entities,
);
let input_catalog = match mode {
EmissionMode::Audit => sha256(&write_fixture(events)),
EmissionMode::Throughput => hash_events_compact(events),
};
let contract_h = chain(b"contract", &contract.hash(), &input_catalog);
let bank_h = chain(b"bank", &bank_hash(), &contract_h);
let detector_registry_h = chain(b"detreg", &motif_registry_hash(), &bank_h);
let kernel_sequence_h = chain(
b"kseq",
&contract.kernel_sequence_hash(),
&detector_registry_h,
);
let window_bytes_hash = match mode {
EmissionMode::Audit => sha256(&serialize_window_features(features)),
EmissionMode::Throughput => hash_window_features_compact(features),
};
let window_h = chain(b"window", &window_bytes_hash, &kernel_sequence_h);
let residual_bytes_hash = match mode {
EmissionMode::Audit => sha256(&serialize_residual_cells(residuals)),
EmissionMode::Throughput => hash_residual_cells_compact(residuals),
};
let residual_h = chain(b"residual", &residual_bytes_hash, &window_h);
let sign_bytes_hash = match mode {
EmissionMode::Audit => sha256(&serialize_sign_cells(signs)),
EmissionMode::Throughput => hash_sign_cells_compact(signs),
};
let sign_h = chain(b"sign", &sign_bytes_hash, &residual_h);
let detector_bytes_hash = match mode {
EmissionMode::Audit => sha256(&serialize_detector_cells(detectors)),
EmissionMode::Throughput => hash_detector_cells_compact(detectors),
};
let detector_h = chain(b"detector", &detector_bytes_hash, &sign_h);
let consensus_bytes_hash = match mode {
EmissionMode::Audit => sha256(&serialize_consensus_cells(consensus)),
EmissionMode::Throughput => hash_consensus_cells_compact(consensus),
};
let consensus_h = chain(b"consensus", &consensus_bytes_hash, &detector_h);
let candidate_bytes_hash = match mode {
EmissionMode::Audit => sha256(&serialize_candidates(candidates)),
EmissionMode::Throughput => hash_candidates_compact(candidates),
};
let candidate_h = chain(b"candidate", &candidate_bytes_hash, &consensus_h);
let episode_h = chain(
b"episode",
&sha256(&serialize_episodes(&episodes)),
&candidate_h,
);
let hashes = IntermediateHashes {
input_catalog,
contract: contract_h,
bank: bank_h,
detector_registry: detector_registry_h,
kernel_sequence: kernel_sequence_h,
window_feature: window_h,
residual_field: residual_h,
sign_field: sign_h,
detector_cell: detector_h,
consensus_grid: consensus_h,
candidate_interval: candidate_h,
episode: episode_h,
};
let initial_verdict = if backend == "cuda" {
FinalVerdict::GpuReplayAdmissible
} else {
FinalVerdict::CpuOnlyAdmissible
};
let mut case = CaseFile {
version: CASE_FILE_VERSION,
backend,
mode,
hashes,
episodes,
final_case_file_hash: [0u8; 32],
final_verdict: initial_verdict,
};
if case.episodes.iter().any(|e| !e.is_bank_admitted()) {
case.final_verdict = FinalVerdict::SemanticBypassRejected;
} else if case.hashes.bank != chain(b"bank", &contract.bank_hash, &case.hashes.contract)
&& contract.bank_hash != [0u8; 32]
{
case.final_verdict = FinalVerdict::BankMismatch;
} else if case.hashes.detector_registry
!= chain(
b"detreg",
&contract.detector_registry_hash,
&case.hashes.bank,
)
&& contract.detector_registry_hash != [0u8; 32]
{
case.final_verdict = FinalVerdict::DetectorRegistryMismatch;
}
case.final_case_file_hash = compute_final_hash(&case);
case
}
#[allow(clippy::too_many_arguments)]
#[must_use]
pub fn build_throughput_from_artifacts_and_device_digests(
events: &[TraceEvent],
contract: &Contract,
backend: &'static str,
features: &[WindowFeature],
residual_digest: [u8; 32],
sign_digest: [u8; 32],
detector_digest: [u8; 32],
consensus_digest: [u8; 32],
consensus_cells: &[ConsensusCell],
candidates: &[CandidateInterval],
) -> CaseFile {
let mode = EmissionMode::Throughput;
let episodes = bank_collapse(
candidates,
consensus_cells,
contract.n_windows,
contract.n_entities,
);
let input_catalog = hash_events_compact(events);
let contract_h = chain(b"contract", &contract.hash(), &input_catalog);
let bank_h = chain(b"bank", &bank_hash(), &contract_h);
let detector_registry_h = chain(b"detreg", &motif_registry_hash(), &bank_h);
let kernel_sequence_h = chain(
b"kseq",
&contract.kernel_sequence_hash(),
&detector_registry_h,
);
let window_bytes_hash = hash_window_features_compact(features);
let window_h = chain(b"window", &window_bytes_hash, &kernel_sequence_h);
let residual_h = chain(b"residual", &residual_digest, &window_h);
let sign_h = chain(b"sign", &sign_digest, &residual_h);
let detector_h = chain(b"detector", &detector_digest, &sign_h);
let consensus_h = chain(b"consensus", &consensus_digest, &detector_h);
let candidate_bytes_hash = hash_candidates_compact(candidates);
let candidate_h = chain(b"candidate", &candidate_bytes_hash, &consensus_h);
let episode_h = chain(
b"episode",
&sha256(&serialize_episodes(&episodes)),
&candidate_h,
);
let hashes = IntermediateHashes {
input_catalog,
contract: contract_h,
bank: bank_h,
detector_registry: detector_registry_h,
kernel_sequence: kernel_sequence_h,
window_feature: window_h,
residual_field: residual_h,
sign_field: sign_h,
detector_cell: detector_h,
consensus_grid: consensus_h,
candidate_interval: candidate_h,
episode: episode_h,
};
let initial_verdict = if backend == "cuda" {
FinalVerdict::GpuReplayAdmissible
} else {
FinalVerdict::CpuOnlyAdmissible
};
let mut case = CaseFile {
version: CASE_FILE_VERSION,
backend,
mode,
hashes,
episodes,
final_case_file_hash: [0u8; 32],
final_verdict: initial_verdict,
};
if case.episodes.iter().any(|e| !e.is_bank_admitted()) {
case.final_verdict = FinalVerdict::SemanticBypassRejected;
} else if case.hashes.bank != chain(b"bank", &contract.bank_hash, &case.hashes.contract)
&& contract.bank_hash != [0u8; 32]
{
case.final_verdict = FinalVerdict::BankMismatch;
} else if case.hashes.detector_registry
!= chain(
b"detreg",
&contract.detector_registry_hash,
&case.hashes.bank,
)
&& contract.detector_registry_hash != [0u8; 32]
{
case.final_verdict = FinalVerdict::DetectorRegistryMismatch;
}
case.final_case_file_hash = compute_final_hash(&case);
case
}
#[derive(Copy, Clone, Debug)]
pub struct FixtureHashes {
pub input_catalog: [u8; 32],
pub window_feature: [u8; 32],
}
impl FixtureHashes {
#[must_use]
pub fn compute(events: &[TraceEvent], features: &[WindowFeature]) -> Self {
Self {
input_catalog: hash_events_compact(events),
window_feature: hash_window_features_compact(features),
}
}
}
#[allow(clippy::too_many_arguments)]
#[must_use]
pub fn build_throughput_compact_verdict_from_device_digests(
contract: &Contract,
backend: &'static str,
fixture: &FixtureHashes,
residual_digest: [u8; 32],
sign_digest: [u8; 32],
detector_digest: [u8; 32],
consensus_digest: [u8; 32],
candidates: &[CandidateInterval],
) -> CaseFile {
let mode = EmissionMode::Throughput;
let episodes = bank_collapse(candidates, &[], contract.n_windows, contract.n_entities);
let contract_h = chain(b"contract", &contract.hash(), &fixture.input_catalog);
let bank_h = chain(b"bank", &bank_hash(), &contract_h);
let effective_registry_hash = if contract.detector_registry_hash == [0u8; 32] {
motif_registry_hash()
} else {
contract.detector_registry_hash
};
let detector_registry_h = chain(b"detreg", &effective_registry_hash, &bank_h);
let kernel_sequence_h = chain(
b"kseq",
&contract.kernel_sequence_hash(),
&detector_registry_h,
);
let window_h = chain(b"window", &fixture.window_feature, &kernel_sequence_h);
let residual_h = chain(b"residual", &residual_digest, &window_h);
let sign_h = chain(b"sign", &sign_digest, &residual_h);
let detector_h = chain(b"detector", &detector_digest, &sign_h);
let consensus_h = chain(b"consensus", &consensus_digest, &detector_h);
let candidate_bytes_hash = hash_candidates_compact(candidates);
let candidate_h = chain(b"candidate", &candidate_bytes_hash, &consensus_h);
let episode_h = chain(
b"episode",
&sha256(&serialize_episodes(&episodes)),
&candidate_h,
);
let hashes = IntermediateHashes {
input_catalog: fixture.input_catalog,
contract: contract_h,
bank: bank_h,
detector_registry: detector_registry_h,
kernel_sequence: kernel_sequence_h,
window_feature: window_h,
residual_field: residual_h,
sign_field: sign_h,
detector_cell: detector_h,
consensus_grid: consensus_h,
candidate_interval: candidate_h,
episode: episode_h,
};
let initial_verdict = if backend == "cuda" {
FinalVerdict::GpuReplayAdmissible
} else {
FinalVerdict::CpuOnlyAdmissible
};
let mut case = CaseFile {
version: CASE_FILE_VERSION,
backend,
mode,
hashes,
episodes,
final_case_file_hash: [0u8; 32],
final_verdict: initial_verdict,
};
if case.episodes.iter().any(|e| !e.is_bank_admitted()) {
case.final_verdict = FinalVerdict::SemanticBypassRejected;
} else if case.hashes.bank != chain(b"bank", &contract.bank_hash, &case.hashes.contract)
&& contract.bank_hash != [0u8; 32]
{
case.final_verdict = FinalVerdict::BankMismatch;
} else if case.hashes.detector_registry
!= chain(
b"detreg",
&contract.detector_registry_hash,
&case.hashes.bank,
)
&& contract.detector_registry_hash != [0u8; 32]
{
case.final_verdict = FinalVerdict::DetectorRegistryMismatch;
}
case.final_case_file_hash = compute_final_hash(&case);
case
}
#[allow(clippy::too_many_arguments)]
#[must_use]
pub fn build_compact_summary_from_device_digests(
events: &[TraceEvent],
contract: &Contract,
backend: &'static str,
features: &[WindowFeature],
residual_digest: [u8; 32],
sign_digest: [u8; 32],
detector_digest: [u8; 32],
consensus_digest: [u8; 32],
candidates: &[CandidateInterval],
) -> CompactCaseSummary {
let mode = EmissionMode::Throughput;
let input_catalog = hash_events_compact(events);
let contract_h = chain(b"contract", &contract.hash(), &input_catalog);
let bank_h = chain(b"bank", &bank_hash(), &contract_h);
let detector_registry_h = chain(b"detreg", &motif_registry_hash(), &bank_h);
let kernel_sequence_h = chain(
b"kseq",
&contract.kernel_sequence_hash(),
&detector_registry_h,
);
let window_bytes_hash = hash_window_features_compact(features);
let window_h = chain(b"window", &window_bytes_hash, &kernel_sequence_h);
let residual_h = chain(b"residual", &residual_digest, &window_h);
let sign_h = chain(b"sign", &sign_digest, &residual_h);
let detector_h = chain(b"detector", &detector_digest, &sign_h);
let consensus_h = chain(b"consensus", &consensus_digest, &detector_h);
let candidate_bytes_hash = hash_candidates_compact(candidates);
let candidate_h = chain(b"candidate", &candidate_bytes_hash, &consensus_h);
let hashes = IntermediateHashes {
input_catalog,
contract: contract_h,
bank: bank_h,
detector_registry: detector_registry_h,
kernel_sequence: kernel_sequence_h,
window_feature: window_h,
residual_field: residual_h,
sign_field: sign_h,
detector_cell: detector_h,
consensus_grid: consensus_h,
candidate_interval: candidate_h,
episode: [0u8; 32],
};
let mut breach_flags: u32 = 0;
if hashes.bank != chain(b"bank", &contract.bank_hash, &hashes.contract)
&& contract.bank_hash != [0u8; 32]
{
breach_flags |= breach_flags::BANK_HASH_MISMATCH;
}
if hashes.detector_registry != chain(b"detreg", &contract.detector_registry_hash, &hashes.bank)
&& contract.detector_registry_hash != [0u8; 32]
{
breach_flags |= breach_flags::DETECTOR_REGISTRY_MISMATCH;
}
CompactCaseSummary {
backend,
mode,
hashes,
candidates: candidates.to_vec(),
breach_flags,
}
}
fn write_case_canonical_skipping_self_hash(case: &CaseFile, buf: &mut Vec<u8>) {
buf.push(b'{');
write_key(buf, "backend");
write_str(buf, case.backend);
buf.push(b',');
write_key(buf, "episodes");
buf.push(b'[');
for (i, ep) in case.episodes.iter().enumerate() {
if i > 0 {
buf.push(b',');
}
write_episode(buf, ep);
}
buf.push(b']');
buf.push(b',');
write_key(buf, "final_verdict");
write_str(buf, case.final_verdict.name());
buf.push(b',');
write_key(buf, "hashes");
write_hashes(buf, &case.hashes);
buf.push(b',');
write_key(buf, "mode");
write_str(buf, case.mode.name());
buf.push(b',');
write_key(buf, "version");
write_str(buf, case.version);
buf.push(b'}');
}
fn write_hashes(buf: &mut Vec<u8>, h: &IntermediateHashes) {
buf.push(b'{');
let entries: [(&str, &[u8; 32]); 12] = [
("bank", &h.bank),
("candidate_interval", &h.candidate_interval),
("consensus_grid", &h.consensus_grid),
("contract", &h.contract),
("detector_cell", &h.detector_cell),
("detector_registry", &h.detector_registry),
("episode", &h.episode),
("input_catalog", &h.input_catalog),
("kernel_sequence", &h.kernel_sequence),
("residual_field", &h.residual_field),
("sign_field", &h.sign_field),
("window_feature", &h.window_feature),
];
for (i, (key, val)) in entries.iter().enumerate() {
if i > 0 {
buf.push(b',');
}
write_key(buf, key);
buf.push(b'"');
buf.extend_from_slice(b"sha256:");
for byte in *val {
const HEX: &[u8; 16] = b"0123456789abcdef";
buf.push(HEX[(byte >> 4) as usize]);
buf.push(HEX[(byte & 0x0F) as usize]);
}
buf.push(b'"');
}
buf.push(b'}');
}
fn write_episode(buf: &mut Vec<u8>, ep: &Episode) {
buf.push(b'{');
write_key(buf, "admitted");
write_str(
buf,
if ep.is_bank_admitted() {
"true"
} else {
"false"
},
);
buf.push(b',');
write_key(buf, "detector_bit_count");
write_u64(buf, u64::from(ep.detector_bit_count));
buf.push(b',');
write_key(buf, "end_window");
write_u64(buf, u64::from(ep.end_window));
buf.push(b',');
write_key(buf, "entity_id");
write_u64(buf, u64::from(ep.entity_id));
buf.push(b',');
write_key(buf, "motif");
write_str(buf, ep.motif.name());
buf.push(b',');
write_key(buf, "peak_drift_q_raw");
write_q16_raw(buf, ep.peak_drift_q);
buf.push(b',');
write_key(buf, "peak_residual_q_raw");
write_q16_raw(buf, ep.peak_residual_q);
buf.push(b',');
write_key(buf, "peak_slew_q_raw");
write_q16_raw(buf, ep.peak_slew_q);
buf.push(b',');
write_key(buf, "peak_state");
write_str(buf, grammar_name(ep.peak_state));
buf.push(b',');
write_key(buf, "reason");
write_str(buf, reason_name(ep.reason));
buf.push(b',');
write_key(buf, "start_window");
write_u64(buf, u64::from(ep.start_window));
buf.push(b'}');
}
const fn grammar_name(s: GrammarState) -> &'static str {
match s {
GrammarState::Admissible => "Admissible",
GrammarState::Boundary => "Boundary",
GrammarState::Violation => "Violation",
GrammarState::Recovery => "Recovery",
}
}
const fn reason_name(r: ReasonCode) -> &'static str {
match r {
ReasonCode::Admissible => "Admissible",
ReasonCode::BoundaryApproach => "BoundaryApproach",
ReasonCode::SustainedOutwardDrift => "SustainedOutwardDrift",
ReasonCode::AbruptSlewViolation => "AbruptSlewViolation",
ReasonCode::RecurrentBoundaryGrazing => "RecurrentBoundaryGrazing",
ReasonCode::EnvelopeViolation => "EnvelopeViolation",
ReasonCode::DriftWithRecovery => "DriftWithRecovery",
ReasonCode::SingleCrossing => "SingleCrossing",
}
}
fn write_q16_raw(buf: &mut Vec<u8>, q: Q16) {
let raw = q.raw();
if raw < 0 {
buf.push(b'-');
write_u64(buf, (-(raw as i64)) as u64);
} else {
write_u64(buf, raw as u64);
}
}
fn compute_final_hash(case: &CaseFile) -> [u8; 32] {
let mut buf: Vec<u8> = Vec::new();
write_case_canonical_skipping_self_hash(case, &mut buf);
sha256(&buf)
}
#[must_use]
pub fn emit(case: &CaseFile) -> Vec<u8> {
let mut buf: Vec<u8> = Vec::new();
buf.push(b'{');
write_key(&mut buf, "backend");
write_str(&mut buf, case.backend);
buf.push(b',');
write_key(&mut buf, "episodes");
buf.push(b'[');
for (i, ep) in case.episodes.iter().enumerate() {
if i > 0 {
buf.push(b',');
}
write_episode(&mut buf, ep);
}
buf.push(b']');
buf.push(b',');
write_key(&mut buf, "final_case_file_hash");
buf.push(b'"');
buf.extend_from_slice(b"sha256:");
for byte in &case.final_case_file_hash {
const HEX: &[u8; 16] = b"0123456789abcdef";
buf.push(HEX[(byte >> 4) as usize]);
buf.push(HEX[(byte & 0x0F) as usize]);
}
buf.push(b'"');
buf.push(b',');
write_key(&mut buf, "final_verdict");
write_str(&mut buf, case.final_verdict.name());
buf.push(b',');
write_key(&mut buf, "hashes");
write_hashes(&mut buf, &case.hashes);
buf.push(b',');
write_key(&mut buf, "mode");
write_str(&mut buf, case.mode.name());
buf.push(b',');
write_key(&mut buf, "version");
write_str(&mut buf, case.version);
buf.push(b'}');
buf
}
#[must_use]
pub fn compare(cpu: &CaseFile, gpu: &CaseFile) -> FinalVerdict {
if cpu.final_verdict == FinalVerdict::SemanticBypassRejected
|| gpu.final_verdict == FinalVerdict::SemanticBypassRejected
{
return FinalVerdict::SemanticBypassRejected;
}
if cpu.mode != gpu.mode {
if cpu.hashes.contract != gpu.hashes.contract {
return FinalVerdict::ContractBreach;
}
if cpu.hashes.bank != gpu.hashes.bank {
return FinalVerdict::BankMismatch;
}
if cpu.hashes.detector_registry != gpu.hashes.detector_registry {
return FinalVerdict::DetectorRegistryMismatch;
}
if cpu.hashes.kernel_sequence != gpu.hashes.kernel_sequence {
return FinalVerdict::KernelSequenceMismatch;
}
if cpu.episodes != gpu.episodes {
return FinalVerdict::NumericMismatch;
}
return FinalVerdict::ReplayAdmissible;
}
if cpu.hashes.contract != gpu.hashes.contract {
return FinalVerdict::ContractBreach;
}
if cpu.hashes.bank != gpu.hashes.bank {
return FinalVerdict::BankMismatch;
}
if cpu.hashes.detector_registry != gpu.hashes.detector_registry {
return FinalVerdict::DetectorRegistryMismatch;
}
if cpu.hashes.kernel_sequence != gpu.hashes.kernel_sequence {
return FinalVerdict::KernelSequenceMismatch;
}
let stages: [(&[u8; 32], &[u8; 32]); 7] = [
(&cpu.hashes.window_feature, &gpu.hashes.window_feature),
(&cpu.hashes.residual_field, &gpu.hashes.residual_field),
(&cpu.hashes.sign_field, &gpu.hashes.sign_field),
(&cpu.hashes.detector_cell, &gpu.hashes.detector_cell),
(&cpu.hashes.consensus_grid, &gpu.hashes.consensus_grid),
(
&cpu.hashes.candidate_interval,
&gpu.hashes.candidate_interval,
),
(&cpu.hashes.episode, &gpu.hashes.episode),
];
for (a, b) in stages {
if a != b {
return FinalVerdict::NumericMismatch;
}
}
if cpu.backend == gpu.backend && cpu.final_case_file_hash != gpu.final_case_file_hash {
return FinalVerdict::NumericMismatch;
}
FinalVerdict::ReplayAdmissible
}
fn serialize_window_features(features: &[WindowFeature]) -> Vec<u8> {
let mut buf: Vec<u8> = Vec::with_capacity(features.len() * 64);
for f in features {
write_u64(&mut buf, u64::from(f.entity_id));
buf.push(b':');
write_u64(&mut buf, u64::from(f.window_idx));
buf.push(b':');
write_u64(&mut buf, u64::from(f.event_count));
buf.push(b':');
write_u64(&mut buf, u64::from(f.error_count));
buf.push(b':');
write_u64(&mut buf, f.sum_latency_us);
buf.push(b';');
}
buf
}
fn serialize_residual_cells(cells: &[ResidualCell]) -> Vec<u8> {
let mut buf: Vec<u8> = Vec::with_capacity(cells.len() * 48);
for c in cells {
write_u64(&mut buf, u64::from(c.entity_id));
buf.push(b':');
write_u64(&mut buf, u64::from(c.window_idx));
buf.push(b':');
write_q16_raw(&mut buf, c.residual_latency_q);
buf.push(b':');
write_q16_raw(&mut buf, c.residual_error_q);
buf.push(b';');
}
buf
}
fn serialize_sign_cells(cells: &[SignCell]) -> Vec<u8> {
let mut buf: Vec<u8> = Vec::with_capacity(cells.len() * 64);
for c in cells {
write_u64(&mut buf, u64::from(c.entity_id));
buf.push(b':');
write_u64(&mut buf, u64::from(c.window_idx));
buf.push(b':');
write_q16_raw(&mut buf, c.norm_q);
buf.push(b':');
write_q16_raw(&mut buf, c.drift_q);
buf.push(b':');
write_q16_raw(&mut buf, c.slew_q);
buf.push(b';');
}
buf
}
fn serialize_detector_cells(cells: &[DetectorCell]) -> Vec<u8> {
let mut buf: Vec<u8> = Vec::with_capacity(cells.len() * 32);
for c in cells {
write_u64(&mut buf, u64::from(c.entity_id));
buf.push(b':');
write_u64(&mut buf, u64::from(c.window_idx));
buf.push(b':');
write_u64(&mut buf, u64::from(c.detector_mask));
buf.push(b';');
}
buf
}
fn serialize_consensus_cells(cells: &[ConsensusCell]) -> Vec<u8> {
let mut buf: Vec<u8> = Vec::with_capacity(cells.len() * 96);
for c in cells {
write_u64(&mut buf, u64::from(c.entity_id));
buf.push(b':');
write_u64(&mut buf, u64::from(c.window_idx));
buf.push(b':');
write_u64(&mut buf, u64::from(c.detector_count));
buf.push(b':');
write_q16_raw(&mut buf, c.axis1_residual_q);
buf.push(b':');
write_q16_raw(&mut buf, c.axis2_drift_q);
buf.push(b':');
write_q16_raw(&mut buf, c.axis3_slew_q);
buf.push(b':');
write_q16_raw(&mut buf, c.axis4_temporal_q);
buf.push(b':');
write_q16_raw(&mut buf, c.axis7_consensus_q);
buf.push(b';');
}
buf
}
fn serialize_candidates(cs: &[CandidateInterval]) -> Vec<u8> {
let mut buf: Vec<u8> = Vec::with_capacity(cs.len() * 80);
for c in cs {
write_u64(&mut buf, u64::from(c.entity_id));
buf.push(b':');
write_u64(&mut buf, u64::from(c.start_window));
buf.push(b':');
write_u64(&mut buf, u64::from(c.end_window));
buf.push(b':');
write_u64(&mut buf, u64::from(c.union_mask));
buf.push(b':');
write_q16_raw(&mut buf, c.peak_residual_q);
buf.push(b':');
write_q16_raw(&mut buf, c.peak_drift_q);
buf.push(b':');
write_q16_raw(&mut buf, c.peak_slew_q);
buf.push(b':');
write_q16_raw(&mut buf, c.peak_temporal_q);
buf.push(b':');
write_q16_raw(&mut buf, c.peak_consensus_q);
buf.push(b':');
write_q16_raw(&mut buf, c.entity_avg_q);
buf.push(b':');
write_q16_raw(&mut buf, c.grid_avg_q);
buf.push(b';');
}
buf
}
pub const COMPACT_INPUT_DOMAIN: &str = "DSFB-GPU-DEBUG:input-event-stream:v1";
pub const TRACE_EVENT_LAYOUT_TAG: &str = "TraceEventV1";
fn hash_events_compact(events: &[TraceEvent]) -> [u8; 32] {
let mut buf: Vec<u8> = Vec::with_capacity(
COMPACT_INPUT_DOMAIN.len() + TRACE_EVENT_LAYOUT_TAG.len() + 16 + events.len() * 44,
);
buf.extend_from_slice(COMPACT_INPUT_DOMAIN.as_bytes());
buf.push(0);
buf.extend_from_slice(TRACE_EVENT_LAYOUT_TAG.as_bytes());
buf.push(0);
buf.extend_from_slice(&(events.len() as u64).to_le_bytes());
buf.push(0);
for e in events {
buf.extend_from_slice(&e.ts_ns.to_le_bytes());
buf.extend_from_slice(&e.entity_id.to_le_bytes());
buf.extend_from_slice(&e.route_id.to_le_bytes());
buf.extend_from_slice(&e.span_id.to_le_bytes());
buf.extend_from_slice(&e.parent_span_id.to_le_bytes());
buf.extend_from_slice(&e.latency_us.to_le_bytes());
buf.extend_from_slice(&e.status_code.to_le_bytes());
buf.extend_from_slice(&e.error_code.to_le_bytes());
buf.extend_from_slice(&e.event_kind.to_le_bytes());
buf.extend_from_slice(&e.flags.to_le_bytes());
}
sha256(&buf)
}
fn hash_window_features_compact(features: &[WindowFeature]) -> [u8; 32] {
let mut buf: Vec<u8> = Vec::with_capacity(features.len() * 24);
for f in features {
buf.extend_from_slice(&f.window_idx.to_le_bytes());
buf.extend_from_slice(&f.entity_id.to_le_bytes());
buf.extend_from_slice(&f.event_count.to_le_bytes());
buf.extend_from_slice(&f.error_count.to_le_bytes());
buf.extend_from_slice(&f.sum_latency_us.to_le_bytes());
}
sha256(&buf)
}
fn hash_residual_cells_compact(cells: &[ResidualCell]) -> [u8; 32] {
let mut buf: Vec<u8> = Vec::with_capacity(cells.len() * 16);
for c in cells {
buf.extend_from_slice(&c.window_idx.to_le_bytes());
buf.extend_from_slice(&c.entity_id.to_le_bytes());
buf.extend_from_slice(&c.residual_latency_q.raw().to_le_bytes());
buf.extend_from_slice(&c.residual_error_q.raw().to_le_bytes());
}
sha256(&buf)
}
fn hash_sign_cells_compact(cells: &[SignCell]) -> [u8; 32] {
let mut buf: Vec<u8> = Vec::with_capacity(cells.len() * 20);
for c in cells {
buf.extend_from_slice(&c.window_idx.to_le_bytes());
buf.extend_from_slice(&c.entity_id.to_le_bytes());
buf.extend_from_slice(&c.norm_q.raw().to_le_bytes());
buf.extend_from_slice(&c.drift_q.raw().to_le_bytes());
buf.extend_from_slice(&c.slew_q.raw().to_le_bytes());
}
sha256(&buf)
}
fn hash_detector_cells_compact(cells: &[DetectorCell]) -> [u8; 32] {
let mut buf: Vec<u8> = Vec::with_capacity(cells.len() * 12);
for c in cells {
buf.extend_from_slice(&c.window_idx.to_le_bytes());
buf.extend_from_slice(&c.entity_id.to_le_bytes());
buf.extend_from_slice(&c.detector_mask.to_le_bytes());
}
sha256(&buf)
}
fn hash_consensus_cells_compact(cells: &[ConsensusCell]) -> [u8; 32] {
let mut buf: Vec<u8> = Vec::with_capacity(cells.len() * 32);
for c in cells {
buf.extend_from_slice(&c.window_idx.to_le_bytes());
buf.extend_from_slice(&c.entity_id.to_le_bytes());
buf.extend_from_slice(&c.detector_count.to_le_bytes());
buf.extend_from_slice(&c.axis1_residual_q.raw().to_le_bytes());
buf.extend_from_slice(&c.axis2_drift_q.raw().to_le_bytes());
buf.extend_from_slice(&c.axis3_slew_q.raw().to_le_bytes());
buf.extend_from_slice(&c.axis4_temporal_q.raw().to_le_bytes());
buf.extend_from_slice(&c.axis7_consensus_q.raw().to_le_bytes());
}
sha256(&buf)
}
fn hash_candidates_compact(cs: &[CandidateInterval]) -> [u8; 32] {
let mut buf: Vec<u8> = Vec::with_capacity(cs.len() * 48);
for c in cs {
buf.extend_from_slice(&c.entity_id.to_le_bytes());
buf.extend_from_slice(&c.start_window.to_le_bytes());
buf.extend_from_slice(&c.end_window.to_le_bytes());
buf.extend_from_slice(&c.length_windows.to_le_bytes());
buf.extend_from_slice(&c.union_mask.to_le_bytes());
buf.extend_from_slice(&c.peak_residual_q.raw().to_le_bytes());
buf.extend_from_slice(&c.peak_drift_q.raw().to_le_bytes());
buf.extend_from_slice(&c.peak_slew_q.raw().to_le_bytes());
buf.extend_from_slice(&c.peak_temporal_q.raw().to_le_bytes());
buf.extend_from_slice(&c.peak_consensus_q.raw().to_le_bytes());
buf.extend_from_slice(&c.entity_avg_q.raw().to_le_bytes());
buf.extend_from_slice(&c.grid_avg_q.raw().to_le_bytes());
}
sha256(&buf)
}
fn serialize_episodes(eps: &[Episode]) -> Vec<u8> {
let mut buf: Vec<u8> = Vec::with_capacity(eps.len() * 64);
for e in eps {
write_u64(&mut buf, u64::from(e.entity_id));
buf.push(b':');
write_u64(&mut buf, u64::from(e.start_window));
buf.push(b':');
write_u64(&mut buf, u64::from(e.end_window));
buf.push(b':');
buf.extend_from_slice(e.motif.name().as_bytes());
buf.push(b':');
buf.extend_from_slice(reason_name(e.reason).as_bytes());
buf.push(b';');
}
buf
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fixture::{synthesize, DEFAULT_SEED};
fn make_contract_for_test() -> Contract {
let mut c = Contract::canonical();
c.pin_bank_hash(bank_hash());
c.pin_detector_registry_hash(motif_registry_hash());
c
}
#[test]
fn build_cpu_is_deterministic_byte_exact() {
let events = synthesize(DEFAULT_SEED);
let contract = make_contract_for_test();
let a = build_cpu(&events, &contract);
let b = build_cpu(&events, &contract);
assert_eq!(a.final_case_file_hash, b.final_case_file_hash);
assert_eq!(emit(&a), emit(&b));
}
#[test]
fn build_cpu_admits_canonically() {
let events = synthesize(DEFAULT_SEED);
let contract = make_contract_for_test();
let case = build_cpu(&events, &contract);
assert_eq!(case.final_verdict, FinalVerdict::CpuOnlyAdmissible);
assert!(case
.episodes
.iter()
.any(|e| e.entity_id == 3 && e.motif == BankMotif::LatencyRamp));
assert!(case
.episodes
.iter()
.any(|e| e.entity_id == 7 && e.motif == BankMotif::ErrorBurst));
assert!(case
.episodes
.iter()
.any(|e| e.entity_id == 11 && e.motif == BankMotif::SlewShockRecovery));
}
#[test]
fn bank_mismatch_when_pinned_hash_diverges() {
let events = synthesize(DEFAULT_SEED);
let mut contract = make_contract_for_test();
contract.bank_hash[0] ^= 0xFF;
let case = build_cpu(&events, &contract);
assert_eq!(case.final_verdict, FinalVerdict::BankMismatch);
}
#[test]
fn detector_registry_mismatch_when_pinned_hash_diverges() {
let events = synthesize(DEFAULT_SEED);
let mut contract = make_contract_for_test();
contract.detector_registry_hash[0] ^= 0xFF;
let case = build_cpu(&events, &contract);
assert_eq!(case.final_verdict, FinalVerdict::DetectorRegistryMismatch);
}
#[test]
fn semantic_bypass_attempt_is_rejected() {
let events = synthesize(DEFAULT_SEED);
let contract = make_contract_for_test();
let mut case = build_cpu(&events, &contract);
case.episodes.push(Episode::bypass_for_testing(
0,
0,
1,
BankMotif::ConfuserTransient,
));
let mut rerun = build_cpu(&events, &contract);
rerun.episodes.push(Episode::bypass_for_testing(
0,
0,
1,
BankMotif::ConfuserTransient,
));
if rerun.episodes.iter().any(|e| !e.is_bank_admitted()) {
rerun.final_verdict = FinalVerdict::SemanticBypassRejected;
}
assert_eq!(rerun.final_verdict, FinalVerdict::SemanticBypassRejected);
}
#[test]
fn compare_identical_case_files_yields_replay_admissible() {
let events = synthesize(DEFAULT_SEED);
let contract = make_contract_for_test();
let a = build_cpu(&events, &contract);
let b = a.clone();
assert_eq!(compare(&a, &b), FinalVerdict::ReplayAdmissible);
}
#[test]
fn compact_input_digest_is_deterministic_across_runs() {
let a = build_cpu_throughput(&synthesize(DEFAULT_SEED), &make_contract_for_test());
let b = build_cpu_throughput(&synthesize(DEFAULT_SEED), &make_contract_for_test());
assert_eq!(a.hashes.input_catalog, b.hashes.input_catalog);
}
#[test]
fn compact_input_digest_changes_when_one_event_byte_changes() {
let mut events = synthesize(DEFAULT_SEED);
let baseline = build_cpu_throughput(&events, &make_contract_for_test());
events[0].latency_us ^= 0x0000_00FF;
let mutated = build_cpu_throughput(&events, &make_contract_for_test());
assert_ne!(baseline.hashes.input_catalog, mutated.hashes.input_catalog);
}
#[test]
fn compact_input_digest_changes_when_event_count_changes() {
let events = synthesize(DEFAULT_SEED);
let mut shorter = events.clone();
shorter.pop();
let full = build_cpu_throughput(&events, &make_contract_for_test());
let truncated = build_cpu_throughput(&shorter, &make_contract_for_test());
assert_ne!(full.hashes.input_catalog, truncated.hashes.input_catalog);
}
#[test]
fn audit_input_digest_is_unchanged_by_compact_hash_additions() {
const GOLDEN_INPUT_CATALOG_HEX: &str =
"1eeefffa2a1029672a9e6a55e575c928fca926e8077e6784a392597ccd640487";
use std::fmt::Write;
let case = build_cpu(&synthesize(DEFAULT_SEED), &make_contract_for_test());
let mut hex = String::with_capacity(64);
for b in &case.hashes.input_catalog {
write!(hex, "{b:02x}").unwrap();
}
assert_eq!(hex, GOLDEN_INPUT_CATALOG_HEX);
}
#[test]
fn compact_and_audit_input_digests_disagree_by_design() {
let events = synthesize(DEFAULT_SEED);
let audit = build_cpu(&events, &make_contract_for_test());
let throughput = build_cpu_throughput(&events, &make_contract_for_test());
assert_ne!(audit.hashes.input_catalog, throughput.hashes.input_catalog);
}
#[test]
fn compare_detects_per_stage_numeric_mismatch() {
let events = synthesize(DEFAULT_SEED);
let contract = make_contract_for_test();
let mut a = build_cpu(&events, &contract);
let b = a.clone();
a.hashes.residual_field[0] ^= 0x01;
assert_eq!(compare(&a, &b), FinalVerdict::NumericMismatch);
}
}