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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
// node --test javascript/src/compose/compose.test.mjs (needs `cargo wasm` and `cargo host`; no network)
import assert from "node:assert/strict";
import { mkdtemp, readFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { test } from "node:test";
import { pathToFileURL } from "node:url";
import { atlibs } from "../../../rust/host/conformance.mjs";
import { loadComposition, runCompose, staged, validateComposition } from "./index.js";
const wasm = new Uint8Array(await readFile(new URL("../../../target/wasm32-unknown-unknown/release/compose.wasm", import.meta.url)));
const spec = JSON.parse(await readFile(new URL("../../../examples/rust/compose/module.json", import.meta.url), "utf8"));
const LOOKUP = "com.example.compose.lookup";
// ctx.wasm.run for any module bytes, in process.
const instances = new Map();
const ctx = {
wasm: {
run: async ({ module, entry, input }) => {
if (!instances.has(module)) instances.set(module, new WebAssembly.Instance(new WebAssembly.Module(module), {}).exports);
const exports = instances.get(module);
const data = new TextEncoder().encode(JSON.stringify(input));
const ptr = exports.alloc(data.length);
new Uint8Array(exports.memory.buffer, ptr, data.length).set(data);
const packed = BigInt.asUintN(64, exports[entry](ptr, data.length));
return JSON.parse(new TextDecoder().decode(new Uint8Array(exports.memory.buffer, Number(packed >> 32n), Number(packed & 0xffffffffn))));
},
},
};
// The depth and cycle tests route x.y.a, x.y.b, x.y.c, x.y.never between instances: hooks of the module.
const hooks = [...spec.hooks, ...["x.y.a", "x.y.b", "x.y.c", "x.y.never"].map((nsid) => ({ nsid, reason: "a test route" }))];
// ...and serve them at caller
const implementsAll = [...spec.implements, ...["x.y.a", "x.y.b", "x.y.c", "x.y.never"].map((nsid) => ({ nsid, entry: "caller" }))];
const load = async (key) => ({ ...spec, hooks, implements: implementsAll, module: wasm, ...(key === "compose-net" && { calls: ["com.example.compose.net"] }), ...(key === "compose-http" && { calls: ["com.fedfork.exec.http.fetch"], http: ["https://svc.example"] }) });
const compose = (manifest) => loadComposition(ctx, { name: "test", ...manifest }, { load });
const toy = (name, extra) => ({ name, module: "compose", ...extra });
const edge = (from, nsid, to, extra) => ({ from, nsid, to, reason: `${from} asks ${to}`, ...extra });
test("A calls B over an edge and B calls A's callback; the answer comes back", async () => {
const comp = await compose({ instances: [toy("a"), toy("b")], edges: [edge("a", LOOKUP, "b")] });
const ran = await runCompose(ctx, comp, "a", "caller", { nsid: LOOKUP, input: { n: 21 } });
assert.equal(ran.error, undefined);
assert.deepEqual(ran.result, {
q: { ok: true, output: { doubled: 42, ack: { seen: { step: 1 } }, caller: { instance: "a", nsid: LOOKUP, reason: "a asks b" } } },
progress: [{ step: 1 }],
});
assert.deepEqual(ran.graph.map(({ from, nsid, to, ok }) => [from, nsid, to, ok]), [
["a", "cap", undefined, true], // the reply
["b", "cap", undefined, true], // the progress callback
["a", LOOKUP, "b", true],
]);
});
test("a caller handler gets the call, and the module's callback as a function", async () => {
let late;
const comp = await compose({
instances: [toy("a")],
handlers: [{ name: "confirm" }],
edges: [{ from: "a", nsid: "com.example.compose.confirm", handler: "confirm", reason: "the user approves" }],
});
const seen = [];
const ran = await runCompose(ctx, comp, "a", "caller", { nsid: "com.example.compose.confirm", input: { n: 1 } }, {
handlers: {
confirm: async ({ n, progress }, { caller, reason }) => {
seen.push([n, caller, reason]);
late = progress;
return { approved: true, ack: await progress({ asked: n }) };
},
},
});
assert.deepEqual(seen, [[1, "a", "the user approves"]]);
assert.deepEqual(ran.result, { q: { ok: true, output: { approved: true, ack: { seen: { asked: 1 } } } }, progress: [{ asked: 1 }] });
await assert.rejects(late({}), /capability revoked/);
});
test("JS functions in the root input are callbacks the root may call; forged tokens are refused", async () => {
const comp = await compose({ instances: [toy("b")] });
const ran = await runCompose(ctx, comp, "b", "lookup", { n: 2, progress: async (data) => ({ host: data }) });
assert.deepEqual(ran.result, { doubled: 4, ack: { host: { step: 1 } }, caller: null });
const forged = await runCompose(ctx, comp, "b", "lookup", { n: 2, progress: { $cap: `cap.${"0".repeat(32)}.1` } });
assert.match(forged.result.ack.error, /is not a capability this run holds/);
});
test("a policy module allows or denies the call before it is made", async () => {
const comp = await compose({
instances: [toy("a"), toy("b"), toy("even")],
edges: [edge("a", LOOKUP, "b", { policies: [{ instance: "even", entry: "even" }] })],
});
const denied = await runCompose(ctx, comp, "a", "caller", { nsid: LOOKUP, input: { n: 21 } });
assert.deepEqual(denied.result.q, { ok: false, error: "policy even denied: n 21 is not even" });
assert.ok(!denied.graph.some((g) => g.to === "b" && g.ok));
const allowed = await runCompose(ctx, comp, "a", "caller", { nsid: LOOKUP, input: { n: 4 } });
assert.equal(allowed.result.q.output.doubled, 8);
assert.deepEqual(allowed.graph.find((g) => g.policy), { from: "a", nsid: LOOKUP, policy: "even", ok: true });
});
test("a policy run does not hold the caller's capabilities", async () => {
const comp = await compose({
instances: [toy("a"), toy("b"), toy("spy")],
edges: [edge("a", LOOKUP, "b", { policies: [{ instance: "spy", entry: "spy" }] })],
});
const invoked = [];
const ran = await runCompose(ctx, comp, "a", "caller", { nsid: LOOKUP, input: { n: 4, host: async (data) => (invoked.push(data), "secret") } });
assert.deepEqual(invoked, [], "the policy could not invoke the root's host function");
assert.equal(ran.result.q.output.doubled, 8);
assert.deepEqual(ran.graph.find((g) => g.policy), { from: "a", nsid: LOOKUP, policy: "spy", ok: true });
assert.ok(ran.graph.some((g) => g.from === "spy" && g.nsid === "cap" && g.error === "not held"));
});
test("the host's budget and limits cap the manifest's", async () => {
// limits: the smaller of the host's and the manifest's, whichever sets it lower
const deep = await compose({
instances: [toy("a"), toy("b"), toy("c")],
edges: [edge("a", "x.y.b", "b"), edge("b", "x.y.c", "c")],
limits: { depth: 50 },
});
const capped = await runCompose(ctx, deep, "a", "caller", { nsid: "x.y.b", input: { nsid: "x.y.c", input: { nsid: "x.y.d" } } }, { limits: { depth: 2 } });
assert.deepEqual(capped.result.q.output.q, { ok: false, error: "composition exceeded depth 2" });
const lowered = await runCompose(ctx, { ...deep, manifest: { ...deep.manifest, limits: { depth: 2 } } }, "a", "caller", { nsid: "x.y.b", input: { nsid: "x.y.c", input: { nsid: "x.y.d" } } }, { limits: { depth: 50 } });
assert.deepEqual(lowered.result.q.output.q, { ok: false, error: "composition exceeded depth 2" });
const slow = await compose({
instances: [toy("w", { budget: { wallMs: 1e9 } })],
handlers: [{ name: "hang" }],
edges: [{ from: "w", nsid: "x.y.never", handler: "hang", reason: "never answers" }],
});
const timed = await runCompose(ctx, slow, "w", "caller", { nsid: "x.y.never" }, { budget: { wallMs: 50 }, handlers: { hang: () => new Promise(() => {}) } });
assert.match(String(timed.error), /exceeded 50 ms/, "a manifest budget cannot raise the host's wallMs");
// only positive finite numbers count: a host value that is not one leaves the manifest's (or the default)
const own = await compose({ ...slow.manifest, instances: [toy("w", { budget: { wallMs: 50 } })] });
const ownTimed = await runCompose(ctx, own, "w", "caller", { nsid: "x.y.never" }, { budget: { wallMs: "lots", calls: -1 }, handlers: { hang: () => new Promise(() => {}) } });
assert.match(String(ownTimed.error), /exceeded 50 ms/, "with no usable host value the manifest's own applies");
for (const budget of [{ calls: "1" }, { calls: 0 }, { wallMs: Infinity }, { wallMs: "unlimited" }, { nope: 1 }, [1]]) {
assert.throws(() => validateComposition({ instances: [toy("a", { budget })] }), /budget must map/, JSON.stringify(budget));
}
// unlimited is the host's alone: a manifest cannot say it nor raise past a finite host limit, and under an
// unlimited host its own lower limit still holds
for (const limits of [{ calls: Infinity }, { calls: "unlimited" }]) {
assert.throws(() => validateComposition({ instances: [toy("a")], limits }), /limits must map/, String(limits.calls));
}
const chain = { nsid: "x.y.b", input: { nsid: "x.y.c", input: { nsid: "x.y.d" } } };
const raised = await runCompose(ctx, { ...deep, manifest: { ...deep.manifest, limits: { calls: 50 } } }, "a", "caller", chain, { limits: { calls: 1 } });
assert.match(raised.result.q.output.q.error, /composition exceeded 1 calls/);
const under = await runCompose(ctx, { ...deep, manifest: { ...deep.manifest, limits: { calls: 1 } } }, "a", "caller", chain, { limits: { calls: Infinity } });
assert.match(under.result.q.output.q.error, /composition exceeded 1 calls/);
const free = await runCompose(ctx, deep, "a", "caller", chain, { limits: { calls: Infinity }, budget: { calls: Infinity, wallMs: Infinity } });
assert.equal(free.error, undefined);
assert.doesNotMatch(JSON.stringify(free.result), /exceeded/);
assert.equal(free.result.q.output.q.output.q.ok, false, "c's call has no edge");
});
test("calls with no edge are refused unless allowlisted; allowlisted ones use the instance's session and policies", async () => {
const comp = await compose({
instances: [toy("a"), toy("n", { module: "compose-net", session: "net", policies: [{ instance: "even", entry: "even" }] }), toy("even")],
});
const refused = await runCompose(ctx, comp, "a", "caller", { nsid: "com.example.compose.net", input: { n: 2 } });
assert.match(refused.result.q.error, /a may not call com.example.compose.net: no edge/);
const sessions = [];
const callFor = (inst) => async (nsid, request) => (sessions.push([inst.session, nsid, request.input.n]), { via: inst.session });
const made = await runCompose(ctx, comp, "n", "caller", { nsid: "com.example.compose.net", input: { n: 2 } }, { callFor });
assert.deepEqual(made.result.q, { ok: true, output: { via: "net" } });
assert.deepEqual(sessions, [["net", "com.example.compose.net", 2]]);
const odd = await runCompose(ctx, comp, "n", "caller", { nsid: "com.example.compose.net", input: { n: 3 } }, { callFor });
assert.match(odd.result.q.error, /policy even denied/);
assert.equal(sessions.length, 1);
});
test("the host's http serviceAuth is minted with the instance's session (callFor), not a call the module makes", async () => {
const comp = await compose({ instances: [toy("h", { module: "compose-http", session: "s" })] });
const sent = [];
const httpCtx = { ...ctx, http: { fetch: async (url, init) => (sent.push([url, init.headers.authorization]), new Response("ok")) } };
const http = { "https://svc.example": { serviceAuth: { aud: "did:web:svc.example", lxm: "x.y.z" } } };
const fetch = { nsid: "com.fedfork.exec.http.fetch", input: { url: "https://svc.example/x" } };
const minted = [];
const callFor = (inst) => async (nsid, request) => (minted.push([inst.session, nsid, request.params]), { token: "T" });
const ran = await runCompose(httpCtx, comp, "h", "caller", fetch, { callFor, http });
assert.equal(ran.result.q.ok, true, JSON.stringify(ran.result));
assert.equal(ran.result.q.output.status, 200);
// the module lists no getServiceAuth and no edge routes it: the host mints it, as runModule does on its own
assert.deepEqual(minted, [["s", "com.atproto.server.getServiceAuth", { aud: "did:web:svc.example", lxm: "x.y.z" }]]);
assert.deepEqual(sent, [["https://svc.example/x", "Bearer T"]]);
// no session: no token, and nothing sent
const none = await runCompose(httpCtx, comp, "h", "caller", fetch, { http });
assert.match(none.result.q.error, /needs a service-auth token \(aud did:web:svc.example, lxm x.y.z\)/);
assert.equal(sent.length, 1);
});
test("cycles and depth past the limit are refused", async () => {
const comp = await compose({
instances: [toy("a"), toy("b"), toy("c")],
edges: [edge("a", "x.y.b", "b"), edge("b", "x.y.a", "a"), edge("b", "x.y.c", "c")],
});
const cycle = await runCompose(ctx, comp, "a", "caller", { nsid: "x.y.b", input: { nsid: "x.y.a", input: { n: 1 } } });
assert.deepEqual(cycle.result.q.output.q, { ok: false, error: "cycle: a -> b -> a" });
const deep = await runCompose(ctx, comp, "a", "caller", { nsid: "x.y.b", input: { nsid: "x.y.c", input: { nsid: "x.y.d" } } }, { limits: { depth: 2 } });
assert.deepEqual(deep.result.q.output.q, { ok: false, error: "composition exceeded depth 2" });
const few = await runCompose(ctx, comp, "a", "caller", { nsid: "x.y.b", input: { nsid: "x.y.c", input: { nsid: "x.y.d" } } }, { limits: { calls: 1 } });
assert.match(few.result.q.output.q.error, /composition exceeded 1 calls/);
});
test("manifests are checked: reasons, declared handlers, routes, pins", async () => {
assert.throws(() => validateComposition({ instances: [toy("a")], edges: [{ from: "a", nsid: LOOKUP, to: "a" }] }), /reason is required/);
assert.throws(() => validateComposition({ instances: [toy("a")], edges: [{ ...edge("a", LOOKUP, "a"), entry: "lookup" }] }), /edge a com.example.compose.lookup: an edge has no entry: it reaches the entry its target's module implements/);
assert.throws(() => validateComposition({ instances: [toy("a")], edges: [edge("a", LOOKUP, "zz")] }), /to is not an instance/);
assert.throws(() => validateComposition({ instances: [toy("a")], edges: [{ from: "a", nsid: LOOKUP, handler: "h", reason: "r" }] }), /not declared/);
await assert.rejects(compose({ instances: [toy("a", { pin: "0".repeat(64) })] }), /composition: instance a module does not match its pin/);
assert.throws(() => validateComposition({ instances: [toy("a", { pin: "deadbeef" })] }), /composition: instance a pin must be 64 lowercase hex characters/);
const comp = await compose({ instances: [toy("a")], handlers: [{ name: "confirm" }] });
await assert.rejects(runCompose(ctx, comp, "a", "caller", {}), /handler confirm was not supplied/);
});
test("an edge reaches the entry its target implements; a target that does not implement the NSID is refused", async () => {
const comp = await compose({ instances: [toy("a"), toy("b")], edges: [edge("a", LOOKUP, "b")] });
assert.deepEqual((await runCompose(ctx, comp, "a", "caller", { nsid: LOOKUP, input: { n: 2 } })).graph.at(-1), { from: "a", nsid: LOOKUP, to: "b", entry: "lookup", reason: "a asks b", ok: true });
const bare = (key) => ({ ...spec, implements: undefined, module: wasm, id: key });
await assert.rejects(loadComposition(ctx, { name: "t", instances: [toy("a"), toy("b")], edges: [edge("a", LOOKUP, "b")] }, { load: bare }),
/composition: edge a com.example.compose.lookup: b \(compose\) does not implement com.example.compose.lookup/);
});
test("an instance's input is its module's config: keys, types, parameter types, required fields", async () => {
const ok = await compose({ parameters: [{ key: "n", type: "integer" }], instances: [toy("a", { input: { n: { $param: "n" } } }), toy("b", { input: { n: 3 } })] });
assert.equal(ok.manifest.instances.length, 2);
const refused = async (manifest, re) => assert.rejects(compose(manifest), re);
await refused({ instances: [toy("a", { input: { m: 1 } })] }, /composition: instance a: input m is not in compose's config \(n\)/);
await refused({ instances: [toy("a", { input: { n: "3" } })] }, /composition: instance a: input n must be of type integer/);
await refused({ parameters: [{ key: "n" }], instances: [toy("a", { input: { n: { $param: "n" } } })] }, /composition: instance a: input n is integer, but parameter n is string/);
await refused({ instances: [toy("a", { input: [1] })] }, /composition: instance a: input must be an object/);
const config = (config) => async (key) => ({ ...(await load(key)), config });
await assert.rejects(loadComposition(ctx, { name: "t", instances: [toy("a", { input: { n: 1 } })] }, { load: config(undefined) }), /composition: instance a: input n is not in compose's config$/);
await assert.rejects(loadComposition(ctx, { name: "t", instances: [toy("a")] }, { load: config([{ key: "n", label: "n", required: true }]) }), /composition: instance a: input n is required by compose's config/);
await assert.rejects(loadComposition(ctx, { name: "t", instances: [toy("a")] }, { load: config([{ key: "n", type: "date" }]) }), /module config must be a list of \{ key, label, type\?/);
});
test("atlibs stage stages a composition; a host loads it back from the staged directory", async () => {
const out = await mkdtemp(join(tmpdir(), "atlibs-compose-"));
const manifest = new URL("../../../examples/rust/compose/composition.json", import.meta.url).pathname;
await atlibs(["stage", "--target", "server", "--out", out, manifest]);
const written = JSON.parse(await readFile(join(out, "composition.json"), "utf8"));
assert.deepEqual(written.instances.map((i) => [i.module, i.pin.length]), [["compose", 64], ["compose", 64], ["compose", 64]]);
assert.equal(written.target, "server");
assert.equal(JSON.parse(await readFile(join(out, "stage.json"), "utf8")).modules.compose.pin, written.instances[0].pin);
const fileFetch = async (url) => new Response(await readFile(new URL(url)));
const comp = await loadComposition(ctx, written, { load: staged(pathToFileURL(`${out}/`), fileFetch) });
const ran = await runCompose(ctx, comp, "ui", "caller", { nsid: "com.example.compose.lookup", input: { n: 6 } }, { handlers: { confirm: () => true } });
assert.equal(ran.result.q.output.doubled, 12);
await assert.rejects(atlibs(["stage", "--target", "edge", "--out", out, manifest]), /--target must be spa or server/);
});
test("staged() takes only module record fields: a staged json cannot pick its id or ledger owner", async () => {
const files = { "modules/m.json": JSON.stringify({ ...spec, id: "/etc/passwd", owner: "someone-else", uri: "at://did:plc:other/x/y", sha256: "00" }), "modules/m.wasm": wasm };
const base = "https://site.example/app/";
const mod = await staged(base, async (url) => new Response(files[url.href.slice(base.length)]))("m");
assert.equal(mod.id, "https://site.example/app/modules/m.wasm");
assert.equal(mod.owner, undefined);
assert.equal(mod.uri, undefined);
assert.equal(mod.sha256, undefined);
assert.deepEqual(mod.calls, spec.calls);
});
// Review 3b #3, #6 and the policy-stdin note, with a stub engine (ctx.wasm.run by entry).
const stub = (entries) => ({ wasm: { run: async ({ entry, input }) => entries[entry](input) } });
const ASK = "com.example.ask.x";
test("limits: depth and calls must be positive numbers; a manifest's bad values never disable the cap", async () => {
for (const limits of [{ calls: "x" }, { calls: null }, { depth: 0 }, { depth: Infinity }, { other: 1 }, [], "8"]) {
assert.throws(() => validateComposition({ instances: [{ name: "a", module: "m" }], limits }), /composition: limits must map depth, calls to positive numbers/, JSON.stringify(limits));
}
validateComposition({ instances: [{ name: "a", module: "m" }], limits: { depth: 3, calls: 10 } });
let handled = 0;
const go = ({ event }) => (event.type === "start" ? { calls: Array.from({ length: 150 }, (_, i) => ({ id: `c${i}`, nsid: ASK })) } : {});
const manifest = { instances: [{ name: "a", module: "m", budget: { calls: 500 } }], handlers: [{ name: "h" }], edges: [{ from: "a", nsid: ASK, handler: "h", reason: "r" }], limits: { calls: "x" } };
const ran = await runCompose(stub({ go }), { manifest, modules: { a: { module: new Uint8Array([1]) } } }, "a", "go", {}, { handlers: { h: async () => (handled++, 1) } });
assert.equal(handled, 100, "an unvalidated manifest's limits.calls 'x' leaves the default 100");
assert.match(ran.calls.find((c) => !c.ok).error, /composition exceeded 100 calls/);
});
test("subscribing to a held capability is refused before it runs", async () => {
let invoked = 0;
const closed = [];
const go = ({ event, ping }) => {
if (event.type === "start") return { calls: [{ id: "c", nsid: ping.$cap, subscribe: true, params: { hi: 1 } }] };
if (event.type === "closed" || event.type === "result") closed.push(event.error);
return {};
};
const ran = await runCompose(stub({ go }), { manifest: { instances: [{ name: "a", module: "m" }] }, modules: { a: { module: new Uint8Array([1]) } } }, "a", "go", { ping: async () => (invoked++, {}) });
assert.equal(invoked, 0);
assert.match(closed[0], /cap\.[0-9a-f]+\.\d+ cannot be subscribed to/);
assert.deepEqual(ran.graph, []);
});
test("a policy sees the call without its stdin; the handler still gets it", async () => {
const seen = {};
const entries = {
go: ({ event }) => (event.type === "start" ? { calls: [{ id: "x", nsid: ASK, input: { stdin: "tok", a: 1 } }] } : { result: event, done: true }),
evaluate: ({ call }) => ((seen.policy = call.input), { result: { allow: true }, done: true }),
};
const manifest = { instances: [{ name: "a", module: "m" }, { name: "p", module: "m" }], handlers: [{ name: "h" }], edges: [{ from: "a", nsid: ASK, handler: "h", reason: "r", policies: [{ instance: "p" }] }] };
const modules = { a: { module: new Uint8Array([1]) }, p: { module: new Uint8Array([1]) } };
const ran = await runCompose(stub(entries), { manifest, modules }, "a", "go", {}, { handlers: { h: async (input) => ((seen.handler = input), "ok") } });
assert.equal(ran.result.ok, true, ran.result.error);
assert.deepEqual(seen, { policy: { a: 1 }, handler: { stdin: "tok", a: 1 } });
});
test("the host's held values start the root's run", async () => {
const stub = { wasm: { run: async () => ({ result: { t: { $held: "t" } }, done: true }) } };
const comp = await loadComposition(stub, { name: "test", instances: [toy("a")] }, { load });
const ran = await runCompose(stub, comp, "a", "caller", {}, { held: { t: [1] } });
assert.equal(ran.error, undefined);
assert.deepEqual(ran.result, { t: [1] });
});