mod classification;
mod config_change;
mod envelope;
mod file_changed;
mod instructions_loaded;
mod json_helpers;
mod path_extraction;
mod path_normalize;
#[cfg(test)]
mod tests;
#[cfg(fuzzing)]
pub use classification::effective_command_for_fuzzing;
pub use classification::{
classify_command, is_known_action_tool, is_schema_introspection, KNOWN_ACTION_TOOLS,
};
pub use config_change::{local_violations, project_violations, ConfigViolation, ExpectedFloor};
pub use envelope::{extract_apply_patch_files, MAX_APPLY_PATCH_FILES};
pub use file_changed::{parse_file_changed, FileChangedPayload};
pub use instructions_loaded::{parse_instructions_loaded, InstructionsLoadedPayload};
pub use json_helpers::has_file_deleted_signal;
pub use path_extraction::{extract_file_path, extract_file_paths, normalize_action};
pub use path_normalize::normalize_path;
use classification::{
effective_command, ACTION_TOOL_DB_CLIENT, ACTION_TOOL_FILE_READ, ACTION_TOOL_PATH,
};
#[cfg(test)]
use json_helpers::json_has_signal;
use json_helpers::{any_qualifying_gotcha, json_bool, json_f32, json_str, json_string_array};
use path_extraction::{shell_tokens, split_at_shell_operator};
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CommandClass {
CatLike,
GrepLike,
DbClientLike,
PathMutating,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Action {
pub tool: String,
pub target_path: Option<String>,
pub host: Option<String>,
pub argv: Vec<String>,
pub files: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ShadowOutcome {
Block,
Steer,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Decision {
Allow,
Deny {
file_key: String,
reason: String,
origin: DenyOrigin,
},
AlreadyConsulted { context: String },
Advisory { context: String },
Liability { staleness: f32, context: String },
Tombstone,
NoRecord,
NotFileRead,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DenyOrigin {
Gotcha,
ConsultMandate,
Policy,
}
impl DenyOrigin {
pub fn deny_event(self, key: String) -> HookEvent {
match self {
DenyOrigin::Gotcha => HookEvent::BlockedUnconsultedRead { key },
DenyOrigin::ConsultMandate => HookEvent::FloorConsultBlocked { key },
DenyOrigin::Policy => HookEvent::PolicyConsultBlocked { key },
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum HookEvent {
Hit { key: String },
Miss { key: String },
BlockedUnconsultedRead { key: String },
CodexShellBlocked { key: String },
UnclassifiedPolicyLiteralBypass { key: String },
ComplianceHit { key: String },
EditConsulted { key: String },
EditBlocked { key: String },
FloorConsultBlocked { key: String },
PolicyConsultBlocked { key: String },
PolicyConsulted { key: String },
PolicySteered { key: String },
PolicyShadowObserved {
key: String,
would: ShadowOutcome,
action: Option<Action>,
},
TombstoneBypassedDeny { key: String },
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PolicyVerdict {
pub key: String,
pub rule: String,
pub requires_key: String,
pub block: bool,
pub satisfied: bool,
pub stage: crate::store::PolicyStage,
}
pub fn evaluate_policy_verdicts(verdicts: &[PolicyVerdict], strict: bool) -> EnforcementResult {
struct PolicyDeny {
key: String,
reason: String,
}
let mut context = Vec::new();
let mut events = Vec::new();
let mut denied: Option<PolicyDeny> = None;
for verdict in verdicts {
if matches!(verdict.stage, crate::store::PolicyStage::Off) {
continue;
}
if matches!(verdict.stage, crate::store::PolicyStage::Shadow) {
if !verdict.block {
events.push(HookEvent::PolicyShadowObserved {
key: verdict.key.clone(),
would: ShadowOutcome::Steer,
action: None,
});
} else if !verdict.satisfied {
events.push(HookEvent::PolicyShadowObserved {
key: verdict.key.clone(),
would: ShadowOutcome::Block,
action: None,
});
}
continue;
}
if verdict.block && !verdict.satisfied && strict {
denied.get_or_insert_with(|| PolicyDeny {
key: verdict.key.clone(),
reason: format!(
"mati: policy {} blocked this action. Consult first: mem_get(\"{}\")",
verdict.key, verdict.requires_key
),
});
} else if verdict.block && verdict.satisfied {
events.push(HookEvent::PolicyConsulted {
key: verdict.key.clone(),
});
} else {
context.push(verdict.rule.clone());
events.push(HookEvent::PolicySteered {
key: verdict.key.clone(),
});
}
}
if let Some(deny) = denied {
let mut events: Vec<HookEvent> = events
.into_iter()
.filter(|event| matches!(event, HookEvent::PolicyShadowObserved { .. }))
.collect();
events.push(DenyOrigin::Policy.deny_event(deny.key.clone()));
return EnforcementResult {
decision: Decision::Deny {
file_key: deny.key,
reason: deny.reason,
origin: DenyOrigin::Policy,
},
events,
};
}
if !context.is_empty() {
return EnforcementResult {
decision: Decision::Advisory {
context: context.join("\n"),
},
events,
};
}
EnforcementResult {
decision: Decision::Allow,
events,
}
}
pub struct EnforcementInput {
pub rel_path: String,
pub file_record: Option<serde_json::Value>,
pub gotcha_records: HashMap<String, serde_json::Value>,
pub already_consulted: bool,
pub file_exists: Option<bool>,
}
pub struct EnforcementResult {
pub decision: Decision,
pub events: Vec<HookEvent>,
}
pub fn evaluate(input: &EnforcementInput) -> EnforcementResult {
let file_key = format!("file:{}", input.rel_path);
let file_record = match &input.file_record {
Some(r) if r.is_object() => r,
_ => {
return EnforcementResult {
decision: Decision::NoRecord,
events: vec![HookEvent::Miss { key: file_key }],
};
}
};
let confidence = json_f32(file_record, "/confidence/value");
let quality = json_f32(file_record, "/quality/value");
let staleness = json_f32(file_record, "/staleness/value");
let staleness_tier = json_str(file_record, "/staleness/tier");
if has_file_deleted_signal(file_record) && input.file_exists != Some(true) {
let would_have_denied = input.file_exists == Some(false)
&& any_qualifying_gotcha(
&json_string_array(file_record, "/payload/gotcha_keys"),
&input.gotcha_records,
);
let event = if would_have_denied {
HookEvent::TombstoneBypassedDeny { key: file_key }
} else {
HookEvent::Miss { key: file_key }
};
return EnforcementResult {
decision: Decision::Tombstone,
events: vec![event],
};
}
let purpose = json_str(file_record, "/value");
let mut context_lines: Vec<String> = Vec::new();
if !purpose.is_empty() {
context_lines.push(format!("Purpose: {purpose}"));
}
let mut deny_signal = false;
let gotcha_keys = json_string_array(file_record, "/payload/gotcha_keys");
for gkey in &gotcha_keys {
let grec = match input.gotcha_records.get(gkey.as_str()) {
Some(r) if r.is_object() => r,
_ => continue,
};
let confirmed = json_bool(grec, "/payload/confirmed");
let gconfidence = json_f32(grec, "/confidence/value");
let gquality = json_f32(grec, "/quality/value");
let rule = json_str(grec, "/value");
if confirmed && gconfidence >= 0.6 && gquality >= 0.4 {
deny_signal = true;
if !rule.is_empty() {
context_lines.push(format!("\u{26a0} {rule}"));
}
}
}
if staleness >= 0.4 {
context_lines.push(format!(
"Warning: record staleness {staleness:.2} — verify critical details."
));
}
{
let blast_tier = json_str(file_record, "/payload/blast_radius/tier");
if blast_tier == "high" || blast_tier == "critical" {
let blast_direct = file_record
.pointer("/payload/blast_radius/direct")
.and_then(|v| v.as_u64())
.unwrap_or(0);
context_lines.push(format!(
"\u{26a0} Blast radius: {blast_direct} direct importers ({blast_tier}) — modify carefully"
));
}
}
if deny_signal {
if input.already_consulted {
let context = if context_lines.is_empty() {
format!(
"Gotcha exists for {} — proceed with awareness",
input.rel_path
)
} else {
context_lines.join("\n")
};
return EnforcementResult {
decision: Decision::AlreadyConsulted { context },
events: vec![HookEvent::ComplianceHit { key: file_key }],
};
}
let safe_path = &input.rel_path;
let staleness_note = if staleness >= 0.4 {
format!(" (staleness {staleness:.2} — verify critical details)")
} else {
String::new()
};
return EnforcementResult {
decision: Decision::Deny {
file_key: file_key.clone(),
reason: format!(
"[mati] Confirmed gotcha on {safe_path} — \
call mem_get(\"file:{safe_path}\") and read the record \
before accessing this file.{staleness_note}"
),
origin: DenyOrigin::Gotcha,
},
events: vec![DenyOrigin::Gotcha.deny_event(file_key)],
};
}
if staleness_tier == "tombstone" {
return EnforcementResult {
decision: Decision::Tombstone,
events: vec![HookEvent::Miss { key: file_key }],
};
}
if staleness_tier == "liability" {
return EnforcementResult {
decision: Decision::Liability {
staleness,
context: format!(
"WARNING: STALE record for {} is a liability (staleness {:.2}). \
Read the file directly — the cached record is too stale to trust.",
input.rel_path, staleness
),
},
events: vec![HookEvent::Hit { key: file_key }],
};
}
if confidence >= 0.3 && quality >= 0.4 {
let context = if context_lines.is_empty() {
format!(
"Record exists for {} — confidence {confidence:.2}",
input.rel_path
)
} else {
context_lines.join("\n")
};
return EnforcementResult {
decision: Decision::Advisory { context },
events: vec![HookEvent::Hit { key: file_key }],
};
}
EnforcementResult {
decision: Decision::Allow,
events: vec![],
}
}