@uuaid/agent-commons 0.2.0-alpha.2 → 0.2.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONFORMANCE.md +1 -1
- package/NOTICE +4 -7
- package/PROVENANCE.json +12 -17
- package/README.md +103 -12
- package/bin/agent-commons.mjs +8 -3
- package/examples/Dockerfile +1 -1
- package/package.json +5 -2
- package/src/carrier.mjs +7 -2
- package/src/channels.mjs +1 -1
- package/src/config.mjs +2 -1
- package/src/continuity.mjs +14 -7
- package/src/governance-scope.mjs +1 -1
- package/src/governance.mjs +4 -2
- package/src/index.d.ts +3 -1
- package/src/pillar.mjs +2 -2
- package/src/profiles.mjs +2 -1
- package/src/relay/auth.mjs +294 -0
- package/src/relay/board.mjs +223 -0
- package/src/relay/claims.mjs +25 -0
- package/src/relay/cli.mjs +49 -0
- package/src/relay/client.mjs +437 -0
- package/src/relay/docs/reconnect-v1.md +27 -0
- package/src/relay/docs/working-rules-v1.md +42 -0
- package/src/relay/enroll.mjs +232 -0
- package/src/relay/export-cli.mjs +47 -0
- package/src/relay/governance.mjs +1328 -0
- package/src/relay/mailbox.mjs +103 -0
- package/src/relay/records.mjs +146 -0
- package/src/relay/render.mjs +38 -0
- package/src/relay/roster.mjs +134 -0
- package/src/relay/routes.mjs +87 -0
- package/src/relay/schema.mjs +1081 -0
- package/src/relay/seed.mjs +89 -0
- package/src/relay/serve.mjs +116 -0
- package/src/relay/server.mjs +633 -0
- package/src/relay/signing.mjs +693 -0
- package/src/relay/test-mode.mjs +73 -0
- package/src/runtime.mjs +25 -12
- package/src/spaces/manifest.mjs +245 -0
- package/src/spaces/pillar-group.mjs +12 -0
- package/src/spaces/policy.mjs +229 -0
- package/src/spaces/store.mjs +325 -0
- package/src/trust.mjs +44 -5
- package/src/uuaid-namespace.d.mts +6 -0
- package/src/uuaid-namespace.mjs +48 -0
- package/src/vendor/pillar/blob/blob.mjs +12 -14
- package/src/vendor/pillar/groups-v2.mjs +1108 -0
- package/src/vendor/pillar/groups.mjs +175 -0
- package/src/vendor/pillar/identity/jcs.mjs +4 -5
- package/src/vendor/pillar/identity/keychain.mjs +51 -2
- package/src/vendor/pillar/net/carrier-client.mjs +29 -7
- package/src/vendor/pillar/net/refusals.mjs +4 -2
- package/test/channels.test.mjs +0 -268
- package/test/cohort.test.mjs +0 -36
- package/test/collaboration.test.mjs +0 -459
- package/test/continuity.test.mjs +0 -229
- package/test/custody.negative.test.mjs +0 -165
- package/test/derivation-parity.test.mjs +0 -19
- package/test/fixtures/derivation-vectors.json +0 -26
- package/test/fixtures/ed25519-small-order.vectors.json +0 -15
- package/test/governance-expected-red.expected-red.mjs +0 -241
- package/test/governance-scope.test.mjs +0 -142
- package/test/governance-t1-t10.test.mjs +0 -477
- package/test/governance.test.mjs +0 -514
- package/test/hold-admission.test.mjs +0 -1185
- package/test/keychain-strict-ed25519.test.mjs +0 -240
- package/test/offline.test.mjs +0 -152
- package/test/runtime-channels.test.mjs +0 -1140
- package/test/runtime.test.mjs +0 -201
- package/test/security-runtime.test.mjs +0 -104
- package/test/strict-ed25519-consumers.test.mjs +0 -234
- package/test/transport.test.mjs +0 -353
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Adapted from pillar test/unit/keychain-strict-ed25519.test.mjs
|
|
3
|
-
* Source sha256: e115b19e6881d5a64d0a87b7f6ba0bbac05f7aa1290778bca68447fda3a9a5ae (pillar commit 2ef548d)
|
|
4
|
-
*
|
|
5
|
-
* Changes made for agent-commons layout (Item 3, C3):
|
|
6
|
-
* 1. Adjusted import paths: Keychain and helpers from ../src/vendor/pillar/identity/keychain.mjs,
|
|
7
|
-
* envelope methods from ../src/vendor/pillar/net/envelope.mjs, and vectors fixture from ./fixtures/ed25519-small-order.vectors.json.
|
|
8
|
-
* 2. In withMutant(), placed temporary mutant trees under os.tmpdir() using mkdtempSync rather than next to test/ or src/.
|
|
9
|
-
* 3. Symlinked the nearest ancestor node_modules containing @noble/curves into the temporary directory so @noble/curves resolves in ESM.
|
|
10
|
-
* 4. Cleaned up temporary mutant directories in withMutant() finally block.
|
|
11
|
-
*/
|
|
12
|
-
import test from "node:test";
|
|
13
|
-
import assert from "node:assert/strict";
|
|
14
|
-
import { readFileSync, writeFileSync, cpSync, rmSync, mkdtempSync, symlinkSync } from "node:fs";
|
|
15
|
-
import { tmpdir } from "node:os";
|
|
16
|
-
import { join, dirname, basename } from "node:path";
|
|
17
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
18
|
-
import { ed25519 } from "@noble/curves/ed25519.js";
|
|
19
|
-
import { Keychain, isStrictEd25519PublicKey, localIdFromKey } from "../src/vendor/pillar/identity/keychain.mjs";
|
|
20
|
-
import { seal, open, decrypt } from "../src/vendor/pillar/net/envelope.mjs";
|
|
21
|
-
|
|
22
|
-
const SRC = new URL("../src/vendor/pillar/", import.meta.url);
|
|
23
|
-
const FIXTURE = JSON.parse(readFileSync(new URL("./fixtures/ed25519-small-order.vectors.json", import.meta.url), "utf-8"));
|
|
24
|
-
|
|
25
|
-
const IDENTITY = "01" + "00".repeat(31);
|
|
26
|
-
/** R = the identity point, S = 0. Verifies under OpenSSL for any message when A is the identity point. */
|
|
27
|
-
const FORGED_SIG = "01" + "00".repeat(31) + "00".repeat(32);
|
|
28
|
-
/** A point of order 8 (canonical encoding). */
|
|
29
|
-
const T8 = "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a";
|
|
30
|
-
const MESSAGES = ["", "a", "pillar", "x".repeat(97)].map((s) => Buffer.from(s, "utf-8"));
|
|
31
|
-
|
|
32
|
-
function torsionPoints() {
|
|
33
|
-
const t = ed25519.Point.fromHex(T8, true);
|
|
34
|
-
const out = [];
|
|
35
|
-
let p = ed25519.Point.ZERO;
|
|
36
|
-
for (let k = 0; k < 8; k++) {
|
|
37
|
-
out.push(p.toHex());
|
|
38
|
-
p = p.add(t);
|
|
39
|
-
}
|
|
40
|
-
return out;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/** Little-endian encodings of y = p + j (j = 0, 1), with and without the sign bit: non-canonical small-order points. */
|
|
44
|
-
function nonCanonicalSmallOrder() {
|
|
45
|
-
const P = (1n << 255n) - 19n;
|
|
46
|
-
const out = [];
|
|
47
|
-
for (const y of [P, P + 1n]) {
|
|
48
|
-
for (const sign of [0n, 1n]) {
|
|
49
|
-
let v = y | (sign << 255n);
|
|
50
|
-
const b = Buffer.alloc(32);
|
|
51
|
-
for (let i = 0; i < 32; i++) {
|
|
52
|
-
b[i] = Number(v & 0xffn);
|
|
53
|
-
v >>= 8n;
|
|
54
|
-
}
|
|
55
|
-
out.push(b.toString("hex"));
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return out;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function honestKeychain() {
|
|
62
|
-
const kc = Object.create(Keychain.prototype);
|
|
63
|
-
kc._identity = Keychain.generate();
|
|
64
|
-
return kc;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function dropRule(text, rule) {
|
|
68
|
-
const lines = text.split("\n");
|
|
69
|
-
const hits = lines.filter((l) => l.includes(`// @rule ${rule}`)).length;
|
|
70
|
-
assert.equal(hits, 1, `marker "${rule}" must appear exactly once`);
|
|
71
|
-
return lines.filter((l) => !l.includes(`// @rule ${rule}`)).join("\n");
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
let mutantN = 0;
|
|
75
|
-
/**
|
|
76
|
-
* Load copies of keychain.mjs and envelope.mjs with the named rule lines
|
|
77
|
-
* dropped. The copies live in a temporary mirror in os.tmpdir() (never inside
|
|
78
|
-
* src/ or test/, which other tests walk in parallel), with the nearest ancestor
|
|
79
|
-
* node_modules containing @noble/curves symlinked.
|
|
80
|
-
*/
|
|
81
|
-
async function withMutant({ keychain = [], envelope = [] }, fn) {
|
|
82
|
-
const tmpBase = mkdtempSync(join(tmpdir(), `ac-mutant-${process.pid}-${mutantN++}-`));
|
|
83
|
-
const src = join(tmpBase, "pillar");
|
|
84
|
-
cpSync(fileURLToPath(SRC), src, { recursive: true });
|
|
85
|
-
let pkgNodeModules = fileURLToPath(import.meta.resolve("@noble/curves/ed25519.js"));
|
|
86
|
-
while (pkgNodeModules && basename(pkgNodeModules) !== "node_modules") {
|
|
87
|
-
const parent = dirname(pkgNodeModules);
|
|
88
|
-
if (parent === pkgNodeModules) break;
|
|
89
|
-
pkgNodeModules = parent;
|
|
90
|
-
}
|
|
91
|
-
symlinkSync(pkgNodeModules, join(tmpBase, "node_modules"), "dir");
|
|
92
|
-
try {
|
|
93
|
-
const kPath = join(src, "identity/keychain.mjs");
|
|
94
|
-
const ePath = join(src, "net/envelope.mjs");
|
|
95
|
-
let k = readFileSync(kPath, "utf-8");
|
|
96
|
-
for (const r of keychain) k = dropRule(k, r);
|
|
97
|
-
let e = readFileSync(ePath, "utf-8");
|
|
98
|
-
for (const r of envelope) e = dropRule(e, r);
|
|
99
|
-
writeFileSync(kPath, k);
|
|
100
|
-
writeFileSync(ePath, e);
|
|
101
|
-
const K = await import(pathToFileURL(kPath).href);
|
|
102
|
-
const E = await import(pathToFileURL(ePath).href);
|
|
103
|
-
return await fn({ Keychain: K.Keychain, open: E.open });
|
|
104
|
-
} finally {
|
|
105
|
-
rmSync(tmpBase, { recursive: true, force: true });
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/** An envelope "from" the identity-point uuaid, built with no private key (the forge). */
|
|
110
|
-
function forgedEnvelope(recipientKc) {
|
|
111
|
-
const honest = seal(honestKeychain(), {
|
|
112
|
-
recipient: recipientKc._identity.uuaid,
|
|
113
|
-
recipientPublicKey: recipientKc._identity.publicKeyHex,
|
|
114
|
-
payload: { text: "forged" },
|
|
115
|
-
});
|
|
116
|
-
const sender = `uuaid:foundation:agent:${localIdFromKey(Buffer.from(IDENTITY, "hex"))}`;
|
|
117
|
-
const { transportSignature: _s, ...body } = honest;
|
|
118
|
-
return {
|
|
119
|
-
...body,
|
|
120
|
-
sender,
|
|
121
|
-
transportSignature: { alg: "ed25519", keyId: sender, publicKey: IDENTITY, signature: FORGED_SIG, created: body.createdAt },
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// ───────────────────────────────────────────────────────── the key rule itself ──
|
|
126
|
-
|
|
127
|
-
test("isStrictEd25519PublicKey: an honest key passes, in either case", () => {
|
|
128
|
-
const id = Keychain.generate();
|
|
129
|
-
assert.equal(isStrictEd25519PublicKey(id.publicKeyHex), true);
|
|
130
|
-
assert.equal(isStrictEd25519PublicKey(id.publicKeyHex.toUpperCase()), true);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
test("isStrictEd25519PublicKey: all 8 small-order points are refused", () => {
|
|
134
|
-
const pts = torsionPoints();
|
|
135
|
-
assert.equal(new Set(pts).size, 8, "8 distinct torsion points");
|
|
136
|
-
for (const hex of pts) {
|
|
137
|
-
assert.equal(ed25519.Point.fromHex(hex, true).isSmallOrder(), true, `${hex} is small order`);
|
|
138
|
-
assert.equal(isStrictEd25519PublicKey(hex), false, `${hex} refused`);
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
test("isStrictEd25519PublicKey: non-canonical encodings (y >= p) are refused", () => {
|
|
143
|
-
for (const hex of nonCanonicalSmallOrder()) assert.equal(isStrictEd25519PublicKey(hex), false, `${hex} refused`);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
test("isStrictEd25519PublicKey: a mixed-order key (honest + torsion) is refused", () => {
|
|
147
|
-
const honest = ed25519.Point.fromHex(Keychain.generate().publicKeyHex, false);
|
|
148
|
-
const mixed = honest.add(ed25519.Point.fromHex(T8, true)).toHex();
|
|
149
|
-
assert.equal(isStrictEd25519PublicKey(mixed), false);
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
test("isStrictEd25519PublicKey: junk is refused without throwing", () => {
|
|
153
|
-
for (const x of [undefined, null, 7, "", "zz".repeat(32), "00".repeat(31), "00".repeat(33)]) {
|
|
154
|
-
assert.equal(isStrictEd25519PublicKey(x), false);
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
test("vector 3s2: the identity-point member key derives its uuaid, so only the strict rule refuses it", () => {
|
|
159
|
-
const v = FIXTURE["3s2-small-order-member-key"];
|
|
160
|
-
assert.equal(`uuaid:foundation:agent:${localIdFromKey(Buffer.from(v.key, "hex"))}`, v.uuaid);
|
|
161
|
-
assert.equal(isStrictEd25519PublicKey(v.key), false);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
// ──────────────────────────────────────────── verifyDetached (14 call sites) ──
|
|
165
|
-
|
|
166
|
-
test("verifyDetached: an honest signature still verifies", () => {
|
|
167
|
-
const kc = honestKeychain();
|
|
168
|
-
const msg = Buffer.from("hello", "utf-8");
|
|
169
|
-
assert.equal(Keychain.verifyDetached(kc._identity.publicKeyHex, msg, kc.sign(msg)), true);
|
|
170
|
-
assert.equal(Keychain.verifyDetached(kc._identity.publicKeyHex, Buffer.from("other"), kc.sign(msg)), false);
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
test("keychain.strict-key — vector 1s (identity-point signer): CONTROL refused, MUTANT verifies", async () => {
|
|
174
|
-
const v = FIXTURE["1s-small-order-signer"];
|
|
175
|
-
const preimage = Buffer.from(v.signingPreimageHex, "hex");
|
|
176
|
-
const sig = Buffer.from(v.signature, "hex");
|
|
177
|
-
assert.equal(Keychain.verifyDetached(v.publicKey, preimage, sig), false, "CONTROL");
|
|
178
|
-
await withMutant({ keychain: ["keychain.strict-key"] }, ({ Keychain: K }) => {
|
|
179
|
-
assert.equal(K.verifyDetached(v.publicKey, preimage, sig), true, "MUTANT: OpenSSL alone accepts 1s");
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
test("keychain.strict-key — every torsion key: CONTROL refuses every forged signature, MUTANT accepts some", async () => {
|
|
184
|
-
const pts = torsionPoints();
|
|
185
|
-
const trial = (K) => {
|
|
186
|
-
const accepted = new Set();
|
|
187
|
-
for (const A of pts) {
|
|
188
|
-
for (const R of pts) {
|
|
189
|
-
const sig = Buffer.from(R + "00".repeat(32), "hex");
|
|
190
|
-
for (const m of MESSAGES) {
|
|
191
|
-
let ok = false;
|
|
192
|
-
try { ok = K.verifyDetached(A, m, sig); } catch (_e) { ok = false; }
|
|
193
|
-
if (ok) accepted.add(A);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
return accepted;
|
|
198
|
-
};
|
|
199
|
-
assert.equal(trial(Keychain).size, 0, "CONTROL: no torsion key verifies anything");
|
|
200
|
-
await withMutant({ keychain: ["keychain.strict-key"] }, ({ Keychain: K }) => {
|
|
201
|
-
const n = trial(K).size;
|
|
202
|
-
console.log(` MUTANT: ${n} of 8 torsion keys verify a forged signature under OpenSSL alone`);
|
|
203
|
-
assert.ok(n >= 1, "MUTANT: OpenSSL alone accepts at least one torsion key");
|
|
204
|
-
});
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
// ─────────────────────────────────────────────── open() / openDelegated() ──
|
|
208
|
-
|
|
209
|
-
test("an honest envelope still opens and decrypts", () => {
|
|
210
|
-
const to = honestKeychain();
|
|
211
|
-
const env = seal(honestKeychain(), { recipient: to._identity.uuaid, recipientPublicKey: to._identity.publicKeyHex, payload: { text: "hi" } });
|
|
212
|
-
assert.deepEqual(open(env), { ok: true });
|
|
213
|
-
assert.equal(decrypt(to, env).text, "hi");
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
test("envelope.open-strict-key + keychain.strict-key — the forged envelope: CONTROL refused at both layers, MUTANT opens", async () => {
|
|
217
|
-
const to = honestKeychain();
|
|
218
|
-
const env = forgedEnvelope(to);
|
|
219
|
-
assert.deepEqual(open(env), { ok: false, reason: "weak-publicKey" }, "CONTROL");
|
|
220
|
-
await withMutant({ keychain: ["keychain.strict-key"] }, ({ open: o }) => {
|
|
221
|
-
assert.deepEqual(o(env), { ok: false, reason: "weak-publicKey" }, "open() alone still refuses it");
|
|
222
|
-
});
|
|
223
|
-
await withMutant({ envelope: ["envelope.open-strict-key"] }, ({ open: o }) => {
|
|
224
|
-
assert.deepEqual(o(env), { ok: false, reason: "bad-signature" }, "verifyDetached alone still refuses it");
|
|
225
|
-
});
|
|
226
|
-
await withMutant({ keychain: ["keychain.strict-key"], envelope: ["envelope.open-strict-key"] }, ({ open: o }) => {
|
|
227
|
-
assert.deepEqual(o(env), { ok: true }, "MUTANT: both checks gone, the forgery opens");
|
|
228
|
-
assert.equal(decrypt(to, env).text, "forged", "MUTANT: and the recipient reads it as from the identity-point uuaid");
|
|
229
|
-
});
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
test("envelope.open-delegated-strict-key — a delegated envelope with a small-order signer: CONTROL refused first, MUTANT gets past it", async () => {
|
|
233
|
-
const env = { ...forgedEnvelope(honestKeychain()), delegation: {} };
|
|
234
|
-
assert.deepEqual(open(env), { ok: false, reason: "weak-publicKey" }, "CONTROL");
|
|
235
|
-
await withMutant({ envelope: ["envelope.open-delegated-strict-key"] }, ({ open: o }) => {
|
|
236
|
-
const r = o(env);
|
|
237
|
-
assert.equal(r.ok, false);
|
|
238
|
-
assert.notEqual(r.reason, "weak-publicKey", `MUTANT: the gate is gone (now ${r.reason})`);
|
|
239
|
-
});
|
|
240
|
-
});
|
package/test/offline.test.mjs
DELETED
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
import test from "node:test";
|
|
2
|
-
import assert from "node:assert/strict";
|
|
3
|
-
import { createServer } from "node:http";
|
|
4
|
-
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
5
|
-
import { tmpdir } from "node:os";
|
|
6
|
-
import { join } from "node:path";
|
|
7
|
-
import { Keychain } from "../src/pillar.mjs";
|
|
8
|
-
import { REQUIRED_STANDARDS, TrustError } from "../src/trust.mjs";
|
|
9
|
-
import { isOffline, OFFLINE_ENV } from "../src/offline.mjs";
|
|
10
|
-
import { initialize } from "../src/config.mjs";
|
|
11
|
-
import { CarrierClient } from "../src/index.mjs";
|
|
12
|
-
|
|
13
|
-
const href = process.env.TRUST_MODULE
|
|
14
|
-
? new URL(process.env.TRUST_MODULE, import.meta.url).href
|
|
15
|
-
: new URL("../src/trust.mjs", import.meta.url).href;
|
|
16
|
-
const { RegistryTrust } = await import(href);
|
|
17
|
-
|
|
18
|
-
function identity() {
|
|
19
|
-
const keychain = new Keychain("unused");
|
|
20
|
-
keychain._identity = Keychain.generate();
|
|
21
|
-
return keychain._identity;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function localPolicy(idn) {
|
|
25
|
-
return {
|
|
26
|
-
mode: "local",
|
|
27
|
-
standardPins: Object.fromEntries(REQUIRED_STANDARDS.map((code) => [code, "ab".repeat(32)])),
|
|
28
|
-
agents: {
|
|
29
|
-
[idn.uuaid]: { kind: "agent", publicKey: idn.publicKeyHex, capabilities: ["commons:message"] },
|
|
30
|
-
},
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function publishedFetch(calls) {
|
|
35
|
-
return async (url) => {
|
|
36
|
-
calls.push(String(url));
|
|
37
|
-
return new Response(JSON.stringify({
|
|
38
|
-
standards: REQUIRED_STANDARDS.map((code) => ({ code, stage: "published", content_hash: "ab".repeat(32) })),
|
|
39
|
-
}), { status: 200 });
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
test("offline predicate: default on, exact env opt-in, global policy is not a local run", () => {
|
|
44
|
-
assert.equal(isOffline({}), true);
|
|
45
|
-
assert.equal(isOffline({ [OFFLINE_ENV]: "1" }), true);
|
|
46
|
-
assert.equal(isOffline({ [OFFLINE_ENV]: "0" }), false);
|
|
47
|
-
assert.equal(isOffline({ [OFFLINE_ENV]: "true" }), true);
|
|
48
|
-
assert.equal(isOffline({}, { mode: "local" }), true);
|
|
49
|
-
assert.equal(isOffline({}, { mode: "local", offline: true }), true);
|
|
50
|
-
assert.equal(isOffline({}, { mode: "global" }), false);
|
|
51
|
-
assert.equal(isOffline({}, { mode: "global", offline: true }), true);
|
|
52
|
-
assert.equal(isOffline({}, { mode: "local", offline: false }), false);
|
|
53
|
-
assert.equal(isOffline({ [OFFLINE_ENV]: "1" }, { mode: "global", offline: false }), true);
|
|
54
|
-
assert.equal(isOffline({ [OFFLINE_ENV]: "0" }, { offline: true }), false);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test("local policy does not fetch standards, pins, or authorize", async () => {
|
|
58
|
-
const idn = identity();
|
|
59
|
-
const calls = [];
|
|
60
|
-
const trust = new RegistryTrust({ policy: localPolicy(idn), fetchImpl: publishedFetch(calls), env: {} });
|
|
61
|
-
assert.equal(calls.length, 0, "constructor fetched");
|
|
62
|
-
const admitted = await trust.authorize(idn.uuaid, idn.publicKeyHex);
|
|
63
|
-
assert.equal(admitted.tier, "local-policy-pinned");
|
|
64
|
-
assert.equal(calls.length, 0, "authorize fetched");
|
|
65
|
-
let standardsError = null;
|
|
66
|
-
try { await trust.standards(); } catch (error) { standardsError = error; }
|
|
67
|
-
assert.equal(calls.length, 0, "standards fetched");
|
|
68
|
-
assert.equal(standardsError?.code, "OFFLINE");
|
|
69
|
-
let pinError = null;
|
|
70
|
-
try { await trust.publishedProfilePin("IAASO-1001", "ab".repeat(32)); } catch (error) { pinError = error; }
|
|
71
|
-
assert.equal(calls.length, 0, "publishedProfilePin fetched");
|
|
72
|
-
assert.equal(pinError?.code, "OFFLINE");
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
test("global policy still fetches through the stub unless offline is forced", async () => {
|
|
76
|
-
const idn = identity();
|
|
77
|
-
const policy = { ...localPolicy(idn), mode: "global" };
|
|
78
|
-
policy.agents[idn.uuaid] = { ...policy.agents[idn.uuaid], credentialId: "credential-test" };
|
|
79
|
-
const calls = [];
|
|
80
|
-
const fetchImpl = async (url) => {
|
|
81
|
-
calls.push(String(url));
|
|
82
|
-
if (String(url).includes("/v1/standards")) {
|
|
83
|
-
return new Response(JSON.stringify({
|
|
84
|
-
standards: REQUIRED_STANDARDS.map((code) => ({ code, stage: "published", content_hash: "ab".repeat(32) })),
|
|
85
|
-
}), { status: 200 });
|
|
86
|
-
}
|
|
87
|
-
if (String(url).includes("/resolve/")) return new Response(JSON.stringify({ agent: { uuaid: idn.uuaid, status: "active" } }), { status: 200 });
|
|
88
|
-
return new Response(JSON.stringify({ credential_id: "credential-test", agent_uuaid: idn.uuaid, valid: true, signatureValid: true, active: true, notExpired: true }), { status: 200 });
|
|
89
|
-
};
|
|
90
|
-
const open = new RegistryTrust({ policy, fetchImpl, env: {} });
|
|
91
|
-
assert.equal((await open.authorize(idn.uuaid, idn.publicKeyHex)).tier, "registry-credential-and-policy");
|
|
92
|
-
assert.ok(calls.length > 0);
|
|
93
|
-
const blockedCalls = [];
|
|
94
|
-
const blocked = new RegistryTrust({ policy, fetchImpl: publishedFetch(blockedCalls), env: { [OFFLINE_ENV]: "1" } });
|
|
95
|
-
await assert.rejects(blocked.authorize(idn.uuaid, idn.publicKeyHex), (error) => error instanceof TrustError && error.code === "OFFLINE");
|
|
96
|
-
assert.equal(blockedCalls.length, 0);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test("init writes offline:true and does not call the registry", () => {
|
|
100
|
-
const home = mkdtempSync(join(tmpdir(), "ac-offline-"));
|
|
101
|
-
try {
|
|
102
|
-
const created = initialize({ home, target: "local" });
|
|
103
|
-
const config = JSON.parse(readFileSync(join(home, "config.json"), "utf8"));
|
|
104
|
-
assert.equal(config.policy.mode, "local");
|
|
105
|
-
assert.equal(config.policy.offline, true);
|
|
106
|
-
assert.deepEqual(config.carriers, []);
|
|
107
|
-
assert.equal(created.registryRegistration, "not-performed");
|
|
108
|
-
assert.equal(created.certified, false);
|
|
109
|
-
} finally {
|
|
110
|
-
rmSync(home, { recursive: true, force: true });
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
test("carrier client refuses pillar egress and still allows loopback", async () => {
|
|
115
|
-
const keychain = {
|
|
116
|
-
_identity: {
|
|
117
|
-
uuaid: "uuaid:foundation:agent:01234567-89ab-4cde-8fab-0123456789ab",
|
|
118
|
-
publicKeyHex: "ab".repeat(32),
|
|
119
|
-
},
|
|
120
|
-
sign: () => Buffer.alloc(64),
|
|
121
|
-
};
|
|
122
|
-
const previous = globalThis.fetch;
|
|
123
|
-
const remoteCalls = [];
|
|
124
|
-
globalThis.fetch = async (url, init) => {
|
|
125
|
-
remoteCalls.push(String(url));
|
|
126
|
-
return previous(url, init);
|
|
127
|
-
};
|
|
128
|
-
try {
|
|
129
|
-
const remote = new CarrierClient({ keychain, carriers: ["https://pillar.uuaid.org"], env: {} });
|
|
130
|
-
await assert.rejects(remote.deliver({ id: "local-only" }), (error) => error.code === "offline");
|
|
131
|
-
await assert.rejects(remote.fetchInbox("https://pillar.uuaid.org"), (error) => error.code === "offline");
|
|
132
|
-
assert.equal(remoteCalls.length, 0);
|
|
133
|
-
} finally {
|
|
134
|
-
globalThis.fetch = previous;
|
|
135
|
-
}
|
|
136
|
-
const hits = [];
|
|
137
|
-
const server = createServer((req, res) => {
|
|
138
|
-
hits.push(req.url ?? "");
|
|
139
|
-
res.writeHead(200, { "content-type": "application/json" });
|
|
140
|
-
res.end("{}");
|
|
141
|
-
});
|
|
142
|
-
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
143
|
-
try {
|
|
144
|
-
const { port } = server.address();
|
|
145
|
-
const local = new CarrierClient({ keychain, carriers: [`http://127.0.0.1:${port}`], env: {} });
|
|
146
|
-
await assert.rejects(local.deliver({ id: "loopback" }));
|
|
147
|
-
assert.equal(hits.length, 1);
|
|
148
|
-
assert.match(hits[0], /^\/v1\/envelopes/);
|
|
149
|
-
} finally {
|
|
150
|
-
await new Promise((resolve) => server.close(resolve));
|
|
151
|
-
}
|
|
152
|
-
});
|