# Policies A policy is a module that answers one question, "allow this?", with a verdict: `{ "allow": bool, "violations": [{ "msg", "policyId" }] }`. Whoever runs a module decides which policies guard it; the module itself takes no policy input. ## A policy on an edge The simplest policy guards one edge of a composition. `compose`'s `even` allows calls whose `n` is even: `examples/rust/compose/src/lib.rs` ```rust fn even_handler(run: Run) -> Result { let n = run.args["call"]["input"]["n"].as_i64(); let allow = n.is_some_and(|n| n % 2 == 0); let violations = if allow { json!([]) } else { json!([{ "msg": format!("n {} is not even", n.map_or("missing".into(), |n| n.to_string())), "policyId": "even" }]) }; Ok(Handle::new().result(json!({ "allow": allow, "violations": violations })).done()) } ``` It is named in the edge's `policies` ([Compositions](compositions.md)); a deny fails the call with `policy even denied: …` and the callee never runs. ## A policy module A reusable policy implements a contract, `com.publicdomainrelay.temp.market.evaluatePolicy`: the socialweb.computer market's input (who is asking, about whom, in `scope` or `full` mode, with the RFP, bid and accept records) and the verdict as output. The market's modules live in [atlibs-socialweb-computer-requester](knotless://johnandersen777.bsky.social/atlibs-socialweb-computer-requester); `modules/policy-open/module.json` there declares it: ```json norun "implements": [ { "nsid": "com.publicdomainrelay.temp.market.evaluatePolicy", "entry": "evaluate" } ] ``` ## Let a module be guarded: an `all-allow` hook A module that must check a policy declares the contract as a hook with `combine: "all-allow"` and a `default`. The socialweb.computer requester does, before it takes a bid (`modules/requester/module.json`): ```json norun "nsid": "com.publicdomainrelay.temp.market.evaluatePolicy", "required": true, "combine": "all-allow", "default": "policy-requester-tangled-vouch", "reason": "kcp: each bid's bidder in scope mode, then the chosen winner in full mode with the rfp and bid refs, before the accept; every routed policy must allow" ``` - No route: the host runs the `default` module, so the requester never runs unguarded. - Routes: every routed policy runs in edge order, and the first deny is the answer. An error or a malformed answer is a deny naming the route. - A route is an instance whose module implements the contract, or a caller handler (`--handler approve=wasm:#`, `https://…`, `exec:…`; in JS `handlers: { approve }`). Two policies that both must allow (`modules/requester/policy-composition.json`): ```json norun "instances": [ { "name": "requester", "module": "requester", "session": "requester" }, { "name": "vouch", "module": "policy-requester-tangled-vouch" }, { "name": "payload", "module": "policy-bid-payload" } ], "edges": [ { "from": "requester", "nsid": "com.publicdomainrelay.temp.market.evaluatePolicy", "to": "vouch", "reason": "kcp requester-tangled-vouch: the requester or its operator vouched for the bidder" }, { "from": "requester", "nsid": "com.publicdomainrelay.temp.market.evaluatePolicy", "to": "payload", "reason": "kcp bid-payload: only bids with an allowed payload (default: free)" } ], "limits": { "depth": 4, "calls": 40 } ``` ## Policies an RFP attaches On the market an RFP can name more policies. `rfpPolicyRoutes(manifest, from, records)` adds one instance and edge per attached policy record after the module's own, so its own policy still runs first and the RFP's are ANDed after it. A record naming a policy no module implements fails closed. A record's `args` are the instance's input, so a policy module declares `config: [{ "key": "args", "label": "Arguments", "type": "object" }]`. `javascript/src/compose/index.js` ```js * Add the policies an RFP attaches to `from`'s policy hook, after the routes it has: pass a * loaded manifest (loadComposition(...).manifest: its default routes the hook), so the requester's or the * bidder's own policy still runs, first, and the RFP's are ANDed after it (combine all-allow). Each * computer.socialweb.temp.policy.ghalite record's `name` (kcp-canonicalised) is the module policy- (the * same publisher's record for an AT-URI instance), its `args` the instance's input.args. Throws (fail closed) * for a hook not routed yet, a record that is not an object, or a name no policy module implements. * @param {object} manifest a loaded composition's manifest * @param {string} from the instance whose hook (com.publicdomainrelay.temp.market.evaluatePolicy, combine all-allow) they serve * @param {object[]} records the attached policy records' values * @returns {object} the manifest with one instance and edge per record added after its own; load it again to run it */ export function rfpPolicyRoutes(manifest, from, records) { ``` ## The market policies The socialweb.computer market's policies (`policy-open`, `policy-requester-only-me`, `policy-requester-mutuals`, `policy-requester-tangled-vouch`, `policy-bidder-only-me`, `policy-bidder-mutuals`, `policy-bidder-tangled-vouch`, `policy-bid-payload`, `policy-under-4-cpus`) share their logic in `crates/policy` of [atlibs-socialweb-computer-requester](knotless://johnandersen777.bsky.social/atlibs-socialweb-computer-requester), with the requester that uses them and their tests. Previous: [Compositions](compositions.md) | Next: [Transports](transports.md)