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
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { mkdtemp, readFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { test } from "node:test";
import { atlibs } from "../../../rust/host/conformance.mjs";
import { appRepo, appRepoAnswer, appRepoHandler, clientId, clientMetadata, clientMetadataHandler, composeRecord, pageClientId, enabledFeatures, grantedSessions, policyCall, requestScope, scopeAllows } from "./index.js";
const T = "com.publicdomainrelay.temp";
const ATIRC = "did:web:irc.fedfork.com#atirc";
// Session records: requester and atirc as small inline fixtures (repo writes with scope groups; rpc through a
// proxy), fedproxy is this repo's statusphere record.
const fixture = (name, groups) => ({
$type: "com.fedfork.exec.oauth.clientMetadata",
name,
clientMetadata: { client_id: `https://${name}.example/oauth-client-metadata.json`, client_name: name, client_uri: `https://${name}.example`, redirect_uris: [`https://${name}.example`], scope: ["atproto", ...Object.values(groups).flat()].join(" ") },
scopeGroups: Object.entries(groups).map(([key, scopes]) => ({ key, label: key, scopes })),
createdAt: "2026-11-08T00:00:00.000Z",
});
const records = {
requester: fixture("requester", {
associate: [`repo:${T}.badgeBlueKeys?action=create`],
market: [`repo:${T}.market.rfp?action=create&action=delete`, `rpc:${T}.market.submitRfp?aud=*`],
signal: [`repo:${T}.compute.events.guest.signalWebRTC?action=create&action=delete`],
}),
atirc: fixture("atirc", { read: [`rpc:chat.atirc.list?aud=did:web:irc.fedfork.com%23atirc`], post: [`rpc:chat.atirc.post?aud=did:web:irc.fedfork.com%23atirc`] }),
fedproxy: JSON.parse(await readFile(new URL("../../../records/statusphere.json", import.meta.url), "utf8")),
};
const create = (collection) => ["com.atproto.repo.createRecord", { input: { repo: "did:plc:me", collection, record: {} } }];
test("composeRecord unions scopes and keys scope groups by session", () => {
const composed = composeRecord({ requester: records.requester, atirc: records.atirc }, { name: "ui", clientId: "https://ui.example/oauth/ui/client-metadata.json", createdAt: "2026-11-08T00:00:00.000Z" });
const scopes = composed.clientMetadata.scope.split(" ");
assert.equal(scopes[0], "atproto");
assert.equal(scopes.filter((s) => s === "atproto").length, 1);
assert.ok(scopes.includes(`rpc:chat.atirc.post?aud=did:web:irc.fedfork.com%23atirc`));
assert.ok(scopes.includes(`repo:${T}.market.rfp?action=create&action=delete`));
assert.deepEqual(composed.scopeGroups.map((g) => g.key), ["requester.associate", "requester.market", "requester.signal", "atirc.read", "atirc.post"]);
assert.deepEqual(composed.clientMetadata.redirect_uris, ["https://ui.example"]);
});
test("clientMetadata rebases the template onto the site and forces a public DPoP client", () => {
const doc = clientMetadata(records.fedproxy, { clientId: "https://site.example/oauth/fedproxy/client-metadata.json", redirectUri: "https://site.example/" });
assert.equal(doc.client_uri, "https://site.example");
assert.equal(doc.client_id, "https://site.example/oauth/fedproxy/client-metadata.json");
assert.deepEqual(doc.redirect_uris, ["https://site.example/"]);
assert.equal(doc.token_endpoint_auth_method, "none");
assert.equal(doc.dpop_bound_access_tokens, true);
assert.throws(() => clientMetadata({ name: "x", clientMetadata: { client_id: "https://x.example/c.json", scope: "repo:a.b.c" } }, { clientId: "https://s.example/c", redirectUri: "https://s.example/" }), /atproto/);
});
test("clientId is the staged id online and the loopback client on localhost", () => {
const client = { clientId: "https://ui.example/oauth/ui/client-metadata.json", scope: "atproto rpc:chat.atirc.list?aud=*" };
assert.equal(clientId(client, new URL("https://ui.example/")), client.clientId);
const loopback = new URL(clientId(client, new URL("http://localhost:8000/ui/index.html")));
assert.equal(loopback.origin, "http://localhost");
assert.equal(loopback.searchParams.get("scope"), client.scope);
assert.equal(loopback.searchParams.get("redirect_uri"), "http://127.0.0.1:8000/ui/");
});
test("scopeAllows maps calls to repo and rpc permissions", () => {
const scope = records.requester.clientMetadata.scope;
assert.ok(scopeAllows(scope, ...create(`${T}.market.rfp`)));
assert.ok(!scopeAllows(scope, ...create("com.fedproxy.sshPublicKey")));
assert.ok(scopeAllows(scope, "com.atproto.repo.deleteRecord", { input: { collection: `${T}.market.rfp` } }), "the contract cleans up its records");
assert.ok(!scopeAllows(scope, "com.atproto.repo.deleteRecord", { input: { collection: `${T}.market.bid` } }));
assert.ok(!scopeAllows(scope, "com.atproto.repo.putRecord", { input: { collection: `${T}.market.rfp` } }), "put needs create and update");
assert.ok(scopeAllows(`atproto repo:${T}.market.rfp`, "com.atproto.repo.putRecord", { input: { collection: `${T}.market.rfp` } }));
assert.ok(scopeAllows(scope, `${T}.market.submitRfp`, { input: {}, service: "https://bidder.example:8443" }));
assert.ok(scopeAllows("atproto", "com.atproto.repo.listRecords", { params: {} }), "reads need no scope");
assert.ok(scopeAllows("atproto", "com.atproto.repo.getRecord", { params: {}, service: "did:plc:bidder" }));
assert.ok(scopeAllows("atproto", "com.fedfork.exec.attest", { input: {} }));
// getServiceAuth needs rpc:<lxm>?aud=<aud> (the PDS's rule for an OAuth session)
const cocore = "atproto rpc:dev.cocore.inference.dispatch?aud=did:web:cocore.dev";
const mint = (aud, lxm) => ["com.atproto.server.getServiceAuth", { params: { aud, lxm } }];
assert.ok(scopeAllows(cocore, ...mint("did:web:cocore.dev", "dev.cocore.inference.dispatch")));
assert.ok(!scopeAllows(cocore, ...mint("did:web:evil.example", "dev.cocore.inference.dispatch")));
assert.ok(!scopeAllows(cocore, ...mint("did:web:cocore.dev", "x.y.z")));
assert.ok(!scopeAllows("atproto rpc:com.atproto.server.getServiceAuth?aud=*", ...mint("did:web:cocore.dev", "x.y.z")));
assert.ok(scopeAllows("atproto", "com.fedfork.exec.webrtc.connect", { input: {} }), "host pseudo-calls need no scope");
const atirc = records.atirc.clientMetadata.scope;
assert.ok(scopeAllows(atirc, "chat.atirc.post", { input: {}, proxy: ATIRC }));
assert.ok(!scopeAllows(atirc, "chat.atirc.post", { input: {}, proxy: "did:web:evil.example#atirc" }));
assert.ok(!scopeAllows(atirc, ...create(`${T}.market.rfp`)));
assert.ok(scopeAllows(`atproto rpc?lxm=a.b.c&lxm=d.e.f&aud=*`, "d.e.f", { proxy: "did:web:x.example#svc" }));
assert.ok(scopeAllows("atproto repo?collection=a.b.c&action=delete", "com.atproto.repo.applyWrites", { input: { writes: [{ $type: "com.atproto.repo.applyWrites#delete", collection: "a.b.c" }] } }));
assert.ok(scopeAllows("atproto transition:generic", ...create("any.thing.here")));
assert.ok(!scopeAllows("atproto transition:generic", "chat.bsky.convo.sendMessage", { input: {} }));
// a public subscribe mints no token; one with serviceAuth needs the rpc scope for its service
assert.ok(scopeAllows("atproto", "com.atproto.sync.subscribeRepos", { service: "https://relay.example", subscribe: true }));
assert.ok(!scopeAllows("atproto", "a.b.watch", { service: "https://svc.example", subscribe: true, serviceAuth: {} }));
assert.ok(scopeAllows("atproto rpc:a.b.watch?aud=did:web:svc.example", "a.b.watch", { service: "https://svc.example", subscribe: true, serviceAuth: {} }));
assert.ok(scopeAllows("atproto blob:*/*", "com.atproto.repo.uploadBlob", { input: {} }) && !scopeAllows("atproto", "com.atproto.repo.uploadBlob", { input: {} }));
});
const policy = {
sessions: { requester: { scope: records.requester.clientMetadata.scope }, atirc: { scope: records.atirc.clientMetadata.scope } },
modules: { requester: { session: "requester" }, atirc: { session: "atirc" } },
features: { agents: { requires: ["requester", "atirc"], grants: { agent: ["requester", "atirc"] } } },
};
test("one sign-in: requestScope asks for some sessions, grantedSessions reads them back", () => {
const scope = requestScope(policy, ["atirc"]);
assert.equal(scope, records.atirc.clientMetadata.scope);
assert.deepEqual(grantedSessions(policy, scope), ["atirc"]);
// a session's optional scope groups (stage.json required): one signed in before them stays connected
const later = { sessions: { requester: { scope: "atproto repo:a?action=create repo:b?action=create", required: "atproto repo:a?action=create" } } };
assert.deepEqual(grantedSessions(later, "atproto repo:a?action=create"), ["requester"]);
assert.deepEqual(grantedSessions(later, "atproto"), []);
assert.deepEqual(grantedSessions(policy, requestScope(policy, ["atirc", "requester"])).sort(), ["atirc", "requester"]);
assert.deepEqual(enabledFeatures(policy, ["atirc"]), []);
assert.deepEqual(enabledFeatures(policy, ["atirc", "requester"]), ["agents"]);
});
test("policyCall keeps each module in its own session until a feature grants more", async () => {
const made = [];
const session = (name) => async (nsid, request) => (made.push([name, nsid, request.proxy]), { ok: name });
const both = { requester: session("requester"), atirc: session("atirc") };
assert.deepEqual(await policyCall(policy, "atirc", both)("chat.atirc.post", { input: {}, proxy: ATIRC }), { ok: "atirc" });
await assert.rejects(policyCall(policy, "atirc", both)(...create(`${T}.market.rfp`)), /atirc may not call com.atproto.repo.createRecord with sessions atirc/);
assert.deepEqual(await policyCall(policy, "requester", both)(...create(`${T}.market.rfp`)), { ok: "requester" });
// The agent module has no session of its own: with both connected the agents feature lends it both.
const agent = policyCall(policy, "agent", both);
assert.deepEqual(await agent(...create(`${T}.market.rfp`)), { ok: "requester" });
assert.deepEqual(await agent("chat.atirc.list", { params: {}, proxy: ATIRC }), { ok: "atirc" });
// With atirc alone the feature is off, so the agent has nothing.
await assert.rejects(policyCall(policy, "agent", { atirc: both.atirc })("chat.atirc.list", { proxy: ATIRC }), /no connected session/);
// A session that is not connected is never used, even for the module that owns it.
await assert.rejects(policyCall(policy, "requester", { atirc: both.atirc })(...create(`${T}.market.rfp`)), /no connected session/);
// Calls that need no scope go through any connected session, for a module with no session of its own too.
assert.deepEqual(await policyCall(policy, "agent", { atirc: both.atirc })("com.atproto.repo.getRecord", { params: {} }), { ok: "atirc" });
await assert.rejects(policyCall(policy, "agent", {})("com.atproto.repo.getRecord", { params: {} }), /agent may not call com.atproto.repo.getRecord with no connected session/);
assert.equal(made.length, 5);
});
test("atlibs stage writes local modules and their client metadata at known paths", async () => {
const out = await mkdtemp(join(tmpdir(), "atlibs-stage-"));
const record = join(out, "ui.record.json");
const { stdout } = await atlibs(["stage", "--out", out, "--origin", "https://ui.example/app", "--name", "ui", "--record", record, "statusphere", "vibescrobble"]);
const manifest = JSON.parse(stdout);
const stagedRecord = JSON.parse(await readFile(new URL("../../../records/vibescrobble.json", import.meta.url), "utf8"));
assert.deepEqual(JSON.parse(await readFile(join(out, "stage.json"), "utf8")), manifest);
assert.equal(manifest.clientId, "https://ui.example/app/oauth/ui/client-metadata.json");
assert.deepEqual(Object.keys(manifest.sessions), ["statusphere", "vibescrobble"]);
const doc = JSON.parse(await readFile(join(out, "oauth/ui/client-metadata.json"), "utf8"));
assert.equal(doc.client_id, manifest.clientId);
assert.equal(doc.scope, manifest.scope);
assert.deepEqual(doc.redirect_uris, ["https://ui.example/app/"]);
const vibescrobble = JSON.parse(await readFile(join(out, "oauth/vibescrobble/client-metadata.json"), "utf8"));
assert.equal(vibescrobble.client_id, manifest.sessions.vibescrobble.clientId);
assert.deepEqual(vibescrobble, clientMetadata(stagedRecord, { clientId: vibescrobble.client_id, redirectUri: "https://ui.example/app/" }), "the Rust twin of clientMetadata");
const wasm = await readFile(join(out, manifest.modules.vibescrobble.wasm));
assert.equal(createHash("sha256").update(wasm).digest("hex"), manifest.modules.vibescrobble.sha256);
assert.deepEqual(JSON.parse(await readFile(join(out, "modules/vibescrobble.json"), "utf8")), JSON.parse(await readFile(new URL("../../../examples/rust/vibescrobble/module.json", import.meta.url), "utf8")));
assert.equal(JSON.parse(await readFile(record, "utf8")).name, "ui");
await assert.rejects(atlibs(["stage", "--out", out, "--origin", "http://ui.example", "vibescrobble"]), /must be https/);
// --oauth-path moves the clients; --target server stages the records for clientMetadataHandler instead
const moved = JSON.parse((await atlibs(["stage", "--out", out, "--origin", "https://ui.example/", "--oauth-path", "auth/clients/", "vibescrobble"])).stdout);
assert.equal(moved.clientId, "https://ui.example/auth/clients/vibescrobble/client-metadata.json");
const server = JSON.parse((await atlibs(["stage", "--target", "server", "--out", join(out, "srv"), "vibescrobble"])).stdout);
assert.deepEqual(server.sessions.vibescrobble.record, stagedRecord);
});
test("clientMetadataHandler serves each session's metadata, and the composite, on origin (or a loopback request's)", async () => {
const handler = clientMetadataHandler({ sessions: { atirc: { record: records.atirc }, fedproxy: { record: records.fedproxy } } }, { name: "ui" });
const get = (url, headers) => handler(new Request(url, { headers }));
const doc = await get("http://127.0.0.1:8787/oauth/atirc/client-metadata.json").json();
assert.equal(doc.client_id, "http://127.0.0.1:8787/oauth/atirc/client-metadata.json");
assert.deepEqual(doc.redirect_uris, ["http://127.0.0.1:8787/"]);
assert.equal(doc.scope, records.atirc.clientMetadata.scope);
// a public Host (or X-Forwarded-*) is never trusted without origin
assert.equal(get("http://site.example/oauth/fedproxy/client-metadata.json", { "x-forwarded-proto": "https" }).status, 500);
const ui = await get("http://localhost:8787/oauth/ui/client-metadata.json").json();
assert.equal(ui.scope, composeRecord({ atirc: records.atirc, fedproxy: records.fedproxy }, { name: "ui", clientId: ui.client_id }).clientMetadata.scope);
assert.equal(get("http://127.0.0.1/oauth/nope/client-metadata.json").status, 404);
assert.equal(get("https://site.example/elsewhere"), undefined);
assert.equal(handler(new Request("https://site.example/oauth/atirc/client-metadata.json", { method: "POST" })), undefined);
const fixed = clientMetadataHandler({ atirc: records.atirc }, { path: "auth", origin: "https://fixed.example/" });
assert.equal((await fixed(new Request("http://x/auth/atirc/client-metadata.json")).json()).client_id, "https://fixed.example/auth/atirc/client-metadata.json");
});
test("pageClientId: loopback on localhost, the staged client when deployed, else the fallback", async () => {
const at = (href) => Object.assign(new URL(href), { href });
const staged = async (url) => (String(url) === "https://site.example/app/stage.json" ? Response.json({ clientId: "https://site.example/app/oauth/ui/client-metadata.json" }) : new Response("", { status: 404 }));
const local = await pageClientId("atproto repo:a", { location: at("http://localhost:8000/app/index.html"), fetch: staged });
assert.equal(new URL(local).searchParams.get("redirect_uri"), "http://127.0.0.1:8000/app/");
assert.equal(await pageClientId("atproto", { location: at("https://site.example/app/index.html"), fetch: staged }), "https://site.example/app/oauth/ui/client-metadata.json");
assert.equal(await pageClientId("atproto", { location: at("https://other.example/x/"), fetch: staged, fallback: "https://other.example/oauth-client-metadata.json" }), "https://other.example/oauth-client-metadata.json");
await assert.rejects(pageClientId("atproto", { location: at("https://other.example/x/"), fetch: staged }), /atlibs stage --target spa/);
const template = async () => Response.json({ clientId: "https://atlibs.invalid/oauth/ui/client-metadata.json" });
await assert.rejects(pageClientId("atproto", { location: at("https://site.example/app/"), fetch: template }), /is a template/);
});
// An app record: co/core's registration on the template origin (the record's client_id), served on another.
const REGISTRATION = { $type: "dev.cocore.app.registration", name: "App", website: "https://app.example", returnUrls: ["https://app.example/done"], createdAt: "2026-11-10T00:00:00.000Z" };
const appRecord = { $type: "com.fedfork.exec.oauth.clientMetadata", name: "app", clientMetadata: { client_id: "https://app.example/oauth/app/client-metadata.json", scope: "atproto" }, createdAt: "2026-11-10T00:00:00.000Z", app: { records: [{ collection: "dev.cocore.app.registration", rkey: "self", value: REGISTRATION }], wellKnown: { "cocore-app.json": { did: "did:web:app.example" } } } };
test("appRepo: the app's did:web, its records rebased onto the origin with their CIDs, the well-known files; read-only", async () => {
const repo = await appRepo({ sessions: { app: { record: appRecord }, other: { record: records.atirc } } }, { origin: "https://deploy.example/page", publicKeyMultibase: "zDnaeKey" });
assert.equal(repo.did, "did:web:deploy.example");
assert.deepEqual(repo.document.verificationMethod, [{ id: "did:web:deploy.example#atproto", type: "Multikey", controller: "did:web:deploy.example", publicKeyMultibase: "zDnaeKey" }]);
assert.deepEqual(repo.document.service, [{ id: "#atproto_pds", type: "AtprotoPersonalDataServer", serviceEndpoint: "https://deploy.example" }]);
const value = { ...REGISTRATION, website: "https://deploy.example", returnUrls: ["https://deploy.example/done"] };
// the same CID as rust/host oauth::AppRepo's test
assert.deepEqual(repo.records, [{ uri: "at://did:web:deploy.example/dev.cocore.app.registration/self", cid: "bafyreiact3y3dp5yjllsptqtj57iv4kuzvo4rtxvpzakl7el5qgx6fp6nm", value, collection: "dev.cocore.app.registration", rkey: "self" }]);
const answer = (path) => {
const [status, type, body] = appRepoAnswer(repo, path);
return [status, type, type === "text/plain" ? body : JSON.parse(body)];
};
assert.deepEqual(answer("/.well-known/cocore-app.json"), [200, "application/json", { did: "did:web:deploy.example" }]);
assert.deepEqual(answer("/.well-known/atproto-did"), [200, "text/plain", "did:web:deploy.example"]);
assert.deepEqual(answer("/xrpc/com.atproto.repo.getRecord?repo=did:web:deploy.example&collection=dev.cocore.app.registration&rkey=self")[2].value, value);
assert.deepEqual(answer("/xrpc/com.atproto.repo.getRecord?repo=deploy.example&collection=dev.cocore.app.registration&rkey=nope"), [400, "application/json", { error: "RecordNotFound", message: "Could not locate record: at://did:web:deploy.example/dev.cocore.app.registration/nope" }]);
assert.equal(answer("/xrpc/com.atproto.repo.listRecords?repo=did:plc:other&collection=x")[2].error, "RepoNotFound");
assert.deepEqual(answer("/xrpc/com.atproto.repo.describeRepo?repo=did:web:deploy.example")[2].collections, ["dev.cocore.app.registration"]);
for (const path of ["/xrpc/com.atproto.server.getServiceAuth?aud=x", "/xrpc/com.atproto.server.createAccount", "/xrpc/com.atproto.sync.getRepo?did=did:web:deploy.example", "/.well-known/other.json", "/"]) assert.equal(appRepoAnswer(repo, path), undefined, path);
const handler = appRepoHandler(repo);
const res = handler(new Request("https://deploy.example/.well-known/did.json"));
assert.equal(res.headers.get("access-control-allow-origin"), "*");
assert.equal((await res.json()).id, "did:web:deploy.example");
assert.equal(handler(new Request("https://elsewhere.example/.well-known/did.json")), undefined, "another Host is not this identity");
assert.equal(handler(new Request("https://deploy.example/.well-known/did.json", { method: "POST" })), undefined);
await assert.rejects(appRepo({ app: appRecord }, { origin: "http://127.0.0.1:8787", publicKeyMultibase: "z" }), /public https origin/);
const clash = { ...appRecord, clientMetadata: { ...appRecord.clientMetadata, client_id: "https://other.example/c.json" } };
await assert.rejects(appRepo({ app: appRecord, clash }, { origin: "https://deploy.example", publicKeyMultibase: "z" }), /declared twice, differently/);
await assert.rejects(appRepo({ bad: { ...appRecord, app: { wellKnown: { "did.json": {} } } } }, { origin: "https://deploy.example", publicKeyMultibase: "z" }), /not a file name it may serve/);
});
test("atlibs stage --origin writes the app identity as files a static host serves (one record: getRecord too)", async () => {
const dir = await mkdtemp(join(tmpdir(), "atlibs-app-"));
const { cp, mkdir, writeFile } = await import("node:fs/promises");
const root = new URL("../../../", import.meta.url).pathname;
await mkdir(join(dir, "statusphere"), { recursive: true });
await cp(join(root, "examples/rust/statusphere/module.json"), join(dir, "statusphere/module.json"));
await cp(join(root, "target/wasm32-unknown-unknown/release/statusphere.wasm"), join(dir, "target/wasm32-unknown-unknown/release/statusphere.wasm"), { recursive: true });
const session = JSON.parse(await readFile(join(dir, "statusphere/module.json"), "utf8")).session;
await mkdir(join(dir, "records"));
await writeFile(join(dir, `records/${session}.json`), JSON.stringify({ ...appRecord, name: session }));
const out = join(dir, "out");
const manifest = JSON.parse((await atlibs(["stage", "--out", out, "--origin", "https://deploy.example/", join(dir, "statusphere")])).stdout);
assert.equal(manifest.app.did, "did:web:deploy.example");
const file = async (path) => readFile(join(out, path), "utf8");
const doc = JSON.parse(await file(".well-known/did.json"));
// the Rust twin of appRepo: the same files from the same key
const repo = await appRepo({ [session]: appRecord }, { origin: "https://deploy.example", publicKeyMultibase: doc.verificationMethod[0].publicKeyMultibase });
assert.match(doc.verificationMethod[0].publicKeyMultibase, /^zDn/, "a P-256 did:key the host keeps");
for (const [path, query] of [[".well-known/did.json", ""], [".well-known/atproto-did", ""], [".well-known/cocore-app.json", ""], ["xrpc/com.atproto.repo.getRecord", "?repo=did:web:deploy.example&collection=dev.cocore.app.registration&rkey=self"], ["xrpc/com.atproto.repo.listRecords", "?repo=did:web:deploy.example&collection=dev.cocore.app.registration"], ["xrpc/com.atproto.repo.describeRepo", "?repo=did:web:deploy.example"]]) {
const [, type, body] = appRepoAnswer(repo, `/${path}${query}`);
assert.deepEqual(type === "text/plain" ? await file(path) : JSON.parse(await file(path)), type === "text/plain" ? body : JSON.parse(body), path);
}
assert.deepEqual(manifest.app.files.sort(), [".well-known/atproto-did", ".well-known/cocore-app.json", ".well-known/did.json", "xrpc/com.atproto.repo.describeRepo", "xrpc/com.atproto.repo.getRecord", "xrpc/com.atproto.repo.listRecords"]);
// the key is kept: staging again names the same identity key
const again = JSON.parse((await atlibs(["stage", "--out", join(dir, "out2"), "--origin", "https://deploy.example/", join(dir, "statusphere")])).stdout);
assert.equal(again.app.did, manifest.app.did);
assert.equal(JSON.parse(await readFile(join(dir, "out2/.well-known/did.json"), "utf8")).verificationMethod[0].publicKeyMultibase, doc.verificationMethod[0].publicKeyMultibase);
});