@remnic/core 9.7.10 → 9.7.11
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/dist/access-boundary.d.ts +1 -1
- package/dist/access-boundary.js +1 -1
- package/dist/access-cli.js +7 -7
- package/dist/access-http.d.ts +2 -1
- package/dist/access-http.js +5 -5
- package/dist/access-mcp.js +4 -4
- package/dist/access-operations-batch.js +2 -2
- package/dist/access-operations.d.ts +4 -4
- package/dist/access-operations.js +3 -3
- package/dist/access-schema.d.ts +64 -64
- package/dist/access-surface-catalog.js +2 -0
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/{chunk-N3LWQJ6Y.js → chunk-5U65RIVI.js} +294 -2
- package/dist/chunk-5U65RIVI.js.map +1 -0
- package/dist/{chunk-UZRIKUFS.js → chunk-6LM6PPDX.js} +3 -1
- package/dist/chunk-6LM6PPDX.js.map +1 -0
- package/dist/chunk-7XRUYC6G.js +1794 -0
- package/dist/chunk-7XRUYC6G.js.map +1 -0
- package/dist/{chunk-VDLMAJXS.js → chunk-GHVJASQ4.js} +2 -2
- package/dist/{chunk-7EX4YOKH.js → chunk-JYRFO4EH.js} +2 -2
- package/dist/{chunk-QVALQE4P.js → chunk-QHT4W3RU.js} +126 -8
- package/dist/chunk-QHT4W3RU.js.map +1 -0
- package/dist/{chunk-7CKS4AXY.js → chunk-W6R43NLA.js} +3 -3
- package/dist/cli.js +6 -6
- package/dist/index.d.ts +3990 -4
- package/dist/index.js +54 -7
- package/dist/orchestrator.js +7 -7
- package/dist/schemas.d.ts +52 -52
- package/dist/shared-context/manager.d.ts +8 -8
- package/dist/transfer/types.d.ts +66 -66
- package/package.json +2 -2
- package/src/access-boundary.ts +2 -0
- package/src/access-http.ts +147 -6
- package/src/access-mcp-cancellation.test.ts +2 -2
- package/src/access-operations.ts +2 -0
- package/src/access-surface-catalog.ts +2 -0
- package/src/index.ts +53 -0
- package/src/relay/mission-access.test.ts +379 -0
- package/src/relay/mission-access.ts +125 -0
- package/src/relay/mission-fixture.ts +301 -0
- package/src/relay/mission.test.ts +964 -0
- package/src/relay/mission.ts +1725 -0
- package/dist/chunk-FNJX2O6G.js +0 -467
- package/dist/chunk-FNJX2O6G.js.map +0 -1
- package/dist/chunk-N3LWQJ6Y.js.map +0 -1
- package/dist/chunk-QVALQE4P.js.map +0 -1
- package/dist/chunk-UZRIKUFS.js.map +0 -1
- /package/dist/{chunk-VDLMAJXS.js.map → chunk-GHVJASQ4.js.map} +0 -0
- /package/dist/{chunk-7EX4YOKH.js.map → chunk-JYRFO4EH.js.map} +0 -0
- /package/dist/{chunk-7CKS4AXY.js.map → chunk-W6R43NLA.js.map} +0 -0
package/src/index.ts
CHANGED
|
@@ -387,6 +387,59 @@ export {
|
|
|
387
387
|
} from "./access-http.js";
|
|
388
388
|
export { EngramMcpServer } from "./access-mcp.js";
|
|
389
389
|
|
|
390
|
+
// Remnic Relay — host-agnostic mission evidence and receipt contract.
|
|
391
|
+
export {
|
|
392
|
+
RELAY_MISSION_DEFAULT_EVENT_LIMIT,
|
|
393
|
+
RELAY_MISSION_MAX_EVENT_LIMIT,
|
|
394
|
+
RELAY_MISSION_MAX_EVENTS,
|
|
395
|
+
RELAY_MISSION_MAX_FILE_BYTES,
|
|
396
|
+
RELAY_MISSION_MAX_LINE_BYTES,
|
|
397
|
+
RELAY_MISSION_SCHEMA_VERSION,
|
|
398
|
+
RelayEvidenceRefSchema,
|
|
399
|
+
RelayMissionEventInputSchema,
|
|
400
|
+
RelayMissionEventSchema,
|
|
401
|
+
RelayMissionPayloadSchema,
|
|
402
|
+
RelayMissionReadOptionsSchema,
|
|
403
|
+
RelayMissionStore,
|
|
404
|
+
RelayMissionStoreError,
|
|
405
|
+
compareRelayEvents,
|
|
406
|
+
reduceRelayMission,
|
|
407
|
+
relayMissionReceiptDigest,
|
|
408
|
+
type RelayAgentOutputSnapshot,
|
|
409
|
+
type RelayAgentSnapshot,
|
|
410
|
+
type RelayConflictSnapshot,
|
|
411
|
+
type RelayCorrectionSnapshot,
|
|
412
|
+
type RelayDecisionSnapshot,
|
|
413
|
+
type RelayEvidenceRef,
|
|
414
|
+
type RelayMissionAppendResult,
|
|
415
|
+
type RelayMissionEvent,
|
|
416
|
+
type RelayMissionEventInput,
|
|
417
|
+
type RelayMissionPayload,
|
|
418
|
+
type RelayMissionReadOptions,
|
|
419
|
+
type RelayMissionSnapshot,
|
|
420
|
+
type RelayMissionStatus,
|
|
421
|
+
type RelayMissionStoreErrorCode,
|
|
422
|
+
type RelayMissionStoreOptions,
|
|
423
|
+
type RelayPropagationSnapshot,
|
|
424
|
+
type RelayRecallSnapshot,
|
|
425
|
+
type RelayTestSnapshot,
|
|
426
|
+
} from "./relay/mission.js";
|
|
427
|
+
export {
|
|
428
|
+
RELAY_DEMO_MISSION_ID,
|
|
429
|
+
RELAY_DEMO_NAMESPACE,
|
|
430
|
+
createRelayMissionFixture,
|
|
431
|
+
} from "./relay/mission-fixture.js";
|
|
432
|
+
export {
|
|
433
|
+
appendRelayMissionEvent,
|
|
434
|
+
readRelayMission,
|
|
435
|
+
relayMissionAppendOperation,
|
|
436
|
+
relayMissionReadOperation,
|
|
437
|
+
type RelayMissionAppendAccessInput,
|
|
438
|
+
type RelayMissionAppendAccessOutput,
|
|
439
|
+
type RelayMissionReadAccessInput,
|
|
440
|
+
type RelayMissionReadAccessOutput,
|
|
441
|
+
} from "./relay/mission-access.js";
|
|
442
|
+
|
|
390
443
|
// agentAccessHttp.authToken SecretRef resolution (issue #757). Exposed so
|
|
391
444
|
// host-specific bootstrap code (the OpenClaw plugin in `src/index.ts`, the
|
|
392
445
|
// standalone server, the CLI) can resolve a SecretRef to a literal bearer
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
|
|
7
|
+
import { EngramAccessHttpServer } from "../access-http.js";
|
|
8
|
+
import { EngramAccessInputError, type EngramAccessService } from "../access-service.js";
|
|
9
|
+
import type { StorageManager } from "../storage.js";
|
|
10
|
+
import { RELAY_DEMO_MISSION_ID, RELAY_DEMO_NAMESPACE, createRelayMissionFixture } from "./mission-fixture.js";
|
|
11
|
+
import type { RelayMissionSnapshot } from "./mission.js";
|
|
12
|
+
|
|
13
|
+
async function withTempRoot(run: (root: string) => Promise<void>): Promise<void> {
|
|
14
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-relay-http-"));
|
|
15
|
+
try {
|
|
16
|
+
await run(root);
|
|
17
|
+
} finally {
|
|
18
|
+
await rm(root, { recursive: true, force: true });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function fakeService(
|
|
23
|
+
root: string,
|
|
24
|
+
calls: Array<{ mode: "read" | "write"; namespace?: string; principal?: string }>
|
|
25
|
+
): EngramAccessService {
|
|
26
|
+
const storage = { dir: root } as StorageManager;
|
|
27
|
+
return {
|
|
28
|
+
configRef: {
|
|
29
|
+
defaultNamespace: RELAY_DEMO_NAMESPACE,
|
|
30
|
+
namespacesEnabled: true,
|
|
31
|
+
},
|
|
32
|
+
getReadableStorageForNamespace: async (namespace?: string, principal?: string) => {
|
|
33
|
+
if (namespace === "forbidden") throw new EngramAccessInputError("namespace is not readable");
|
|
34
|
+
calls.push({ mode: "read", namespace, principal });
|
|
35
|
+
return { namespace: namespace ?? RELAY_DEMO_NAMESPACE, storage };
|
|
36
|
+
},
|
|
37
|
+
getWritableStorageForNamespace: async (namespace?: string, principal?: string) => {
|
|
38
|
+
if (namespace === "forbidden") throw new EngramAccessInputError("namespace is not writable");
|
|
39
|
+
calls.push({ mode: "write", namespace, principal });
|
|
40
|
+
return { namespace: namespace ?? RELAY_DEMO_NAMESPACE, storage };
|
|
41
|
+
},
|
|
42
|
+
} as unknown as EngramAccessService;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function authHeaders(token = "relay-token"): Record<string, string> {
|
|
46
|
+
return {
|
|
47
|
+
authorization: `Bearer ${token}`,
|
|
48
|
+
"content-type": "application/json",
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
test("HTTP fixture endpoint returns one complete, namespace-authorized Relay receipt", async () => {
|
|
53
|
+
await withTempRoot(async (root) => {
|
|
54
|
+
const calls: Array<{
|
|
55
|
+
mode: "read" | "write";
|
|
56
|
+
namespace?: string;
|
|
57
|
+
principal?: string;
|
|
58
|
+
}> = [];
|
|
59
|
+
const server = new EngramAccessHttpServer({
|
|
60
|
+
service: fakeService(root, calls),
|
|
61
|
+
port: 0,
|
|
62
|
+
authToken: "relay-token",
|
|
63
|
+
principal: "relay-operator",
|
|
64
|
+
adminConsoleEnabled: false,
|
|
65
|
+
});
|
|
66
|
+
const status = await server.start();
|
|
67
|
+
const base = `http://127.0.0.1:${status.port}/engram/v1/relay/missions/${RELAY_DEMO_MISSION_ID}`;
|
|
68
|
+
try {
|
|
69
|
+
const fixture = createRelayMissionFixture();
|
|
70
|
+
const blankNamespace = await fetch(`${base}/events`, {
|
|
71
|
+
method: "POST",
|
|
72
|
+
headers: authHeaders(),
|
|
73
|
+
body: JSON.stringify({ namespace: "", event: fixture[0] }),
|
|
74
|
+
});
|
|
75
|
+
assert.equal(blankNamespace.status, 400);
|
|
76
|
+
|
|
77
|
+
const flatBody = await fetch(`${base}/events`, {
|
|
78
|
+
method: "POST",
|
|
79
|
+
headers: authHeaders(),
|
|
80
|
+
body: JSON.stringify(fixture[0]),
|
|
81
|
+
});
|
|
82
|
+
assert.equal(flatBody.status, 400);
|
|
83
|
+
|
|
84
|
+
for (const input of fixture) {
|
|
85
|
+
const response = await fetch(`${base}/events`, {
|
|
86
|
+
method: "POST",
|
|
87
|
+
headers: authHeaders(),
|
|
88
|
+
body: JSON.stringify({ namespace: RELAY_DEMO_NAMESPACE, event: input }),
|
|
89
|
+
});
|
|
90
|
+
const responseBody = await response.text();
|
|
91
|
+
assert.equal(response.status, 201, responseBody);
|
|
92
|
+
assert.equal(
|
|
93
|
+
(JSON.parse(responseBody) as { event: { authenticatedPrincipal?: string } }).event.authenticatedPrincipal,
|
|
94
|
+
"relay-operator"
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const replay = await fetch(`${base}/events`, {
|
|
99
|
+
method: "POST",
|
|
100
|
+
headers: authHeaders(),
|
|
101
|
+
body: JSON.stringify({ namespace: RELAY_DEMO_NAMESPACE, event: fixture[0] }),
|
|
102
|
+
});
|
|
103
|
+
assert.equal(replay.status, 200);
|
|
104
|
+
assert.equal(((await replay.json()) as { replayed: boolean }).replayed, true);
|
|
105
|
+
|
|
106
|
+
const response = await fetch(`${base}?namespace=${RELAY_DEMO_NAMESPACE}&limit=20`, {
|
|
107
|
+
headers: authHeaders(),
|
|
108
|
+
});
|
|
109
|
+
assert.equal(response.status, 200);
|
|
110
|
+
assert.equal(response.headers.get("cache-control"), "no-store");
|
|
111
|
+
const snapshot = (await response.json()) as RelayMissionSnapshot;
|
|
112
|
+
assert.equal(snapshot.receipt.complete, true);
|
|
113
|
+
assert.deepEqual(snapshot.receipt.activeDecisionIds, ["decision-refresh-after-expiry"]);
|
|
114
|
+
assert.deepEqual(snapshot.receipt.supersededDecisionIds, ["decision-new-token-every-request"]);
|
|
115
|
+
assert.equal(snapshot.agents.length, 3);
|
|
116
|
+
assert.equal(snapshot.tests.at(-1)?.status, "passed");
|
|
117
|
+
assert.ok(calls.every((call) => call.namespace === RELAY_DEMO_NAMESPACE));
|
|
118
|
+
assert.ok(calls.every((call) => call.principal === "relay-operator"));
|
|
119
|
+
} finally {
|
|
120
|
+
await server.stop();
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("HTTP Relay surface distinguishes empty, invalid, unauthorized, and backend-failed reads", async () => {
|
|
126
|
+
await withTempRoot(async (root) => {
|
|
127
|
+
const service = fakeService(root, []);
|
|
128
|
+
const server = new EngramAccessHttpServer({
|
|
129
|
+
service,
|
|
130
|
+
port: 0,
|
|
131
|
+
authTokenEntriesGetter: () => [
|
|
132
|
+
{
|
|
133
|
+
token: "read-token",
|
|
134
|
+
connector: "codex",
|
|
135
|
+
capabilities: {
|
|
136
|
+
version: 1,
|
|
137
|
+
ops: ["relay_mission_read"],
|
|
138
|
+
namespaces: [RELAY_DEMO_NAMESPACE],
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
adminConsoleEnabled: false,
|
|
143
|
+
});
|
|
144
|
+
const status = await server.start();
|
|
145
|
+
const origin = `http://127.0.0.1:${status.port}`;
|
|
146
|
+
const emptyUrl = `${origin}/engram/v1/relay/missions/empty-mission?namespace=${RELAY_DEMO_NAMESPACE}`;
|
|
147
|
+
try {
|
|
148
|
+
const unauthenticated = await fetch(emptyUrl);
|
|
149
|
+
assert.equal(unauthenticated.status, 401);
|
|
150
|
+
|
|
151
|
+
const empty = await fetch(emptyUrl, { headers: authHeaders("read-token") });
|
|
152
|
+
assert.equal(empty.status, 200);
|
|
153
|
+
assert.deepEqual(await empty.json(), assertEmptyMissionShape());
|
|
154
|
+
|
|
155
|
+
const badLimit = await fetch(`${emptyUrl}&limit=501`, {
|
|
156
|
+
headers: authHeaders("read-token"),
|
|
157
|
+
});
|
|
158
|
+
assert.equal(badLimit.status, 400);
|
|
159
|
+
|
|
160
|
+
const blankNamespace = await fetch(`${origin}/engram/v1/relay/missions/empty-mission?namespace=`, {
|
|
161
|
+
headers: authHeaders("read-token"),
|
|
162
|
+
});
|
|
163
|
+
assert.equal(blankNamespace.status, 400);
|
|
164
|
+
|
|
165
|
+
const badSince = await fetch(`${emptyUrl}&since=${encodeURIComponent("2026-01-01T00:00:00+99:99")}`, {
|
|
166
|
+
headers: authHeaders("read-token"),
|
|
167
|
+
});
|
|
168
|
+
assert.equal(badSince.status, 400);
|
|
169
|
+
|
|
170
|
+
const traversal = await fetch(
|
|
171
|
+
`${origin}/engram/v1/relay/missions/%2E%2Eproduction?namespace=${RELAY_DEMO_NAMESPACE}`,
|
|
172
|
+
{
|
|
173
|
+
headers: authHeaders("read-token"),
|
|
174
|
+
}
|
|
175
|
+
);
|
|
176
|
+
assert.equal(traversal.status, 400);
|
|
177
|
+
|
|
178
|
+
const wrongNamespace = await fetch(`${origin}/engram/v1/relay/missions/empty-mission?namespace=forbidden`, {
|
|
179
|
+
headers: authHeaders("read-token"),
|
|
180
|
+
});
|
|
181
|
+
assert.equal(wrongNamespace.status, 403);
|
|
182
|
+
|
|
183
|
+
const deniedWrite = await fetch(`${origin}/engram/v1/relay/missions/${RELAY_DEMO_MISSION_ID}/events`, {
|
|
184
|
+
method: "POST",
|
|
185
|
+
headers: authHeaders("read-token"),
|
|
186
|
+
body: JSON.stringify({
|
|
187
|
+
namespace: RELAY_DEMO_NAMESPACE,
|
|
188
|
+
event: createRelayMissionFixture()[0],
|
|
189
|
+
}),
|
|
190
|
+
});
|
|
191
|
+
assert.equal(deniedWrite.status, 403);
|
|
192
|
+
} finally {
|
|
193
|
+
await server.stop();
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
await withTempRoot(async (root) => {
|
|
198
|
+
const missingRoot = path.join(root, "missing-storage-root");
|
|
199
|
+
const server = new EngramAccessHttpServer({
|
|
200
|
+
service: fakeService(missingRoot, []),
|
|
201
|
+
port: 0,
|
|
202
|
+
authToken: "relay-token",
|
|
203
|
+
adminConsoleEnabled: false,
|
|
204
|
+
});
|
|
205
|
+
const status = await server.start();
|
|
206
|
+
try {
|
|
207
|
+
const response = await fetch(`http://127.0.0.1:${status.port}/engram/v1/relay/missions/backend-failure`, {
|
|
208
|
+
headers: authHeaders(),
|
|
209
|
+
});
|
|
210
|
+
assert.equal(response.status, 500);
|
|
211
|
+
assert.equal(((await response.json()) as { code: string }).code, "relay_unsafe_path");
|
|
212
|
+
} finally {
|
|
213
|
+
await server.stop();
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test("HTTP Relay write quota is charged only for a real append, never an idempotent replay", async () => {
|
|
219
|
+
await withTempRoot(async (root) => {
|
|
220
|
+
const server = new EngramAccessHttpServer({
|
|
221
|
+
service: fakeService(root, []),
|
|
222
|
+
port: 0,
|
|
223
|
+
authToken: "relay-token",
|
|
224
|
+
adminConsoleEnabled: false,
|
|
225
|
+
writeRateLimitMaxRequests: 1,
|
|
226
|
+
writeRateLimitWindowMs: 60_000,
|
|
227
|
+
});
|
|
228
|
+
const status = await server.start();
|
|
229
|
+
const url = `http://127.0.0.1:${status.port}/engram/v1/relay/missions/${RELAY_DEMO_MISSION_ID}/events`;
|
|
230
|
+
const fixture = createRelayMissionFixture();
|
|
231
|
+
try {
|
|
232
|
+
const first = await fetch(url, {
|
|
233
|
+
method: "POST",
|
|
234
|
+
headers: authHeaders(),
|
|
235
|
+
body: JSON.stringify({ event: fixture[0] }),
|
|
236
|
+
});
|
|
237
|
+
assert.equal(first.status, 201);
|
|
238
|
+
|
|
239
|
+
const replay = await fetch(url, {
|
|
240
|
+
method: "POST",
|
|
241
|
+
headers: authHeaders(),
|
|
242
|
+
body: JSON.stringify({ event: fixture[0] }),
|
|
243
|
+
});
|
|
244
|
+
assert.equal(replay.status, 200);
|
|
245
|
+
assert.equal(((await replay.json()) as { replayed: boolean }).replayed, true);
|
|
246
|
+
|
|
247
|
+
const secondEvent = await fetch(url, {
|
|
248
|
+
method: "POST",
|
|
249
|
+
headers: authHeaders(),
|
|
250
|
+
body: JSON.stringify({ event: fixture[1] }),
|
|
251
|
+
});
|
|
252
|
+
assert.equal(secondEvent.status, 429);
|
|
253
|
+
assert.equal(((await secondEvent.json()) as { code: string }).code, "write_rate_limited");
|
|
254
|
+
} finally {
|
|
255
|
+
await server.stop();
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test("HTTP Relay reserves global write quota across concurrent mission appends", async () => {
|
|
261
|
+
await withTempRoot(async (root) => {
|
|
262
|
+
const server = new EngramAccessHttpServer({
|
|
263
|
+
service: fakeService(root, []),
|
|
264
|
+
port: 0,
|
|
265
|
+
authToken: "relay-token",
|
|
266
|
+
adminConsoleEnabled: false,
|
|
267
|
+
writeRateLimitMaxRequests: 1,
|
|
268
|
+
writeRateLimitWindowMs: 60_000,
|
|
269
|
+
});
|
|
270
|
+
const status = await server.start();
|
|
271
|
+
const missionIds = ["concurrent-mission-a", "concurrent-mission-b"];
|
|
272
|
+
const startEvent = createRelayMissionFixture()[0];
|
|
273
|
+
assert.ok(startEvent);
|
|
274
|
+
const urls = missionIds.map(
|
|
275
|
+
(missionId) => `http://127.0.0.1:${status.port}/engram/v1/relay/missions/${missionId}/events`
|
|
276
|
+
);
|
|
277
|
+
try {
|
|
278
|
+
const responses = await Promise.all(
|
|
279
|
+
urls.map((url) =>
|
|
280
|
+
fetch(url, {
|
|
281
|
+
method: "POST",
|
|
282
|
+
headers: authHeaders(),
|
|
283
|
+
body: JSON.stringify({ event: startEvent }),
|
|
284
|
+
})
|
|
285
|
+
)
|
|
286
|
+
);
|
|
287
|
+
assert.deepEqual(
|
|
288
|
+
responses.map((response) => response.status).sort((a, b) => a - b),
|
|
289
|
+
[201, 429]
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
const winnerIndex = responses.findIndex((response) => response.status === 201);
|
|
293
|
+
assert.notEqual(winnerIndex, -1);
|
|
294
|
+
const replay = await fetch(urls[winnerIndex] ?? "", {
|
|
295
|
+
method: "POST",
|
|
296
|
+
headers: authHeaders(),
|
|
297
|
+
body: JSON.stringify({ event: startEvent }),
|
|
298
|
+
});
|
|
299
|
+
assert.equal(replay.status, 200);
|
|
300
|
+
assert.equal(((await replay.json()) as { replayed: boolean }).replayed, true);
|
|
301
|
+
} finally {
|
|
302
|
+
await server.stop();
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test("Relay write quota releases the exact reservation when timestamps collide", () => {
|
|
308
|
+
const server = new EngramAccessHttpServer({
|
|
309
|
+
service: {} as EngramAccessService,
|
|
310
|
+
port: 0,
|
|
311
|
+
authToken: "relay-token",
|
|
312
|
+
adminConsoleEnabled: false,
|
|
313
|
+
});
|
|
314
|
+
const internals = server as unknown as {
|
|
315
|
+
writeRequestSlots: Array<{ readonly recordedAt: number }>;
|
|
316
|
+
reserveWriteRateLimitSlot: () => () => void;
|
|
317
|
+
};
|
|
318
|
+
const originalNow = Date.now;
|
|
319
|
+
try {
|
|
320
|
+
Date.now = () => 1_754_000_000_000;
|
|
321
|
+
const releaseCommitted = internals.reserveWriteRateLimitSlot();
|
|
322
|
+
const committedSlot = internals.writeRequestSlots[0];
|
|
323
|
+
const releaseFailed = internals.reserveWriteRateLimitSlot();
|
|
324
|
+
const failedSlot = internals.writeRequestSlots[1];
|
|
325
|
+
assert.ok(committedSlot);
|
|
326
|
+
assert.ok(failedSlot);
|
|
327
|
+
assert.notEqual(committedSlot, failedSlot);
|
|
328
|
+
|
|
329
|
+
releaseFailed();
|
|
330
|
+
assert.deepEqual(internals.writeRequestSlots, [committedSlot]);
|
|
331
|
+
|
|
332
|
+
releaseCommitted();
|
|
333
|
+
assert.deepEqual(internals.writeRequestSlots, []);
|
|
334
|
+
} finally {
|
|
335
|
+
Date.now = originalNow;
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
function assertEmptyMissionShape(): RelayMissionSnapshot {
|
|
340
|
+
return {
|
|
341
|
+
schemaVersion: "1",
|
|
342
|
+
missionId: "empty-mission",
|
|
343
|
+
namespace: RELAY_DEMO_NAMESPACE,
|
|
344
|
+
found: false,
|
|
345
|
+
readHealth: "empty",
|
|
346
|
+
status: "not_started",
|
|
347
|
+
mission: {
|
|
348
|
+
title: null,
|
|
349
|
+
objective: null,
|
|
350
|
+
runMode: null,
|
|
351
|
+
startedAt: null,
|
|
352
|
+
completedAt: null,
|
|
353
|
+
},
|
|
354
|
+
agents: [],
|
|
355
|
+
decisions: [],
|
|
356
|
+
conflicts: [],
|
|
357
|
+
corrections: [],
|
|
358
|
+
tests: [],
|
|
359
|
+
propagation: [],
|
|
360
|
+
outcome: null,
|
|
361
|
+
receipt: {
|
|
362
|
+
complete: false,
|
|
363
|
+
missingEvidence: [],
|
|
364
|
+
activeDecisionIds: [],
|
|
365
|
+
supersededDecisionIds: [],
|
|
366
|
+
coldStartVerified: false,
|
|
367
|
+
passingOutcomeVerified: false,
|
|
368
|
+
},
|
|
369
|
+
bounds: {
|
|
370
|
+
totalEvents: 0,
|
|
371
|
+
returnedEvents: 0,
|
|
372
|
+
corruptLines: 0,
|
|
373
|
+
truncated: false,
|
|
374
|
+
since: null,
|
|
375
|
+
until: null,
|
|
376
|
+
},
|
|
377
|
+
events: [],
|
|
378
|
+
};
|
|
379
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import { type OperationContext, defineOperation } from "../access-boundary.js";
|
|
4
|
+
import type { EngramAccessService } from "../access-service.js";
|
|
5
|
+
import {
|
|
6
|
+
RELAY_MISSION_MAX_EVENT_LIMIT,
|
|
7
|
+
RelayIsoDateTimeSchema,
|
|
8
|
+
type RelayMissionAppendResult,
|
|
9
|
+
type RelayMissionEventInput,
|
|
10
|
+
RelayMissionEventInputSchema,
|
|
11
|
+
RelayMissionIdSchema,
|
|
12
|
+
type RelayMissionReadOptions,
|
|
13
|
+
RelayMissionReadOptionsSchema,
|
|
14
|
+
type RelayMissionSnapshot,
|
|
15
|
+
RelayMissionStore,
|
|
16
|
+
} from "./mission.js";
|
|
17
|
+
|
|
18
|
+
const namespaceSchema = z.string().trim().min(1).max(128).nullable().optional();
|
|
19
|
+
|
|
20
|
+
const RelayMissionAppendAccessSchema = z
|
|
21
|
+
.object({
|
|
22
|
+
missionId: RelayMissionIdSchema,
|
|
23
|
+
namespace: namespaceSchema,
|
|
24
|
+
event: RelayMissionEventInputSchema,
|
|
25
|
+
})
|
|
26
|
+
.strict();
|
|
27
|
+
|
|
28
|
+
const optionalLimitSchema = z.preprocess((value) => {
|
|
29
|
+
if (value === undefined || value === null) return undefined;
|
|
30
|
+
if (typeof value === "string" && /^(?:0|[1-9]\d*)$/.test(value)) return Number(value);
|
|
31
|
+
return value;
|
|
32
|
+
}, z.number().int().min(1).max(RELAY_MISSION_MAX_EVENT_LIMIT).optional());
|
|
33
|
+
|
|
34
|
+
const RelayMissionReadAccessSchema = z
|
|
35
|
+
.object({
|
|
36
|
+
missionId: RelayMissionIdSchema,
|
|
37
|
+
namespace: namespaceSchema,
|
|
38
|
+
since: RelayIsoDateTimeSchema.optional(),
|
|
39
|
+
until: RelayIsoDateTimeSchema.optional(),
|
|
40
|
+
limit: optionalLimitSchema,
|
|
41
|
+
})
|
|
42
|
+
.strict()
|
|
43
|
+
.refine(
|
|
44
|
+
(value) =>
|
|
45
|
+
value.since === undefined || value.until === undefined || Date.parse(value.since) < Date.parse(value.until),
|
|
46
|
+
{ message: "since must be earlier than until", path: ["until"] }
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
export interface RelayMissionAppendAccessInput {
|
|
50
|
+
missionId: string;
|
|
51
|
+
namespace?: string | null;
|
|
52
|
+
event: RelayMissionEventInput;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface RelayMissionReadAccessInput extends RelayMissionReadOptions {
|
|
56
|
+
missionId: string;
|
|
57
|
+
namespace?: string | null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface RelayMissionAppendAccessOutput {
|
|
61
|
+
result: RelayMissionAppendResult;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface RelayMissionReadAccessOutput {
|
|
65
|
+
result: RelayMissionSnapshot;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Shared service contract for transports that need to append Relay evidence.
|
|
70
|
+
* Namespace resolution stays in the existing access service so the append
|
|
71
|
+
* inherits the same tenancy and principal rules as every memory write.
|
|
72
|
+
*/
|
|
73
|
+
export async function appendRelayMissionEvent(
|
|
74
|
+
service: EngramAccessService,
|
|
75
|
+
input: RelayMissionAppendAccessInput,
|
|
76
|
+
authenticatedPrincipal?: string,
|
|
77
|
+
beforeAppend?: () => void | Promise<void>
|
|
78
|
+
): Promise<RelayMissionAppendResult> {
|
|
79
|
+
const resolved = await service.getWritableStorageForNamespace(input.namespace ?? undefined, authenticatedPrincipal);
|
|
80
|
+
const store = new RelayMissionStore({
|
|
81
|
+
rootDir: resolved.storage.dir,
|
|
82
|
+
namespace: resolved.namespace,
|
|
83
|
+
});
|
|
84
|
+
return store.append(input.missionId, input.event, { authenticatedPrincipal, beforeAppend });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Namespace-authorized, bounded Relay snapshot read shared by HTTP and UI. */
|
|
88
|
+
export async function readRelayMission(
|
|
89
|
+
service: EngramAccessService,
|
|
90
|
+
input: RelayMissionReadAccessInput,
|
|
91
|
+
authenticatedPrincipal?: string
|
|
92
|
+
): Promise<RelayMissionSnapshot> {
|
|
93
|
+
const resolved = await service.getReadableStorageForNamespace(input.namespace ?? undefined, authenticatedPrincipal);
|
|
94
|
+
const store = new RelayMissionStore({
|
|
95
|
+
rootDir: resolved.storage.dir,
|
|
96
|
+
namespace: resolved.namespace,
|
|
97
|
+
});
|
|
98
|
+
const options = RelayMissionReadOptionsSchema.parse({
|
|
99
|
+
...(input.since === undefined ? {} : { since: input.since }),
|
|
100
|
+
...(input.until === undefined ? {} : { until: input.until }),
|
|
101
|
+
...(input.limit === undefined ? {} : { limit: input.limit }),
|
|
102
|
+
});
|
|
103
|
+
return store.read(input.missionId, options);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export const relayMissionAppendOperation = defineOperation<
|
|
107
|
+
RelayMissionAppendAccessInput,
|
|
108
|
+
RelayMissionAppendAccessOutput
|
|
109
|
+
>({
|
|
110
|
+
name: "relay_mission_append",
|
|
111
|
+
description: "Append one evidence-backed event to an isolated Relay mission.",
|
|
112
|
+
schema: RelayMissionAppendAccessSchema as z.ZodType<RelayMissionAppendAccessInput>,
|
|
113
|
+
handler: async (input, ctx: OperationContext) => ({
|
|
114
|
+
result: await appendRelayMissionEvent(ctx.service, input, ctx.authenticatedPrincipal, ctx.hooks?.enforceWriteQuota),
|
|
115
|
+
}),
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
export const relayMissionReadOperation = defineOperation<RelayMissionReadAccessInput, RelayMissionReadAccessOutput>({
|
|
119
|
+
name: "relay_mission_read",
|
|
120
|
+
description: "Read one bounded, browser-ready Relay mission receipt.",
|
|
121
|
+
schema: RelayMissionReadAccessSchema as z.ZodType<RelayMissionReadAccessInput>,
|
|
122
|
+
handler: async (input, ctx: OperationContext) => ({
|
|
123
|
+
result: await readRelayMission(ctx.service, input, ctx.authenticatedPrincipal),
|
|
124
|
+
}),
|
|
125
|
+
});
|