1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# 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<Value>) -> Result<Handle, String> {
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:<ref>#<entry>`,
`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-<name> (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)