@hypercarrier/rarebit 0.1.0-alpha.1
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/AGENTS.md +14 -0
- package/CHANGELOG.md +10 -0
- package/LICENSE +21 -0
- package/README.md +142 -0
- package/RUNBOOK.md +36 -0
- package/SECURITY.md +11 -0
- package/VISUAL-LANGUAGE.md +89 -0
- package/bin/rarebit.mjs +20 -0
- package/package.json +96 -0
- package/schemas/rarebit.schema.json +618 -0
- package/scripts/e2e-recall-pi083.mjs +199 -0
- package/scripts/e2e-summary-cache.mjs +257 -0
- package/scripts/verify-package.mjs +81 -0
- package/src/automatic-summary-policy.mjs +210 -0
- package/src/extension.mjs +516 -0
- package/src/herdr-activity.mjs +111 -0
- package/src/index.mjs +13 -0
- package/src/lifecycle.mjs +322 -0
- package/src/rarebit-activity.mjs +48 -0
- package/src/rarebit-artifact-state.mjs +839 -0
- package/src/rarebit-cli.mjs +305 -0
- package/src/rarebit-command.mjs +281 -0
- package/src/rarebit-core.mjs +525 -0
- package/src/rarebit-model.mjs +226 -0
- package/src/rarebit-recall.mjs +182 -0
- package/src/rarebit-service.mjs +579 -0
- package/src/rarebit-session.mjs +227 -0
- package/src/rarebit-settings.mjs +79 -0
- package/src/rarebit-store.mjs +771 -0
- package/src/rarebit-visual-language.mjs +110 -0
- package/src/types.d.ts +601 -0
- package/test/automatic-summary-policy.test.mjs +476 -0
- package/test/extension-activity.test.mjs +73 -0
- package/test/fixtures/branch-mixed.json +117 -0
- package/test/fixtures/conformance-corpus.v1.json +306 -0
- package/test/fixtures/truncated-mixed.jsonl +2 -0
- package/test/herdr-activity.test.mjs +93 -0
- package/test/lifecycle-contract-independent.test.mjs +627 -0
- package/test/rarebit-activity.test.mjs +82 -0
- package/test/rarebit-artifact-state.test.mjs +440 -0
- package/test/rarebit-cli.test.mjs +222 -0
- package/test/rarebit-command.test.mjs +127 -0
- package/test/rarebit-core.test.mjs +461 -0
- package/test/rarebit-cross-consumer-conformance.test.mjs +150 -0
- package/test/rarebit-independent-contract.test.mjs +317 -0
- package/test/rarebit-recall.test.mjs +434 -0
- package/test/rarebit-sidecar-protocol.test.mjs +538 -0
- package/test/rarebit-visual-language.test.mjs +75 -0
- package/test/summary-attention-independent.test.mjs +172 -0
- package/test/types-history.ts +13 -0
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import {
|
|
4
|
+
mkdtemp,
|
|
5
|
+
mkdir,
|
|
6
|
+
readFile,
|
|
7
|
+
writeFile,
|
|
8
|
+
appendFile,
|
|
9
|
+
} from "node:fs/promises";
|
|
10
|
+
import Ajv from "ajv";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
import {
|
|
14
|
+
processRarebitSummary,
|
|
15
|
+
processRarebitTitle,
|
|
16
|
+
} from "../src/rarebit-service.mjs";
|
|
17
|
+
import {
|
|
18
|
+
readRarebitCurrent,
|
|
19
|
+
readRarebitHistory,
|
|
20
|
+
releaseRarebitJob,
|
|
21
|
+
reserveRarebitJob,
|
|
22
|
+
settleRarebitJob,
|
|
23
|
+
} from "../src/rarebit-store.mjs";
|
|
24
|
+
import { selectRarebits, sha256 } from "../src/rarebit-core.mjs";
|
|
25
|
+
import { extractRarebitSynthesisReceipt } from "../src/rarebit-model.mjs";
|
|
26
|
+
import {
|
|
27
|
+
exactSelectionApplies,
|
|
28
|
+
requestPrefixApplies,
|
|
29
|
+
validateRarebitArtifactReceipt,
|
|
30
|
+
} from "../src/rarebit-artifact-state.mjs";
|
|
31
|
+
|
|
32
|
+
const entry = (id, role, content) => ({
|
|
33
|
+
id,
|
|
34
|
+
type: "message",
|
|
35
|
+
message: {
|
|
36
|
+
role,
|
|
37
|
+
content,
|
|
38
|
+
stopReason: role === "assistant" ? "stop" : undefined,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
async function fixture(name = "session") {
|
|
42
|
+
const root = await mkdtemp(join(tmpdir(), "rarebit-sidecar-"));
|
|
43
|
+
const sessionRoot = join(root, "sessions");
|
|
44
|
+
const rarebitRoot = join(root, "rarebit");
|
|
45
|
+
const dir = join(sessionRoot, name);
|
|
46
|
+
await mkdir(dir, { recursive: true });
|
|
47
|
+
const sessionFile = join(dir, "trace.jsonl");
|
|
48
|
+
await writeFile(sessionFile, "{}\n");
|
|
49
|
+
return { root, sessionRoot, rarebitRoot, sessionFile };
|
|
50
|
+
}
|
|
51
|
+
async function materialize(f, branch, id = "s", options = {}) {
|
|
52
|
+
await writeFile(
|
|
53
|
+
f.sessionFile,
|
|
54
|
+
[
|
|
55
|
+
JSON.stringify({ type: "session", id }),
|
|
56
|
+
...branch.map((value) => JSON.stringify(value)),
|
|
57
|
+
].join("\n") + "\n",
|
|
58
|
+
);
|
|
59
|
+
return processRarebitSummary(
|
|
60
|
+
{
|
|
61
|
+
sessionManager: {
|
|
62
|
+
getHeader: () => ({ id }),
|
|
63
|
+
getSessionFile: () => f.sessionFile,
|
|
64
|
+
getBranch: () => branch,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
sessionRoot: f.sessionRoot,
|
|
69
|
+
rarebitRoot: f.rarebitRoot,
|
|
70
|
+
summaryPolicy: { minTotalLength: 0, maxRarebitRatio: 1 },
|
|
71
|
+
model: { provider: "test", id: "model" },
|
|
72
|
+
modelClient: {
|
|
73
|
+
complete: async () =>
|
|
74
|
+
JSON.stringify({
|
|
75
|
+
summary: "ok",
|
|
76
|
+
sessionStatus: "finished",
|
|
77
|
+
statusReason: "all_requests_accomplished",
|
|
78
|
+
}),
|
|
79
|
+
},
|
|
80
|
+
...options,
|
|
81
|
+
},
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
function compactReceipt(branch, sessionId = "s") {
|
|
85
|
+
const selected = selectRarebits(branch);
|
|
86
|
+
const latestUser = selected.occurrences
|
|
87
|
+
.filter((value) => value.role === "user")
|
|
88
|
+
.at(-1);
|
|
89
|
+
const model = { provider: "test", id: "model" };
|
|
90
|
+
const observedAt = "2026-07-26T00:00:00.000Z";
|
|
91
|
+
return {
|
|
92
|
+
schemaVersion: 4,
|
|
93
|
+
type: "rarebit_summary",
|
|
94
|
+
status: "ok",
|
|
95
|
+
jobId: "0".repeat(64),
|
|
96
|
+
sessionId,
|
|
97
|
+
branch: {
|
|
98
|
+
leafId: branch.at(-1)?.id ?? null,
|
|
99
|
+
entryCount: branch.length,
|
|
100
|
+
pathHash: sha256(branch.map((value) => String(value?.id ?? ""))),
|
|
101
|
+
},
|
|
102
|
+
observedAt,
|
|
103
|
+
selection: {
|
|
104
|
+
manifestHash: selected.manifestHash,
|
|
105
|
+
selectorVersion: selected.manifest.selectorVersion,
|
|
106
|
+
occurrenceCount: selected.occurrences.length,
|
|
107
|
+
uniquePayloadCount: selected.payloads.length,
|
|
108
|
+
latestUserSourceEntryId: latestUser?.sourceEntryId ?? null,
|
|
109
|
+
},
|
|
110
|
+
lifecycleBoundary: "manual",
|
|
111
|
+
implementationVersion: "hc-rarebit-summary-v4",
|
|
112
|
+
synthesisMode: "forced",
|
|
113
|
+
inputCoveragePolicy: {
|
|
114
|
+
strategy: "complete_or_explicit_overflow",
|
|
115
|
+
maxPromptChars: 1_000,
|
|
116
|
+
},
|
|
117
|
+
promptVersion: "rarebit-summary-v4",
|
|
118
|
+
model,
|
|
119
|
+
modelProvenance: { source: "test", status: "resolved" },
|
|
120
|
+
summary: "ok",
|
|
121
|
+
sessionStatus: "finished",
|
|
122
|
+
statusReason: "all_requests_accomplished",
|
|
123
|
+
synthesis: extractRarebitSynthesisReceipt(
|
|
124
|
+
{},
|
|
125
|
+
{
|
|
126
|
+
requestedModel: model,
|
|
127
|
+
startedAt: observedAt,
|
|
128
|
+
completedAt: observedAt,
|
|
129
|
+
durationMs: 0,
|
|
130
|
+
},
|
|
131
|
+
),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
test("current store reads a valid historical session_start receipt", async () => {
|
|
135
|
+
const f = await fixture();
|
|
136
|
+
const branch = [entry("u", "user", "historical evidence")];
|
|
137
|
+
await writeFile(
|
|
138
|
+
f.sessionFile,
|
|
139
|
+
[
|
|
140
|
+
JSON.stringify({ type: "session", id: "s" }),
|
|
141
|
+
...branch.map((value) => JSON.stringify(value)),
|
|
142
|
+
].join("\n") + "\n",
|
|
143
|
+
);
|
|
144
|
+
const reservation = await reserveRarebitJob({
|
|
145
|
+
jobId: "1".repeat(64),
|
|
146
|
+
sessionFile: f.sessionFile,
|
|
147
|
+
sessionRoot: f.sessionRoot,
|
|
148
|
+
rarebitRoot: f.rarebitRoot,
|
|
149
|
+
});
|
|
150
|
+
await settleRarebitJob(reservation, {
|
|
151
|
+
...compactReceipt(branch),
|
|
152
|
+
lifecycleBoundary: "session_start",
|
|
153
|
+
synthesisMode: "automatic",
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const current = await readRarebitCurrent({
|
|
157
|
+
sessionFile: f.sessionFile,
|
|
158
|
+
sessionRoot: f.sessionRoot,
|
|
159
|
+
rarebitRoot: f.rarebitRoot,
|
|
160
|
+
});
|
|
161
|
+
assert.equal(current.availability, "available");
|
|
162
|
+
assert.equal(current.receipt.lifecycleBoundary, "session_start");
|
|
163
|
+
assert.equal(current.head.receiptOffset, 0);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("one compact sidecar tails current receipt without repeated evidence arrays", async () => {
|
|
167
|
+
const f = await fixture();
|
|
168
|
+
const result = await materialize(f, [entry("u", "user", "x")]);
|
|
169
|
+
const content = await readFile(result.record.path, "utf8");
|
|
170
|
+
assert.equal(content.split("\n").filter(Boolean).length, 2);
|
|
171
|
+
assert.equal(content.includes("entryIds"), false);
|
|
172
|
+
assert.equal(content.includes("occurrences"), false);
|
|
173
|
+
assert.equal(content.includes("payloads"), false);
|
|
174
|
+
const current = await readRarebitCurrent({
|
|
175
|
+
sessionFile: f.sessionFile,
|
|
176
|
+
sessionRoot: f.sessionRoot,
|
|
177
|
+
rarebitRoot: f.rarebitRoot,
|
|
178
|
+
});
|
|
179
|
+
assert.equal(current.receipt.sessionId, "s");
|
|
180
|
+
assert.equal(current.head.receiptOffset, 0);
|
|
181
|
+
assert.equal(current.diagnostics.tornTail, false);
|
|
182
|
+
assert.ok(
|
|
183
|
+
current.artifactState,
|
|
184
|
+
"canonical current reader always composes native/artifact state",
|
|
185
|
+
);
|
|
186
|
+
});
|
|
187
|
+
test("Title derivations share the Session file and repeat the current Summary head", async () => {
|
|
188
|
+
const f = await fixture();
|
|
189
|
+
const branch = [entry("u", "user", "title this")];
|
|
190
|
+
await materialize(f, branch);
|
|
191
|
+
await processRarebitTitle(
|
|
192
|
+
{
|
|
193
|
+
sessionManager: {
|
|
194
|
+
getHeader: () => ({ id: "s" }),
|
|
195
|
+
getSessionFile: () => f.sessionFile,
|
|
196
|
+
getBranch: () => branch,
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
sessionRoot: f.sessionRoot,
|
|
201
|
+
rarebitRoot: f.rarebitRoot,
|
|
202
|
+
sourceEntryId: "u",
|
|
203
|
+
titleDate: "2026-07-26",
|
|
204
|
+
model: { provider: "test", id: "model" },
|
|
205
|
+
modelClient: { complete: async () => ({ text: "Compact protocol" }) },
|
|
206
|
+
},
|
|
207
|
+
);
|
|
208
|
+
const current = await readRarebitCurrent({
|
|
209
|
+
sessionFile: f.sessionFile,
|
|
210
|
+
sessionRoot: f.sessionRoot,
|
|
211
|
+
rarebitRoot: f.rarebitRoot,
|
|
212
|
+
});
|
|
213
|
+
assert.equal(current.receipt.type, "rarebit_summary");
|
|
214
|
+
const history = await readRarebitHistory({
|
|
215
|
+
sessionFile: f.sessionFile,
|
|
216
|
+
sessionRoot: f.sessionRoot,
|
|
217
|
+
rarebitRoot: f.rarebitRoot,
|
|
218
|
+
});
|
|
219
|
+
assert.deepEqual(
|
|
220
|
+
history.records.map(({ record }) => record.type),
|
|
221
|
+
["rarebit_summary", "rarebit_title"],
|
|
222
|
+
);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test("bounded tail works after over 5MiB of prior history and history resumes by offset", async () => {
|
|
226
|
+
const f = await fixture();
|
|
227
|
+
const result = await materialize(f, [entry("u", "user", "x")]);
|
|
228
|
+
await appendFile(result.record.path, " ".repeat(5 * 1024 * 1024) + "\n");
|
|
229
|
+
// A later compact receipt/head is the normal current entrypoint.
|
|
230
|
+
const second = await materialize(f, [entry("u2", "user", "y")], "s2");
|
|
231
|
+
const current = await readRarebitCurrent({
|
|
232
|
+
sessionFile: f.sessionFile,
|
|
233
|
+
sessionRoot: f.sessionRoot,
|
|
234
|
+
rarebitRoot: f.rarebitRoot,
|
|
235
|
+
});
|
|
236
|
+
assert.equal(current.receipt.sessionId, "s2");
|
|
237
|
+
const history = await readRarebitHistory({
|
|
238
|
+
sessionFile: f.sessionFile,
|
|
239
|
+
sessionRoot: f.sessionRoot,
|
|
240
|
+
rarebitRoot: f.rarebitRoot,
|
|
241
|
+
limit: 1,
|
|
242
|
+
});
|
|
243
|
+
assert.equal(history.records.length, 1);
|
|
244
|
+
assert.ok(history.nextOffset);
|
|
245
|
+
});
|
|
246
|
+
test("history resumes on exact byte offsets and skips a partial multibyte record", async () => {
|
|
247
|
+
const f = await fixture();
|
|
248
|
+
await materialize(f, [entry("u1", "user", "one")]);
|
|
249
|
+
await materialize(f, [entry("u😀", "user", "two")]);
|
|
250
|
+
await materialize(f, [entry("u3", "user", "three")]);
|
|
251
|
+
const all = await readRarebitHistory({
|
|
252
|
+
sessionFile: f.sessionFile,
|
|
253
|
+
sessionRoot: f.sessionRoot,
|
|
254
|
+
rarebitRoot: f.rarebitRoot,
|
|
255
|
+
limit: 10,
|
|
256
|
+
});
|
|
257
|
+
assert.equal(all.records.length, 3);
|
|
258
|
+
const bytes = await readFile(all.path);
|
|
259
|
+
const emojiOffset = bytes.indexOf(Buffer.from("😀"), all.records[1].offset);
|
|
260
|
+
assert.ok(emojiOffset > all.records[1].offset);
|
|
261
|
+
const resumed = await readRarebitHistory({
|
|
262
|
+
sessionFile: f.sessionFile,
|
|
263
|
+
sessionRoot: f.sessionRoot,
|
|
264
|
+
rarebitRoot: f.rarebitRoot,
|
|
265
|
+
fromOffset: emojiOffset + 1,
|
|
266
|
+
limit: 1,
|
|
267
|
+
});
|
|
268
|
+
assert.equal(resumed.records.length, 1);
|
|
269
|
+
assert.equal(resumed.records[0].offset, all.records[2].offset);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test("oversized receipts fail before any sidecar byte is written", async () => {
|
|
273
|
+
const f = await fixture();
|
|
274
|
+
const branch = [entry("u", "user", "x")];
|
|
275
|
+
await writeFile(
|
|
276
|
+
f.sessionFile,
|
|
277
|
+
[
|
|
278
|
+
JSON.stringify({ type: "session", id: "s" }),
|
|
279
|
+
...branch.map(JSON.stringify),
|
|
280
|
+
].join("\n") + "\n",
|
|
281
|
+
);
|
|
282
|
+
const reservation = await reserveRarebitJob({
|
|
283
|
+
jobId: "f".repeat(64),
|
|
284
|
+
sessionFile: f.sessionFile,
|
|
285
|
+
sessionRoot: f.sessionRoot,
|
|
286
|
+
rarebitRoot: f.rarebitRoot,
|
|
287
|
+
});
|
|
288
|
+
await assert.rejects(
|
|
289
|
+
settleRarebitJob(reservation, {
|
|
290
|
+
...compactReceipt(branch),
|
|
291
|
+
summary: "x".repeat(50 * 1024),
|
|
292
|
+
}),
|
|
293
|
+
/exceeds protocol byte limit/,
|
|
294
|
+
);
|
|
295
|
+
await releaseRarebitJob(reservation);
|
|
296
|
+
const current = await readRarebitCurrent({
|
|
297
|
+
sessionFile: f.sessionFile,
|
|
298
|
+
sessionRoot: f.sessionRoot,
|
|
299
|
+
rarebitRoot: f.rarebitRoot,
|
|
300
|
+
});
|
|
301
|
+
assert.equal(current.availability, "missing");
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test("compact exact and owner-request prefix applicability re-derives native manifest and anchor", () => {
|
|
305
|
+
const native = selectRarebits([
|
|
306
|
+
entry("u", "user", "one"),
|
|
307
|
+
entry("a", "assistant", "done"),
|
|
308
|
+
entry("u2", "user", "two"),
|
|
309
|
+
]);
|
|
310
|
+
const prefix = selectRarebits([entry("u", "user", "one")]);
|
|
311
|
+
const ref = {
|
|
312
|
+
manifestHash: prefix.manifestHash,
|
|
313
|
+
selectorVersion: prefix.manifest.selectorVersion,
|
|
314
|
+
occurrenceCount: 1,
|
|
315
|
+
uniquePayloadCount: 1,
|
|
316
|
+
latestUserSourceEntryId: "u",
|
|
317
|
+
};
|
|
318
|
+
assert.equal(requestPrefixApplies(ref, native), false); // a later owner request supersedes it
|
|
319
|
+
const current = selectRarebits([
|
|
320
|
+
entry("u", "user", "one"),
|
|
321
|
+
entry("a", "assistant", "done"),
|
|
322
|
+
]);
|
|
323
|
+
assert.equal(requestPrefixApplies(ref, current), true);
|
|
324
|
+
const exact = {
|
|
325
|
+
...ref,
|
|
326
|
+
manifestHash: current.manifestHash,
|
|
327
|
+
occurrenceCount: current.occurrences.length,
|
|
328
|
+
};
|
|
329
|
+
assert.equal(exactSelectionApplies(exact, current), true);
|
|
330
|
+
});
|
|
331
|
+
test("generated successful and terminal failure receipts satisfy runtime and JSON Schema closure", async () => {
|
|
332
|
+
const f = await fixture();
|
|
333
|
+
const success = await materialize(f, [entry("u", "user", "x")]);
|
|
334
|
+
const broken = await fixture("failure");
|
|
335
|
+
const failure = await processRarebitSummary(
|
|
336
|
+
{
|
|
337
|
+
sessionManager: {
|
|
338
|
+
getHeader: () => ({ id: "failure" }),
|
|
339
|
+
getSessionFile: () => broken.sessionFile,
|
|
340
|
+
getBranch: () => [entry("u", "user", "x")],
|
|
341
|
+
},
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
sessionRoot: broken.sessionRoot,
|
|
345
|
+
rarebitRoot: broken.rarebitRoot,
|
|
346
|
+
summaryPolicy: { minTotalLength: 0, maxRarebitRatio: 1 },
|
|
347
|
+
model: { provider: "test", id: "model" },
|
|
348
|
+
modelClient: { complete: async () => "not JSON" },
|
|
349
|
+
},
|
|
350
|
+
);
|
|
351
|
+
const schema = JSON.parse(
|
|
352
|
+
await readFile(
|
|
353
|
+
new URL("../schemas/rarebit.schema.json", import.meta.url),
|
|
354
|
+
"utf8",
|
|
355
|
+
),
|
|
356
|
+
);
|
|
357
|
+
const validate = new Ajv({ strict: false }).compile(schema);
|
|
358
|
+
for (const persisted of [success.record, failure.record]) {
|
|
359
|
+
const { path, ...record } = persisted;
|
|
360
|
+
assert.equal(validateRarebitArtifactReceipt(record).valid, true);
|
|
361
|
+
assert.equal(validate(record), true, JSON.stringify(validate.errors));
|
|
362
|
+
}
|
|
363
|
+
const validSuccess = structuredClone(success.record);
|
|
364
|
+
delete validSuccess.path;
|
|
365
|
+
const invalid = [
|
|
366
|
+
{ ...validSuccess, unexpected: true },
|
|
367
|
+
{ ...validSuccess, branch: { ...validSuccess.branch, pathHash: "bad" } },
|
|
368
|
+
{
|
|
369
|
+
...validSuccess,
|
|
370
|
+
selection: { ...validSuccess.selection, occurrences: [] },
|
|
371
|
+
},
|
|
372
|
+
{ ...validSuccess, status: "failure" },
|
|
373
|
+
{ ...validSuccess, schemaVersion: 999 },
|
|
374
|
+
];
|
|
375
|
+
for (const record of invalid) {
|
|
376
|
+
assert.equal(validateRarebitArtifactReceipt(record).valid, false);
|
|
377
|
+
assert.equal(validate(record), false);
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
test("a delayed receipt stays history but cannot advance the head after native evidence changes under the fence", async () => {
|
|
381
|
+
const f = await fixture();
|
|
382
|
+
const oldBranch = [entry("u", "user", "old")];
|
|
383
|
+
const currentBranch = [...oldBranch, entry("u2", "user", "new")];
|
|
384
|
+
await materialize(f, oldBranch, "s", {
|
|
385
|
+
storeHooks: {
|
|
386
|
+
afterLeaseValidated: async () => {
|
|
387
|
+
await writeFile(
|
|
388
|
+
f.sessionFile,
|
|
389
|
+
[
|
|
390
|
+
JSON.stringify({ type: "session", id: "s" }),
|
|
391
|
+
...currentBranch.map(JSON.stringify),
|
|
392
|
+
].join("\n") + "\n",
|
|
393
|
+
);
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
});
|
|
397
|
+
const current = await readRarebitCurrent({
|
|
398
|
+
sessionFile: f.sessionFile,
|
|
399
|
+
sessionRoot: f.sessionRoot,
|
|
400
|
+
rarebitRoot: f.rarebitRoot,
|
|
401
|
+
});
|
|
402
|
+
assert.equal(current.availability, "missing");
|
|
403
|
+
const history = await readRarebitHistory({
|
|
404
|
+
sessionFile: f.sessionFile,
|
|
405
|
+
sessionRoot: f.sessionRoot,
|
|
406
|
+
rarebitRoot: f.rarebitRoot,
|
|
407
|
+
});
|
|
408
|
+
assert.equal(history.records.length, 1);
|
|
409
|
+
});
|
|
410
|
+
test("expired-lease reclaim fences the old worker without deleting the new lease", async () => {
|
|
411
|
+
const f = await fixture();
|
|
412
|
+
const branch = [entry("u", "user", "x")];
|
|
413
|
+
await writeFile(
|
|
414
|
+
f.sessionFile,
|
|
415
|
+
[
|
|
416
|
+
JSON.stringify({ type: "session", id: "s" }),
|
|
417
|
+
...branch.map(JSON.stringify),
|
|
418
|
+
].join("\n") + "\n",
|
|
419
|
+
);
|
|
420
|
+
const jobId = "d".repeat(64);
|
|
421
|
+
const common = {
|
|
422
|
+
jobId,
|
|
423
|
+
sessionFile: f.sessionFile,
|
|
424
|
+
sessionRoot: f.sessionRoot,
|
|
425
|
+
rarebitRoot: f.rarebitRoot,
|
|
426
|
+
leaseMs: 1,
|
|
427
|
+
};
|
|
428
|
+
const oldLease = await reserveRarebitJob({ ...common, now: 0 });
|
|
429
|
+
const newLease = await reserveRarebitJob({ ...common, now: 10 });
|
|
430
|
+
assert.equal(newLease.acquired, true);
|
|
431
|
+
assert.notEqual(oldLease.leaseToken, newLease.leaseToken);
|
|
432
|
+
await assert.rejects(
|
|
433
|
+
settleRarebitJob(oldLease, compactReceipt(branch)),
|
|
434
|
+
/fenced by a newer worker/,
|
|
435
|
+
);
|
|
436
|
+
await settleRarebitJob(newLease, compactReceipt(branch));
|
|
437
|
+
const current = await readRarebitCurrent({
|
|
438
|
+
sessionFile: f.sessionFile,
|
|
439
|
+
sessionRoot: f.sessionRoot,
|
|
440
|
+
rarebitRoot: f.rarebitRoot,
|
|
441
|
+
});
|
|
442
|
+
assert.equal(current.head.receiptHash.length, 64);
|
|
443
|
+
assert.equal(current.receipt.jobId, jobId);
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
test("stale reclaim waits behind settlement's final token check and append", async () => {
|
|
447
|
+
const f = await fixture();
|
|
448
|
+
const branch = [entry("u", "user", "x")];
|
|
449
|
+
await writeFile(
|
|
450
|
+
f.sessionFile,
|
|
451
|
+
[
|
|
452
|
+
JSON.stringify({ type: "session", id: "s" }),
|
|
453
|
+
...branch.map(JSON.stringify),
|
|
454
|
+
].join("\n") + "\n",
|
|
455
|
+
);
|
|
456
|
+
let enter;
|
|
457
|
+
const entered = new Promise((resolve) => {
|
|
458
|
+
enter = resolve;
|
|
459
|
+
});
|
|
460
|
+
let release;
|
|
461
|
+
const barrier = new Promise((resolve) => {
|
|
462
|
+
release = resolve;
|
|
463
|
+
});
|
|
464
|
+
const jobId = "c".repeat(64);
|
|
465
|
+
const common = {
|
|
466
|
+
jobId,
|
|
467
|
+
sessionFile: f.sessionFile,
|
|
468
|
+
sessionRoot: f.sessionRoot,
|
|
469
|
+
rarebitRoot: f.rarebitRoot,
|
|
470
|
+
leaseMs: 1,
|
|
471
|
+
};
|
|
472
|
+
const first = await reserveRarebitJob({
|
|
473
|
+
...common,
|
|
474
|
+
now: 0,
|
|
475
|
+
hooks: {
|
|
476
|
+
afterLeaseValidated: async () => {
|
|
477
|
+
enter();
|
|
478
|
+
await barrier;
|
|
479
|
+
},
|
|
480
|
+
},
|
|
481
|
+
});
|
|
482
|
+
const settling = settleRarebitJob(first, compactReceipt(branch));
|
|
483
|
+
await entered;
|
|
484
|
+
let reclaimResolved = false;
|
|
485
|
+
const reclaiming = reserveRarebitJob({ ...common, now: 10 }).then((value) => {
|
|
486
|
+
reclaimResolved = true;
|
|
487
|
+
return value;
|
|
488
|
+
});
|
|
489
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
490
|
+
assert.equal(reclaimResolved, false);
|
|
491
|
+
release();
|
|
492
|
+
await settling;
|
|
493
|
+
const reclaimed = await reclaiming;
|
|
494
|
+
assert.equal(reclaimed.acquired, false);
|
|
495
|
+
assert.equal(reclaimed.duplicate, true);
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
test("torn head recovers prior complete head but a complete corrupt head fails closed", async () => {
|
|
499
|
+
const f = await fixture();
|
|
500
|
+
const result = await materialize(f, [entry("u", "user", "x")]);
|
|
501
|
+
await appendFile(result.record.path, '{"type":"rarebit_head"');
|
|
502
|
+
let current = await readRarebitCurrent({
|
|
503
|
+
sessionFile: f.sessionFile,
|
|
504
|
+
sessionRoot: f.sessionRoot,
|
|
505
|
+
rarebitRoot: f.rarebitRoot,
|
|
506
|
+
});
|
|
507
|
+
assert.equal(current.availability, "available");
|
|
508
|
+
assert.equal(current.diagnostics.tornTail, true);
|
|
509
|
+
await appendFile(
|
|
510
|
+
result.record.path,
|
|
511
|
+
'\n{"type":"rarebit_head","protocolVersion":1,"sessionId":"s","receiptOffset":999999,"receiptLength":2,"receiptHash":"' +
|
|
512
|
+
"a".repeat(64) +
|
|
513
|
+
'"}\n',
|
|
514
|
+
);
|
|
515
|
+
current = await readRarebitCurrent({
|
|
516
|
+
sessionFile: f.sessionFile,
|
|
517
|
+
sessionRoot: f.sessionRoot,
|
|
518
|
+
rarebitRoot: f.rarebitRoot,
|
|
519
|
+
});
|
|
520
|
+
assert.equal(current.availability, "unreadable");
|
|
521
|
+
assert.equal(current.diagnostics.reason, "sidecar_head_invalid");
|
|
522
|
+
});
|
|
523
|
+
test("a complete final version-invalid head cannot fall back to a prior valid head", async () => {
|
|
524
|
+
const f = await fixture();
|
|
525
|
+
const result = await materialize(f, [entry("u", "user", "x")]);
|
|
526
|
+
await appendFile(
|
|
527
|
+
result.record.path,
|
|
528
|
+
'{"type":"rarebit_head","protocolVersion":999}\n',
|
|
529
|
+
);
|
|
530
|
+
const current = await readRarebitCurrent({
|
|
531
|
+
sessionFile: f.sessionFile,
|
|
532
|
+
sessionRoot: f.sessionRoot,
|
|
533
|
+
rarebitRoot: f.rarebitRoot,
|
|
534
|
+
});
|
|
535
|
+
assert.equal(current.availability, "unreadable");
|
|
536
|
+
assert.equal(current.receipt, null);
|
|
537
|
+
assert.equal(current.diagnostics.reason, "sidecar_head_invalid");
|
|
538
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import {
|
|
4
|
+
rarebitEventPresentation,
|
|
5
|
+
rarebitOccurrencePresentation,
|
|
6
|
+
rarebitSummaryPresentation,
|
|
7
|
+
} from "../src/rarebit-visual-language.mjs";
|
|
8
|
+
|
|
9
|
+
test("event presentation preserves the canonical occurrence grammar", () => {
|
|
10
|
+
assert.deepEqual(rarebitEventPresentation("user_message"), {
|
|
11
|
+
mark: "□",
|
|
12
|
+
label: "user message",
|
|
13
|
+
tone: "user",
|
|
14
|
+
salience: "standard",
|
|
15
|
+
});
|
|
16
|
+
assert.deepEqual(
|
|
17
|
+
rarebitOccurrencePresentation({
|
|
18
|
+
role: "assistant",
|
|
19
|
+
outcome: "continuation",
|
|
20
|
+
}),
|
|
21
|
+
{
|
|
22
|
+
mark: "•",
|
|
23
|
+
label: "agent continues",
|
|
24
|
+
tone: "continuation",
|
|
25
|
+
salience: "smaller",
|
|
26
|
+
},
|
|
27
|
+
);
|
|
28
|
+
assert.deepEqual(
|
|
29
|
+
rarebitOccurrencePresentation({ role: "assistant", outcome: "stop" }),
|
|
30
|
+
{
|
|
31
|
+
mark: "●",
|
|
32
|
+
label: "agent stops",
|
|
33
|
+
tone: "boundary",
|
|
34
|
+
salience: "larger",
|
|
35
|
+
},
|
|
36
|
+
);
|
|
37
|
+
assert.equal(rarebitEventPresentation("terminal_error").mark, "×");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("Summary presentation keeps ordinary outcomes neutral and attention singular", () => {
|
|
41
|
+
assert.deepEqual(rarebitSummaryPresentation("finished"), {
|
|
42
|
+
mark: null,
|
|
43
|
+
label: "appears finished",
|
|
44
|
+
tone: "neutral",
|
|
45
|
+
salience: "ordinary",
|
|
46
|
+
});
|
|
47
|
+
assert.deepEqual(rarebitSummaryPresentation("needs_attention"), {
|
|
48
|
+
mark: "◆!",
|
|
49
|
+
label: "needs you",
|
|
50
|
+
tone: "attention",
|
|
51
|
+
salience: "attention",
|
|
52
|
+
});
|
|
53
|
+
assert.deepEqual(
|
|
54
|
+
rarebitSummaryPresentation("needs_attention", { sourcePending: true }),
|
|
55
|
+
{
|
|
56
|
+
mark: null,
|
|
57
|
+
label: "needs you · source pending",
|
|
58
|
+
tone: "neutral",
|
|
59
|
+
salience: "ordinary",
|
|
60
|
+
},
|
|
61
|
+
);
|
|
62
|
+
assert.equal(rarebitSummaryPresentation("error").mark, "×");
|
|
63
|
+
assert.equal(rarebitSummaryPresentation("ineligible").tone, "muted");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("unknown semantic roles fail instead of inventing a visual category", () => {
|
|
67
|
+
assert.throws(
|
|
68
|
+
() => rarebitOccurrencePresentation({ role: "tool", outcome: "stop" }),
|
|
69
|
+
/Unknown Rarebit occurrence role\/outcome/,
|
|
70
|
+
);
|
|
71
|
+
assert.throws(
|
|
72
|
+
() => rarebitSummaryPresentation("successful"),
|
|
73
|
+
/Unknown Rarebit Summary status/,
|
|
74
|
+
);
|
|
75
|
+
});
|