use crate::ir_nodes::{IRProgram, IRSessionStep};
use super::effects;
use super::proof_term::{
AggregateSoundnessWitness, CallSoundnessCertificate, CapabilityContainmentWitness,
AuthorizationCoverageWitness, CapabilityGrantabilityWitness,
CapabilityIsolationWitness,
ChannelEgressSoundnessWitness,
ChannelDeliverySoundnessWitness, ComplianceCoverageWitness, CorsPolicyConsistencyWitness,
EffectBudgetedWitness,
EffectRowSoundnessWitness, InterruptibleSessionSoundnessWitness, JsonShapeSoundnessWitness,
ParkedResidualSoundnessWitness, ProofTerm, PropertyClass,
CacheSoundnessWitness, ForgeSoundnessWitness, ResourceBoundsWitness, SavantSoundnessWitness,
DocumentIngestionSoundnessWitness, DocumentProvenanceSoundnessWitness,
ScrapeProvenanceSoundnessWitness, WardenSoundnessWitness,
ShieldHaltGuaranteeWitness, TechnicianCommandSafetyWitness, ToolCallSoundnessWitness,
UpstreamProjectionSoundnessWitness, Witness,
CALL_INTERRUPT_CAUSES, MAX_RETRIES, VALID_BREACH_POLICIES, VALID_BUDGET_PERIODS,
VALID_ON_EXHAUSTED,
};
pub fn artifact_digest(ir: &IRProgram) -> String {
match serde_json::to_value(ir) {
Ok(v) => crate::esk::provenance::content_hash(&v),
Err(_) => "<ir-unserializable>".to_string(),
}
}
fn canonical_classes(raw: &[String]) -> Vec<String> {
let mut v: Vec<String> = raw.to_vec();
v.sort();
v.dedup();
v
}
pub fn derive_compliance_coverage_witness(
endpoint_name: &str,
declared_compliance: &[String],
shield_ref: &str,
ir: &IRProgram,
) -> ComplianceCoverageWitness {
let required_classes = canonical_classes(declared_compliance);
let resolved_shield = if shield_ref.is_empty() {
None
} else {
ir.shields.iter().find(|s| s.name == shield_ref)
};
let shield_present = resolved_shield.is_some();
let provided_classes = resolved_shield
.map(|s| canonical_classes(&s.compliance))
.unwrap_or_default();
let unknown_classes: Vec<String> = required_classes
.iter()
.filter(|c| !crate::esk::compliance::is_known(c))
.cloned()
.collect();
let mut uncovered_classes: Vec<String> =
crate::esk::compliance::covers(provided_classes.iter(), required_classes.iter())
.into_iter()
.collect();
uncovered_classes.sort();
ComplianceCoverageWitness {
endpoint_name: endpoint_name.to_string(),
required_classes,
shield_ref: shield_ref.to_string(),
shield_present,
provided_classes,
unknown_classes,
uncovered_classes,
}
}
pub fn generate_compliance_coverage_proofs(ir: &IRProgram, axon_version: &str) -> Vec<ProofTerm> {
let digest = artifact_digest(ir);
let mut proofs = Vec::new();
for ep in &ir.endpoints {
if ep.compliance.is_empty() {
continue;
}
let witness =
derive_compliance_coverage_witness(&ep.name, &ep.compliance, &ep.shield_ref, ir);
proofs.push(ProofTerm {
property: PropertyClass::ComplianceCoverage,
artifact_digest: digest.clone(),
witness: Witness::ComplianceCoverage(witness),
axon_version: axon_version.to_string(),
});
}
proofs
}
pub fn derive_authorization_coverage_witness(
ep: &crate::ir_nodes::IRAxonEndpoint,
) -> AuthorizationCoverageWitness {
let dispatches = !ep.execute_flow.is_empty();
let has_requires = !ep.requires_capabilities.is_empty();
let has_shield = !ep.shield_ref.is_empty();
let has_compliance = !ep.compliance.is_empty();
let public = ep.public;
let authorized = has_requires || has_shield || has_compliance || public;
AuthorizationCoverageWitness {
endpoint_name: ep.name.clone(),
dispatches,
has_requires,
has_shield,
has_compliance,
public,
authorized,
}
}
pub fn derive_capability_grantability_witness(
ir: &IRProgram,
authorities: &[String],
) -> CapabilityGrantabilityWitness {
use std::collections::BTreeSet;
let required: Vec<String> = ir
.endpoints
.iter()
.filter(|e| !e.execute_flow.is_empty())
.flat_map(|e| e.requires_capabilities.iter().cloned())
.collect::<BTreeSet<String>>()
.into_iter()
.collect();
let mut authorities_sorted: Vec<String> =
authorities.iter().cloned().collect::<BTreeSet<String>>().into_iter().collect();
authorities_sorted.sort();
let grantable = crate::auth_scope::build_grantable_set(authorities_sorted.iter());
let all_grantable = grantable.is_clean()
&& matches!(
crate::auth_scope::check_grantable(&required, &grantable.caps),
crate::auth_scope::GrantabilityVerdict::Grantable
);
CapabilityGrantabilityWitness {
required,
authorities: authorities_sorted,
all_grantable,
}
}
pub fn generate_authorization_coverage_proofs(
ir: &IRProgram,
axon_version: &str,
) -> Vec<ProofTerm> {
let digest = artifact_digest(ir);
let mut proofs = Vec::new();
for ep in &ir.endpoints {
if ep.execute_flow.is_empty() {
continue;
}
proofs.push(ProofTerm {
property: PropertyClass::AuthorizationCoverage,
artifact_digest: digest.clone(),
witness: Witness::AuthorizationCoverage(derive_authorization_coverage_witness(ep)),
axon_version: axon_version.to_string(),
});
}
proofs
}
pub fn generate_capability_grantability_proofs(
ir: &IRProgram,
authorities: &[String],
axon_version: &str,
) -> Vec<ProofTerm> {
let has_requirement = ir
.endpoints
.iter()
.any(|e| !e.execute_flow.is_empty() && !e.requires_capabilities.is_empty());
if !has_requirement {
return Vec::new();
}
vec![ProofTerm {
property: PropertyClass::CapabilityGrantability,
artifact_digest: artifact_digest(ir),
witness: Witness::CapabilityGrantability(derive_capability_grantability_witness(
ir,
authorities,
)),
axon_version: axon_version.to_string(),
}]
}
pub fn derive_effect_row_soundness_witness(
tool_name: &str,
effect_row: &[String],
extension_effect_members: &std::collections::HashSet<String>,
) -> EffectRowSoundnessWitness {
let declared_effects = canonical_classes(effect_row);
let mut unknown_bases = Vec::new();
let mut missing_qualifier = Vec::new();
let mut invalid_stream_qualifier = Vec::new();
let mut has_pure = false;
let mut has_other = false;
for entry in &declared_effects {
if extension_effect_members.contains(entry) || effects::is_epistemic_provenance(entry) {
has_other = true;
continue;
}
let (base, qualifier) = effects::split_effect(entry);
if !effects::is_known_base(base) {
unknown_bases.push(entry.clone());
has_other = true;
continue;
}
if base == "pure" {
has_pure = true;
} else {
has_other = true;
}
if effects::requires_qualifier(base) && qualifier.is_none() {
missing_qualifier.push(entry.clone());
}
if base == "stream" {
if let Some(q) = qualifier {
if !effects::is_valid_stream_qualifier(q) {
invalid_stream_qualifier.push(entry.clone());
}
}
}
}
let purity_violation = has_pure && has_other;
EffectRowSoundnessWitness {
tool_name: tool_name.to_string(),
declared_effects,
unknown_bases,
missing_qualifier,
invalid_stream_qualifier,
purity_violation,
}
}
pub fn extension_effect_members(ir: &IRProgram) -> std::collections::HashSet<String> {
let mut set = std::collections::HashSet::new();
for ext in &ir.extensions {
if ext.category != "effects" {
continue;
}
for m in &ext.members {
let (base, _) = effects::split_effect(&m.name);
if !effects::is_known_base(base) {
set.insert(m.name.clone());
}
}
}
set
}
pub fn generate_effect_row_soundness_proofs(ir: &IRProgram, axon_version: &str) -> Vec<ProofTerm> {
let digest = artifact_digest(ir);
let ext_members = extension_effect_members(ir);
let mut proofs = Vec::new();
for tool in &ir.tools {
if tool.effect_row.is_empty() {
continue;
}
let witness =
derive_effect_row_soundness_witness(&tool.name, &tool.effect_row, &ext_members);
proofs.push(ProofTerm {
property: PropertyClass::EffectRowSoundness,
artifact_digest: digest.clone(),
witness: Witness::EffectRowSoundness(witness),
axon_version: axon_version.to_string(),
});
}
proofs
}
pub fn derive_capability_isolation_witness(
store_name: &str,
capability: &str,
) -> CapabilityIsolationWitness {
let malformed = !capability.is_empty() && !crate::parser::is_valid_capability_slug(capability);
CapabilityIsolationWitness {
store_name: store_name.to_string(),
capability: capability.to_string(),
malformed,
}
}
pub fn generate_capability_isolation_proofs(ir: &IRProgram, axon_version: &str) -> Vec<ProofTerm> {
let digest = artifact_digest(ir);
let mut proofs = Vec::new();
for store in &ir.axonstore_specs {
if store.capability.is_empty() {
continue;
}
let witness = derive_capability_isolation_witness(&store.name, &store.capability);
proofs.push(ProofTerm {
property: PropertyClass::CapabilityIsolation,
artifact_digest: digest.clone(),
witness: Witness::CapabilityIsolation(witness),
axon_version: axon_version.to_string(),
});
}
proofs
}
pub fn derive_endpoint_retry_witness(endpoint_name: &str, retries: i64) -> ResourceBoundsWitness {
ResourceBoundsWitness::EndpointRetry {
endpoint_name: endpoint_name.to_string(),
retries,
in_bounds: (0..=MAX_RETRIES).contains(&retries),
}
}
pub fn derive_socket_credit_witness(socket_name: &str, credit: i64) -> ResourceBoundsWitness {
ResourceBoundsWitness::SocketCredit {
socket_name: socket_name.to_string(),
credit,
positive: credit >= 1,
}
}
pub fn generate_resource_bounds_proofs(ir: &IRProgram, axon_version: &str) -> Vec<ProofTerm> {
let digest = artifact_digest(ir);
let mut proofs = Vec::new();
for ep in &ir.endpoints {
let witness = derive_endpoint_retry_witness(&ep.name, ep.retries);
proofs.push(ProofTerm {
property: PropertyClass::ResourceBounds,
artifact_digest: digest.clone(),
witness: Witness::ResourceBounds(witness),
axon_version: axon_version.to_string(),
});
}
for socket in &ir.sockets {
if let Some(credit) = socket.backpressure_credit {
let witness = derive_socket_credit_witness(&socket.name, credit);
proofs.push(ProofTerm {
property: PropertyClass::ResourceBounds,
artifact_digest: digest.clone(),
witness: Witness::ResourceBounds(witness),
axon_version: axon_version.to_string(),
});
}
}
proofs
}
pub fn derive_shield_halt_witness(
shield_name: &str,
on_breach: &str,
scan: &[String],
sign: &str,
) -> ShieldHaltGuaranteeWitness {
let known_policy = VALID_BREACH_POLICIES.contains(&on_breach);
let scan_count = scan.len();
let vacuous_halt = on_breach == "halt" && scan.is_empty() && sign.is_empty();
ShieldHaltGuaranteeWitness {
shield_name: shield_name.to_string(),
on_breach: on_breach.to_string(),
known_policy,
scan_count,
vacuous_halt,
}
}
pub fn generate_shield_halt_guarantee_proofs(ir: &IRProgram, axon_version: &str) -> Vec<ProofTerm> {
let digest = artifact_digest(ir);
let mut proofs = Vec::new();
for shield in &ir.shields {
if shield.on_breach.is_empty() {
continue;
}
let witness = derive_shield_halt_witness(
&shield.name,
&shield.on_breach,
&shield.scan,
&shield.sign,
);
proofs.push(ProofTerm {
property: PropertyClass::ShieldHaltGuarantee,
artifact_digest: digest.clone(),
witness: Witness::ShieldHaltGuarantee(witness),
axon_version: axon_version.to_string(),
});
}
proofs
}
fn collect_store_accesses(steps: &[crate::ir_nodes::IRFlowNode], out: &mut Vec<String>) {
use crate::ir_nodes::IRFlowNode as N;
for step in steps {
match step {
N::Retrieve(s) => out.push(s.store_name.clone()),
N::Persist(s) => out.push(s.store_name.clone()),
N::Mutate(s) => out.push(s.store_name.clone()),
N::Purge(s) => out.push(s.store_name.clone()),
N::Conditional(c) => {
collect_store_accesses(&c.then_body, out);
collect_store_accesses(&c.else_body, out);
}
N::ForIn(f) => collect_store_accesses(&f.body, out),
N::Quant(q) => collect_store_accesses(&q.body, out),
N::Warden(w) => collect_store_accesses(&w.body, out),
N::Yield(_) => {}
N::Mint(_) => {}
N::Rotate(s) => out.push(s.store_ref.clone()),
N::Listen(l) => collect_store_accesses(&l.body, out),
N::Run(_) => {}
N::Step(_)
| N::Probe(_)
| N::Reason(_)
| N::Validate(_)
| N::Refine(_)
| N::Weave(_)
| N::UseTool(_)
| N::Remember(_)
| N::Recall(_)
| N::Let(_)
| N::Return(_)
| N::Break(_)
| N::Continue(_)
| N::LambdaDataApply(_)
| N::Par(_)
| N::Hibernate(_)
| N::Deliberate(_)
| N::Consensus(_)
| N::Forge(_)
| N::Focus(_)
| N::Associate(_)
| N::Aggregate(_)
| N::Explore(_)
| N::Ingest(_)
| N::ShieldApply(_)
| N::Stream(_)
| N::Navigate(_)
| N::Drill(_)
| N::Trail(_)
| N::Corroborate(_)
| N::OtsApply(_)
| N::MandateApply(_)
| N::ComputeApply(_)
| N::DaemonStep(_)
| N::Emit(_)
| N::Publish(_)
| N::Discover(_)
| N::Transact(_) => {}
}
}
}
pub fn derive_capability_containment_witness(
endpoint_name: &str,
execute_flow: &str,
declared_requires_raw: &[String],
ir: &IRProgram,
) -> CapabilityContainmentWitness {
let declared_requires = canonical_classes(declared_requires_raw);
let flow = ir.flows.iter().find(|f| f.name == execute_flow);
let flow_resolved = flow.is_some();
let mut reached_stores: Vec<String> = Vec::new();
if let Some(f) = flow {
collect_store_accesses(&f.steps, &mut reached_stores);
}
let mut reached_gates: Vec<String> = reached_stores
.iter()
.filter_map(|name| {
ir.axonstore_specs
.iter()
.find(|s| &s.name == name)
.map(|s| s.capability.clone())
})
.filter(|cap| !cap.is_empty())
.collect();
reached_gates.sort();
reached_gates.dedup();
let mut uncovered_gates: Vec<String> =
crate::esk::compliance::covers(declared_requires.iter(), reached_gates.iter())
.into_iter()
.collect();
uncovered_gates.sort();
CapabilityContainmentWitness {
endpoint_name: endpoint_name.to_string(),
execute_flow: execute_flow.to_string(),
flow_resolved,
declared_requires,
reached_gates,
uncovered_gates,
}
}
pub fn generate_capability_containment_proofs(
ir: &IRProgram,
axon_version: &str,
) -> Vec<ProofTerm> {
let digest = artifact_digest(ir);
let mut proofs = Vec::new();
for ep in &ir.endpoints {
let witness = derive_capability_containment_witness(
&ep.name,
&ep.execute_flow,
&ep.requires_capabilities,
ir,
);
if witness.declared_requires.is_empty() && witness.reached_gates.is_empty() {
continue;
}
proofs.push(ProofTerm {
property: PropertyClass::CapabilityContainment,
artifact_digest: digest.clone(),
witness: Witness::CapabilityContainment(witness),
axon_version: axon_version.to_string(),
});
}
proofs
}
fn infer_arg_literal_type(value: &str) -> Option<&'static str> {
if value == "true" || value == "false" {
return Some("Bool");
}
if value.contains('.') && value.parse::<f64>().is_ok() {
return Some("Float");
}
let digits = value.strip_prefix('-').unwrap_or(value);
if !digits.is_empty() && digits.chars().all(|c| c.is_ascii_digit()) {
return Some("Int");
}
None
}
fn tool_arg_types_align(value_ty: &str, decl_ty: &str) -> bool {
let base = decl_ty.trim_end_matches('?').split('<').next().unwrap_or(decl_ty);
base == "Any" || base == value_ty || (base == "Float" && value_ty == "Int")
}
fn collect_named_use_tool_calls<'a>(
steps: &'a [crate::ir_nodes::IRFlowNode],
out: &mut Vec<&'a crate::ir_nodes::IRUseToolStep>,
) {
use crate::ir_nodes::IRFlowNode as N;
for step in steps {
match step {
N::UseTool(u) => {
if !u.named_args.is_empty() {
out.push(u);
}
}
N::Conditional(c) => {
collect_named_use_tool_calls(&c.then_body, out);
collect_named_use_tool_calls(&c.else_body, out);
}
N::ForIn(f) => collect_named_use_tool_calls(&f.body, out),
N::Quant(q) => collect_named_use_tool_calls(&q.body, out),
N::Warden(w) => collect_named_use_tool_calls(&w.body, out),
N::Yield(_) => {}
N::Mint(_) => {}
N::Rotate(_) => {}
N::Listen(l) => collect_named_use_tool_calls(&l.body, out),
N::Run(_) => {}
N::Step(_)
| N::Probe(_)
| N::Reason(_)
| N::Validate(_)
| N::Refine(_)
| N::Weave(_)
| N::Remember(_)
| N::Recall(_)
| N::Let(_)
| N::Return(_)
| N::Break(_)
| N::Continue(_)
| N::LambdaDataApply(_)
| N::Par(_)
| N::Hibernate(_)
| N::Deliberate(_)
| N::Consensus(_)
| N::Forge(_)
| N::Focus(_)
| N::Associate(_)
| N::Aggregate(_)
| N::Explore(_)
| N::Ingest(_)
| N::ShieldApply(_)
| N::Stream(_)
| N::Navigate(_)
| N::Drill(_)
| N::Trail(_)
| N::Corroborate(_)
| N::OtsApply(_)
| N::MandateApply(_)
| N::ComputeApply(_)
| N::DaemonStep(_)
| N::Emit(_)
| N::Publish(_)
| N::Discover(_)
| N::Retrieve(_)
| N::Persist(_)
| N::Mutate(_)
| N::Purge(_)
| N::Transact(_) => {}
}
}
}
fn canonical_names(raw: &[String]) -> Vec<String> {
let mut v = raw.to_vec();
v.sort();
v.dedup();
v
}
pub fn derive_tool_call_soundness_witness(
flow_name: &str,
call_index: usize,
ir: &IRProgram,
) -> Option<ToolCallSoundnessWitness> {
let flow = ir.flows.iter().find(|f| f.name == flow_name)?;
let mut calls = Vec::new();
collect_named_use_tool_calls(&flow.steps, &mut calls);
let call = calls.get(call_index)?;
let tool_name = call.tool_name.clone();
let arg_pairs: Vec<(String, String)> = call
.named_args
.iter()
.map(|a| (a.name.clone(), a.value.clone()))
.collect();
let arg_names: Vec<String> = arg_pairs.iter().map(|(n, _)| n.clone()).collect();
let params: Vec<(String, String, bool)> = ir
.tools
.iter()
.find(|t| t.name == tool_name)
.map(|t| {
t.parameters
.iter()
.map(|p| (p.name.clone(), p.type_name.clone(), p.optional))
.collect()
})
.unwrap_or_default();
let schema_present = !params.is_empty();
let declared_params = canonical_names(
¶ms.iter().map(|(n, _, _)| n.clone()).collect::<Vec<_>>(),
);
let mut seen = std::collections::HashSet::new();
let mut dup = std::collections::HashSet::new();
for name in &arg_names {
if !seen.insert(name.clone()) {
dup.insert(name.clone());
}
}
let duplicate_args = canonical_names(&dup.into_iter().collect::<Vec<_>>());
let unknown_args = canonical_names(
&arg_names
.iter()
.filter(|n| !params.iter().any(|(p, _, _)| &p == n))
.cloned()
.collect::<Vec<_>>(),
);
let mut missing_required: Vec<String> = params
.iter()
.filter(|(p, _, optional)| !optional && !arg_names.iter().any(|n| n == p))
.map(|(p, _, _)| p.clone())
.collect();
missing_required.sort();
missing_required.dedup();
let mut type_mismatches: Vec<String> = Vec::new();
for (name, value) in &arg_pairs {
if let Some((_, decl_ty, _)) = params.iter().find(|(p, _, _)| p == name) {
if let Some(val_ty) = infer_arg_literal_type(value) {
if !tool_arg_types_align(val_ty, decl_ty) {
type_mismatches.push(format!("{name}:{decl_ty}:{val_ty}"));
}
}
}
}
type_mismatches.sort();
type_mismatches.dedup();
Some(ToolCallSoundnessWitness {
flow_name: flow_name.to_string(),
call_index,
tool_name,
arg_names,
declared_params,
schema_present,
unknown_args,
duplicate_args,
missing_required,
type_mismatches,
})
}
pub fn generate_tool_call_soundness_proofs(ir: &IRProgram, axon_version: &str) -> Vec<ProofTerm> {
let digest = artifact_digest(ir);
let mut proofs = Vec::new();
for flow in &ir.flows {
let mut calls = Vec::new();
collect_named_use_tool_calls(&flow.steps, &mut calls);
for (call_index, _) in calls.iter().enumerate() {
let Some(witness) = derive_tool_call_soundness_witness(&flow.name, call_index, ir)
else {
continue;
};
if !witness.schema_present {
continue;
}
proofs.push(ProofTerm {
property: PropertyClass::ToolCallSoundness,
artifact_digest: digest.clone(),
witness: Witness::ToolCallSoundness(witness),
axon_version: axon_version.to_string(),
});
}
}
proofs
}
pub fn derive_effect_budgeted_witness(
daemon_name: &str,
ir: &IRProgram,
) -> Option<EffectBudgetedWitness> {
let daemon = ir.daemons.iter().find(|d| d.name == daemon_name)?;
let budget = daemon.budget.as_ref()?;
let declared_tools = canonical_names(&ir.tools.iter().map(|t| t.name.clone()).collect::<Vec<_>>());
let declared: std::collections::HashSet<&str> =
declared_tools.iter().map(String::as_str).collect();
let mut unresolved_effects = Vec::new();
let mut nonpositive_limits = Vec::new();
let mut invalid_periods = Vec::new();
for q in &budget.quotas {
if !declared.contains(q.effect.as_str()) {
unresolved_effects.push(q.effect.clone());
}
if q.limit <= 0 {
nonpositive_limits.push(format!("{}:{}", q.effect, q.kind));
}
if !VALID_BUDGET_PERIODS.contains(&q.period.as_str()) {
invalid_periods.push(format!("{}:{}", q.effect, q.period));
}
}
Some(EffectBudgetedWitness {
daemon_name: daemon_name.to_string(),
quota_count: budget.quotas.len(),
declared_tools,
unresolved_effects: canonical_names(&unresolved_effects),
nonpositive_limits: {
let mut v = nonpositive_limits;
v.sort();
v
},
invalid_periods: {
let mut v = invalid_periods;
v.sort();
v
},
on_exhausted: budget.on_exhausted.clone(),
on_exhausted_valid: VALID_ON_EXHAUSTED.contains(&budget.on_exhausted.as_str()),
})
}
pub fn generate_effect_budgeted_proofs(ir: &IRProgram, axon_version: &str) -> Vec<ProofTerm> {
let digest = artifact_digest(ir);
let mut proofs = Vec::new();
for daemon in &ir.daemons {
if daemon.budget.is_none() {
continue;
}
let Some(witness) = derive_effect_budgeted_witness(&daemon.name, ir) else {
continue;
};
proofs.push(ProofTerm {
property: PropertyClass::EffectBudgeted,
artifact_digest: digest.clone(),
witness: Witness::EffectBudgeted(witness),
axon_version: axon_version.to_string(),
});
}
proofs
}
pub fn derive_json_shape_soundness_witness(
store_name: &str,
ir: &IRProgram,
) -> Option<JsonShapeSoundnessWitness> {
let store = ir.axonstore_specs.iter().find(|s| s.name == store_name)?;
let columns = match store.column_schema.as_ref()? {
crate::ir_nodes::IRStoreColumnSchema::Inline { columns } => columns,
_ => return None,
};
let mut lens: Vec<(String, String)> = columns
.iter()
.filter_map(|c| c.json_shape.as_ref().map(|s| (c.name.clone(), s.clone())))
.collect();
if lens.is_empty() {
return None;
}
lens.sort();
let declared_types =
canonical_names(&ir.types.iter().map(|t| t.name.clone()).collect::<Vec<_>>());
let declared: std::collections::HashSet<&str> =
declared_types.iter().map(String::as_str).collect();
let lens_columns: Vec<String> = lens.iter().map(|(c, s)| format!("{c}:{s}")).collect();
let unresolved_shapes: Vec<String> = lens
.iter()
.filter(|(_, s)| !declared.contains(s.as_str()))
.map(|(c, s)| format!("{c}:{s}"))
.collect();
Some(JsonShapeSoundnessWitness {
store_name: store_name.to_string(),
declared_types,
lens_columns,
unresolved_shapes,
})
}
pub fn generate_json_shape_soundness_proofs(ir: &IRProgram, axon_version: &str) -> Vec<ProofTerm> {
let digest = artifact_digest(ir);
let mut proofs = Vec::new();
for store in &ir.axonstore_specs {
let Some(witness) = derive_json_shape_soundness_witness(&store.name, ir) else {
continue;
};
proofs.push(ProofTerm {
property: PropertyClass::JsonShapeSoundness,
artifact_digest: digest.clone(),
witness: Witness::JsonShapeSoundness(witness),
axon_version: axon_version.to_string(),
});
}
proofs
}
fn collect_emitted_channels(ir: &IRProgram) -> std::collections::HashSet<String> {
fn walk(steps: &[crate::ir_nodes::IRFlowNode], out: &mut std::collections::HashSet<String>) {
use crate::ir_nodes::IRFlowNode as N;
for step in steps {
match step {
N::Emit(e) => {
out.insert(e.channel_ref.clone());
}
N::Conditional(c) => {
walk(&c.then_body, out);
walk(&c.else_body, out);
}
N::ForIn(f) => walk(&f.body, out),
N::Listen(l) => walk(&l.body, out),
_ => {}
}
}
}
let mut out = std::collections::HashSet::new();
for flow in &ir.flows {
walk(&flow.steps, &mut out);
}
for daemon in &ir.daemons {
for listener in &daemon.listeners {
walk(&listener.body, &mut out);
}
}
out
}
fn collect_listened_channels(ir: &IRProgram) -> std::collections::HashSet<String> {
let mut out = std::collections::HashSet::new();
for daemon in &ir.daemons {
for l in &daemon.listeners {
if axon_frontend::cron::cron_expr(&l.channel).is_none() {
out.insert(l.channel.clone());
}
}
}
out
}
pub fn derive_channel_delivery_soundness_witness(
channel_name: &str,
ir: &IRProgram,
) -> Option<ChannelDeliverySoundnessWitness> {
let ch = ir.channels.iter().find(|c| c.name == channel_name)?;
let consumers = collect_listened_channels(ir);
if !consumers.contains(channel_name) {
return None;
}
let producers = collect_emitted_channels(ir);
Some(ChannelDeliverySoundnessWitness {
channel_name: channel_name.to_string(),
persistence: ch.persistence.clone(),
qos: ch.qos.clone(),
has_producer: producers.contains(channel_name),
has_consumer: true,
})
}
pub fn generate_channel_delivery_soundness_proofs(
ir: &IRProgram,
axon_version: &str,
) -> Vec<ProofTerm> {
let digest = artifact_digest(ir);
let mut proofs = Vec::new();
for ch in &ir.channels {
let Some(witness) = derive_channel_delivery_soundness_witness(&ch.name, ir) else {
continue;
};
proofs.push(ProofTerm {
property: PropertyClass::ChannelDeliverySoundness,
artifact_digest: digest.clone(),
witness: Witness::ChannelDeliverySoundness(witness),
axon_version: axon_version.to_string(),
});
}
proofs
}
pub fn derive_aggregate_soundness_witnesses(
ir: &IRProgram,
) -> Vec<AggregateSoundnessWitness> {
fn witness_for(
flow_name: &str,
s: &crate::ir_nodes::IRRetrieveStep,
) -> Option<AggregateSoundnessWitness> {
if s.aggregate.trim().is_empty() && s.group_by.trim().is_empty() {
return None;
}
let mut w = AggregateSoundnessWitness {
flow_name: flow_name.to_string(),
store_name: s.store_name.clone(),
aggregate: s.aggregate.clone(),
group_by: s.group_by.clone(),
order_by: s.order_by.clone(),
limit_expr: s.limit_expr.clone(),
function: String::new(),
column: String::new(),
group_columns: Vec::new(),
violations: Vec::new(),
};
match crate::store::filter::parse_aggregate_clause(
&s.aggregate,
&s.group_by,
&s.order_by,
&s.limit_expr,
) {
Ok(Some((spec, groups))) => {
w.function = spec.func.label().to_string();
w.column = spec.column.unwrap_or_default();
w.group_columns = groups;
}
Ok(None) => {}
Err(e) => w.violations.push(e.to_string()),
}
Some(w)
}
fn walk(
flow_name: &str,
steps: &[crate::ir_nodes::IRFlowNode],
out: &mut Vec<AggregateSoundnessWitness>,
) {
use crate::ir_nodes::IRFlowNode as N;
for step in steps {
match step {
N::Retrieve(s) => {
if let Some(w) = witness_for(flow_name, s) {
out.push(w);
}
}
N::Conditional(c) => {
walk(flow_name, &c.then_body, out);
walk(flow_name, &c.else_body, out);
}
N::ForIn(f) => walk(flow_name, &f.body, out),
N::Listen(l) => walk(flow_name, &l.body, out),
_ => {}
}
}
}
let mut out = Vec::new();
for flow in &ir.flows {
walk(&flow.name, &flow.steps, &mut out);
}
for daemon in &ir.daemons {
let subject = format!("daemon:{}", daemon.name);
for listener in &daemon.listeners {
walk(&subject, &listener.body, &mut out);
}
}
out.sort_by(|a, b| {
(&a.flow_name, &a.store_name, &a.aggregate, &a.group_by)
.cmp(&(&b.flow_name, &b.store_name, &b.aggregate, &b.group_by))
});
out.dedup();
out
}
pub fn generate_aggregate_soundness_proofs(
ir: &IRProgram,
axon_version: &str,
) -> Vec<ProofTerm> {
let digest = artifact_digest(ir);
derive_aggregate_soundness_witnesses(ir)
.into_iter()
.map(|witness| ProofTerm {
property: PropertyClass::AggregateSoundness,
artifact_digest: digest.clone(),
witness: Witness::AggregateSoundness(witness),
axon_version: axon_version.to_string(),
})
.collect()
}
fn derive_first_signing_publish(
channel_name: &str,
ir: &IRProgram,
) -> Option<(String, String)> {
fn walk(
steps: &[crate::ir_nodes::IRFlowNode],
channel_name: &str,
ir: &IRProgram,
found: &mut Option<(String, String)>,
) {
use crate::ir_nodes::IRFlowNode as N;
for step in steps {
if found.is_some() {
return;
}
match step {
N::Publish(p) if p.channel_ref == channel_name => {
let sign = ir
.shields
.iter()
.find(|s| s.name == p.shield_ref)
.map(|s| s.sign.clone())
.unwrap_or_default();
if !sign.is_empty() {
*found = Some((sign, p.shield_ref.clone()));
}
}
N::Conditional(c) => {
walk(&c.then_body, channel_name, ir, found);
walk(&c.else_body, channel_name, ir, found);
}
N::ForIn(f) => walk(&f.body, channel_name, ir, found),
N::Par(p) => {
for branch in &p.branches {
walk(branch, channel_name, ir, found);
}
}
N::Listen(l) => walk(&l.body, channel_name, ir, found),
N::Quant(q) => walk(&q.body, channel_name, ir, found),
_ => {}
}
}
}
let mut found = None;
for flow in &ir.flows {
walk(&flow.steps, channel_name, ir, &mut found);
if found.is_some() {
return found;
}
}
for daemon in &ir.daemons {
for listener in &daemon.listeners {
walk(&listener.body, channel_name, ir, &mut found);
if found.is_some() {
return found;
}
}
}
found
}
pub fn derive_channel_egress_witness(
channel_name: &str,
ir: &IRProgram,
) -> Option<ChannelEgressSoundnessWitness> {
let ch = ir.channels.iter().find(|c| c.name == channel_name)?;
let (derived_sign, shield_ref) =
derive_first_signing_publish(channel_name, ir).unwrap_or_default();
if ch.egress_sign.is_empty() && derived_sign.is_empty() {
return None;
}
Some(ChannelEgressSoundnessWitness {
channel_name: channel_name.to_string(),
declared_egress_sign: ch.egress_sign.clone(),
derived_sign,
shield_ref,
persistence: ch.persistence.clone(),
durable: ch.persistence == "persistent_axonstore",
})
}
pub fn generate_channel_egress_soundness_proofs(
ir: &IRProgram,
axon_version: &str,
) -> Vec<ProofTerm> {
let digest = artifact_digest(ir);
let mut proofs = Vec::new();
for ch in &ir.channels {
let Some(witness) = derive_channel_egress_witness(&ch.name, ir) else {
continue;
};
proofs.push(ProofTerm {
property: PropertyClass::ChannelEgressSoundness,
artifact_digest: digest.clone(),
witness: Witness::ChannelEgressSoundness(witness),
axon_version: axon_version.to_string(),
});
}
proofs
}
fn ir_handler_reaches_exit(steps: &[IRSessionStep]) -> bool {
match steps.last() {
Some(s) if s.op == "resume" || s.op == "end" => true,
Some(s) if s.op == "select" || s.op == "branch" => {
!s.branches.is_empty() && s.branches.iter().all(|b| ir_handler_reaches_exit(&b.steps))
}
_ => false,
}
}
fn find_interrupt_step<'a>(steps: &'a [IRSessionStep], signal: &str) -> Option<&'a IRSessionStep> {
for s in steps {
if s.op == "interrupt" && s.message_type == signal {
return Some(s);
}
for b in &s.branches {
if let Some(found) = find_interrupt_step(&b.steps, signal) {
return Some(found);
}
}
}
None
}
fn interrupt_signals(steps: &[IRSessionStep]) -> Vec<String> {
let mut out = Vec::new();
fn walk(steps: &[IRSessionStep], out: &mut Vec<String>) {
for s in steps {
if s.op == "interrupt" {
out.push(s.message_type.clone());
}
for b in &s.branches {
walk(&b.steps, out);
}
}
}
walk(steps, &mut out);
out.sort();
out.dedup();
out
}
pub fn derive_interruptible_session_witness(
session_name: &str,
role_name: &str,
signal: &str,
ir: &IRProgram,
) -> Option<InterruptibleSessionSoundnessWitness> {
let session = ir.sessions.iter().find(|s| s.name == session_name)?;
let role = session.roles.iter().find(|r| r.name == role_name)?;
let step = find_interrupt_step(&role.steps, signal)?;
let has_body = step.branches.iter().any(|b| b.label == "body");
let handler = step.branches.iter().find(|b| b.label == "handler");
Some(InterruptibleSessionSoundnessWitness {
session_name: session_name.to_string(),
role_name: role_name.to_string(),
signal: step.message_type.clone(),
signal_in_catalog: CALL_INTERRUPT_CAUSES.contains(&step.message_type.as_str()),
has_body,
has_handler: handler.is_some(),
handler_reaches_exit: handler
.map(|h| ir_handler_reaches_exit(&h.steps))
.unwrap_or(false),
})
}
pub fn generate_interruptible_session_soundness_proofs(
ir: &IRProgram,
axon_version: &str,
) -> Vec<ProofTerm> {
let digest = artifact_digest(ir);
let mut proofs = Vec::new();
for session in &ir.sessions {
for role in &session.roles {
for signal in interrupt_signals(&role.steps) {
let Some(witness) =
derive_interruptible_session_witness(&session.name, &role.name, &signal, ir)
else {
continue;
};
proofs.push(ProofTerm {
property: PropertyClass::InterruptibleSessionSoundness,
artifact_digest: digest.clone(),
witness: Witness::InterruptibleSessionSoundness(witness),
axon_version: axon_version.to_string(),
});
}
}
}
proofs
}
pub fn derive_parked_residual_witness(
socket_name: &str,
ir: &IRProgram,
) -> Option<ParkedResidualSoundnessWitness> {
let socket = ir.sockets.iter().find(|s| s.name == socket_name)?;
let session = ir.sessions.iter().find(|s| s.name == socket.protocol);
let session_has_interrupt = session
.map(|s| {
s.roles
.iter()
.any(|r| !interrupt_signals(&r.steps).is_empty())
})
.unwrap_or(false);
if !session_has_interrupt {
return None;
}
Some(ParkedResidualSoundnessWitness {
socket_name: socket_name.to_string(),
session_name: socket.protocol.clone(),
session_has_interrupt,
reconnect_cognitive_state: socket.reconnect,
legal_basis_declared: socket
.legal_basis
.as_ref()
.map(|b| !b.is_empty())
.unwrap_or(false),
})
}
pub fn generate_parked_residual_soundness_proofs(
ir: &IRProgram,
axon_version: &str,
) -> Vec<ProofTerm> {
let digest = artifact_digest(ir);
let mut proofs = Vec::new();
for socket in &ir.sockets {
let Some(witness) = derive_parked_residual_witness(&socket.name, ir) else {
continue;
};
proofs.push(ProofTerm {
property: PropertyClass::ParkedResidualSoundness,
artifact_digest: digest.clone(),
witness: Witness::ParkedResidualSoundness(witness),
axon_version: axon_version.to_string(),
});
}
proofs
}
fn collect_ir_role_messages(
steps: &[axon_frontend::ir_nodes::IRSessionStep],
sends: &mut Vec<String>,
receives: &mut Vec<String>,
) {
for step in steps {
match step.op.as_str() {
"send" => {
if !sends.contains(&step.message_type) {
sends.push(step.message_type.clone());
}
}
"receive" => {
if !receives.contains(&step.message_type) {
receives.push(step.message_type.clone());
}
}
"select" | "branch" | "interrupt" => {
for b in &step.branches {
collect_ir_role_messages(&b.steps, sends, receives);
}
}
_ => {}
}
}
}
fn is_policy_shaped_config_key(key: &str) -> bool {
let mut chars = key.chars();
let head_ok = chars.next().is_some_and(|c| c.is_ascii_lowercase() || c.is_ascii_digit());
head_ok && chars.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || matches!(c, '_' | '.' | '-'))
}
pub fn derive_upstream_projection_witness(
upstream_name: &str,
ir: &IRProgram,
) -> Option<UpstreamProjectionSoundnessWitness> {
let up = ir.upstreams.iter().find(|u| u.name == upstream_name)?;
let mut required_sends = Vec::new();
let mut required_receives = Vec::new();
let bound_role = ir
.sessions
.iter()
.find(|s| s.name == up.protocol)
.and_then(|s| s.roles.iter().find(|r| r.name == up.role));
if let Some(role) = bound_role {
collect_ir_role_messages(&role.steps, &mut required_sends, &mut required_receives);
}
let mut covered_sends = Vec::new();
let mut covered_receives = Vec::new();
for r in &up.map {
let bucket = if r.direction == "send" { &mut covered_sends } else { &mut covered_receives };
if !bucket.contains(&r.message) {
bucket.push(r.message.clone());
}
}
let mut total = bound_role.is_some();
for (dir, required) in [("send", &required_sends), ("receive", &required_receives)] {
for msg in required.iter() {
let n = up.map.iter().filter(|r| r.direction == dir && &r.message == msg).count();
if n != 1 {
total = false;
}
}
}
for r in &up.map {
let known = if r.direction == "send" {
required_sends.contains(&r.message)
} else {
required_receives.contains(&r.message)
};
if !known {
total = false;
}
}
let receive_json: Vec<(String, Option<String>)> = up
.map
.iter()
.filter(|r| r.direction == "receive" && r.framing == "json")
.map(|r| match (&r.when_field, &r.when_value) {
(None, _) => ("type".to_string(), Some(r.message.clone())),
(Some(f), Some(v)) => (f.clone(), Some(v.clone())),
(Some(f), None) => (f.clone(), None),
})
.collect();
for (i, a) in receive_json.iter().enumerate() {
if receive_json.iter().skip(i + 1).any(|b| a == b) {
total = false;
}
}
if up.map.iter().filter(|r| r.direction == "receive" && r.framing == "binary").count() > 1 {
total = false;
}
Some(UpstreamProjectionSoundnessWitness {
upstream_name: up.name.clone(),
session_name: up.protocol.clone(),
role_name: up.role.clone(),
required_sends,
required_receives,
covered_sends,
covered_receives,
projection_total: total,
config_keys_valid: is_policy_shaped_config_key(&up.resolve) && is_policy_shaped_config_key(&up.secret),
})
}
pub fn generate_upstream_projection_soundness_proofs(
ir: &IRProgram,
axon_version: &str,
) -> Vec<ProofTerm> {
let digest = artifact_digest(ir);
let mut proofs = Vec::new();
for up in &ir.upstreams {
let Some(witness) = derive_upstream_projection_witness(&up.name, ir) else {
continue;
};
proofs.push(ProofTerm {
property: PropertyClass::UpstreamProjectionSoundness,
artifact_digest: digest.clone(),
witness: Witness::UpstreamProjectionSoundness(witness),
axon_version: axon_version.to_string(),
});
}
proofs
}
pub fn generate_call_soundness_certificate(
socket_name: &str,
ir: &IRProgram,
axon_version: &str,
) -> Option<CallSoundnessCertificate> {
let socket = ir.sockets.iter().find(|s| s.name == socket_name)?;
let session_name = socket.protocol.clone();
let mut proofs = Vec::new();
proofs.extend(
generate_interruptible_session_soundness_proofs(ir, axon_version)
.into_iter()
.filter(|p| {
matches!(&p.witness,
Witness::InterruptibleSessionSoundness(w) if w.session_name == session_name)
}),
);
proofs.extend(
generate_parked_residual_soundness_proofs(ir, axon_version)
.into_iter()
.filter(|p| {
matches!(&p.witness,
Witness::ParkedResidualSoundness(w) if w.socket_name == socket_name)
}),
);
proofs.extend(
generate_resource_bounds_proofs(ir, axon_version)
.into_iter()
.filter(|p| p.witness.subject_name() == socket_name),
);
Some(CallSoundnessCertificate {
socket_name: socket_name.to_string(),
session_name,
artifact_digest: artifact_digest(ir),
axon_version: axon_version.to_string(),
proofs,
})
}
pub fn derive_cors_policy_consistency_witness(ir: &IRProgram) -> Option<CorsPolicyConsistencyWitness> {
if ir.cors_policies.is_empty() && ir.endpoints.iter().all(|e| e.cors_ref.is_empty()) {
return None;
}
let declared_cors_names: Vec<String> =
ir.cors_policies.iter().map(|c| c.name.clone()).collect();
let endpoint_cors_refs: Vec<(String, String)> = ir
.endpoints
.iter()
.filter(|e| !e.cors_ref.is_empty())
.map(|e| (e.name.clone(), e.cors_ref.clone()))
.collect();
let all_references_resolve = endpoint_cors_refs
.iter()
.all(|(_, r)| declared_cors_names.contains(r));
let wildcard_credential_violations: Vec<String> = ir
.cors_policies
.iter()
.filter(|c| c.allow_credentials && c.allow_origins.iter().any(|o| o == "*"))
.map(|c| c.name.clone())
.collect();
let mut by_path: std::collections::HashMap<&str, (&str, &str)> = std::collections::HashMap::new();
let mut cross_method_conflicts: Vec<(String, String)> = Vec::new();
for ep in &ir.endpoints {
if ep.path.is_empty() {
continue;
}
match by_path.get(ep.path.as_str()) {
None => {
by_path.insert(ep.path.as_str(), (ep.name.as_str(), ep.cors_ref.as_str()));
}
Some((first_name, first_ref)) => {
if *first_ref != ep.cors_ref {
cross_method_conflicts.push((first_name.to_string(), ep.name.clone()));
}
}
}
}
Some(CorsPolicyConsistencyWitness {
declared_cors_names,
endpoint_cors_refs,
all_references_resolve,
wildcard_credential_violations,
cross_method_conflicts,
})
}
pub fn generate_cors_policy_consistency_proofs(ir: &IRProgram, axon_version: &str) -> Vec<ProofTerm> {
match derive_cors_policy_consistency_witness(ir) {
Some(witness) => vec![ProofTerm {
property: PropertyClass::CorsPolicyConsistency,
artifact_digest: artifact_digest(ir),
witness: Witness::CorsPolicyConsistency(witness),
axon_version: axon_version.to_string(),
}],
None => Vec::new(),
}
}
fn collect_step_now_declarations(
nodes: &[axon_frontend::ir_nodes::IRFlowNode],
out: &mut Vec<(String, String, String)>,
) {
use axon_frontend::ir_nodes::IRFlowNode;
for node in nodes {
match node {
IRFlowNode::Step(s) => {
if let Some(tz) = &s.now_tz {
out.push(("step".to_string(), s.name.clone(), tz.clone()));
}
}
IRFlowNode::Conditional(c) => {
collect_step_now_declarations(&c.then_body, out);
collect_step_now_declarations(&c.else_body, out);
}
IRFlowNode::ForIn(f) => collect_step_now_declarations(&f.body, out),
IRFlowNode::Par(p) => {
for branch in &p.branches {
collect_step_now_declarations(branch, out);
}
}
IRFlowNode::Listen(l) => collect_step_now_declarations(&l.body, out),
IRFlowNode::Warden(w) => collect_step_now_declarations(&w.body, out),
IRFlowNode::Quant(q) => collect_step_now_declarations(&q.body, out),
_ => {}
}
}
}
fn now_zone_shape_ok(zone: &str) -> bool {
let t = zone.trim();
t == "UTC" || (t.contains('/') && !t.starts_with('/') && !t.ends_with('/'))
}
pub fn derive_temporal_context_soundness_witness(
ir: &IRProgram,
) -> Option<super::proof_term::TemporalContextSoundnessWitness> {
let mut declarations: Vec<(String, String, String)> = Vec::new();
for c in &ir.contexts {
if let Some(tz) = &c.now_tz {
declarations.push(("context".to_string(), c.name.clone(), tz.clone()));
}
}
for flow in &ir.flows {
collect_step_now_declarations(&flow.steps, &mut declarations);
}
if declarations.is_empty() {
return None;
}
let mut format_violations: Vec<String> = Vec::new();
let mut unknown_zones: Vec<String> = Vec::new();
for (_, _, zone) in &declarations {
if !now_zone_shape_ok(zone) {
if !format_violations.contains(zone) {
format_violations.push(zone.clone());
}
} else if crate::window::parse_tz(zone).is_none() {
if !unknown_zones.contains(zone) {
unknown_zones.push(zone.clone());
}
}
}
Some(super::proof_term::TemporalContextSoundnessWitness {
declarations,
format_violations,
unknown_zones,
})
}
pub fn generate_temporal_context_soundness_proofs(
ir: &IRProgram,
axon_version: &str,
) -> Vec<ProofTerm> {
match derive_temporal_context_soundness_witness(ir) {
Some(witness) => vec![ProofTerm {
property: PropertyClass::TemporalContextSoundness,
artifact_digest: artifact_digest(ir),
witness: Witness::TemporalContextSoundness(witness),
axon_version: axon_version.to_string(),
}],
None => Vec::new(),
}
}
fn collect_mint_sites(
flow_name: &str,
nodes: &[axon_frontend::ir_nodes::IRFlowNode],
out: &mut Vec<(String, String, String)>,
) {
use axon_frontend::ir_nodes::IRFlowNode;
for node in nodes {
match node {
IRFlowNode::Mint(m) => out.push((
flow_name.to_string(),
m.credential_ref.clone(),
m.binding.clone(),
)),
IRFlowNode::Conditional(c) => {
collect_mint_sites(flow_name, &c.then_body, out);
collect_mint_sites(flow_name, &c.else_body, out);
}
IRFlowNode::ForIn(f) => collect_mint_sites(flow_name, &f.body, out),
IRFlowNode::Par(p) => {
for branch in &p.branches {
collect_mint_sites(flow_name, branch, out);
}
}
IRFlowNode::Listen(l) => collect_mint_sites(flow_name, &l.body, out),
IRFlowNode::Warden(w) => collect_mint_sites(flow_name, &w.body, out),
IRFlowNode::Quant(q) => collect_mint_sites(flow_name, &q.body, out),
_ => {}
}
}
}
fn credential_contract_ok(c: &axon_frontend::ir_nodes::IRCredential) -> bool {
let slug_ok = |s: &str| {
!s.is_empty()
&& s.split('.').all(|seg| {
let mut ch = seg.chars();
matches!(ch.next(), Some('a'..='z'))
&& ch.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_')
})
};
!c.grants.is_empty()
&& c.grants.iter().all(|g| slug_ok(g))
&& c.ttl_secs > 0
&& c.ttl_secs <= 86_400
}
pub fn derive_credential_attenuation_witness(
ir: &IRProgram,
) -> Option<super::proof_term::CredentialAttenuationWitness> {
let contracts: Vec<(String, u64, Vec<String>)> = ir
.credentials
.iter()
.map(|c| (c.name.clone(), c.ttl_secs, c.grants.clone()))
.collect();
let mut mints: Vec<(String, String, String)> = Vec::new();
for flow in &ir.flows {
collect_mint_sites(&flow.name, &flow.steps, &mut mints);
}
if contracts.is_empty() && mints.is_empty() {
return None;
}
let declared: std::collections::HashSet<&str> =
ir.credentials.iter().map(|c| c.name.as_str()).collect();
let mut unresolved_mints: Vec<String> = Vec::new();
for (_, cred_ref, _) in &mints {
if !declared.contains(cred_ref.as_str()) && !unresolved_mints.contains(cred_ref) {
unresolved_mints.push(cred_ref.clone());
}
}
let invalid_contracts: Vec<String> = ir
.credentials
.iter()
.filter(|c| !credential_contract_ok(c))
.map(|c| c.name.clone())
.collect();
Some(super::proof_term::CredentialAttenuationWitness {
contracts,
mints,
unresolved_mints,
invalid_contracts,
})
}
pub fn generate_credential_attenuation_proofs(
ir: &IRProgram,
axon_version: &str,
) -> Vec<ProofTerm> {
match derive_credential_attenuation_witness(ir) {
Some(witness) => vec![ProofTerm {
property: PropertyClass::CredentialAttenuation,
artifact_digest: artifact_digest(ir),
witness: Witness::CredentialAttenuation(witness),
axon_version: axon_version.to_string(),
}],
None => Vec::new(),
}
}
fn collect_rotate_sites(
flow_name: &str,
nodes: &[axon_frontend::ir_nodes::IRFlowNode],
out: &mut Vec<(String, String, String, String)>,
) {
use axon_frontend::ir_nodes::IRFlowNode;
for node in nodes {
match node {
IRFlowNode::Rotate(r) => out.push((
flow_name.to_string(),
r.store_ref.clone(),
r.tool_ref.clone(),
r.binding.clone(),
)),
IRFlowNode::Conditional(c) => {
collect_rotate_sites(flow_name, &c.then_body, out);
collect_rotate_sites(flow_name, &c.else_body, out);
}
IRFlowNode::ForIn(f) => collect_rotate_sites(flow_name, &f.body, out),
IRFlowNode::Par(p) => {
for branch in &p.branches {
collect_rotate_sites(flow_name, branch, out);
}
}
IRFlowNode::Listen(l) => collect_rotate_sites(flow_name, &l.body, out),
IRFlowNode::Warden(w) => collect_rotate_sites(flow_name, &w.body, out),
IRFlowNode::Quant(q) => collect_rotate_sites(flow_name, &q.body, out),
_ => {}
}
}
}
fn collect_secrets_write_violations(
flow_name: &str,
nodes: &[axon_frontend::ir_nodes::IRFlowNode],
secrets_stores: &std::collections::HashSet<&str>,
out: &mut Vec<(String, String, String)>,
) {
use axon_frontend::ir_nodes::IRFlowNode;
let mut push = |verb: &str, store: &str, out: &mut Vec<(String, String, String)>| {
if secrets_stores.contains(store) {
out.push((flow_name.to_string(), verb.to_string(), store.to_string()));
}
};
for node in nodes {
match node {
IRFlowNode::Persist(s) => push("persist", &s.store_name, out),
IRFlowNode::Mutate(s) => push("mutate", &s.store_name, out),
IRFlowNode::Purge(s) => push("purge", &s.store_name, out),
IRFlowNode::Conditional(c) => {
collect_secrets_write_violations(flow_name, &c.then_body, secrets_stores, out);
collect_secrets_write_violations(flow_name, &c.else_body, secrets_stores, out);
}
IRFlowNode::ForIn(f) => {
collect_secrets_write_violations(flow_name, &f.body, secrets_stores, out)
}
IRFlowNode::Par(p) => {
for branch in &p.branches {
collect_secrets_write_violations(flow_name, branch, secrets_stores, out);
}
}
IRFlowNode::Listen(l) => {
collect_secrets_write_violations(flow_name, &l.body, secrets_stores, out)
}
IRFlowNode::Warden(w) => {
collect_secrets_write_violations(flow_name, &w.body, secrets_stores, out)
}
IRFlowNode::Quant(q) => {
collect_secrets_write_violations(flow_name, &q.body, secrets_stores, out)
}
_ => {}
}
}
}
fn secrets_class_ok(class: &str) -> bool {
!class.is_empty()
&& class.split('.').all(|seg| {
let mut ch = seg.chars();
matches!(ch.next(), Some('a'..='z'))
&& ch.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_')
})
}
pub fn derive_secret_custody_witness(
ir: &IRProgram,
) -> Option<super::proof_term::SecretCustodySoundnessWitness> {
let stores: Vec<(String, String)> = ir
.axonstore_specs
.iter()
.filter(|s| s.backend == "secrets")
.map(|s| (s.name.clone(), s.class.clone()))
.collect();
let mut rotates: Vec<(String, String, String, String)> = Vec::new();
for flow in &ir.flows {
collect_rotate_sites(&flow.name, &flow.steps, &mut rotates);
}
if stores.is_empty() && rotates.is_empty() {
return None;
}
let secrets_names: std::collections::HashSet<&str> =
stores.iter().map(|(n, _)| n.as_str()).collect();
let declared_tools: std::collections::HashSet<&str> =
ir.tools.iter().map(|t| t.name.as_str()).collect();
let mut unresolved_stores: Vec<String> = Vec::new();
let mut unresolved_tools: Vec<String> = Vec::new();
for (_, store_ref, tool_ref, _) in &rotates {
if !secrets_names.contains(store_ref.as_str()) && !unresolved_stores.contains(store_ref)
{
unresolved_stores.push(store_ref.clone());
}
if !declared_tools.contains(tool_ref.as_str()) && !unresolved_tools.contains(tool_ref) {
unresolved_tools.push(tool_ref.clone());
}
}
let invalid_classes: Vec<String> = stores
.iter()
.filter(|(_, class)| !secrets_class_ok(class))
.map(|(n, _)| n.clone())
.collect();
let mut write_violations: Vec<(String, String, String)> = Vec::new();
for flow in &ir.flows {
collect_secrets_write_violations(
&flow.name,
&flow.steps,
&secrets_names,
&mut write_violations,
);
}
let partition_violations: Vec<String> = ir
.tools
.iter()
.filter(|t| !t.secret_partition.is_empty())
.filter(|t| {
let no_secret = t.secret.is_empty();
let technician = t.target.is_some();
let param_ok = t
.parameters
.iter()
.any(|p| p.name == t.secret_partition && p.type_name == "String" && !p.optional);
no_secret || technician || !param_ok
})
.map(|t| t.name.clone())
.collect();
Some(super::proof_term::SecretCustodySoundnessWitness {
stores,
rotates,
unresolved_stores,
unresolved_tools,
invalid_classes,
write_violations,
partition_violations,
})
}
pub fn generate_secret_custody_proofs(
ir: &IRProgram,
axon_version: &str,
) -> Vec<ProofTerm> {
match derive_secret_custody_witness(ir) {
Some(witness) => vec![ProofTerm {
property: PropertyClass::SecretCustodySoundness,
artifact_digest: artifact_digest(ir),
witness: Witness::SecretCustodySoundness(witness),
axon_version: axon_version.to_string(),
}],
None => Vec::new(),
}
}
fn ir_session_has_confirm_branch(steps: &[axon_frontend::ir_nodes::IRSessionStep]) -> bool {
for step in steps {
if step.op == "branch" {
let has_approved = step
.branches
.iter()
.any(|b| b.label == axon_frontend::technician::CONFIRM_APPROVED_LABEL);
let has_denied = step
.branches
.iter()
.any(|b| b.label == axon_frontend::technician::CONFIRM_DENIED_LABEL);
if has_approved && has_denied {
return true;
}
}
for b in &step.branches {
if ir_session_has_confirm_branch(&b.steps) {
return true;
}
}
}
false
}
pub fn derive_technician_command_safety_witness(
tool: &axon_frontend::ir_nodes::IRToolSpec,
ir: &IRProgram,
) -> Option<TechnicianCommandSafetyWitness> {
let target_socket = tool.target.clone()?;
let session_name = ir
.sockets
.iter()
.find(|s| s.name == target_socket)
.map(|s| s.protocol.clone())
.unwrap_or_default();
let risk = tool.risk.clone().unwrap_or_default();
let param_names: std::collections::HashSet<&str> =
tool.parameters.iter().map(|p| p.name.as_str()).collect();
let mut unbound_placeholders = Vec::new();
let mut partial_tokens = Vec::new();
for tok in &tool.argv {
match axon_frontend::technician::classify_argv_token(tok) {
axon_frontend::technician::ArgvToken::Placeholder(name) => {
if !param_names.contains(name.as_str()) {
unbound_placeholders.push(name);
}
}
axon_frontend::technician::ArgvToken::Partial(t) => partial_tokens.push(t),
axon_frontend::technician::ArgvToken::Literal(_) => {}
}
}
let argv_present = tool.provider != "bash" || !tool.argv.is_empty();
let confirm_branch_reachable =
if risk == axon_frontend::technician::RISK_DESTRUCTIVE {
ir.sessions
.iter()
.find(|s| s.name == session_name)
.map(|s| s.roles.iter().any(|r| ir_session_has_confirm_branch(&r.steps)))
.unwrap_or(false)
} else {
true
};
Some(TechnicianCommandSafetyWitness {
tool_name: tool.name.clone(),
target_socket,
session_name,
risk,
argv: tool.argv.clone(),
argv_present,
unbound_placeholders,
partial_tokens,
confirm_branch_reachable,
})
}
pub fn generate_technician_command_safety_proofs(
ir: &IRProgram,
axon_version: &str,
) -> Vec<ProofTerm> {
let digest = artifact_digest(ir);
let mut proofs = Vec::new();
for tool in &ir.tools {
let Some(witness) = derive_technician_command_safety_witness(tool, ir) else {
continue;
};
proofs.push(ProofTerm {
property: PropertyClass::TechnicianCommandSafety,
artifact_digest: digest.clone(),
witness: Witness::TechnicianCommandSafety(witness),
axon_version: axon_version.to_string(),
});
}
proofs
}
pub fn derive_cache_soundness_witness(ir: &IRProgram) -> Option<CacheSoundnessWitness> {
let uses_cache = !ir.caches.is_empty()
|| ir
.tools
.iter()
.any(|t| !t.cache.is_empty() && t.cache != "none");
if !uses_cache {
return None;
}
let cache_names: Vec<String> = ir.caches.iter().map(|c| c.name.clone()).collect();
let default_count = ir.caches.iter().filter(|c| c.default_policy).count();
let base = |e: &str| e.split_once(':').map(|(b, _)| b).unwrap_or(e).to_string();
let widened_without_ttl: Vec<String> = ir
.caches
.iter()
.filter(|c| {
let pure_only = c.apply_to_effects.iter().all(|e| base(e) == "pure");
!pure_only && c.ttl.is_none()
})
.map(|c| c.name.clone())
.collect();
let channel_names: std::collections::HashSet<&str> =
ir.channels.iter().map(|c| c.name.as_str()).collect();
let cache_name_set: std::collections::HashSet<&str> =
cache_names.iter().map(|s| s.as_str()).collect();
let mut unresolved_refs = Vec::new();
for c in &ir.caches {
for ch in &c.invalidate_on {
if !channel_names.contains(ch.as_str()) {
unresolved_refs.push(format!("cache '{}' invalidate_on '{}'", c.name, ch));
}
}
}
for t in &ir.tools {
if !t.cache.is_empty() && t.cache != "none" && !cache_name_set.contains(t.cache.as_str()) {
unresolved_refs.push(format!("tool '{}' cache '{}'", t.name, t.cache));
}
}
Some(CacheSoundnessWitness {
cache_names,
default_count,
widened_without_ttl,
unresolved_refs,
})
}
pub fn generate_cache_soundness_proofs(ir: &IRProgram, axon_version: &str) -> Vec<ProofTerm> {
match derive_cache_soundness_witness(ir) {
Some(witness) => vec![ProofTerm {
property: PropertyClass::CacheSoundness,
artifact_digest: artifact_digest(ir),
witness: Witness::CacheSoundness(witness),
axon_version: axon_version.to_string(),
}],
None => Vec::new(),
}
}
const SCRAPE_PROVIDERS: &[&str] = &["scrape_http", "scrape_dom", "scrape_crawl"];
fn effect_base(e: &str) -> &str {
e.split_once(':').map(|(b, _)| b).unwrap_or(e)
}
fn walk_ir_for_injection(
steps: &[crate::ir_nodes::IRFlowNode],
is_web_tool: &dyn Fn(&str) -> bool,
shielded_agents: &std::collections::HashSet<String>,
web_producer: &mut bool,
unshielded_belief: &mut bool,
shield_present: &mut bool,
) {
use crate::ir_nodes::IRFlowNode;
for step in steps {
match step {
IRFlowNode::Step(s) => {
for tref in [&s.apply_ref, &s.navigate_ref] {
if !tref.is_empty() && is_web_tool(tref) {
*web_producer = true;
}
}
let is_cognitive = !s.ask.is_empty() || !s.persona_ref.is_empty();
let agent_shielded =
!s.persona_ref.is_empty() && shielded_agents.contains(&s.persona_ref);
if is_cognitive && !agent_shielded {
*unshielded_belief = true;
}
}
IRFlowNode::UseTool(u) => {
if is_web_tool(&u.tool_name) {
*web_producer = true;
}
}
IRFlowNode::ShieldApply(_) => {
*shield_present = true;
}
IRFlowNode::Conditional(c) => {
walk_ir_for_injection(
&c.then_body,
is_web_tool,
shielded_agents,
web_producer,
unshielded_belief,
shield_present,
);
walk_ir_for_injection(
&c.else_body,
is_web_tool,
shielded_agents,
web_producer,
unshielded_belief,
shield_present,
);
}
IRFlowNode::ForIn(f) => {
walk_ir_for_injection(
&f.body,
is_web_tool,
shielded_agents,
web_producer,
unshielded_belief,
shield_present,
);
}
_ => {}
}
}
}
pub fn derive_scrape_provenance_soundness_witness(
ir: &IRProgram,
) -> Option<ScrapeProvenanceSoundnessWitness> {
let scrape_tools: Vec<(String, String)> = ir
.tools
.iter()
.filter(|t| SCRAPE_PROVIDERS.contains(&t.provider.as_str()))
.map(|t| (t.name.clone(), t.provider.clone()))
.collect();
if scrape_tools.is_empty() {
return None;
}
let mut tools_missing_web = Vec::new();
let mut dom_tools_with_network = Vec::new();
for t in ir
.tools
.iter()
.filter(|t| SCRAPE_PROVIDERS.contains(&t.provider.as_str()))
{
let has = |b: &str| t.effect_row.iter().any(|e| effect_base(e) == b);
if !has("web") {
tools_missing_web.push(t.name.clone());
}
if t.provider == "scrape_dom" && has("network") {
dom_tools_with_network.push(t.name.clone());
}
}
let web_tool_names: std::collections::HashSet<String> = ir
.tools
.iter()
.filter(|t| t.effect_row.iter().any(|e| effect_base(e) == "web"))
.map(|t| t.name.clone())
.collect();
let is_web_tool = |name: &str| web_tool_names.contains(name);
let shielded_agents: std::collections::HashSet<String> = ir
.agents
.iter()
.filter(|a| !a.shield_ref.is_empty())
.map(|a| a.name.clone())
.collect();
let mut unshielded_flows = Vec::new();
for flow in &ir.flows {
let (mut web_producer, mut unshielded_belief, mut shield_present) = (false, false, false);
walk_ir_for_injection(
&flow.steps,
&is_web_tool,
&shielded_agents,
&mut web_producer,
&mut unshielded_belief,
&mut shield_present,
);
if web_producer && unshielded_belief && !shield_present {
unshielded_flows.push(flow.name.clone());
}
}
Some(ScrapeProvenanceSoundnessWitness {
scrape_tools,
tools_missing_web,
dom_tools_with_network,
unshielded_flows,
})
}
pub fn generate_scrape_provenance_soundness_proofs(
ir: &IRProgram,
axon_version: &str,
) -> Vec<ProofTerm> {
match derive_scrape_provenance_soundness_witness(ir) {
Some(witness) => vec![ProofTerm {
property: PropertyClass::ScrapeProvenanceSoundness,
artifact_digest: artifact_digest(ir),
witness: Witness::ScrapeProvenanceSoundness(witness),
axon_version: axon_version.to_string(),
}],
None => Vec::new(),
}
}
const DOC_TARGETS: &[&str] = &["docx", "pptx", "xlsx"];
fn doc_assertive_slot(kind: &str) -> Option<&'static str> {
match kind {
"para" | "notes" | "footnote" | "heading" => Some("text"),
"table" => Some("rows"),
"chart" => Some("series"),
"formula" => Some("expr"),
"bullets" => Some("items"),
"placeholder" => Some("text"),
"row" => Some("cells"),
_ => None,
}
}
fn effect_base_doc(e: &str) -> &str {
e.split_once(':').map(|(b, _)| b).unwrap_or(e)
}
fn walk_doc_blocks_for_barrier(
doc_name: &str,
blocks: &[crate::ir_nodes::IRDocBlock],
epistemic_ok: bool,
out: &mut Vec<String>,
) {
for b in blocks {
if let Some(slot) = doc_assertive_slot(&b.kind) {
if let Some(f) = b.fields.iter().find(|f| f.name == slot) {
if f.kind == "ref" {
let attributed = b.fields.iter().any(|x| x.name == "attribute");
if !attributed && !epistemic_ok {
out.push(format!("{doc_name}.{}.{slot}", b.kind));
}
}
}
}
walk_doc_blocks_for_barrier(doc_name, &b.children, epistemic_ok, out);
}
}
pub fn derive_document_provenance_soundness_witness(
ir: &IRProgram,
) -> Option<DocumentProvenanceSoundnessWitness> {
if ir.documents.is_empty() {
return None;
}
let documents: Vec<(String, String)> = ir
.documents
.iter()
.map(|d| (d.name.clone(), d.target.clone()))
.collect();
let mut bad_targets = Vec::new();
let mut sensitive_without_legal = Vec::new();
let mut unattributed_slots = Vec::new();
for d in &ir.documents {
if !DOC_TARGETS.contains(&d.target.as_str()) {
bad_targets.push(d.name.clone());
}
let has_sensitive = d.effect_row.iter().any(|e| effect_base_doc(e) == "sensitive");
let has_legal = d.effect_row.iter().any(|e| effect_base_doc(e) == "legal");
if has_sensitive && !has_legal {
sensitive_without_legal.push(d.name.clone());
}
let epistemic_ok = matches!(d.epistemic_mode.as_str(), "believe" | "know");
walk_doc_blocks_for_barrier(&d.name, &d.blocks, epistemic_ok, &mut unattributed_slots);
}
Some(DocumentProvenanceSoundnessWitness {
documents,
bad_targets,
sensitive_without_legal,
unattributed_slots,
})
}
pub fn generate_document_provenance_soundness_proofs(
ir: &IRProgram,
axon_version: &str,
) -> Vec<ProofTerm> {
match derive_document_provenance_soundness_witness(ir) {
Some(witness) => vec![ProofTerm {
property: PropertyClass::DocumentProvenanceSoundness,
artifact_digest: artifact_digest(ir),
witness: Witness::DocumentProvenanceSoundness(witness),
axon_version: axon_version.to_string(),
}],
None => Vec::new(),
}
}
pub fn derive_document_ingestion_soundness_witness(
ir: &IRProgram,
) -> Option<DocumentIngestionSoundnessWitness> {
let ingest_class = |t: &crate::ir_nodes::IRToolSpec| -> Option<String> {
t.effect_row.iter().find_map(|e| e.strip_prefix("ingest:").map(|c| c.to_string()))
};
let ingest_tools: Vec<(String, String)> = ir
.tools
.iter()
.filter_map(|t| ingest_class(t).map(|c| (t.name.clone(), c)))
.collect();
if ingest_tools.is_empty() {
return None;
}
let mut inferred_ceiling_violations = Vec::new();
for t in &ir.tools {
let has_inferred = t.effect_row.iter().any(|e| e == "ingest:inferred");
let has_know = t.effect_row.iter().any(|e| e == "epistemic:know");
if has_inferred && has_know {
inferred_ceiling_violations.push(t.name.clone());
}
}
let ingest_tool_names: std::collections::HashSet<String> = ir
.tools
.iter()
.filter(|t| ingest_class(t).is_some())
.map(|t| t.name.clone())
.collect();
let is_ingest_tool = |name: &str| ingest_tool_names.contains(name);
let shielded_agents: std::collections::HashSet<String> = ir
.agents
.iter()
.filter(|a| !a.shield_ref.is_empty())
.map(|a| a.name.clone())
.collect();
let mut unshielded_flows = Vec::new();
for flow in &ir.flows {
let (mut producer, mut unshielded_belief, mut shield_present) = (false, false, false);
walk_ir_for_injection(
&flow.steps,
&is_ingest_tool,
&shielded_agents,
&mut producer,
&mut unshielded_belief,
&mut shield_present,
);
if producer && unshielded_belief && !shield_present {
unshielded_flows.push(flow.name.clone());
}
}
Some(DocumentIngestionSoundnessWitness {
ingest_tools,
inferred_ceiling_violations,
unshielded_flows,
})
}
pub fn generate_document_ingestion_soundness_proofs(
ir: &IRProgram,
axon_version: &str,
) -> Vec<ProofTerm> {
match derive_document_ingestion_soundness_witness(ir) {
Some(witness) => vec![ProofTerm {
property: PropertyClass::DocumentIngestionSoundness,
artifact_digest: artifact_digest(ir),
witness: Witness::DocumentIngestionSoundness(witness),
axon_version: axon_version.to_string(),
}],
None => Vec::new(),
}
}
const FORGE_MODES: &[&str] = &["combinatorial", "exploratory", "transformational"];
fn collect_forge_blocks<'a>(
steps: &'a [axon_frontend::ir_nodes::IRFlowNode],
flow_name: &str,
out: &mut Vec<(String, &'a axon_frontend::ir_nodes::IRForgeBlock)>,
) {
use axon_frontend::ir_nodes::IRFlowNode;
for step in steps {
match step {
IRFlowNode::Forge(f) => out.push((flow_name.to_string(), f)),
IRFlowNode::Conditional(c) => {
collect_forge_blocks(&c.then_body, flow_name, out);
collect_forge_blocks(&c.else_body, flow_name, out);
}
IRFlowNode::ForIn(f) => collect_forge_blocks(&f.body, flow_name, out),
_ => {}
}
}
}
pub(crate) fn __collect_forge_for_check<'a>(
steps: &'a [axon_frontend::ir_nodes::IRFlowNode],
out: &mut Vec<&'a axon_frontend::ir_nodes::IRForgeBlock>,
) {
use axon_frontend::ir_nodes::IRFlowNode;
for step in steps {
match step {
IRFlowNode::Forge(f) => out.push(f),
IRFlowNode::Conditional(c) => {
__collect_forge_for_check(&c.then_body, out);
__collect_forge_for_check(&c.else_body, out);
}
IRFlowNode::ForIn(f) => __collect_forge_for_check(&f.body, out),
_ => {}
}
}
}
pub fn derive_forge_soundness_witness(
flow_name: &str,
forge: &axon_frontend::ir_nodes::IRForgeBlock,
ir: &IRProgram,
) -> ForgeSoundnessWitness {
let mode_ok = forge.mode.is_empty() || FORGE_MODES.contains(&forge.mode.as_str());
let novelty_in_range = forge.novelty >= 0.0 && forge.novelty <= 1.0;
let bounds_ok = forge.depth >= 1 && forge.branches >= 1;
let seed_and_type_present =
!forge.seed.trim().is_empty() && !forge.output_type.trim().is_empty();
let constraints_ok = forge.constraints_ref.is_empty()
|| ir
.anchors
.iter()
.any(|a| a.name == forge.constraints_ref && a.confidence_floor.is_some());
ForgeSoundnessWitness {
forge_name: forge.name.clone(),
flow_name: flow_name.to_string(),
mode: forge.mode.clone(),
novelty_milli: (forge.novelty * 1000.0).round() as i64,
depth: forge.depth,
branches: forge.branches,
constraints_ref: forge.constraints_ref.clone(),
mode_ok,
novelty_in_range,
bounds_ok,
seed_and_type_present,
constraints_ok,
}
}
pub fn generate_forge_soundness_proofs(ir: &IRProgram, axon_version: &str) -> Vec<ProofTerm> {
let digest = artifact_digest(ir);
let mut forges = Vec::new();
for flow in &ir.flows {
collect_forge_blocks(&flow.steps, &flow.name, &mut forges);
}
forges
.into_iter()
.map(|(flow_name, forge)| ProofTerm {
property: PropertyClass::ForgeSoundness,
artifact_digest: digest.clone(),
witness: Witness::ForgeSoundness(derive_forge_soundness_witness(&flow_name, forge, ir)),
axon_version: axon_version.to_string(),
})
.collect()
}
const SAVANT_DEPTHS: &[&str] = &["standard", "deep", "hyper"];
const SAVANT_DIVERGENCES: &[&str] = &["low", "med", "high"];
pub fn derive_savant_soundness_witness(
savant: &axon_frontend::ir_nodes::IRSavant,
ir: &IRProgram,
) -> SavantSoundnessWitness {
let domain_present = !savant.domain.trim().is_empty();
let mandate_ok = !savant.mandates.is_empty()
&& savant
.mandates
.iter()
.all(|m| !m.objective.trim().is_empty() && !m.output_type.trim().is_empty());
let max_iterations = savant
.budget
.as_ref()
.and_then(|b| b.max_iterations)
.unwrap_or(0);
let budget_bounded = max_iterations > 0;
let cognition_ok = savant.cognition.as_ref().is_none_or(|c| {
let depth_ok = c.depth.is_empty() || SAVANT_DEPTHS.contains(&c.depth.as_str());
let div_ok = c.divergence.is_empty() || SAVANT_DIVERGENCES.contains(&c.divergence.as_str());
let thr_ok = c.entropic_threshold.is_none_or(|t| t > 0.0);
depth_ok && div_ok && thr_ok
});
let memory_ref_ok = savant.memory.as_ref().is_none_or(|m| {
m.backend.is_empty()
|| ir.memories.iter().any(|x| x.name == m.backend)
|| ir.corpus_specs.iter().any(|x| x.name == m.backend)
});
SavantSoundnessWitness {
savant_name: savant.name.clone(),
mandate_count: savant.mandates.len() as i64,
max_iterations,
domain_present,
mandate_ok,
budget_bounded,
cognition_ok,
memory_ref_ok,
}
}
pub fn generate_savant_soundness_proofs(ir: &IRProgram, axon_version: &str) -> Vec<ProofTerm> {
let digest = artifact_digest(ir);
ir.savants
.iter()
.map(|savant| ProofTerm {
property: PropertyClass::SavantSoundness,
artifact_digest: digest.clone(),
witness: Witness::SavantSoundness(derive_savant_soundness_witness(savant, ir)),
axon_version: axon_version.to_string(),
})
.collect()
}
const SCOPE_DEPTHS: &[&str] = &["static_artifact", "memory_dump", "live_network"];
fn collect_warden_blocks<'a>(
steps: &'a [axon_frontend::ir_nodes::IRFlowNode],
flow_name: &str,
out: &mut Vec<(String, &'a axon_frontend::ir_nodes::IRWarden)>,
) {
use axon_frontend::ir_nodes::IRFlowNode;
for step in steps {
match step {
IRFlowNode::Warden(w) => {
out.push((flow_name.to_string(), w));
collect_warden_blocks(&w.body, flow_name, out);
}
IRFlowNode::Conditional(c) => {
collect_warden_blocks(&c.then_body, flow_name, out);
collect_warden_blocks(&c.else_body, flow_name, out);
}
IRFlowNode::ForIn(f) => collect_warden_blocks(&f.body, flow_name, out),
IRFlowNode::Quant(q) => collect_warden_blocks(&q.body, flow_name, out),
_ => {}
}
}
}
pub(crate) fn __collect_warden_for_check<'a>(
steps: &'a [axon_frontend::ir_nodes::IRFlowNode],
out: &mut Vec<&'a axon_frontend::ir_nodes::IRWarden>,
) {
use axon_frontend::ir_nodes::IRFlowNode;
for step in steps {
match step {
IRFlowNode::Warden(w) => {
out.push(w);
__collect_warden_for_check(&w.body, out);
}
IRFlowNode::Conditional(c) => {
__collect_warden_for_check(&c.then_body, out);
__collect_warden_for_check(&c.else_body, out);
}
IRFlowNode::ForIn(f) => __collect_warden_for_check(&f.body, out),
IRFlowNode::Quant(q) => __collect_warden_for_check(&q.body, out),
_ => {}
}
}
}
pub fn derive_warden_soundness_witness(
flow_name: &str,
warden: &axon_frontend::ir_nodes::IRWarden,
ir: &IRProgram,
) -> WardenSoundnessWitness {
let scope = ir.scopes.iter().find(|s| s.name == warden.scope_ref);
WardenSoundnessWitness {
warden_target: warden.target.clone(),
flow_name: flow_name.to_string(),
scope_ref: warden.scope_ref.clone(),
scope_resolves: scope.is_some(),
targets_nonempty: scope.is_some_and(|s| !s.targets.is_empty()),
depth_ok: scope
.is_some_and(|s| s.depth.is_empty() || SCOPE_DEPTHS.contains(&s.depth.as_str())),
approver_present: scope.is_some_and(|s| !s.approver.trim().is_empty()),
}
}
pub fn generate_warden_soundness_proofs(ir: &IRProgram, axon_version: &str) -> Vec<ProofTerm> {
let digest = artifact_digest(ir);
let mut wardens = Vec::new();
for flow in &ir.flows {
collect_warden_blocks(&flow.steps, &flow.name, &mut wardens);
}
wardens
.into_iter()
.map(|(flow_name, warden)| ProofTerm {
property: PropertyClass::WardenSoundness,
artifact_digest: digest.clone(),
witness: Witness::WardenSoundness(derive_warden_soundness_witness(
&flow_name, warden, ir,
)),
axon_version: axon_version.to_string(),
})
.collect()
}
pub fn generate_all_proofs(ir: &IRProgram, axon_version: &str) -> Vec<ProofTerm> {
let mut proofs = Vec::new();
proofs.extend(generate_compliance_coverage_proofs(ir, axon_version));
proofs.extend(generate_effect_row_soundness_proofs(ir, axon_version));
proofs.extend(generate_capability_isolation_proofs(ir, axon_version));
proofs.extend(generate_resource_bounds_proofs(ir, axon_version));
proofs.extend(generate_shield_halt_guarantee_proofs(ir, axon_version));
proofs.extend(generate_capability_containment_proofs(ir, axon_version));
proofs.extend(generate_tool_call_soundness_proofs(ir, axon_version));
proofs.extend(generate_effect_budgeted_proofs(ir, axon_version));
proofs.extend(generate_json_shape_soundness_proofs(ir, axon_version));
proofs.extend(generate_channel_delivery_soundness_proofs(ir, axon_version));
proofs.extend(generate_aggregate_soundness_proofs(ir, axon_version));
proofs.extend(generate_channel_egress_soundness_proofs(ir, axon_version));
proofs.extend(generate_interruptible_session_soundness_proofs(ir, axon_version));
proofs.extend(generate_parked_residual_soundness_proofs(ir, axon_version));
proofs.extend(generate_upstream_projection_soundness_proofs(ir, axon_version));
proofs.extend(generate_cors_policy_consistency_proofs(ir, axon_version));
proofs.extend(generate_technician_command_safety_proofs(ir, axon_version));
proofs.extend(generate_cache_soundness_proofs(ir, axon_version));
proofs.extend(generate_scrape_provenance_soundness_proofs(ir, axon_version));
proofs.extend(generate_document_provenance_soundness_proofs(ir, axon_version));
proofs.extend(generate_document_ingestion_soundness_proofs(ir, axon_version));
proofs.extend(generate_forge_soundness_proofs(ir, axon_version));
proofs.extend(generate_savant_soundness_proofs(ir, axon_version));
proofs.extend(generate_warden_soundness_proofs(ir, axon_version));
proofs.extend(generate_authorization_coverage_proofs(ir, axon_version));
proofs.extend(generate_temporal_context_soundness_proofs(ir, axon_version));
proofs.extend(generate_credential_attenuation_proofs(ir, axon_version));
proofs.extend(generate_secret_custody_proofs(ir, axon_version));
proofs
}