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