#![forbid(unsafe_code)]
#[cfg(feature = "dv25")]
use logline_core as _;
use serde::{Deserialize, Serialize};
use tdln_compiler::CompiledIntent;
use thiserror::Error;
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum Decision {
Allow,
Deny,
NeedsConsent,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LogEvent {
pub kind: String,
pub payload: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GateOutput {
pub decision: Decision,
pub audit: serde_json::Value,
pub proof_ref: [u8; 32],
pub events: Vec<LogEvent>,
}
#[derive(Debug, Error)]
pub enum GateError {
#[error("invalid input")]
Invalid,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PolicyCtx {
pub allow_freeform: bool,
}
pub fn preflight(intent: &CompiledIntent, _ctx: &PolicyCtx) -> Result<GateOutput, GateError> {
let audit = serde_json::json!({
"ast_cid": hex::encode(intent.proof.ast_cid),
"canon_cid": hex::encode(intent.proof.canon_cid),
});
let events = vec![LogEvent {
kind: "policy.preflight".into(),
payload: audit.clone(),
}];
Ok(GateOutput {
decision: Decision::NeedsConsent,
audit,
proof_ref: intent.cid,
events,
})
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Consent {
pub accepted: bool,
}
pub fn decide(
intent: &CompiledIntent,
consent: &Consent,
ctx: &PolicyCtx,
) -> Result<GateOutput, GateError> {
let mut out = preflight(intent, ctx)?;
let decision = if consent.accepted && ctx.allow_freeform {
Decision::Allow
} else if !consent.accepted {
Decision::NeedsConsent
} else {
Decision::Deny
};
out.events.push(LogEvent {
kind: "policy.decision".into(),
payload: serde_json::json!({ "decision": format!("{:?}", decision) }),
});
out.decision = decision;
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
use tdln_compiler::{compile, CompileCtx};
#[test]
fn pipeline() {
let ctx = CompileCtx {
rule_set: "v1".into(),
};
let compiled = compile("hello world", &ctx).unwrap();
let gctx = PolicyCtx {
allow_freeform: true,
};
let pf = preflight(&compiled, &gctx).unwrap();
assert_eq!(pf.decision, Decision::NeedsConsent);
let dec = decide(&compiled, &Consent { accepted: true }, &gctx).unwrap();
assert_eq!(dec.decision, Decision::Allow);
assert!(dec.events.len() >= 2);
}
}