1#![forbid(unsafe_code)]
7
8use serde::{Deserialize, Serialize};
9use tdln_compiler::CompiledIntent;
10use thiserror::Error;
11
12#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
13pub enum Decision {
14 Allow,
15 Deny,
16 NeedsConsent,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct LogEvent {
21 pub kind: String,
22 pub payload: serde_json::Value,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct GateOutput {
27 pub decision: Decision,
28 pub audit: serde_json::Value,
29 pub proof_ref: [u8; 32],
30 pub events: Vec<LogEvent>,
31}
32
33#[derive(Debug, Error)]
34pub enum GateError {
35 #[error("invalid input")]
36 Invalid,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct PolicyCtx {
41 pub allow_freeform: bool,
42}
43
44pub fn preflight(intent: &CompiledIntent, _ctx: &PolicyCtx) -> Result<GateOutput, GateError> {
45 let audit = serde_json::json!({
47 "ast_cid": hex::encode(intent.proof.ast_cid),
48 "canon_cid": hex::encode(intent.proof.canon_cid),
49 });
50 let events = vec![LogEvent {
51 kind: "policy.preflight".into(),
52 payload: audit.clone(),
53 }];
54 Ok(GateOutput {
55 decision: Decision::NeedsConsent,
56 audit,
57 proof_ref: intent.cid,
58 events,
59 })
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct Consent {
64 pub accepted: bool,
65}
66
67pub fn decide(
68 intent: &CompiledIntent,
69 consent: &Consent,
70 ctx: &PolicyCtx,
71) -> Result<GateOutput, GateError> {
72 let mut out = preflight(intent, ctx)?;
73 let decision = if consent.accepted && ctx.allow_freeform {
74 Decision::Allow
75 } else if !consent.accepted {
76 Decision::NeedsConsent
77 } else {
78 Decision::Deny
79 };
80 out.events.push(LogEvent {
81 kind: "policy.decision".into(),
82 payload: serde_json::json!({ "decision": format!("{:?}", decision) }),
83 });
84 out.decision = decision;
85 Ok(out)
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91 use tdln_compiler::{compile, CompileCtx};
92 #[test]
93 fn pipeline() {
94 let ctx = CompileCtx {
95 rule_set: "v1".into(),
96 };
97 let compiled = compile("hello world", &ctx).unwrap();
98 let gctx = PolicyCtx {
99 allow_freeform: true,
100 };
101 let pf = preflight(&compiled, &gctx).unwrap();
102 assert_eq!(pf.decision, Decision::NeedsConsent);
103 let dec = decide(&compiled, &Consent { accepted: true }, &gctx).unwrap();
104 assert_eq!(dec.decision, Decision::Allow);
105 assert!(dec.events.len() >= 2);
106 }
107}