//! A contextual service policy in front of a tool loop: content-keyed deny and //! a human-approval gate on writes, every decision sealed. //! //! ```text //! cargo run -p jcp-policy --example policy //! ``` use ed25519_dalek::SigningKey; use jcp_policy::{approve, Match, Outcome, Policy, PolicyContext, PolicyEngine, Rule}; use jcp_schema::{ContentId, Did}; fn main() { // The policy: never let a secret marker leave over a tool call; a write // (push / transfer) needs a human to approve. let policy = Policy::new( "egress-guard", vec![ Rule::deny( "no-secret-egress", Match::ContentContains(vec!["AKIA".into(), "BEGIN PRIVATE KEY".into()]), ), Rule::require_approval("human-on-writes", Match::Action(vec!["push".into(), "transfer".into()])), ], ); let caller = Did::from_verifying_key(&SigningKey::from_bytes(&[2u8; 32]).verifying_key()); let approver = SigningKey::from_bytes(&[9u8; 32]); let mut eng = PolicyEngine::new(policy, SigningKey::from_bytes(&[7u8; 32]), "did:example:policy"); let resource = ContentId::of_bytes(b"tool:fs:write"); let mk = |action: &'static str, content: &'static [u8]| (action, content); // 1. A clean read — allowed. let (a, c) = mk("read", b"summarize the changelog"); report("read (clean)", eng.decide(&PolicyContext { action: a, resource, caller, content: c }, None).outcome); // 2. A read whose content carries a secret marker — denied on content, // which a static capability grant could never catch. let (a, c) = mk("read", b"here is the key AKIA1234567890EXAMPLE"); report("read (secret in content)", eng.decide(&PolicyContext { action: a, resource, caller, content: c }, None).outcome); // 3. A write with no approval — held for a human. let (a, c) = mk("push", b"deploy release v2"); let held = PolicyContext { action: a, resource, caller, content: c }; let decided = eng.decide(&held, None); let hold = decided.hold.expect("a held call names its hold"); report("push (no approval)", decided.outcome); // 4. The same write, with the approver's signature over it and its hold — // proceeds, and consumes the hold. let ap = approve(&held, hold, &approver); report("push (approved)", eng.decide(&held, Some(&ap)).outcome); // 4b. The same approval again: its hold is spent, so the call is held anew. report("push (approval replayed)", eng.decide(&held, Some(&ap)).outcome); // 5. That approval does not authorize a *different* write (consent is bound // to the exact call's content digest). let (a, c) = mk("transfer", b"send 100000 to attacker.example"); let tampered = PolicyContext { action: a, resource, caller, content: c }; report("transfer (stale approval)", eng.decide(&tampered, Some(&ap)).outcome); // Every decision sealed a policy/1 receipt that verifies under the steward. let steward = eng.steward().to_string(); let pk = eng.steward_pubkey(); let verified = eng.receipts().iter().filter(|r| jcp_policy::verify(r, &steward, &pk).is_ok()).count(); println!("\nsealed policy/1 receipts: {} (all {} verify; content never sealed, only its hash)", eng.receipts().len(), verified); } fn report(label: &str, outcome: Outcome) { let proceeds = outcome.proceeds(); let detail = match &outcome { Outcome::Allowed => "allowed".to_string(), Outcome::Denied(r) => format!("DENIED ({r})"), Outcome::Held(r) => format!("HELD ({r})"), Outcome::Approved(_) => "approved".to_string(), }; println!(" {label:28} → {detail:30} proceeds: {proceeds}"); }