@uuaid/agent-commons 0.0.0-stage → 0.2.0-alpha.2
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 +26 -0
- package/LICENSE +201 -0
- package/NOTICE +25 -0
- package/PROVENANCE.json +29 -0
- package/README.md +316 -2
- package/bin/agent-commons.mjs +71 -0
- package/examples/Dockerfile +12 -0
- package/examples/agent.mjs +17 -0
- package/examples/global-policy.json +9 -0
- package/examples/run-cohort.mjs +68 -0
- package/package.json +31 -4
- package/profiles/agentnet.chat.json +11 -0
- package/profiles/local.json +11 -0
- package/profiles/zilligon.com.json +11 -0
- package/profiles/zilligons.com.json +11 -0
- package/src/carrier.mjs +79 -0
- package/src/channels.d.mts +75 -0
- package/src/channels.mjs +158 -0
- package/src/cohort.mjs +61 -0
- package/src/collaboration.mjs +875 -0
- package/src/config.mjs +39 -0
- package/src/continuity.d.mts +24 -0
- package/src/continuity.mjs +594 -0
- package/src/governance-scope.d.mts +46 -0
- package/src/governance-scope.mjs +139 -0
- package/src/governance.mjs +353 -0
- package/src/index.d.ts +109 -0
- package/src/index.mjs +15 -0
- package/src/loop.mjs +71 -0
- package/src/offline.d.mts +7 -0
- package/src/offline.mjs +29 -0
- package/src/pillar.mjs +59 -0
- package/src/profiles.d.mts +12 -0
- package/src/profiles.mjs +91 -0
- package/src/runtime.mjs +346 -0
- package/src/store.mjs +73 -0
- package/src/transport.d.mts +76 -0
- package/src/transport.mjs +267 -0
- package/src/trust.mjs +65 -0
- package/src/vendor/pillar/blob/blob.mjs +510 -0
- package/src/vendor/pillar/crypto/e2e.mjs +97 -0
- package/src/vendor/pillar/identity/jcs.mjs +35 -0
- package/src/vendor/pillar/identity/keychain.mjs +192 -0
- package/src/vendor/pillar/identity/tier.mjs +130 -0
- package/src/vendor/pillar/net/carrier-client.mjs +197 -0
- package/src/vendor/pillar/net/delegation.mjs +209 -0
- package/src/vendor/pillar/net/envelope.mjs +258 -0
- package/src/vendor/pillar/net/refusals.mjs +127 -0
- package/src/vendor/pillar/net/seed-tier.mjs +185 -0
- package/test/channels.test.mjs +268 -0
- package/test/cohort.test.mjs +36 -0
- package/test/collaboration.test.mjs +459 -0
- package/test/continuity.test.mjs +229 -0
- package/test/custody.negative.test.mjs +165 -0
- package/test/derivation-parity.test.mjs +19 -0
- package/test/fixtures/derivation-vectors.json +26 -0
- package/test/fixtures/ed25519-small-order.vectors.json +15 -0
- package/test/governance-expected-red.expected-red.mjs +241 -0
- package/test/governance-scope.test.mjs +142 -0
- package/test/governance-t1-t10.test.mjs +477 -0
- package/test/governance.test.mjs +514 -0
- package/test/hold-admission.test.mjs +1185 -0
- package/test/keychain-strict-ed25519.test.mjs +240 -0
- package/test/offline.test.mjs +152 -0
- package/test/runtime-channels.test.mjs +1140 -0
- package/test/runtime.test.mjs +201 -0
- package/test/security-runtime.test.mjs +104 -0
- package/test/strict-ed25519-consumers.test.mjs +234 -0
- package/test/transport.test.mjs +353 -0
|
@@ -0,0 +1,1185 @@
|
|
|
1
|
+
// L11: Hold Before Admission in Agent Commons Engine (T1-T16 Two-Pole Suite)
|
|
2
|
+
// Code mutants for these properties: the project design
|
|
3
|
+
import test from "node:test";
|
|
4
|
+
import assert from "node:assert/strict";
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
import {
|
|
7
|
+
AgentCommons,
|
|
8
|
+
CommonsStore,
|
|
9
|
+
Keychain,
|
|
10
|
+
createProfile,
|
|
11
|
+
MemoryLoopbackTransport,
|
|
12
|
+
} from "../src/index.mjs";
|
|
13
|
+
import { seal, envelopeSha } from "../src/pillar.mjs";
|
|
14
|
+
import { encode, digest } from "../src/profiles.mjs";
|
|
15
|
+
|
|
16
|
+
function makeKey() {
|
|
17
|
+
const k = new Keychain("test-key");
|
|
18
|
+
k._identity = Keychain.generate();
|
|
19
|
+
return k;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function makeTestEnv({ holdBeforeAdmission = true } = {}) {
|
|
23
|
+
const atlasKey = makeKey();
|
|
24
|
+
const lyraKey = makeKey();
|
|
25
|
+
const atlasId = atlasKey._identity.uuaid;
|
|
26
|
+
const lyraId = lyraKey._identity.uuaid;
|
|
27
|
+
const channelUri = "channel://local/community/general";
|
|
28
|
+
|
|
29
|
+
const policy = {
|
|
30
|
+
mode: "local",
|
|
31
|
+
offline: true,
|
|
32
|
+
channelsEnabled: true,
|
|
33
|
+
holdBeforeAdmission,
|
|
34
|
+
channels: {
|
|
35
|
+
[channelUri]: {
|
|
36
|
+
members: [lyraId], // atlasId intentionally omitted from channel members
|
|
37
|
+
allowForwarding: false,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
agents: {
|
|
41
|
+
[atlasId]: {
|
|
42
|
+
kind: "agent",
|
|
43
|
+
publicKey: atlasKey._identity.publicKeyHex,
|
|
44
|
+
capabilities: ["commons:message", "commons:evolve"],
|
|
45
|
+
},
|
|
46
|
+
[lyraId]: {
|
|
47
|
+
kind: "agent",
|
|
48
|
+
publicKey: lyraKey._identity.publicKeyHex,
|
|
49
|
+
capabilities: ["commons:message", "commons:evolve"],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const profile = createProfile({
|
|
55
|
+
namespace: "local/test",
|
|
56
|
+
name: "Test Profile",
|
|
57
|
+
scope: "local",
|
|
58
|
+
fixtures: ["fixture one", "fixture two"],
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const lyraStore = new CommonsStore(":memory:");
|
|
62
|
+
const transport = new MemoryLoopbackTransport();
|
|
63
|
+
|
|
64
|
+
const lyraRuntime = new AgentCommons({
|
|
65
|
+
keychain: lyraKey,
|
|
66
|
+
store: lyraStore,
|
|
67
|
+
policy,
|
|
68
|
+
transport,
|
|
69
|
+
});
|
|
70
|
+
lyraRuntime.addProfile(profile);
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
atlasKey,
|
|
74
|
+
lyraKey,
|
|
75
|
+
atlasId,
|
|
76
|
+
lyraId,
|
|
77
|
+
channelUri,
|
|
78
|
+
policy,
|
|
79
|
+
profile,
|
|
80
|
+
lyraStore,
|
|
81
|
+
transport,
|
|
82
|
+
lyraRuntime,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function makeSealedMessage(senderKey, recipientKey, profile, {
|
|
87
|
+
thread = "channel://local/community/general",
|
|
88
|
+
body = "test-message",
|
|
89
|
+
kind = "message",
|
|
90
|
+
expiresAt = new Date(Date.now() + 3600000).toISOString(),
|
|
91
|
+
id = randomUUID(),
|
|
92
|
+
} = {}) {
|
|
93
|
+
const payload = {
|
|
94
|
+
v: "agent-commons/1",
|
|
95
|
+
id,
|
|
96
|
+
kind,
|
|
97
|
+
thread,
|
|
98
|
+
profileId: profile.id,
|
|
99
|
+
wire: encode(body, profile.lexicon),
|
|
100
|
+
bodyHash: digest(body),
|
|
101
|
+
expiresAt,
|
|
102
|
+
};
|
|
103
|
+
return {
|
|
104
|
+
payload,
|
|
105
|
+
envelope: seal(senderKey, {
|
|
106
|
+
recipient: recipientKey._identity.uuaid,
|
|
107
|
+
recipientPublicKey: recipientKey._identity.publicKeyHex,
|
|
108
|
+
kind: "agent-commons/1",
|
|
109
|
+
payload,
|
|
110
|
+
}),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// =========================================================================
|
|
115
|
+
// T1: Hold before admission for invited sender, released after policy update
|
|
116
|
+
// =========================================================================
|
|
117
|
+
test("T1: CONTROL - Invited sender message is held, then accepted on releaseHeld after admission manifest", async () => {
|
|
118
|
+
const env = makeTestEnv({ holdBeforeAdmission: true });
|
|
119
|
+
const { payload, envelope } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, { thread: env.channelUri, body: "hello-hold" });
|
|
120
|
+
|
|
121
|
+
const res = await env.lyraRuntime.receive(envelope);
|
|
122
|
+
assert.equal(res.held, true);
|
|
123
|
+
assert.equal(res.id, envelope.id);
|
|
124
|
+
assert.equal(res.sender, env.atlasId);
|
|
125
|
+
assert.equal(res.channel, env.channelUri);
|
|
126
|
+
|
|
127
|
+
// Stored in held table, not yet processed/delivered
|
|
128
|
+
assert.equal(env.lyraStore.allHeld().length, 1);
|
|
129
|
+
assert.equal(env.lyraStore.seen(`msg:${env.atlasId}:${payload.id}`), false);
|
|
130
|
+
|
|
131
|
+
// Update policy to admit sender to channel
|
|
132
|
+
env.policy.channels[env.channelUri].members.push(env.atlasId);
|
|
133
|
+
|
|
134
|
+
const released = await env.lyraRuntime.releaseHeld();
|
|
135
|
+
assert.equal(released.length, 1);
|
|
136
|
+
assert.equal(released[0].accepted, true);
|
|
137
|
+
assert.equal(released[0].body, "hello-hold");
|
|
138
|
+
assert.equal(released[0].id, payload.id);
|
|
139
|
+
|
|
140
|
+
// Now leaves hold and is marked processed
|
|
141
|
+
assert.equal(env.lyraStore.allHeld().length, 0);
|
|
142
|
+
assert.equal(env.lyraStore.seen(`msg:${env.atlasId}:${payload.id}`), true);
|
|
143
|
+
|
|
144
|
+
const auditKinds = env.lyraStore.audit().map(r => JSON.parse(r.body).kind);
|
|
145
|
+
assert.ok(auditKinds.includes("hold-added"));
|
|
146
|
+
assert.ok(auditKinds.includes("hold-released"));
|
|
147
|
+
assert.ok(auditKinds.includes("message-received"));
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("T1: CONTROL (inverse) - When hold is disabled, unauthorized sender is refused and lost, releaseHeld finds 0", async () => {
|
|
151
|
+
const env = makeTestEnv({ holdBeforeAdmission: false });
|
|
152
|
+
const { envelope } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, { thread: env.channelUri, body: "hello-mutant" });
|
|
153
|
+
|
|
154
|
+
await assert.rejects(
|
|
155
|
+
() => env.lyraRuntime.receive(envelope),
|
|
156
|
+
(err) => err.code === "UNAUTHORIZED_CHANNEL_SENDER"
|
|
157
|
+
);
|
|
158
|
+
assert.equal(env.lyraStore.allHeld().length, 0);
|
|
159
|
+
const released = await env.lyraRuntime.releaseHeld();
|
|
160
|
+
assert.equal(released.length, 0);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// =========================================================================
|
|
164
|
+
// T2: Stranger (not in policy.agents) is never held
|
|
165
|
+
// =========================================================================
|
|
166
|
+
test("T2: CONTROL - Stranger not in policy.agents is refused with NOT_ADMITTED and never held", async () => {
|
|
167
|
+
const env = makeTestEnv({ holdBeforeAdmission: true });
|
|
168
|
+
const strangerKey = makeKey();
|
|
169
|
+
const { envelope } = makeSealedMessage(strangerKey, env.lyraKey, env.profile, { thread: env.channelUri });
|
|
170
|
+
|
|
171
|
+
await assert.rejects(
|
|
172
|
+
() => env.lyraRuntime.receive(envelope),
|
|
173
|
+
(err) => err.code === "NOT_ADMITTED" || /NOT_ADMITTED/.test(err.message)
|
|
174
|
+
);
|
|
175
|
+
assert.equal(env.lyraStore.allHeld().length, 0);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("T2: CONTROL (inverse) - Stranger envelope does not enter hold even if channel admits stranger ID", async () => {
|
|
179
|
+
const env = makeTestEnv({ holdBeforeAdmission: true });
|
|
180
|
+
const strangerKey = makeKey();
|
|
181
|
+
const strangerId = strangerKey._identity.uuaid;
|
|
182
|
+
env.policy.channels[env.channelUri].members.push(strangerId); // In channel, but NOT in policy.agents
|
|
183
|
+
const { envelope } = makeSealedMessage(strangerKey, env.lyraKey, env.profile, { thread: env.channelUri });
|
|
184
|
+
|
|
185
|
+
await assert.rejects(
|
|
186
|
+
() => env.lyraRuntime.receive(envelope),
|
|
187
|
+
(err) => err.code === "NOT_ADMITTED" || /NOT_ADMITTED/.test(err.message)
|
|
188
|
+
);
|
|
189
|
+
assert.equal(env.lyraStore.allHeld().length, 0);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// =========================================================================
|
|
193
|
+
// T3 (C1): Injected permissive trust plus stranger: direct check fails closed
|
|
194
|
+
// =========================================================================
|
|
195
|
+
test("T3: CONTROL (C1) - Injected permissive trust authorizing strangers is overridden by direct policy check", async () => {
|
|
196
|
+
const strangerKey = makeKey();
|
|
197
|
+
const env = makeTestEnv({ holdBeforeAdmission: true });
|
|
198
|
+
// Injected permissive trust that authorizes everything
|
|
199
|
+
const mockTrust = {
|
|
200
|
+
authorize: async () => ({ tier: "permissive-mock" }),
|
|
201
|
+
standards: async () => [],
|
|
202
|
+
publishedProfilePin: async () => {},
|
|
203
|
+
};
|
|
204
|
+
env.lyraRuntime.trust = mockTrust;
|
|
205
|
+
|
|
206
|
+
const { envelope } = makeSealedMessage(strangerKey, env.lyraKey, env.profile, { thread: env.channelUri });
|
|
207
|
+
|
|
208
|
+
// Stranger passes mock trust.authorize, but fails channel admission AND direct policy.agents check
|
|
209
|
+
await assert.rejects(
|
|
210
|
+
() => env.lyraRuntime.receive(envelope),
|
|
211
|
+
(err) => err.code === "UNAUTHORIZED_CHANNEL_SENDER"
|
|
212
|
+
);
|
|
213
|
+
assert.equal(env.lyraStore.allHeld().length, 0);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test("T3: CONTROL (inverse) (C1) - Simulated trust-trusting hold check would have held stranger; direct check prevents it", async () => {
|
|
217
|
+
const strangerKey = makeKey();
|
|
218
|
+
const env = makeTestEnv({ holdBeforeAdmission: true });
|
|
219
|
+
const mockTrust = {
|
|
220
|
+
authorize: async () => ({ tier: "permissive-mock" }),
|
|
221
|
+
standards: async () => [],
|
|
222
|
+
publishedProfilePin: async () => {},
|
|
223
|
+
};
|
|
224
|
+
env.lyraRuntime.trust = mockTrust;
|
|
225
|
+
const { envelope } = makeSealedMessage(strangerKey, env.lyraKey, env.profile, { thread: env.channelUri });
|
|
226
|
+
|
|
227
|
+
// Verify that stranger is NOT in policy.agents directly
|
|
228
|
+
assert.equal(env.policy.agents[strangerKey._identity.uuaid], undefined);
|
|
229
|
+
await assert.rejects(() => env.lyraRuntime.receive(envelope));
|
|
230
|
+
assert.equal(env.lyraStore.allHeld().length, 0);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// =========================================================================
|
|
234
|
+
// T4 (C1): Admitted UUAID whose transport key differs from policy key
|
|
235
|
+
// =========================================================================
|
|
236
|
+
test("T4: CONTROL (C1) - Admitted sender UUAID signed with wrong transport key is rejected and never held", async () => {
|
|
237
|
+
const env = makeTestEnv({ holdBeforeAdmission: true });
|
|
238
|
+
const otherKey = makeKey();
|
|
239
|
+
// Set policy.agents to have a different public key than Atlas's real key
|
|
240
|
+
env.policy.agents[env.atlasId].publicKey = otherKey._identity.publicKeyHex;
|
|
241
|
+
|
|
242
|
+
// Permissive trust to test hold path's direct check
|
|
243
|
+
const mockTrust = {
|
|
244
|
+
authorize: async () => ({ tier: "mock" }),
|
|
245
|
+
standards: async () => [],
|
|
246
|
+
publishedProfilePin: async () => {},
|
|
247
|
+
};
|
|
248
|
+
env.lyraRuntime.trust = mockTrust;
|
|
249
|
+
|
|
250
|
+
const { envelope } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, { thread: env.channelUri });
|
|
251
|
+
|
|
252
|
+
// Direct hold check fails key comparison (agent.publicKey !== envelope.transportSignature.publicKey)
|
|
253
|
+
await assert.rejects(
|
|
254
|
+
() => env.lyraRuntime.receive(envelope),
|
|
255
|
+
(err) => err.code === "UNAUTHORIZED_CHANNEL_SENDER"
|
|
256
|
+
);
|
|
257
|
+
assert.equal(env.lyraStore.allHeld().length, 0);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test("T4: CONTROL (inverse) (C1) - When transport key matches policy key, envelope IS eligible for hold", async () => {
|
|
261
|
+
const env = makeTestEnv({ holdBeforeAdmission: true });
|
|
262
|
+
const { envelope } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, { thread: env.channelUri });
|
|
263
|
+
|
|
264
|
+
// Key matches policy
|
|
265
|
+
assert.equal(envelope.transportSignature.publicKey, env.policy.agents[env.atlasId].publicKey);
|
|
266
|
+
const res = await env.lyraRuntime.receive(envelope);
|
|
267
|
+
assert.equal(res.held, true);
|
|
268
|
+
assert.equal(env.lyraStore.allHeld().length, 1);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// =========================================================================
|
|
272
|
+
// T5 (C2): UNAUTHORIZED_CHANNEL_SENDER thrown from inside apply(): not held
|
|
273
|
+
// =========================================================================
|
|
274
|
+
test("T5: CONTROL (C2) - Error thrown from apply() escapes receiveInternal and is never held", async () => {
|
|
275
|
+
const env = makeTestEnv({ holdBeforeAdmission: true });
|
|
276
|
+
// Add Atlas to channel so channel admission check passes
|
|
277
|
+
env.policy.channels[env.channelUri].members.push(env.atlasId);
|
|
278
|
+
|
|
279
|
+
// Monkey-patch apply to simulate a throw with UNAUTHORIZED_CHANNEL_SENDER
|
|
280
|
+
env.lyraRuntime.apply = async () => {
|
|
281
|
+
const err = new Error("Simulated apply error");
|
|
282
|
+
err.code = "UNAUTHORIZED_CHANNEL_SENDER";
|
|
283
|
+
throw err;
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const { envelope } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, {
|
|
287
|
+
thread: env.channelUri,
|
|
288
|
+
kind: "profile-control",
|
|
289
|
+
body: JSON.stringify({ kind: "profile-proposal", id: randomUUID(), createdAt: new Date().toISOString(), issuer: env.atlasId, publicKey: env.atlasKey._identity.publicKeyHex, payload: {} }),
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
await assert.rejects(
|
|
293
|
+
() => env.lyraRuntime.receive(envelope),
|
|
294
|
+
(err) => err.code === "UNAUTHORIZED_CHANNEL_SENDER" && err.message === "Simulated apply error"
|
|
295
|
+
);
|
|
296
|
+
assert.equal(env.lyraStore.allHeld().length, 0);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
test("T5: CONTROL (inverse) (C2) - Only assertChannelReceiveAdmission throw is held, not downstream errors", async () => {
|
|
300
|
+
const env = makeTestEnv({ holdBeforeAdmission: true });
|
|
301
|
+
// Channel admission fails -> held
|
|
302
|
+
const { envelope } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, { thread: env.channelUri });
|
|
303
|
+
const res = await env.lyraRuntime.receive(envelope);
|
|
304
|
+
assert.equal(res.held, true);
|
|
305
|
+
assert.equal(env.lyraStore.allHeld().length, 1);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// =========================================================================
|
|
309
|
+
// T6 (C2): UNCONFIGURED_CHANNEL: dropped, not held
|
|
310
|
+
// =========================================================================
|
|
311
|
+
test("T6: CONTROL (C2) - Message to unconfigured channel throws UNCONFIGURED_CHANNEL and is not held", async () => {
|
|
312
|
+
const env = makeTestEnv({ holdBeforeAdmission: true });
|
|
313
|
+
const unconfiguredUri = "channel://local/unconfigured/channel";
|
|
314
|
+
const { envelope } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, { thread: unconfiguredUri });
|
|
315
|
+
|
|
316
|
+
await assert.rejects(
|
|
317
|
+
() => env.lyraRuntime.receive(envelope),
|
|
318
|
+
(err) => err.code === "UNCONFIGURED_CHANNEL"
|
|
319
|
+
);
|
|
320
|
+
assert.equal(env.lyraStore.allHeld().length, 0);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
test("T6: CONTROL (inverse) (C2) - Configuring the channel allows hold to activate instead of dropping", async () => {
|
|
324
|
+
const env = makeTestEnv({ holdBeforeAdmission: true });
|
|
325
|
+
const newUri = "channel://local/new/channel";
|
|
326
|
+
env.policy.channels[newUri] = { members: [env.lyraId], allowForwarding: false };
|
|
327
|
+
const { envelope } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, { thread: newUri });
|
|
328
|
+
|
|
329
|
+
const res = await env.lyraRuntime.receive(envelope);
|
|
330
|
+
assert.equal(res.held, true);
|
|
331
|
+
assert.equal(env.lyraStore.allHeld().length, 1);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// =========================================================================
|
|
335
|
+
// T7 (C3): profile-control from invited sender: refused, not held
|
|
336
|
+
// =========================================================================
|
|
337
|
+
test("T7: CONTROL (C3) - profile-control envelope is refused immediately and not held", async () => {
|
|
338
|
+
const env = makeTestEnv({ holdBeforeAdmission: true });
|
|
339
|
+
const { envelope } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, {
|
|
340
|
+
thread: env.channelUri,
|
|
341
|
+
kind: "profile-control",
|
|
342
|
+
body: JSON.stringify({ kind: "profile-proposal", id: randomUUID(), createdAt: new Date().toISOString(), issuer: env.atlasId, publicKey: env.atlasKey._identity.publicKeyHex, payload: {} }),
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
await assert.rejects(
|
|
346
|
+
() => env.lyraRuntime.receive(envelope),
|
|
347
|
+
(err) => err.code === "UNAUTHORIZED_CHANNEL_SENDER"
|
|
348
|
+
);
|
|
349
|
+
assert.equal(env.lyraStore.allHeld().length, 0);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
test("T7: CONTROL (inverse) (C3) - kind === 'message' from same sender IS held", async () => {
|
|
353
|
+
const env = makeTestEnv({ holdBeforeAdmission: true });
|
|
354
|
+
const { envelope } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, {
|
|
355
|
+
thread: env.channelUri,
|
|
356
|
+
kind: "message",
|
|
357
|
+
});
|
|
358
|
+
const res = await env.lyraRuntime.receive(envelope);
|
|
359
|
+
assert.equal(res.held, true);
|
|
360
|
+
assert.equal(env.lyraStore.allHeld().length, 1);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
// =========================================================================
|
|
364
|
+
// T8 (C4): Deduplication of carrier duplicates & re-held envelopes
|
|
365
|
+
// =========================================================================
|
|
366
|
+
test("T8: CONTROL (C4) - Carrier duplicate and re-held envelope store 1 entry and count once against bounds", async () => {
|
|
367
|
+
const env = makeTestEnv({ holdBeforeAdmission: true });
|
|
368
|
+
const { envelope } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, { thread: env.channelUri, body: "dedupe-test" });
|
|
369
|
+
|
|
370
|
+
// First receipt -> held
|
|
371
|
+
const r1 = await env.lyraRuntime.receive(envelope);
|
|
372
|
+
assert.equal(r1.held, true);
|
|
373
|
+
assert.equal(env.lyraStore.allHeld().length, 1);
|
|
374
|
+
|
|
375
|
+
// Duplicate receipt of identical envelope -> returned as held, not stored twice
|
|
376
|
+
const r2 = await env.lyraRuntime.receive(envelope);
|
|
377
|
+
assert.equal(r2.held, true);
|
|
378
|
+
assert.equal(env.lyraStore.allHeld().length, 1);
|
|
379
|
+
|
|
380
|
+
// Attempt release while still unadmitted -> stays held, 1 entry
|
|
381
|
+
const released = await env.lyraRuntime.releaseHeld();
|
|
382
|
+
assert.equal(released.length, 0);
|
|
383
|
+
assert.equal(env.lyraStore.allHeld().length, 1);
|
|
384
|
+
|
|
385
|
+
const counts = env.lyraStore.holdCounts(env.channelUri, env.atlasId);
|
|
386
|
+
assert.equal(counts.channelSender, 1);
|
|
387
|
+
assert.equal(counts.total, 1);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
test("T8: CONTROL (inverse) (C4) - Distinct envelopes with different message IDs create distinct held rows", async () => {
|
|
391
|
+
const env = makeTestEnv({ holdBeforeAdmission: true });
|
|
392
|
+
const { envelope: env1 } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, { thread: env.channelUri, body: "msg-1" });
|
|
393
|
+
const { envelope: env2 } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, { thread: env.channelUri, body: "msg-2" });
|
|
394
|
+
|
|
395
|
+
await env.lyraRuntime.receive(env1);
|
|
396
|
+
await env.lyraRuntime.receive(env2);
|
|
397
|
+
assert.equal(env.lyraStore.allHeld().length, 2);
|
|
398
|
+
const counts = env.lyraStore.holdCounts(env.channelUri, env.atlasId);
|
|
399
|
+
assert.equal(counts.channelSender, 2);
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
// =========================================================================
|
|
403
|
+
// T9a (R5): Bounds: 20 per (channel, sender), 100 per channel, 500 total
|
|
404
|
+
// =========================================================================
|
|
405
|
+
test("T9a: CONTROL (R5) - Bounds 20 per (channel, sender), 100 per channel, 500 total refuse with exact reason", async () => {
|
|
406
|
+
const env = makeTestEnv({ holdBeforeAdmission: true });
|
|
407
|
+
|
|
408
|
+
// 1. Channel-sender bound: 20 envelopes from Atlas into channelUri
|
|
409
|
+
for (let i = 0; i < 20; i++) {
|
|
410
|
+
const { envelope } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, { thread: env.channelUri, body: `cs-${i}` });
|
|
411
|
+
const r = await env.lyraRuntime.receive(envelope);
|
|
412
|
+
assert.equal(r.held, true);
|
|
413
|
+
}
|
|
414
|
+
assert.equal(env.lyraStore.holdCounts(env.channelUri, env.atlasId).channelSender, 20);
|
|
415
|
+
|
|
416
|
+
// 21st envelope from Atlas to same channel is refused with 'channel-sender'
|
|
417
|
+
const { envelope: cs21 } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, { thread: env.channelUri, body: "cs-21" });
|
|
418
|
+
await assert.rejects(
|
|
419
|
+
() => env.lyraRuntime.receive(cs21),
|
|
420
|
+
(err) => err.code === "UNAUTHORIZED_CHANNEL_SENDER"
|
|
421
|
+
);
|
|
422
|
+
let audit = env.lyraStore.audit().map(r => JSON.parse(r.body));
|
|
423
|
+
let refused = audit.find(e => e.kind === "hold-refused" && e.id === cs21.id);
|
|
424
|
+
assert.ok(refused);
|
|
425
|
+
assert.equal(refused.reason, "channel-sender");
|
|
426
|
+
|
|
427
|
+
// 2. Channel bound: 100 per channel
|
|
428
|
+
// We already have 20 from Atlas. We add 4 more senders with 20 envelopes each (= 100 total for channelUri).
|
|
429
|
+
const extraSenders = [];
|
|
430
|
+
for (let s = 0; s < 4; s++) {
|
|
431
|
+
const k = makeKey();
|
|
432
|
+
const id = k._identity.uuaid;
|
|
433
|
+
env.policy.agents[id] = { kind: "agent", publicKey: k._identity.publicKeyHex, capabilities: ["commons:message"] };
|
|
434
|
+
extraSenders.push(k);
|
|
435
|
+
for (let i = 0; i < 20; i++) {
|
|
436
|
+
const { envelope } = makeSealedMessage(k, env.lyraKey, env.profile, { thread: env.channelUri, body: `ch-${s}-${i}` });
|
|
437
|
+
const r = await env.lyraRuntime.receive(envelope);
|
|
438
|
+
assert.equal(r.held, true);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
assert.equal(env.lyraStore.holdCounts(env.channelUri, env.atlasId).channel, 100);
|
|
442
|
+
|
|
443
|
+
// 101st envelope for channelUri from a fresh 6th sender: channel-sender is 0, sender is 0, but channel is 100 -> refused with 'channel'
|
|
444
|
+
const sender6 = makeKey();
|
|
445
|
+
env.policy.agents[sender6._identity.uuaid] = { kind: "agent", publicKey: sender6._identity.publicKeyHex, capabilities: ["commons:message"] };
|
|
446
|
+
const { envelope: ch101 } = makeSealedMessage(sender6, env.lyraKey, env.profile, { thread: env.channelUri, body: "ch-101" });
|
|
447
|
+
await assert.rejects(
|
|
448
|
+
() => env.lyraRuntime.receive(ch101),
|
|
449
|
+
(err) => err.code === "UNAUTHORIZED_CHANNEL_SENDER"
|
|
450
|
+
);
|
|
451
|
+
audit = env.lyraStore.audit().map(r => JSON.parse(r.body));
|
|
452
|
+
refused = audit.find(e => e.kind === "hold-refused" && e.id === ch101.id);
|
|
453
|
+
assert.ok(refused);
|
|
454
|
+
assert.equal(refused.reason, "channel");
|
|
455
|
+
|
|
456
|
+
// 3. Total bound: 500 across engine
|
|
457
|
+
// Channel 1 has 100. We populate channels 2, 3, 4, 5 with 100 each -> 500 total.
|
|
458
|
+
for (let c = 2; c <= 5; c++) {
|
|
459
|
+
const cUri = `channel://local/c${c}/general`;
|
|
460
|
+
env.policy.channels[cUri] = { members: [env.lyraId], allowForwarding: false };
|
|
461
|
+
for (let s = 0; s < 5; s++) {
|
|
462
|
+
const k = makeKey();
|
|
463
|
+
env.policy.agents[k._identity.uuaid] = { kind: "agent", publicKey: k._identity.publicKeyHex, capabilities: ["commons:message"] };
|
|
464
|
+
for (let i = 0; i < 20; i++) {
|
|
465
|
+
const { envelope } = makeSealedMessage(k, env.lyraKey, env.profile, { thread: cUri, body: `tot-${c}-${s}-${i}` });
|
|
466
|
+
const r = await env.lyraRuntime.receive(envelope);
|
|
467
|
+
assert.equal(r.held, true);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
assert.equal(env.lyraStore.holdCounts(env.channelUri, env.atlasId).total, 500);
|
|
472
|
+
|
|
473
|
+
// 501st envelope into channel 6 from a fresh sender: refused with 'total'
|
|
474
|
+
const c6Uri = "channel://local/c6/general";
|
|
475
|
+
env.policy.channels[c6Uri] = { members: [env.lyraId], allowForwarding: false };
|
|
476
|
+
const freshSender = makeKey();
|
|
477
|
+
env.policy.agents[freshSender._identity.uuaid] = { kind: "agent", publicKey: freshSender._identity.publicKeyHex, capabilities: ["commons:message"] };
|
|
478
|
+
const { envelope: tot501 } = makeSealedMessage(freshSender, env.lyraKey, env.profile, { thread: c6Uri, body: "tot-501" });
|
|
479
|
+
await assert.rejects(
|
|
480
|
+
() => env.lyraRuntime.receive(tot501),
|
|
481
|
+
(err) => err.code === "UNAUTHORIZED_CHANNEL_SENDER"
|
|
482
|
+
);
|
|
483
|
+
audit = env.lyraStore.audit().map(r => JSON.parse(r.body));
|
|
484
|
+
refused = audit.find(e => e.kind === "hold-refused" && e.id === tot501.id);
|
|
485
|
+
assert.ok(refused);
|
|
486
|
+
assert.equal(refused.reason, "total");
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
test("T9a: CONTROL (inverse) (R5) - Below bounds, envelopes are held without refusal", async () => {
|
|
490
|
+
const env = makeTestEnv({ holdBeforeAdmission: true });
|
|
491
|
+
const { envelope } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, { thread: env.channelUri, body: "under-bound" });
|
|
492
|
+
const res = await env.lyraRuntime.receive(envelope);
|
|
493
|
+
assert.equal(res.held, true);
|
|
494
|
+
const audit = env.lyraStore.audit().map(r => JSON.parse(r.body));
|
|
495
|
+
assert.equal(audit.some(e => e.kind === "hold-refused"), false);
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
// =========================================================================
|
|
499
|
+
// T9b (R5): 10-minute expiry and payload expiresAt
|
|
500
|
+
// =========================================================================
|
|
501
|
+
test("T9b: CONTROL (R5) - Expired held entry is purged on release with hold-expired", async () => {
|
|
502
|
+
const env = makeTestEnv({ holdBeforeAdmission: true });
|
|
503
|
+
const { envelope } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, {
|
|
504
|
+
thread: env.channelUri,
|
|
505
|
+
body: "will-expire",
|
|
506
|
+
// 5 seconds from now
|
|
507
|
+
expiresAt: new Date(Date.now() + 5000).toISOString(),
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
const res = await env.lyraRuntime.receive(envelope);
|
|
511
|
+
assert.equal(res.held, true);
|
|
512
|
+
assert.equal(env.lyraStore.allHeld().length, 1);
|
|
513
|
+
|
|
514
|
+
// Directly set expires_at in database to simulated past time
|
|
515
|
+
env.lyraStore.db.prepare("UPDATE held SET expires_at=? WHERE id=?").run(Date.now() - 1000, envelope.id);
|
|
516
|
+
|
|
517
|
+
// Admit Atlas and release
|
|
518
|
+
env.policy.channels[env.channelUri].members.push(env.atlasId);
|
|
519
|
+
const released = await env.lyraRuntime.releaseHeld();
|
|
520
|
+
assert.equal(released.length, 0); // Not delivered because expired
|
|
521
|
+
assert.equal(env.lyraStore.allHeld().length, 0); // Removed from hold
|
|
522
|
+
|
|
523
|
+
const audit = env.lyraStore.audit().map(r => JSON.parse(r.body));
|
|
524
|
+
const expiredEvent = audit.find(e => e.kind === "hold-expired" && e.id === envelope.id);
|
|
525
|
+
assert.ok(expiredEvent);
|
|
526
|
+
assert.equal(expiredEvent.reason, "expired");
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
test("T9b: CONTROL (inverse) (R5) - Non-expired held entry is released and accepted", async () => {
|
|
530
|
+
const env = makeTestEnv({ holdBeforeAdmission: true });
|
|
531
|
+
const { envelope } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, { thread: env.channelUri, body: "unexpired" });
|
|
532
|
+
await env.lyraRuntime.receive(envelope);
|
|
533
|
+
|
|
534
|
+
env.policy.channels[env.channelUri].members.push(env.atlasId);
|
|
535
|
+
const released = await env.lyraRuntime.releaseHeld();
|
|
536
|
+
assert.equal(released.length, 1);
|
|
537
|
+
assert.equal(released[0].accepted, true);
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
// =========================================================================
|
|
541
|
+
// T9c (R5): Held body is never returned before acceptance
|
|
542
|
+
// =========================================================================
|
|
543
|
+
test("T9c: CONTROL (R5) - Held return value contains no body, wire, or payload content", async () => {
|
|
544
|
+
const env = makeTestEnv({ holdBeforeAdmission: true });
|
|
545
|
+
const secretBody = "ultra-secret-body-content";
|
|
546
|
+
const { envelope } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, { thread: env.channelUri, body: secretBody });
|
|
547
|
+
|
|
548
|
+
const res = await env.lyraRuntime.receive(envelope);
|
|
549
|
+
assert.equal(res.held, true);
|
|
550
|
+
assert.equal(res.body, undefined);
|
|
551
|
+
assert.equal(res.wire, undefined);
|
|
552
|
+
assert.equal(res.payload, undefined);
|
|
553
|
+
assert.equal(Object.keys(res).sort().join(","), "channel,held,id,sender");
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
test("T9c: CONTROL (inverse) (R5) - Upon acceptance, body is properly returned to caller", async () => {
|
|
557
|
+
const env = makeTestEnv({ holdBeforeAdmission: true });
|
|
558
|
+
const bodyText = "accepted-body-content";
|
|
559
|
+
const { envelope } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, { thread: env.channelUri, body: bodyText });
|
|
560
|
+
await env.lyraRuntime.receive(envelope);
|
|
561
|
+
|
|
562
|
+
env.policy.channels[env.channelUri].members.push(env.atlasId);
|
|
563
|
+
const released = await env.lyraRuntime.releaseHeld();
|
|
564
|
+
assert.equal(released.length, 1);
|
|
565
|
+
assert.equal(released[0].body, bodyText);
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
// =========================================================================
|
|
569
|
+
// T13 (R1): Admitted sender flooding 5 channels caps at 20 across all channels
|
|
570
|
+
// =========================================================================
|
|
571
|
+
test("T13: CONTROL (R1) - Admitted sender flooding 5 channels stops at 20 across channels; 6th channel sender succeeds", async () => {
|
|
572
|
+
const env = makeTestEnv({ holdBeforeAdmission: true });
|
|
573
|
+
|
|
574
|
+
// Create 5 channels
|
|
575
|
+
const channels = [];
|
|
576
|
+
for (let i = 1; i <= 5; i++) {
|
|
577
|
+
const cUri = `channel://local/flood-${i}/general`;
|
|
578
|
+
env.policy.channels[cUri] = { members: [env.lyraId], allowForwarding: false };
|
|
579
|
+
channels.push(cUri);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// Atlas sends 4 messages to each of the 5 channels = 20 total
|
|
583
|
+
for (const cUri of channels) {
|
|
584
|
+
for (let m = 0; m < 4; m++) {
|
|
585
|
+
const { envelope } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, { thread: cUri, body: `flood-${m}` });
|
|
586
|
+
const r = await env.lyraRuntime.receive(envelope);
|
|
587
|
+
assert.equal(r.held, true);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const countsAtlas = env.lyraStore.holdCounts(channels[0], env.atlasId);
|
|
592
|
+
assert.equal(countsAtlas.sender, 20);
|
|
593
|
+
assert.equal(countsAtlas.channelSender, 4);
|
|
594
|
+
|
|
595
|
+
// 21st message from Atlas to channel 1: channelSender is 4 (< 20), but sender count is 20 -> refused with 'sender'
|
|
596
|
+
const { envelope: flood21 } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, { thread: channels[0], body: "flood-21" });
|
|
597
|
+
await assert.rejects(
|
|
598
|
+
() => env.lyraRuntime.receive(flood21),
|
|
599
|
+
(err) => err.code === "UNAUTHORIZED_CHANNEL_SENDER"
|
|
600
|
+
);
|
|
601
|
+
const audit = env.lyraStore.audit().map(r => JSON.parse(r.body));
|
|
602
|
+
const refused = audit.find(e => e.kind === "hold-refused" && e.id === flood21.id);
|
|
603
|
+
assert.ok(refused);
|
|
604
|
+
assert.equal(refused.reason, "sender");
|
|
605
|
+
|
|
606
|
+
// An independent sender in channel 6 succeeds in being held
|
|
607
|
+
const c6Uri = "channel://local/flood-6/general";
|
|
608
|
+
env.policy.channels[c6Uri] = { members: [env.lyraId], allowForwarding: false };
|
|
609
|
+
const independentKey = makeKey();
|
|
610
|
+
env.policy.agents[independentKey._identity.uuaid] = { kind: "agent", publicKey: independentKey._identity.publicKeyHex, capabilities: ["commons:message"] };
|
|
611
|
+
const { envelope: indEnv } = makeSealedMessage(independentKey, env.lyraKey, env.profile, { thread: c6Uri, body: "ind-1" });
|
|
612
|
+
const indRes = await env.lyraRuntime.receive(indEnv);
|
|
613
|
+
assert.equal(indRes.held, true);
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
test("T13: CONTROL (inverse) (R1) - A sender with < 20 messages across channels is not refused by sender cap", async () => {
|
|
617
|
+
const env = makeTestEnv({ holdBeforeAdmission: true });
|
|
618
|
+
const { envelope } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, { thread: env.channelUri, body: "single-send" });
|
|
619
|
+
const r = await env.lyraRuntime.receive(envelope);
|
|
620
|
+
assert.equal(r.held, true);
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
// =========================================================================
|
|
624
|
+
// T14 (R2): Sender removed during hold is dropped on releaseHeld
|
|
625
|
+
// =========================================================================
|
|
626
|
+
test("T14: CONTROL (R2) - Sender removed from policy.agents during hold is dropped on release with hold-dropped", async () => {
|
|
627
|
+
const env = makeTestEnv({ holdBeforeAdmission: true });
|
|
628
|
+
const { envelope } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, { thread: env.channelUri, body: "will-be-dropped" });
|
|
629
|
+
|
|
630
|
+
await env.lyraRuntime.receive(envelope);
|
|
631
|
+
assert.equal(env.lyraStore.allHeld().length, 1);
|
|
632
|
+
|
|
633
|
+
// Remove Atlas from policy.agents while envelope is held
|
|
634
|
+
delete env.policy.agents[env.atlasId];
|
|
635
|
+
|
|
636
|
+
// Also admit Atlas in channel (to prove channel admission isn't what drops it)
|
|
637
|
+
env.policy.channels[env.channelUri].members.push(env.atlasId);
|
|
638
|
+
|
|
639
|
+
const released = await env.lyraRuntime.releaseHeld();
|
|
640
|
+
assert.equal(released.length, 0); // Dropped, not delivered
|
|
641
|
+
assert.equal(env.lyraStore.allHeld().length, 0); // Removed from hold
|
|
642
|
+
|
|
643
|
+
const audit = env.lyraStore.audit().map(r => JSON.parse(r.body));
|
|
644
|
+
const dropped = audit.find(e => e.kind === "hold-dropped" && e.id === envelope.id);
|
|
645
|
+
assert.ok(dropped);
|
|
646
|
+
assert.equal(dropped.reason, "sender-not-admitted");
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
test("T14: CONTROL (inverse) (R2) - When sender remains in policy.agents, releaseHeld accepts message", async () => {
|
|
650
|
+
const env = makeTestEnv({ holdBeforeAdmission: true });
|
|
651
|
+
const { envelope } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, { thread: env.channelUri, body: "remains-admitted" });
|
|
652
|
+
await env.lyraRuntime.receive(envelope);
|
|
653
|
+
|
|
654
|
+
env.policy.channels[env.channelUri].members.push(env.atlasId);
|
|
655
|
+
const released = await env.lyraRuntime.releaseHeld();
|
|
656
|
+
assert.equal(released.length, 1);
|
|
657
|
+
assert.equal(released[0].accepted, true);
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
// =========================================================================
|
|
661
|
+
// T15 (R3): Message accepted directly while copy is held -> duplicate: true on release
|
|
662
|
+
// =========================================================================
|
|
663
|
+
test("T15: CONTROL (R3) - Message accepted directly while copy is held returns duplicate: true on release with 1 delivery", async () => {
|
|
664
|
+
const env = makeTestEnv({ holdBeforeAdmission: true });
|
|
665
|
+
const { payload, envelope } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, { thread: env.channelUri, body: "seen-race" });
|
|
666
|
+
|
|
667
|
+
// 1. Envelope arrives and is held
|
|
668
|
+
await env.lyraRuntime.receive(envelope);
|
|
669
|
+
assert.equal(env.lyraStore.allHeld().length, 1);
|
|
670
|
+
|
|
671
|
+
// 2. Channel policy updated to admit Atlas
|
|
672
|
+
env.policy.channels[env.channelUri].members.push(env.atlasId);
|
|
673
|
+
|
|
674
|
+
// 3. Same envelope is received directly via receive() and accepted
|
|
675
|
+
const directRes = await env.lyraRuntime.receive(envelope);
|
|
676
|
+
assert.equal(directRes.accepted, true);
|
|
677
|
+
assert.equal(directRes.id, payload.id);
|
|
678
|
+
assert.equal(env.lyraStore.seen(`msg:${env.atlasId}:${payload.id}`), true);
|
|
679
|
+
|
|
680
|
+
// 4. releaseHeld() runs: seen check catches it -> leaves hold, returns duplicate: true
|
|
681
|
+
const releaseRes = await env.lyraRuntime.releaseHeld();
|
|
682
|
+
assert.equal(releaseRes.length, 1);
|
|
683
|
+
assert.equal(releaseRes[0].duplicate, true);
|
|
684
|
+
assert.equal(env.lyraStore.allHeld().length, 0);
|
|
685
|
+
|
|
686
|
+
// Audit has exactly 1 message-received event (1 delivery)
|
|
687
|
+
const audit = env.lyraStore.audit().map(r => JSON.parse(r.body));
|
|
688
|
+
const messageReceived = audit.filter(e => e.kind === "message-received" && e.envelopeId === envelope.id);
|
|
689
|
+
assert.equal(messageReceived.length, 1);
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
test("T15: CONTROL (inverse) (R3) - Without prior direct delivery, release delivers normally", async () => {
|
|
693
|
+
const env = makeTestEnv({ holdBeforeAdmission: true });
|
|
694
|
+
const { envelope } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, { thread: env.channelUri, body: "no-race" });
|
|
695
|
+
await env.lyraRuntime.receive(envelope);
|
|
696
|
+
|
|
697
|
+
env.policy.channels[env.channelUri].members.push(env.atlasId);
|
|
698
|
+
const releaseRes = await env.lyraRuntime.releaseHeld();
|
|
699
|
+
assert.equal(releaseRes.length, 1);
|
|
700
|
+
assert.equal(releaseRes[0].accepted, true);
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
// =========================================================================
|
|
704
|
+
// T16 (R6): poll() suppresses quarantined-inbound row for held envelopes
|
|
705
|
+
// =========================================================================
|
|
706
|
+
test("T16: CONTROL (R6) - poll() advances carrier cursor and writes hold-added with zero quarantined-inbound rows", async () => {
|
|
707
|
+
const env = makeTestEnv({ holdBeforeAdmission: true });
|
|
708
|
+
const { envelope } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, { thread: env.channelUri, body: "poll-hold" });
|
|
709
|
+
|
|
710
|
+
await env.transport.deliver(envelope);
|
|
711
|
+
const polled = await env.lyraRuntime.poll();
|
|
712
|
+
|
|
713
|
+
assert.equal(polled.length, 1);
|
|
714
|
+
assert.equal(polled[0].held, true);
|
|
715
|
+
assert.equal(polled[0].id, envelope.id);
|
|
716
|
+
|
|
717
|
+
// Check audit: hold-added present, quarantined-inbound absent
|
|
718
|
+
const audit = env.lyraStore.audit().map(r => JSON.parse(r.body));
|
|
719
|
+
assert.ok(audit.some(e => e.kind === "hold-added" && e.id === envelope.id));
|
|
720
|
+
assert.equal(audit.some(e => e.kind === "quarantined-inbound"), false);
|
|
721
|
+
|
|
722
|
+
// Carrier cursor advanced
|
|
723
|
+
const cursors = env.lyraStore.get("cursors");
|
|
724
|
+
assert.ok(cursors);
|
|
725
|
+
assert.ok(Object.values(cursors).some(seq => seq > 0));
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
test("T16: CONTROL (inverse) (R6) - When hold is disabled, poll() writes quarantined-inbound", async () => {
|
|
729
|
+
const env = makeTestEnv({ holdBeforeAdmission: false });
|
|
730
|
+
const { envelope } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, { thread: env.channelUri, body: "poll-refused" });
|
|
731
|
+
|
|
732
|
+
await env.transport.deliver(envelope);
|
|
733
|
+
const polled = await env.lyraRuntime.poll();
|
|
734
|
+
|
|
735
|
+
assert.equal(polled.length, 1);
|
|
736
|
+
assert.equal(polled[0].rejected, true);
|
|
737
|
+
|
|
738
|
+
const audit = env.lyraStore.audit().map(r => JSON.parse(r.body));
|
|
739
|
+
assert.ok(audit.some(e => e.kind === "quarantined-inbound" && e.id === envelope.id));
|
|
740
|
+
assert.equal(audit.some(e => e.kind === "hold-added"), false);
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
// =========================================================================
|
|
744
|
+
// T10 (C6): Audit rows never contain body, wire, or plaintext
|
|
745
|
+
// =========================================================================
|
|
746
|
+
test("T10: CONTROL (C6) - Audit rows for hold-added, hold-released, hold-dropped, hold-refused contain zero body or wire data", async () => {
|
|
747
|
+
const env = makeTestEnv({ holdBeforeAdmission: true });
|
|
748
|
+
const secretText = "confidential-payload-cannot-leak";
|
|
749
|
+
const { envelope } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, { thread: env.channelUri, body: secretText });
|
|
750
|
+
|
|
751
|
+
await env.lyraRuntime.receive(envelope);
|
|
752
|
+
env.policy.channels[env.channelUri].members.push(env.atlasId);
|
|
753
|
+
await env.lyraRuntime.releaseHeld();
|
|
754
|
+
|
|
755
|
+
const holdRows = env.lyraStore.audit().filter(r => JSON.parse(r.body).kind?.startsWith("hold-"));
|
|
756
|
+
assert.ok(holdRows.length >= 2);
|
|
757
|
+
for (const row of holdRows) {
|
|
758
|
+
const raw = row.body;
|
|
759
|
+
assert.equal(raw.includes(secretText), false);
|
|
760
|
+
assert.equal(raw.includes("wire"), false);
|
|
761
|
+
assert.equal(raw.includes("payload"), false);
|
|
762
|
+
}
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
test("T10: CONTROL (inverse) (C6) - Audit events contain required metadata fields", async () => {
|
|
766
|
+
const env = makeTestEnv({ holdBeforeAdmission: true });
|
|
767
|
+
const { envelope } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, { thread: env.channelUri, body: "metadata-check" });
|
|
768
|
+
await env.lyraRuntime.receive(envelope);
|
|
769
|
+
|
|
770
|
+
const audit = env.lyraStore.audit().map(r => JSON.parse(r.body));
|
|
771
|
+
const holdEvent = audit.find(e => e.kind === "hold-added");
|
|
772
|
+
assert.ok(holdEvent);
|
|
773
|
+
assert.equal(holdEvent.id, envelope.id);
|
|
774
|
+
assert.equal(holdEvent.sender, env.atlasId);
|
|
775
|
+
assert.equal(holdEvent.channel, env.channelUri);
|
|
776
|
+
assert.equal(holdEvent.sha, envelopeSha(envelope));
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
// =========================================================================
|
|
780
|
+
// T11: Default policy behavior unchanged (holdBeforeAdmission defaults to false)
|
|
781
|
+
// =========================================================================
|
|
782
|
+
test("T11: CONTROL - Without holdBeforeAdmission flag, behavior matches f4aeab9 baseline", async () => {
|
|
783
|
+
const env = makeTestEnv();
|
|
784
|
+
delete env.policy.holdBeforeAdmission; // unset flag
|
|
785
|
+
|
|
786
|
+
const { envelope } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, { thread: env.channelUri });
|
|
787
|
+
await assert.rejects(
|
|
788
|
+
() => env.lyraRuntime.receive(envelope),
|
|
789
|
+
(err) => err.code === "UNAUTHORIZED_CHANNEL_SENDER"
|
|
790
|
+
);
|
|
791
|
+
assert.equal(env.lyraStore.allHeld().length, 0);
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
test("T11: CONTROL (inverse) - Explicit holdBeforeAdmission: true enables hold", async () => {
|
|
795
|
+
const env = makeTestEnv({ holdBeforeAdmission: true });
|
|
796
|
+
const { envelope } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, { thread: env.channelUri });
|
|
797
|
+
const res = await env.lyraRuntime.receive(envelope);
|
|
798
|
+
assert.equal(res.held, true);
|
|
799
|
+
assert.equal(env.lyraStore.allHeld().length, 1);
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
// =========================================================================
|
|
803
|
+
// T12 (C5): Multi-page bounded flush in flushInternal
|
|
804
|
+
// =========================================================================
|
|
805
|
+
test("T12: CONTROL (C5) - 120 pending outbox rows all flush in 1 call; 1,100 rows stops at 1,000", async () => {
|
|
806
|
+
const k1 = makeKey();
|
|
807
|
+
const k2 = makeKey();
|
|
808
|
+
const policy = {
|
|
809
|
+
mode: "local",
|
|
810
|
+
offline: true,
|
|
811
|
+
channelsEnabled: false,
|
|
812
|
+
agents: {
|
|
813
|
+
[k1._identity.uuaid]: { kind: "agent", publicKey: k1._identity.publicKeyHex, capabilities: ["commons:message"] },
|
|
814
|
+
[k2._identity.uuaid]: { kind: "agent", publicKey: k2._identity.publicKeyHex, capabilities: ["commons:message"] },
|
|
815
|
+
},
|
|
816
|
+
};
|
|
817
|
+
const profile = createProfile({ namespace: "local/test", name: "Test", scope: "local", fixtures: ["f1", "f2"] });
|
|
818
|
+
const transport = new MemoryLoopbackTransport();
|
|
819
|
+
|
|
820
|
+
// Test part 1: 120 queued rows all flush in 1 call (3 pages: 50, 50, 20)
|
|
821
|
+
const runtime1 = new AgentCommons({ keychain: k1, policy, transport });
|
|
822
|
+
runtime1.addProfile(profile);
|
|
823
|
+
|
|
824
|
+
for (let i = 0; i < 120; i++) {
|
|
825
|
+
await runtime1.send({ recipient: k2._identity.uuaid, profileId: profile.id, body: `msg-${i}`, deferFlush: true });
|
|
826
|
+
}
|
|
827
|
+
assert.equal(runtime1.store.db.prepare("SELECT COUNT(*) as n FROM outbox WHERE state='pending'").get().n, 120);
|
|
828
|
+
|
|
829
|
+
const res1 = await runtime1.flush();
|
|
830
|
+
assert.equal(res1.length, 120);
|
|
831
|
+
assert.equal(runtime1.store.db.prepare("SELECT COUNT(*) as n FROM outbox WHERE state='pending'").get().n, 0);
|
|
832
|
+
|
|
833
|
+
// Test part 2: 1,100 queued rows stops at hard cap of 20 pages = 1,000 rows
|
|
834
|
+
const runtime2 = new AgentCommons({ keychain: k1, policy, transport });
|
|
835
|
+
runtime2.addProfile(profile);
|
|
836
|
+
|
|
837
|
+
for (let i = 0; i < 1100; i++) {
|
|
838
|
+
await runtime2.send({ recipient: k2._identity.uuaid, profileId: profile.id, body: `msg-${i}`, deferFlush: true });
|
|
839
|
+
}
|
|
840
|
+
assert.equal(runtime2.store.db.prepare("SELECT COUNT(*) as n FROM outbox WHERE state='pending'").get().n, 1100);
|
|
841
|
+
|
|
842
|
+
const res2 = await runtime2.flush();
|
|
843
|
+
assert.equal(res2.length, 1000);
|
|
844
|
+
assert.equal(runtime2.store.db.prepare("SELECT COUNT(*) as n FROM outbox WHERE state='pending'").get().n, 100);
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
test("T12: CONTROL (inverse) (C5) - Flush with zero pending rows returns empty results without infinite loop", async () => {
|
|
848
|
+
const k1 = makeKey();
|
|
849
|
+
const runtime = new AgentCommons({ keychain: k1, policy: { mode: "local" }, transport: new MemoryLoopbackTransport() });
|
|
850
|
+
const res = await runtime.flush();
|
|
851
|
+
assert.deepEqual(res, []);
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
// =========================================================================
|
|
855
|
+
// Concurrency & Interleaving Proof: 50-Cycle Stress with Fixed Seed
|
|
856
|
+
// =========================================================================
|
|
857
|
+
test("Concurrency & Interleaving: 50-cycle interleaving stress proof between poll() and releaseHeld()", async () => {
|
|
858
|
+
const FIXED_SEED = 0x11062026;
|
|
859
|
+
console.log(`[L11 Interleaving Test] PRNG Fixed Seed: 0x${FIXED_SEED.toString(16)} (${FIXED_SEED})`);
|
|
860
|
+
|
|
861
|
+
function makePrng(seed) {
|
|
862
|
+
let s = seed >>> 0;
|
|
863
|
+
return () => {
|
|
864
|
+
s = (Math.imul(s, 1664525) + 1013904223) >>> 0;
|
|
865
|
+
return s / 4294967296;
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
const rand = makePrng(FIXED_SEED);
|
|
869
|
+
|
|
870
|
+
const receiverKey = makeKey();
|
|
871
|
+
const receiverId = receiverKey._identity.uuaid;
|
|
872
|
+
const channelUri = "channel://local/c/stress";
|
|
873
|
+
const profile = createProfile({ namespace: "local/test", name: "Test", scope: "local", fixtures: ["f1", "f2"] });
|
|
874
|
+
|
|
875
|
+
const senderKeys = [];
|
|
876
|
+
const senderIds = [];
|
|
877
|
+
for (let i = 0; i < 4; i++) {
|
|
878
|
+
const k = makeKey();
|
|
879
|
+
senderKeys.push(k);
|
|
880
|
+
senderIds.push(k._identity.uuaid);
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
const policy = {
|
|
884
|
+
mode: "local",
|
|
885
|
+
offline: true,
|
|
886
|
+
channelsEnabled: true,
|
|
887
|
+
holdBeforeAdmission: true,
|
|
888
|
+
channels: {
|
|
889
|
+
[channelUri]: { members: [receiverId], allowForwarding: false },
|
|
890
|
+
},
|
|
891
|
+
agents: {
|
|
892
|
+
[receiverId]: { kind: "agent", publicKey: receiverKey._identity.publicKeyHex, capabilities: ["commons:message"] },
|
|
893
|
+
},
|
|
894
|
+
};
|
|
895
|
+
for (const k of senderKeys) {
|
|
896
|
+
policy.agents[k._identity.uuaid] = { kind: "agent", publicKey: k._identity.publicKeyHex, capabilities: ["commons:message"] };
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
const transport = new MemoryLoopbackTransport();
|
|
900
|
+
const store = new CommonsStore(":memory:");
|
|
901
|
+
const runtime = new AgentCommons({ keychain: receiverKey, store, policy, transport });
|
|
902
|
+
runtime.addProfile(profile);
|
|
903
|
+
|
|
904
|
+
const deliveredMessages = new Map();
|
|
905
|
+
|
|
906
|
+
for (let cycle = 0; cycle < 50; cycle++) {
|
|
907
|
+
const senderIdx = Math.floor(rand() * senderKeys.length);
|
|
908
|
+
const senderKey = senderKeys[senderIdx];
|
|
909
|
+
const sId = senderIds[senderIdx];
|
|
910
|
+
const msgId = randomUUID();
|
|
911
|
+
|
|
912
|
+
const { envelope } = makeSealedMessage(senderKey, receiverKey, profile, {
|
|
913
|
+
thread: channelUri,
|
|
914
|
+
body: `stress-msg-${cycle}`,
|
|
915
|
+
id: msgId,
|
|
916
|
+
});
|
|
917
|
+
|
|
918
|
+
await transport.deliver(envelope);
|
|
919
|
+
|
|
920
|
+
// Dynamically admit sender with 50% probability
|
|
921
|
+
if (rand() > 0.5) {
|
|
922
|
+
if (!policy.channels[channelUri].members.includes(sId)) {
|
|
923
|
+
policy.channels[channelUri].members.push(sId);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// Concurrently trigger poll() and releaseHeld()
|
|
928
|
+
const [pollRes, releaseRes] = await Promise.all([
|
|
929
|
+
runtime.poll(),
|
|
930
|
+
runtime.releaseHeld(),
|
|
931
|
+
]);
|
|
932
|
+
|
|
933
|
+
for (const r of pollRes) {
|
|
934
|
+
if (r?.accepted) {
|
|
935
|
+
assert.equal(deliveredMessages.has(r.id), false, `Duplicate delivery via poll for ${r.id}`);
|
|
936
|
+
deliveredMessages.set(r.id, r);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
for (const r of releaseRes) {
|
|
940
|
+
if (r?.accepted) {
|
|
941
|
+
assert.equal(deliveredMessages.has(r.id), false, `Duplicate delivery via releaseHeld for ${r.id}`);
|
|
942
|
+
deliveredMessages.set(r.id, r);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// Final phase: admit all senders and release any remaining held messages
|
|
948
|
+
for (const sId of senderIds) {
|
|
949
|
+
if (!policy.channels[channelUri].members.includes(sId)) {
|
|
950
|
+
policy.channels[channelUri].members.push(sId);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
const finalRelease = await runtime.releaseHeld();
|
|
954
|
+
for (const r of finalRelease) {
|
|
955
|
+
if (r?.accepted) {
|
|
956
|
+
assert.equal(deliveredMessages.has(r.id), false, `Duplicate delivery in final release for ${r.id}`);
|
|
957
|
+
deliveredMessages.set(r.id, r);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// Invariants:
|
|
962
|
+
// 1. Every message sent was delivered exactly once
|
|
963
|
+
assert.equal(deliveredMessages.size, 50);
|
|
964
|
+
|
|
965
|
+
// 2. Zero quarantined-inbound rows in audit log
|
|
966
|
+
const audit = store.audit().map(r => JSON.parse(r.body));
|
|
967
|
+
const quarantined = audit.filter(e => e.kind === "quarantined-inbound");
|
|
968
|
+
assert.equal(quarantined.length, 0);
|
|
969
|
+
|
|
970
|
+
// 3. Store audit integrity is intact
|
|
971
|
+
assert.equal(store.verify(), true);
|
|
972
|
+
|
|
973
|
+
// 4. Zero held messages remain
|
|
974
|
+
assert.equal(store.allHeld().length, 0);
|
|
975
|
+
});
|
|
976
|
+
|
|
977
|
+
// =========================================================================
|
|
978
|
+
// Rework 1 F1: Expired held row pruning before bounds and deduplication
|
|
979
|
+
// =========================================================================
|
|
980
|
+
test("Rework 1 F1 (a): CONTROL - 20 expired rows for (channel, sender) are pruned so fresh eligible message is held", async () => {
|
|
981
|
+
const env = makeTestEnv({ holdBeforeAdmission: true });
|
|
982
|
+
for (let i = 0; i < 20; i++) {
|
|
983
|
+
const { envelope } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, { thread: env.channelUri, body: `old-${i}` });
|
|
984
|
+
env.lyraStore.hold(envelope, `msg:${env.atlasId}:${randomUUID()}`, env.channelUri, env.atlasId, envelopeSha(envelope), Date.now() - 1000);
|
|
985
|
+
}
|
|
986
|
+
assert.equal(env.lyraStore.holdCounts(env.channelUri, env.atlasId).channelSender, 20);
|
|
987
|
+
|
|
988
|
+
const { envelope: freshEnv } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, { thread: env.channelUri, body: "fresh-msg" });
|
|
989
|
+
const res = await env.lyraRuntime.receive(freshEnv);
|
|
990
|
+
assert.equal(res.held, true);
|
|
991
|
+
assert.equal(env.lyraStore.holdCounts(env.channelUri, env.atlasId).channelSender, 1);
|
|
992
|
+
const audit = env.lyraStore.audit().map(r => JSON.parse(r.body));
|
|
993
|
+
const expiredEvents = audit.filter(e => e.kind === "hold-expired");
|
|
994
|
+
assert.equal(expiredEvents.length, 20);
|
|
995
|
+
});
|
|
996
|
+
|
|
997
|
+
test("Rework 1 F1 (b): CONTROL - Expired row with id X is pruned so same envelope X arriving again is held as live", async () => {
|
|
998
|
+
const env = makeTestEnv({ holdBeforeAdmission: true });
|
|
999
|
+
const { payload, envelope } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, { thread: env.channelUri, body: "replay-after-expiry" });
|
|
1000
|
+
const msgKey = `msg:${env.atlasId}:${payload.id}`;
|
|
1001
|
+
env.lyraStore.hold(envelope, msgKey, env.channelUri, env.atlasId, envelopeSha(envelope), Date.now() - 1000);
|
|
1002
|
+
assert.equal(env.lyraStore.allHeld().length, 1);
|
|
1003
|
+
|
|
1004
|
+
const res = await env.lyraRuntime.receive(envelope);
|
|
1005
|
+
assert.equal(res.held, true);
|
|
1006
|
+
const live = env.lyraStore.allHeld();
|
|
1007
|
+
assert.equal(live.length, 1);
|
|
1008
|
+
assert.ok(live[0].expires_at > Date.now());
|
|
1009
|
+
const audit = env.lyraStore.audit().map(r => JSON.parse(r.body));
|
|
1010
|
+
assert.ok(audit.some(e => e.kind === "hold-expired" && e.id === envelope.id));
|
|
1011
|
+
assert.ok(audit.some(e => e.kind === "hold-added" && e.id === envelope.id));
|
|
1012
|
+
});
|
|
1013
|
+
|
|
1014
|
+
// =========================================================================
|
|
1015
|
+
// Rework 1 F2: Transient error handling in releaseHeldInternal
|
|
1016
|
+
// =========================================================================
|
|
1017
|
+
test("Rework 1 F2 (c): CONTROL - Injected trust throwing transient error keeps entry held with hold-deferred, accepted on later release", async () => {
|
|
1018
|
+
const env = makeTestEnv({ holdBeforeAdmission: true });
|
|
1019
|
+
const { payload, envelope } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, { thread: env.channelUri, body: "transient-retry" });
|
|
1020
|
+
|
|
1021
|
+
await env.lyraRuntime.receive(envelope);
|
|
1022
|
+
assert.equal(env.lyraStore.allHeld().length, 1);
|
|
1023
|
+
|
|
1024
|
+
env.policy.channels[env.channelUri].members.push(env.atlasId);
|
|
1025
|
+
|
|
1026
|
+
let callCount = 0;
|
|
1027
|
+
const originalTrust = env.lyraRuntime.trust;
|
|
1028
|
+
env.lyraRuntime.trust = {
|
|
1029
|
+
...originalTrust,
|
|
1030
|
+
authorize: async (uuaid, key, cap) => {
|
|
1031
|
+
if (callCount++ === 0) {
|
|
1032
|
+
const err = new Error("Registry temporarily offline");
|
|
1033
|
+
err.transient = true;
|
|
1034
|
+
err.code = "REGISTRY_UNAVAILABLE";
|
|
1035
|
+
throw err;
|
|
1036
|
+
}
|
|
1037
|
+
return originalTrust.authorize(uuaid, key, cap);
|
|
1038
|
+
},
|
|
1039
|
+
};
|
|
1040
|
+
|
|
1041
|
+
const res1 = await env.lyraRuntime.releaseHeld();
|
|
1042
|
+
assert.equal(res1.length, 0);
|
|
1043
|
+
assert.equal(env.lyraStore.allHeld().length, 1);
|
|
1044
|
+
|
|
1045
|
+
const audit = env.lyraStore.audit().map(r => JSON.parse(r.body));
|
|
1046
|
+
const deferred = audit.find(e => e.kind === "hold-deferred" && e.id === envelope.id);
|
|
1047
|
+
assert.ok(deferred);
|
|
1048
|
+
assert.equal(deferred.reason, "REGISTRY_UNAVAILABLE");
|
|
1049
|
+
assert.equal(audit.some(e => e.kind === "hold-dropped"), false);
|
|
1050
|
+
|
|
1051
|
+
const res2 = await env.lyraRuntime.releaseHeld();
|
|
1052
|
+
assert.equal(res2.length, 1);
|
|
1053
|
+
assert.equal(res2[0].accepted, true);
|
|
1054
|
+
assert.equal(res2[0].id, payload.id);
|
|
1055
|
+
assert.equal(env.lyraStore.allHeld().length, 0);
|
|
1056
|
+
});
|
|
1057
|
+
|
|
1058
|
+
test("Rework 1 F2 (d): CONTROL - Two held entries where first defers halts release pass so second is not released ahead", async () => {
|
|
1059
|
+
const env = makeTestEnv({ holdBeforeAdmission: true });
|
|
1060
|
+
const { payload: p1, envelope: env1 } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, { thread: env.channelUri, body: "msg-1" });
|
|
1061
|
+
const { payload: p2, envelope: env2 } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, { thread: env.channelUri, body: "msg-2" });
|
|
1062
|
+
|
|
1063
|
+
await env.lyraRuntime.receive(env1);
|
|
1064
|
+
await env.lyraRuntime.receive(env2);
|
|
1065
|
+
assert.equal(env.lyraStore.allHeld().length, 2);
|
|
1066
|
+
|
|
1067
|
+
env.policy.channels[env.channelUri].members.push(env.atlasId);
|
|
1068
|
+
|
|
1069
|
+
let calls = 0;
|
|
1070
|
+
const originalTrust = env.lyraRuntime.trust;
|
|
1071
|
+
env.lyraRuntime.trust = {
|
|
1072
|
+
...originalTrust,
|
|
1073
|
+
authorize: async (uuaid, key, cap) => {
|
|
1074
|
+
calls++;
|
|
1075
|
+
if (calls === 1) {
|
|
1076
|
+
const err = new Error("Backend timeout");
|
|
1077
|
+
err.transient = true;
|
|
1078
|
+
err.code = "BACKEND_TIMEOUT";
|
|
1079
|
+
throw err;
|
|
1080
|
+
}
|
|
1081
|
+
return originalTrust.authorize(uuaid, key, cap);
|
|
1082
|
+
},
|
|
1083
|
+
};
|
|
1084
|
+
|
|
1085
|
+
const res1 = await env.lyraRuntime.releaseHeld();
|
|
1086
|
+
assert.equal(res1.length, 0);
|
|
1087
|
+
assert.equal(env.lyraStore.allHeld().length, 2);
|
|
1088
|
+
assert.equal(calls, 1);
|
|
1089
|
+
|
|
1090
|
+
env.lyraRuntime.trust = originalTrust;
|
|
1091
|
+
const res2 = await env.lyraRuntime.releaseHeld();
|
|
1092
|
+
assert.equal(res2.length, 2);
|
|
1093
|
+
assert.equal(res2[0].id, p1.id);
|
|
1094
|
+
assert.equal(res2[1].id, p2.id);
|
|
1095
|
+
assert.equal(env.lyraStore.allHeld().length, 0);
|
|
1096
|
+
});
|
|
1097
|
+
|
|
1098
|
+
// =========================================================================
|
|
1099
|
+
// L13 Item A: Duplicate release appends hold-released audit row
|
|
1100
|
+
// =========================================================================
|
|
1101
|
+
test("L13 Item A: CONTROL - Duplicate release appends hold-released audit row with reason duplicate-of-accepted", async () => {
|
|
1102
|
+
const env = makeTestEnv({ holdBeforeAdmission: true });
|
|
1103
|
+
const { payload, envelope } = makeSealedMessage(env.atlasKey, env.lyraKey, env.profile, { thread: env.channelUri, body: "audit-duplicate-release" });
|
|
1104
|
+
|
|
1105
|
+
// 1. Envelope arrives and is held
|
|
1106
|
+
const heldRes = await env.lyraRuntime.receive(envelope);
|
|
1107
|
+
assert.equal(heldRes.held, true);
|
|
1108
|
+
assert.equal(env.lyraStore.allHeld().length, 1);
|
|
1109
|
+
|
|
1110
|
+
// 2. Channel policy updated to admit Atlas
|
|
1111
|
+
env.policy.channels[env.channelUri].members.push(env.atlasId);
|
|
1112
|
+
|
|
1113
|
+
// 3. Same envelope accepted directly
|
|
1114
|
+
const directRes = await env.lyraRuntime.receive(envelope);
|
|
1115
|
+
assert.equal(directRes.accepted, true);
|
|
1116
|
+
|
|
1117
|
+
// 4. Release held copy
|
|
1118
|
+
const releaseRes = await env.lyraRuntime.releaseHeld();
|
|
1119
|
+
assert.equal(releaseRes.length, 1);
|
|
1120
|
+
assert.equal(releaseRes[0].duplicate, true);
|
|
1121
|
+
|
|
1122
|
+
// Held row is gone
|
|
1123
|
+
assert.equal(env.lyraStore.allHeld().length, 0);
|
|
1124
|
+
|
|
1125
|
+
// Exactly one hold-released row with reason "duplicate-of-accepted" exists
|
|
1126
|
+
const audit = env.lyraStore.audit().map(r => JSON.parse(r.body));
|
|
1127
|
+
const duplicateReleased = audit.filter(e => e.kind === "hold-released" && e.reason === "duplicate-of-accepted");
|
|
1128
|
+
assert.equal(duplicateReleased.length, 1);
|
|
1129
|
+
assert.equal(duplicateReleased[0].id, envelope.id);
|
|
1130
|
+
assert.equal(duplicateReleased[0].sender, env.atlasId);
|
|
1131
|
+
assert.equal(duplicateReleased[0].channel, env.channelUri);
|
|
1132
|
+
assert.equal(duplicateReleased[0].sha, envelopeSha(envelope));
|
|
1133
|
+
});
|
|
1134
|
+
|
|
1135
|
+
// =========================================================================
|
|
1136
|
+
// L13 Item B: pruneExpiredHeld in transaction deletes strictly by key
|
|
1137
|
+
// =========================================================================
|
|
1138
|
+
test("L13 Item B: CONTROL - pruneExpiredHeld deletes strictly by key inside transaction and preserves interleaved row", () => {
|
|
1139
|
+
const store = new CommonsStore(":memory:");
|
|
1140
|
+
const now = 10000;
|
|
1141
|
+
// Hold initial row that is expired at `now`
|
|
1142
|
+
store.hold({ id: "h1" }, "k1", "chan1", "sender1", "sha1", now - 1000);
|
|
1143
|
+
|
|
1144
|
+
// Wrap store.db.prepare for the SELECT call
|
|
1145
|
+
const originalPrepare = store.db.prepare.bind(store.db);
|
|
1146
|
+
let injected = false;
|
|
1147
|
+
store.db.prepare = (sql) => {
|
|
1148
|
+
const stmt = originalPrepare(sql);
|
|
1149
|
+
if (sql.includes("FROM held WHERE expires_at<=?") && !injected) {
|
|
1150
|
+
const origAll = stmt.all.bind(stmt);
|
|
1151
|
+
stmt.all = (...args) => {
|
|
1152
|
+
const result = origAll(...args);
|
|
1153
|
+
// Simulate interleaved write of an expired row right after SELECT
|
|
1154
|
+
originalPrepare("INSERT INTO held(id,msg_key,channel,sender,envelope,sha,expires_at,created_at) VALUES(?,?,?,?,?,?,?,?)").run(
|
|
1155
|
+
"injected-id", "injected-key", "chan1", "sender1", JSON.stringify({ id: "injected-id" }), "sha-inj", now - 500, now - 500
|
|
1156
|
+
);
|
|
1157
|
+
injected = true;
|
|
1158
|
+
return result;
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
return stmt;
|
|
1162
|
+
};
|
|
1163
|
+
|
|
1164
|
+
const returned = store.pruneExpiredHeld(now);
|
|
1165
|
+
store.db.prepare = originalPrepare;
|
|
1166
|
+
|
|
1167
|
+
// 1. Every row the prune removed is in its return value
|
|
1168
|
+
assert.equal(returned.length, 1);
|
|
1169
|
+
assert.equal(returned[0].id, "h1");
|
|
1170
|
+
assert.equal(store.isHeld("h1", "k1"), false);
|
|
1171
|
+
|
|
1172
|
+
// 2. The injected row is either returned or still present
|
|
1173
|
+
const returnedIds = new Set(returned.map(r => r.id));
|
|
1174
|
+
assert.equal(returnedIds.has("injected-id"), false);
|
|
1175
|
+
assert.equal(store.isHeld("injected-id", "injected-key"), true, "injected row must still be present if not returned by prune");
|
|
1176
|
+
|
|
1177
|
+
const allRemaining = store.allHeld();
|
|
1178
|
+
assert.equal(allRemaining.length, 1);
|
|
1179
|
+
assert.equal(allRemaining[0].id, "injected-id");
|
|
1180
|
+
|
|
1181
|
+
store.close();
|
|
1182
|
+
});
|
|
1183
|
+
|
|
1184
|
+
|
|
1185
|
+
|