~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/atlibs

0
0
main
116 lines · 5.4 KB Raw History
# 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)