use std::collections::BTreeMap;
use std::fmt::Write as _;
use serde::Deserialize as _;
use serde_json::Value;
use sha2::{Digest as _, Sha256};
use crate::generated::types::{Lever, PolicyMode, T1Node, T2RegisterProgram, Verdict};
use super::bundle::LoadedArtifact;
use super::kleene::Kleene;
use super::tier1::ScanTable;
use super::types::{
Contribution, EvalContext, RunState, SessionState, StateLayout, MODE_ENFORCE, MODE_MONITOR,
};
pub const SAT_MAX: i64 = (1_i64 << 53) - 1;
pub const PRE_EVENTS: &[&str] = &["pre_tool_use", "session_start", "message_display"];
pub const POST_EVENTS: &[&str] = &["post_tool_use", "post_tool_batch", "stop"];
pub const ADD_SAT_SOURCES: &[&str] = &["spend_delta.tokens", "spend_delta.micro_usd"];
pub const CMP_GE_BANKS: &[&str] = &["c", "a", "t", "run"];
pub const OPS: &[&str] = &[
"MATCH",
"INC_SAT",
"RESET",
"CMP_GE",
"SET",
"CLR",
"TEST",
"TS_STORE",
"TS_ELAPSED",
"ADD_SAT",
"RUN_TRACK",
"AND",
"OR",
"NOT",
"VERDICT",
"ANOMALY",
"DONE",
];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MalformedState {
pub expected: StateLayout,
pub found: String,
}
impl std::fmt::Display for MalformedState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"session state disagrees with the bundle's state_layout: expected {:?}, found {}",
self.expected, self.found
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Half {
Pre,
Post,
}
pub fn half_for(event_type: &str) -> Option<Half> {
if PRE_EVENTS.contains(&event_type) {
Some(Half::Pre)
} else if POST_EVENTS.contains(&event_type) {
Some(Half::Post)
} else {
None
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct ProgramResult {
pub fired: bool,
pub verdict: Option<Verdict>,
pub anomalies: Vec<String>,
}
pub fn sat(value: i64, delta: i64) -> i64 {
value.saturating_add(delta).min(SAT_MAX)
}
pub fn validate_program(body: &T2RegisterProgram) -> Result<(), String> {
for (half, instructions) in [("pre", &body.pre), ("post", &body.post)] {
for instruction in instructions.iter() {
validate_instruction(half, instruction)?;
}
}
Ok(())
}
fn validate_instruction(half: &str, instruction: &[Value]) -> Result<(), String> {
let Some(op) = instruction.first().and_then(Value::as_str) else {
return Err(format!(
"{half}: instruction is not a non-empty [op, …] array"
));
};
if !OPS.contains(&op) {
return Err(format!("{half}: unknown op {op:?}"));
}
let args = &instruction[1..];
let literal_indices: &[usize] = match op {
"MATCH" => &[1],
"INC_SAT" | "RESET" | "SET" | "CLR" | "TS_STORE" => &[0, 1],
"CMP_GE" => &[1, 2, 3],
"TEST" | "TS_ELAPSED" | "NOT" => &[0, 1],
"ADD_SAT" => &[0],
"AND" | "OR" => &[0, 1, 2],
"VERDICT" | "DONE" => &[0],
"ANOMALY" => &[0],
_ => &[],
};
for &position in literal_indices {
if let Some(operand) = args.get(position) {
if operand_index(Some(operand)).is_none() {
return Err(format!(
"{half}: {op} operand {position} is not an integer register literal: {operand}"
));
}
}
}
match op {
"MATCH" => {
let Some(node) = args.first() else {
return Err(format!("{half}: MATCH needs a node and a destination"));
};
if !node.is_object() {
return Err(format!("{half}: MATCH operand 0 is not a node"));
}
if args.len() < 2 {
return Err(format!("{half}: MATCH needs a destination register"));
}
let parsed = T1Node::deserialize(node)
.map_err(|e| format!("{half}: MATCH node does not parse: {e}"))?;
super::tier1::validate_node(Some(&parsed)).map_err(|e| format!("{half}: MATCH {e}"))?;
}
"CMP_GE" => {
let bank = args.first().and_then(Value::as_str);
if !bank.is_some_and(|bank| CMP_GE_BANKS.contains(&bank)) {
return Err(format!("{half}: CMP_GE bank {:?}", args.first()));
}
if args.len() < 4 {
return Err(format!(
"{half}: CMP_GE needs bank, index, K and a destination"
));
}
}
"ADD_SAT" => {
let source = args.get(1).and_then(Value::as_str);
if !source.is_some_and(|source| ADD_SAT_SOURCES.contains(&source)) {
return Err(format!("{half}: ADD_SAT source {:?}", args.get(1)));
}
}
"ANOMALY" if args.get(1).and_then(Value::as_str).is_none() => {
return Err(format!("{half}: ANOMALY needs a code string"));
}
_ => {}
}
Ok(())
}
fn operand_index(operand: Option<&Value>) -> Option<usize> {
let value = operand?.as_i64()?;
usize::try_from(value).ok()
}
fn operand_int(operand: Option<&Value>) -> Option<i64> {
operand?.as_i64()
}
pub fn bundle_state_layout(artifacts: &[LoadedArtifact]) -> StateLayout {
let mut layout = StateLayout::default();
for artifact in artifacts {
let Some(body) = artifact.as_t2() else {
continue;
};
let Some(declared) = body.state_layout.as_ref() else {
continue;
};
layout.c = layout.c.max(declared.c.unwrap_or(0).max(0) as usize);
layout.f = layout.f.max(declared.f.unwrap_or(0).max(0) as usize);
layout.t = layout.t.max(declared.t.unwrap_or(0).max(0) as usize);
layout.a = layout.a.max(declared.a.unwrap_or(0).max(0) as usize);
layout.run |= declared.run.unwrap_or(false);
}
layout
}
pub fn validate_state(state: &SessionState, layout: &StateLayout) -> Result<(), MalformedState> {
if state.matches(layout) {
return Ok(());
}
Err(MalformedState {
expected: *layout,
found: format!(
"c={} f={} t={} a={} run={}",
state.c.len(),
state.f.len(),
state.t.len(),
state.a.len(),
state.run.is_some()
),
})
}
pub fn action_shape(tool_name: &str, first_effect: Option<(&str, &str)>, program: &str) -> String {
let (verb, target_class) = first_effect.unwrap_or(("", ""));
let mut hasher = Sha256::new();
hasher.update(tool_name.as_bytes());
hasher.update(verb.as_bytes());
hasher.update(target_class.as_bytes());
hasher.update(program.as_bytes());
let digest = hasher.finalize();
let mut hex = String::with_capacity(64);
for byte in digest {
let _ = write!(hex, "{byte:02x}");
}
hex
}
fn shape_of(ctx: &EvalContext<'_>) -> String {
let first = ctx
.classification
.effects
.first()
.map(|effect| (effect.verb.as_str(), effect.target_class.as_str()));
let program = ctx
.classification
.simple
.first()
.map(|simple| simple.program.as_str())
.unwrap_or("");
action_shape(&ctx.event.tool_name, first, program)
}
pub fn run_program(
body: &T2RegisterProgram,
half: Half,
ctx: &mut EvalContext<'_>,
scan: &ScanTable,
state: &mut SessionState,
) -> ProgramResult {
let instructions = match half {
Half::Pre => &body.pre,
Half::Post => &body.post,
};
let mut result = ProgramResult::default();
let mut bools: BTreeMap<usize, bool> = BTreeMap::new();
for instruction in instructions.iter() {
let Some(op) = instruction.first().and_then(Value::as_str) else {
continue;
};
let args = &instruction[1..];
let read = |bools: &BTreeMap<usize, bool>, position: usize| -> bool {
operand_index(args.get(position)).is_some_and(|reg| *bools.get(®).unwrap_or(&false))
};
let guarded = |bools: &BTreeMap<usize, bool>, position: usize| -> bool {
args.len() <= position || read(bools, position)
};
match op {
"MATCH" => {
let (Some(node), Some(dest)) = (args.first(), operand_index(args.get(1))) else {
continue;
};
let Ok(node) = T1Node::deserialize(node) else {
continue;
};
let value = super::tier1::evaluate_node(&node, ctx, scan);
bools.insert(dest, matches!(value, Kleene::True));
}
"INC_SAT" => {
if guarded(&bools, 1) {
if let Some(slot) = operand_index(args.first()).and_then(|i| state.c.get_mut(i))
{
*slot = sat(*slot, 1);
}
}
}
"RESET" => {
if let Some(slot) = operand_index(args.first()).and_then(|i| state.c.get_mut(i)) {
*slot = 0;
}
}
"CMP_GE" => {
let bank = args.first().and_then(Value::as_str).unwrap_or("");
let (Some(index), Some(k), Some(dest)) = (
operand_index(args.get(1)),
operand_int(args.get(2)),
operand_index(args.get(3)),
) else {
continue;
};
let Some(value) = bank_value(state, bank, index) else {
continue;
};
bools.insert(dest, value >= k);
}
"SET" | "CLR" => {
if guarded(&bools, 1) {
if let Some(slot) = operand_index(args.first()).and_then(|i| state.f.get_mut(i))
{
*slot = op == "SET";
}
}
}
"TEST" => {
let (Some(flag), Some(dest)) =
(operand_index(args.first()), operand_index(args.get(1)))
else {
continue;
};
if let Some(value) = state.f.get(flag) {
bools.insert(dest, *value);
}
}
"TS_STORE" => {
if guarded(&bools, 1) {
if let Some(slot) = operand_index(args.first()).and_then(|i| state.t.get_mut(i))
{
*slot = ctx.now_ms;
}
}
}
"TS_ELAPSED" => {
let (Some(stamp), Some(amount)) =
(operand_index(args.first()), operand_index(args.get(1)))
else {
continue;
};
let Some(stored) = state.t.get(stamp).copied() else {
continue;
};
let elapsed = if stored == 0 {
0
} else {
(ctx.now_ms - stored).max(0)
};
if let Some(slot) = state.a.get_mut(amount) {
*slot = elapsed;
}
}
"ADD_SAT" => {
let (Some(index), Some(source)) = (
operand_index(args.first()),
args.get(1).and_then(Value::as_str),
) else {
continue;
};
let delta = event_amount(ctx, source);
if let Some(slot) = state.a.get_mut(index) {
*slot = sat(*slot, delta);
}
}
"RUN_TRACK" => {
let shape = shape_of(ctx);
let run = state.run.get_or_insert_with(RunState::default);
run.len = if run.shape.as_deref() == Some(shape.as_str()) {
run.len.saturating_add(1)
} else {
1
};
run.shape = Some(shape);
}
"AND" | "OR" => {
let Some(dest) = operand_index(args.get(2)) else {
continue;
};
let (left, right) = (read(&bools, 0), read(&bools, 1));
bools.insert(
dest,
if op == "AND" {
left && right
} else {
left || right
},
);
}
"NOT" => {
let Some(dest) = operand_index(args.get(1)) else {
continue;
};
let value = read(&bools, 0);
bools.insert(dest, !value);
}
"VERDICT" => {
if read(&bools, 0) {
result.fired = true;
result.verdict = body.verdict;
}
}
"ANOMALY" if read(&bools, 0) => {
if let Some(code) = args.get(1).and_then(Value::as_str) {
result.anomalies.push(code.to_string());
}
}
"DONE" if read(&bools, 0) => break,
_ => {}
}
}
result
}
fn bank_value(state: &SessionState, bank: &str, index: usize) -> Option<i64> {
match bank {
"c" => state.c.get(index).copied(),
"a" => state.a.get(index).copied(),
"t" => state.t.get(index).copied(),
"run" => Some(state.run.as_ref().map(|run| run.len).unwrap_or(0)),
_ => None,
}
}
fn event_amount(ctx: &EvalContext<'_>, source: &str) -> i64 {
let Some(delta) = ctx.event.spend_delta.as_ref() else {
return 0;
};
match source {
"spend_delta.tokens" => delta.tokens.unwrap_or(0),
"spend_delta.micro_usd" => delta.micro_usd.unwrap_or(0),
_ => 0,
}
}
pub fn contribution(
artifact: &LoadedArtifact,
body: &T2RegisterProgram,
ctx: &mut EvalContext<'_>,
scan: &ScanTable,
state: &mut SessionState,
layout: &StateLayout,
) -> Option<Contribution> {
debug_assert!(
state.matches(layout),
"the caller validates before evaluating"
);
let half = half_for(&ctx.event.event_type)?;
let mut child = ctx.fork();
let result = run_program(body, half, &mut child, scan, state);
ctx.merge_warnings(&child);
if !result.fired && result.anomalies.is_empty() {
return None;
}
let verdict = if result.fired {
result.verdict.unwrap_or(Verdict::Block)
} else {
Verdict::Allow
};
Some(build(
artifact,
declared_mode(artifact),
verdict,
body.reason.clone().unwrap_or_default(),
Vec::new(),
result.anomalies,
))
}
pub fn evicted_contribution(
artifact: &LoadedArtifact,
body: &T2RegisterProgram,
mode: PolicyMode,
layout: &StateLayout,
) -> Option<Contribution> {
let _ = layout;
let reason = body.reason.clone().unwrap_or_default();
match body.on_evict.as_deref().unwrap_or("reinit") {
"unknown" => Some(build(
artifact,
mode.clone(),
on_inconclusive_verdict(artifact, &mode),
reason,
vec!["session_state_evicted".to_string()],
Vec::new(),
)),
"fail_static" => Some(build(
artifact,
mode,
body.verdict.unwrap_or(Verdict::Block),
reason,
Vec::new(),
Vec::new(),
)),
_ => None,
}
}
pub fn on_inconclusive_verdict(artifact: &LoadedArtifact, mode: &PolicyMode) -> Verdict {
let declared = if mode.as_str() == MODE_MONITOR {
"allow_and_flag"
} else {
artifact
.envelope
.on_inconclusive
.as_ref()
.map(|value| value.0.as_str())
.unwrap_or("allow_and_flag")
};
match declared {
"ask" => Verdict::Ask,
"block" => Verdict::Block,
_ => Verdict::Allow,
}
}
pub fn declared_mode(artifact: &LoadedArtifact) -> PolicyMode {
artifact
.envelope
.mode
.clone()
.unwrap_or_else(|| PolicyMode(MODE_ENFORCE.to_string()))
}
pub fn build(
artifact: &LoadedArtifact,
mode: PolicyMode,
verdict: Verdict,
reason: String,
inconclusive: Vec<String>,
anomalies: Vec<String>,
) -> Contribution {
let body = &artifact.envelope.body;
Contribution {
artifact_id: artifact.artifact_id().map(str::to_string),
atom_id: artifact.atom_id().map(str::to_string),
policy_public_id: artifact.envelope.policy_public_id.clone(),
dimension: artifact.envelope.dimension.clone(),
mode,
tier: artifact.envelope.tier,
verdict,
reason,
inconclusive,
anomalies,
hold: None,
exception_ground_key: None,
lever: body.get("lever").and_then(|v| Lever::deserialize(v).ok()),
steer_instruction: body
.get("steer_instruction")
.and_then(Value::as_str)
.map(str::to_string),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::generated::types::T2StateLayout;
use crate::zone_eval::facts::FactSet;
use crate::zone_eval::types::{Classification, Effect, Event, SimpleCommand, SpendDelta};
const NOW: i64 = 1_756_742_400_000;
fn program(pre: serde_json::Value, post: serde_json::Value) -> T2RegisterProgram {
T2RegisterProgram {
pre: serde_json::from_value(pre).expect("pre parses"),
post: serde_json::from_value(post).expect("post parses"),
verdict: Some(Verdict::Block),
reason: Some("tier 2 fired".to_string()),
..Default::default()
}
}
fn state(c: &[i64], f: &[bool], t: &[i64], a: &[i64], run: Option<RunState>) -> SessionState {
SessionState {
c: c.to_vec(),
f: f.to_vec(),
t: t.to_vec(),
a: a.to_vec(),
run,
}
}
fn run_pre(body: &T2RegisterProgram, state: &mut SessionState) -> ProgramResult {
run_pre_with(body, state, &Event::default(), &Classification::default())
}
fn run_pre_with(
body: &T2RegisterProgram,
state: &mut SessionState,
event: &Event,
classification: &Classification,
) -> ProgramResult {
let facts = FactSet::default();
let mut ctx = EvalContext::new(event, classification, &facts, NOW);
run_program(body, Half::Pre, &mut ctx, &ScanTable::default(), state)
}
#[test]
fn the_op_list_carries_no_jump_so_no_loop_is_expressible() {
for op in OPS {
assert!(
!matches!(*op, "JMP" | "JUMP" | "BRANCH" | "CALL" | "LOOP" | "GOTO"),
"{op} would make the bounded-time argument a convention"
);
}
assert_eq!(
OPS.len(),
17,
"the op list is CLOSED — PRD §Bundle schema 2"
);
}
#[test]
fn every_instruction_runs_at_most_once() {
let body = program(
serde_json::json!([
["INC_SAT", 0],
["INC_SAT", 0],
["INC_SAT", 0],
["INC_SAT", 0],
["INC_SAT", 0]
]),
serde_json::json!([]),
);
let mut session = state(&[0], &[], &[], &[], None);
run_pre(&body, &mut session);
assert_eq!(session.c, vec![5], "one pass over the program, and no more");
}
#[test]
fn a_register_index_must_be_an_integer_literal() {
for operand in [
serde_json::json!("c0"),
serde_json::json!({ "reg": 0 }),
serde_json::json!([0]),
serde_json::json!(-1),
] {
let body = program(
serde_json::json!([["INC_SAT", operand]]),
serde_json::json!([]),
);
assert!(
validate_program(&body).is_err(),
"a computed register index must be rejected at load"
);
}
let literal = program(serde_json::json!([["INC_SAT", 0]]), serde_json::json!([]));
assert!(
validate_program(&literal).is_ok(),
"a literal is the only form"
);
}
#[test]
fn arithmetic_is_integer_only() {
let indexed = program(serde_json::json!([["INC_SAT", 0.5]]), serde_json::json!([]));
assert!(validate_program(&indexed).is_err());
let bound = program(
serde_json::json!([["CMP_GE", "c", 0, 3.5, 0]]),
serde_json::json!([]),
);
assert!(validate_program(&bound).is_err());
}
#[test]
fn inc_sat_saturates_at_the_boundary_rather_than_wrapping() {
let body = program(serde_json::json!([["INC_SAT", 0]]), serde_json::json!([]));
let mut at = state(&[SAT_MAX], &[], &[], &[], None);
run_pre(&body, &mut at);
assert_eq!(at.c, vec![SAT_MAX], "saturates; a wrap would give 2^53");
let mut below = state(&[SAT_MAX - 1], &[], &[], &[], None);
run_pre(&body, &mut below);
assert_eq!(below.c, vec![SAT_MAX], "and still counts up to it");
}
#[test]
fn add_sat_saturates_at_the_boundary_rather_than_wrapping() {
let body = program(
serde_json::json!([]),
serde_json::json!([["ADD_SAT", 0, "spend_delta.tokens"]]),
);
let event = Event {
event_type: "post_tool_use".to_string(),
spend_delta: Some(SpendDelta {
tokens: Some(4_200),
micro_usd: None,
}),
..Default::default()
};
let facts = FactSet::default();
let classification = Classification::default();
let mut ctx = EvalContext::new(&event, &classification, &facts, NOW);
let mut at = state(&[], &[], &[], &[SAT_MAX], None);
run_program(&body, Half::Post, &mut ctx, &ScanTable::default(), &mut at);
assert_eq!(at.a, vec![SAT_MAX], "a wrapping add would go negative here");
let mut under = state(&[], &[], &[], &[50_000], None);
run_program(
&body,
Half::Post,
&mut ctx,
&ScanTable::default(),
&mut under,
);
assert_eq!(under.a, vec![54_200]);
}
#[test]
fn sat_never_overflows_the_machine_word_either() {
assert_eq!(sat(SAT_MAX, i64::MAX), SAT_MAX);
assert_eq!(sat(i64::MAX, 1), SAT_MAX);
}
#[test]
fn op_reset_zeroes_a_counter() {
let body = program(serde_json::json!([["RESET", 0]]), serde_json::json!([]));
let mut session = state(&[7], &[], &[], &[], None);
run_pre(&body, &mut session);
assert_eq!(session.c, vec![0]);
}
#[test]
fn op_set_clr_and_test_move_a_flag_both_ways() {
let body = program(
serde_json::json!([["SET", 0], ["TEST", 0, 0], ["CLR", 1, 0]]),
serde_json::json!([]),
);
let mut session = state(&[], &[false, true], &[], &[], None);
run_pre(&body, &mut session);
assert_eq!(
session.f,
vec![true, false],
"SET unconditional, CLR on the bit"
);
}
#[test]
fn op_clr_conditional_does_not_fire_on_a_false_bit() {
let body = program(serde_json::json!([["CLR", 0, 3]]), serde_json::json!([]));
let mut session = state(&[], &[true], &[], &[], None);
run_pre(&body, &mut session);
assert_eq!(session.f, vec![true], "an unset bool register reads false");
}
#[test]
fn op_cmp_ge_reads_all_four_banks() {
let banks = [
("c", state(&[3], &[], &[], &[], None)),
("a", state(&[], &[], &[], &[3], None)),
("t", state(&[], &[], &[3], &[], None)),
(
"run",
state(
&[],
&[],
&[],
&[],
Some(RunState {
shape: Some("x".to_string()),
len: 3,
}),
),
),
];
for (bank, mut session) in banks {
let body = program(
serde_json::json!([["CMP_GE", bank, 0, 3, 0], ["VERDICT", 0]]),
serde_json::json!([]),
);
assert!(run_pre(&body, &mut session).fired, "{bank} at the bound");
let body = program(
serde_json::json!([["CMP_GE", bank, 0, 4, 0], ["VERDICT", 0]]),
serde_json::json!([]),
);
assert!(
!run_pre(&body, &mut session).fired,
"{bank} below the bound"
);
}
}
#[test]
fn op_ts_store_and_ts_elapsed_measure_against_the_injected_now() {
let body = program(
serde_json::json!([["TS_ELAPSED", 0, 0]]),
serde_json::json!([]),
);
let mut session = state(&[], &[], &[NOW - 5_000], &[0], None);
run_pre(&body, &mut session);
assert_eq!(
session.a,
vec![5_000],
"now_ms is a parameter, never a clock"
);
let mut unset = state(&[], &[], &[0], &[0], None);
run_pre(&body, &mut unset);
assert_eq!(unset.a, vec![0]);
let store = program(serde_json::json!([["TS_STORE", 0]]), serde_json::json!([]));
let mut stamped = state(&[], &[], &[0], &[], None);
run_pre(&store, &mut stamped);
assert_eq!(stamped.t, vec![NOW]);
}
#[test]
fn op_and_or_not_are_two_valued() {
let body = program(
serde_json::json!([
["TEST", 0, 0],
["TEST", 1, 1],
["AND", 0, 1, 2],
["OR", 0, 1, 3],
["NOT", 0, 4],
["SET", 2, 2],
["SET", 3, 3],
["SET", 4, 4]
]),
serde_json::json!([]),
);
let mut session = state(&[], &[true, false, false, false, false], &[], &[], None);
run_pre(&body, &mut session);
assert!(!session.f[2], "true ∧ false");
assert!(session.f[3], "true ∨ false");
assert!(!session.f[4], "¬true");
}
#[test]
fn op_verdict_carries_the_body_verdict_and_anomaly_carries_a_code() {
let mut body = program(
serde_json::json!([
["TEST", 0, 0],
["VERDICT", 0],
["ANOMALY", 0, "followed_by_unsatisfied"]
]),
serde_json::json!([]),
);
body.verdict = Some(Verdict::Optimize);
let mut session = state(&[], &[true], &[], &[], None);
let result = run_pre(&body, &mut session);
assert!(result.fired);
assert_eq!(result.verdict, Some(Verdict::Optimize));
assert_eq!(
result.anomalies,
vec!["followed_by_unsatisfied".to_string()]
);
}
#[test]
fn op_done_stops_the_program_and_only_when_taken() {
let taken = program(
serde_json::json!([["SET", 0], ["TEST", 0, 0], ["DONE", 0], ["SET", 1]]),
serde_json::json!([]),
);
let mut session = state(&[], &[false, false], &[], &[], None);
run_pre(&taken, &mut session);
assert_eq!(session.f, vec![true, false], "the tail never ran");
let not_taken = program(
serde_json::json!([["TEST", 0, 0], ["DONE", 0], ["SET", 1]]),
serde_json::json!([]),
);
let mut session = state(&[], &[false, false], &[], &[], None);
run_pre(¬_taken, &mut session);
assert_eq!(session.f, vec![false, true], "the tail ran");
}
#[test]
fn op_match_coerces_bottom_to_false() {
let body = program(
serde_json::json!([
["MATCH", {"op": "leaf", "leaf": {"pred": "fact", "fact": {"fact_id": "approved_registries", "op": "equals", "value": true}}}, 0],
["INC_SAT", 0, 0]
]),
serde_json::json!([]),
);
let mut session = state(&[0], &[], &[], &[], None);
let facts = FactSet::default();
let event = Event::default();
let classification = Classification::default();
let mut ctx = EvalContext::new(&event, &classification, &facts, NOW);
run_program(
&body,
Half::Pre,
&mut ctx,
&ScanTable::default(),
&mut session,
);
assert_eq!(session.c, vec![0], "⊥ is false, so the guard did not open");
let artifact = loaded(body.clone());
let deciding = Event {
event_type: "pre_tool_use".to_string(),
..Default::default()
};
let mut ctx = EvalContext::new(&deciding, &classification, &facts, NOW);
let layout = StateLayout {
c: 1,
..Default::default()
};
let mut forked = state(&[0], &[], &[], &[], None);
let _ = contribution(
&artifact,
&body,
&mut ctx,
&ScanTable::default(),
&mut forked,
&layout,
);
assert!(
ctx.inconclusive.is_empty(),
"a MATCH's ⊥ is dropped, not carried into inconclusive_facts[]"
);
}
fn read_classification() -> Classification {
Classification {
effects: vec![Effect {
verb: "read".to_string().into(),
target_class: "data_store".to_string().into(),
attrs: serde_json::Map::new(),
}],
..Default::default()
}
}
#[test]
fn run_track_ignores_argument_values() {
let body = program(serde_json::json!([["RUN_TRACK"]]), serde_json::json!([]));
let classification = read_classification();
let mut session = state(&[], &[], &[], &[], Some(RunState::default()));
for path in ["/data/warehouse/a.py", "/data/warehouse/b.py"] {
let event = Event {
tool_name: "Read".to_string(),
tool_input: serde_json::json!({ "file_path": path }),
..Default::default()
};
run_pre_with(&body, &mut session, &event, &classification);
}
let run = session.run.expect("the run is tracked");
assert_eq!(run.len, 2, "two different files, one shape, one run");
}
#[test]
fn run_track_restarts_on_a_different_shape() {
let body = program(serde_json::json!([["RUN_TRACK"]]), serde_json::json!([]));
let mut session = state(
&[],
&[],
&[],
&[],
Some(RunState {
shape: Some("something-else".to_string()),
len: 9,
}),
);
let event = Event {
tool_name: "Read".to_string(),
..Default::default()
};
run_pre_with(&body, &mut session, &event, &read_classification());
assert_eq!(session.run.expect("tracked").len, 1);
}
#[test]
fn the_action_shape_is_the_prd_concatenation() {
assert_eq!(
action_shape("Read", Some(("read", "data_store")), ""),
"cfee545cf5b5bfdd83a48a592933feb054e56637409011fbdd2633cc6b17a674"
);
assert_eq!(
action_shape("Bash", Some(("network_egress", "network_host")), "curl"),
"c3d4b38848360f19b22341f1adcafabac5c6d7bd318a7dfd4406ceba9f500279"
);
}
#[test]
fn the_action_shape_reads_the_first_simple_commands_program() {
let classification = Classification {
effects: vec![Effect {
verb: "network_egress".to_string().into(),
target_class: "network_host".to_string().into(),
attrs: serde_json::Map::new(),
}],
simple: vec![SimpleCommand {
program: "curl".to_string(),
..Default::default()
}],
..Default::default()
};
let event = Event {
tool_name: "Bash".to_string(),
..Default::default()
};
let facts = FactSet::default();
let ctx = EvalContext::new(&event, &classification, &facts, NOW);
assert_eq!(
shape_of(&ctx),
"c3d4b38848360f19b22341f1adcafabac5c6d7bd318a7dfd4406ceba9f500279"
);
}
#[test]
fn shape_count_le_decides_in_pre_and_counts_in_post() {
let body = program(
serde_json::json!([
["MATCH", {"op": "leaf", "leaf": {"pred": "exists", "field": "tool.name"}}, 0],
["CMP_GE", "c", 0, 3, 1],
["AND", 0, 1, 2],
["VERDICT", 2]
]),
serde_json::json!([["INC_SAT", 0]]),
);
let matching = Event {
tool_name: "Bash".to_string(),
..Default::default()
};
let mut at_cap = state(&[3], &[], &[], &[], None);
assert!(
run_pre_with(&body, &mut at_cap, &matching, &Classification::default()).fired,
"at the cap, on the action it names"
);
let mut under = state(&[2], &[], &[], &[], None);
assert!(
!run_pre_with(&body, &mut under, &matching, &Classification::default()).fired,
"below the cap"
);
let mut unrelated = state(&[3], &[], &[], &[], None);
assert!(
!run_pre_with(
&body,
&mut unrelated,
&Event::default(),
&Classification::default()
)
.fired,
"the trigger bit is consumed, not computed and dropped"
);
let event = Event {
event_type: "post_tool_use".to_string(),
..Default::default()
};
let facts = FactSet::default();
let classification = Classification::default();
let mut ctx = EvalContext::new(&event, &classification, &facts, NOW);
let mut counting = state(&[0], &[], &[], &[], None);
run_program(
&body,
Half::Post,
&mut ctx,
&ScanTable::default(),
&mut counting,
);
assert_eq!(counting.c, vec![1]);
}
#[test]
fn shape_seen_blocks_until_the_bit_latches() {
let body = program(
serde_json::json!([["TEST", 0, 1], ["NOT", 1, 2], ["VERDICT", 2]]),
serde_json::json!([["SET", 0]]),
);
let mut unseen = state(&[], &[false], &[], &[], None);
assert!(run_pre(&body, &mut unseen).fired, "not yet seen → fires");
let mut seen = state(&[], &[true], &[], &[], None);
assert!(!run_pre(&body, &mut seen).fired, "already seen → allows");
}
#[test]
fn shape_elapsed_le_stores_once_and_does_not_restamp() {
let body = program(
serde_json::json!([
["TEST", 0, 0],
["NOT", 0, 1],
["TS_STORE", 0, 1],
["SET", 0, 1]
]),
serde_json::json!([]),
);
let mut first = state(&[], &[false], &[0], &[], None);
run_pre(&body, &mut first);
assert_eq!(first.t, vec![NOW]);
assert_eq!(first.f, vec![true]);
let mut again = state(&[], &[true], &[NOW - 900_000], &[], None);
run_pre(&body, &mut again);
assert_eq!(again.t, vec![NOW - 900_000], "idempotent — never restamped");
}
#[test]
fn shape_run_le_fires_at_the_cap() {
let body = program(
serde_json::json!([["RUN_TRACK"], ["CMP_GE", "run", 0, 3, 0], ["VERDICT", 0]]),
serde_json::json!([]),
);
let shape = action_shape("Read", Some(("read", "data_store")), "");
let mut session = state(
&[],
&[],
&[],
&[],
Some(RunState {
shape: Some(shape),
len: 2,
}),
);
let event = Event {
tool_name: "Read".to_string(),
..Default::default()
};
let result = run_pre_with(&body, &mut session, &event, &read_classification());
assert!(result.fired, "the third consecutive same-shape action");
}
#[test]
fn shape_budget_le_accrues_in_post_and_decides_in_pre() {
let body = program(
serde_json::json!([["CMP_GE", "a", 0, 100_000, 0], ["VERDICT", 0]]),
serde_json::json!([["ADD_SAT", 0, "spend_delta.tokens"]]),
);
let mut under = state(&[], &[], &[], &[99_999], None);
assert!(!run_pre(&body, &mut under).fired);
let mut at = state(&[], &[], &[], &[100_000], None);
assert!(run_pre(&body, &mut at).fired);
}
#[test]
fn shape_budget_le_without_a_priced_delta_accrues_nothing() {
let body = program(
serde_json::json!([]),
serde_json::json!([["ADD_SAT", 0, "spend_delta.micro_usd"]]),
);
let event = Event {
event_type: "post_tool_use".to_string(),
spend_delta: Some(SpendDelta {
tokens: Some(4_200),
micro_usd: None,
}),
..Default::default()
};
let facts = FactSet::default();
let classification = Classification::default();
let mut ctx = EvalContext::new(&event, &classification, &facts, NOW);
let mut session = state(&[], &[], &[], &[50_000], None);
run_program(
&body,
Half::Post,
&mut ctx,
&ScanTable::default(),
&mut session,
);
assert_eq!(session.a, vec![50_000]);
}
#[test]
fn shape_followed_by_raises_an_anomaly_and_decides_nothing() {
let body = program(
serde_json::json!([]),
serde_json::json!([["TEST", 0, 0], ["ANOMALY", 0, "followed_by_unsatisfied"]]),
);
let event = Event {
event_type: "stop".to_string(),
..Default::default()
};
let facts = FactSet::default();
let classification = Classification::default();
let mut ctx = EvalContext::new(&event, &classification, &facts, NOW);
let mut still_set = state(&[], &[true], &[], &[], None);
let raised = run_program(
&body,
Half::Post,
&mut ctx,
&ScanTable::default(),
&mut still_set,
);
assert!(!raised.fired, "an anomaly records; it never decides");
assert_eq!(
raised.anomalies,
vec!["followed_by_unsatisfied".to_string()]
);
let mut met = state(&[], &[false], &[], &[], None);
let quiet = run_program(&body, Half::Post, &mut ctx, &ScanTable::default(), &mut met);
assert!(quiet.anomalies.is_empty());
}
#[test]
fn each_half_runs_on_its_own_events_and_nothing_else() {
for event_type in PRE_EVENTS {
assert_eq!(half_for(event_type), Some(Half::Pre), "{event_type}");
}
for event_type in POST_EVENTS {
assert_eq!(half_for(event_type), Some(Half::Post), "{event_type}");
}
assert_eq!(
half_for("notification"),
None,
"not an error — just not ours"
);
}
#[test]
fn the_bundle_layout_is_element_wise_across_programs() {
let layout = |c, f, t, a, run| T2StateLayout {
c: Some(c),
f: Some(f),
t: Some(t),
a: Some(a),
run: Some(run),
};
let mut first = program(serde_json::json!([]), serde_json::json!([]));
first.state_layout = Some(layout(1, 0, 0, 0, false));
let mut second = program(serde_json::json!([]), serde_json::json!([]));
second.state_layout = Some(layout(2, 1, 0, 0, true));
let artifacts = vec![loaded(first), loaded(second)];
let union = bundle_state_layout(&artifacts);
assert_eq!(union.c, 2, "MAX, not the sum: a sum would demand three");
assert_eq!(union.f, 1);
assert!(union.run);
}
fn loaded(body: T2RegisterProgram) -> LoadedArtifact {
LoadedArtifact {
envelope: crate::generated::types::PolicyArtifact {
artifact_id: Some("prog".to_string()),
atom_id: Some("atom-prog".to_string()),
tier: Some(2),
..Default::default()
},
body: super::super::bundle::ArtifactBody::T2(Box::new(body)),
}
}
#[test]
fn a_state_that_disagrees_with_the_layout_is_malformed_never_padded() {
let layout = StateLayout {
c: 2,
f: 1,
t: 0,
a: 0,
run: false,
};
let short = state(&[0], &[false], &[], &[], None);
assert!(validate_state(&short, &layout).is_err());
let exact = state(&[0, 0], &[false], &[], &[], None);
assert!(validate_state(&exact, &layout).is_ok());
}
#[test]
fn validate_program_closes_the_op_list_and_the_operand_vocabularies() {
let unknown_op = program(
serde_json::json!([["FROBNICATE", 0]]),
serde_json::json!([]),
);
assert!(validate_program(&unknown_op).is_err());
let bad_bank = program(
serde_json::json!([["CMP_GE", "z", 0, 1, 0]]),
serde_json::json!([]),
);
assert!(validate_program(&bad_bank).is_err());
let bad_source = program(
serde_json::json!([]),
serde_json::json!([["ADD_SAT", 0, "event.anything"]]),
);
assert!(validate_program(&bad_source).is_err());
let not_an_array = T2RegisterProgram {
pre: vec![vec![]],
..Default::default()
};
assert!(validate_program(¬_an_array).is_err());
}
#[test]
fn a_match_over_an_unreadable_node_is_rejected_at_load() {
let body = program(serde_json::json!([["MATCH", 42, 0]]), serde_json::json!([]));
assert!(validate_program(&body).is_err());
}
fn evict_body(on_evict: Option<&str>) -> T2RegisterProgram {
T2RegisterProgram {
on_evict: on_evict.map(str::to_string),
verdict: Some(Verdict::Block),
reason: Some("tier 2 fired".to_string()),
..Default::default()
}
}
fn evict_artifact(on_inconclusive: &str) -> LoadedArtifact {
LoadedArtifact {
envelope: crate::generated::types::PolicyArtifact {
artifact_id: Some("prog".to_string()),
on_inconclusive: Some(on_inconclusive.to_string().into()),
..Default::default()
},
body: super::super::bundle::ArtifactBody::T2(Box::new(evict_body(None))),
}
}
#[test]
fn on_evict_reinit_and_the_default_defer_to_a_blank_state() {
let artifact = evict_artifact("ask");
let enforce = PolicyMode(MODE_ENFORCE.to_string());
let layout = StateLayout::default();
for on_evict in [None, Some("reinit"), Some("something-newer")] {
assert!(
evicted_contribution(&artifact, &evict_body(on_evict), enforce.clone(), &layout)
.is_none(),
"{on_evict:?} counts from zero"
);
}
}
#[test]
fn on_evict_unknown_routes_to_on_inconclusive_and_names_the_gap() {
let artifact = evict_artifact("ask");
let contribution = evicted_contribution(
&artifact,
&evict_body(Some("unknown")),
PolicyMode(MODE_ENFORCE.to_string()),
&StateLayout::default(),
)
.expect("undecidable is a contribution, not a silence");
assert_eq!(contribution.verdict, Verdict::Ask);
assert_eq!(contribution.inconclusive, vec!["session_state_evicted"]);
}
#[test]
fn on_evict_fail_static_keeps_enforcing_and_never_becomes_an_allow() {
let artifact = evict_artifact("ask");
let contribution = evicted_contribution(
&artifact,
&evict_body(Some("fail_static")),
PolicyMode(MODE_ENFORCE.to_string()),
&StateLayout::default(),
)
.expect("fail_static contributes");
assert_eq!(contribution.verdict, Verdict::Block);
assert!(contribution.inconclusive.is_empty());
}
#[test]
fn fail_static_under_monitor_contributes_block_but_does_not_enforce() {
let artifact = evict_artifact("ask");
let body = evict_body(Some("fail_static"));
let layout = StateLayout::default();
let enforcing = evicted_contribution(
&artifact,
&body,
PolicyMode(MODE_ENFORCE.to_string()),
&layout,
)
.expect("fail_static contributes");
assert_eq!(enforcing.verdict, Verdict::Block);
assert!(
enforcing.is_enforcing(),
"an enforce atom blocks the action"
);
let monitored = evicted_contribution(
&artifact,
&body,
PolicyMode(MODE_MONITOR.to_string()),
&layout,
)
.expect("a monitor artifact still contributes");
assert_eq!(
monitored.verdict,
Verdict::Block,
"it still says what it would have done"
);
assert!(
!monitored.is_enforcing(),
"monitor never joins — the kill switch outranks fail-static"
);
}
#[test]
fn monitor_is_always_allow_and_flag_whatever_the_artifact_declares() {
let artifact = evict_artifact("block");
let monitor = evicted_contribution(
&artifact,
&evict_body(Some("unknown")),
PolicyMode(MODE_MONITOR.to_string()),
&StateLayout::default(),
)
.expect("a monitor artifact still contributes");
assert_eq!(monitor.verdict, Verdict::Allow, "monitor never blocks");
}
#[test]
fn an_unevaluated_event_contributes_nothing_and_leaves_the_state_alone() {
let body = program(serde_json::json!([["INC_SAT", 0]]), serde_json::json!([]));
let artifact = loaded(body.clone());
let event = Event {
event_type: "notification".to_string(),
..Default::default()
};
let facts = FactSet::default();
let classification = Classification::default();
let mut ctx = EvalContext::new(&event, &classification, &facts, NOW);
let layout = StateLayout {
c: 1,
..Default::default()
};
let mut session = state(&[4], &[], &[], &[], None);
let contribution = contribution(
&artifact,
&body,
&mut ctx,
&ScanTable::default(),
&mut session,
&layout,
);
assert!(contribution.is_none());
assert_eq!(session.c, vec![4], "untouched");
}
#[test]
fn a_program_that_neither_fires_nor_raises_contributes_nothing() {
let body = program(serde_json::json!([["INC_SAT", 0]]), serde_json::json!([]));
let artifact = loaded(body.clone());
let event = Event {
event_type: "pre_tool_use".to_string(),
..Default::default()
};
let facts = FactSet::default();
let classification = Classification::default();
let mut ctx = EvalContext::new(&event, &classification, &facts, NOW);
let layout = StateLayout {
c: 1,
..Default::default()
};
let mut session = state(&[0], &[], &[], &[], None);
assert!(contribution(
&artifact,
&body,
&mut ctx,
&ScanTable::default(),
&mut session,
&layout
)
.is_none());
assert_eq!(session.c, vec![1], "but the state still advanced");
}
#[test]
fn a_fired_program_carries_the_envelope_and_the_body_reason() {
let body = program(
serde_json::json!([["TEST", 0, 0], ["VERDICT", 0]]),
serde_json::json!([]),
);
let artifact = loaded(body.clone());
let event = Event {
event_type: "pre_tool_use".to_string(),
..Default::default()
};
let facts = FactSet::default();
let classification = Classification::default();
let mut ctx = EvalContext::new(&event, &classification, &facts, NOW);
let layout = StateLayout {
f: 1,
..Default::default()
};
let mut session = state(&[], &[true], &[], &[], None);
let contribution = contribution(
&artifact,
&body,
&mut ctx,
&ScanTable::default(),
&mut session,
&layout,
)
.expect("it fired");
assert_eq!(contribution.verdict, Verdict::Block);
assert_eq!(contribution.reason, "tier 2 fired");
assert_eq!(contribution.artifact_id.as_deref(), Some("prog"));
assert_eq!(contribution.tier, Some(2));
}
#[test]
fn an_anomaly_only_run_contributes_allow() {
let body = program(
serde_json::json!([]),
serde_json::json!([["TEST", 0, 0], ["ANOMALY", 0, "followed_by_unsatisfied"]]),
);
let artifact = loaded(body.clone());
let event = Event {
event_type: "stop".to_string(),
..Default::default()
};
let facts = FactSet::default();
let classification = Classification::default();
let mut ctx = EvalContext::new(&event, &classification, &facts, NOW);
let layout = StateLayout {
f: 1,
..Default::default()
};
let mut session = state(&[], &[true], &[], &[], None);
let contribution = contribution(
&artifact,
&body,
&mut ctx,
&ScanTable::default(),
&mut session,
&layout,
)
.expect("an anomaly is a contribution");
assert_eq!(contribution.verdict, Verdict::Allow);
assert_eq!(contribution.anomalies, vec!["followed_by_unsatisfied"]);
}
}