@hypercarrier/rarebit 0.1.0-alpha.4 → 0.1.0-alpha.6

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.
@@ -0,0 +1,824 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Privacy-safe Rarebit fork canary.
4
+ *
5
+ * This script uses only synthetic Session prose and disposable Pi roots. It
6
+ * exercises the public fork construction/PiQ boundary with real Pi SDK hosts.
7
+ * It never reads or switches the owner's active Session.
8
+ */
9
+ import assert from "node:assert/strict";
10
+ import { chmod, mkdir, mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises";
11
+ import { spawnSync } from "node:child_process";
12
+ import { tmpdir } from "node:os";
13
+ import { dirname, join, resolve } from "node:path";
14
+ import { pathToFileURL } from "node:url";
15
+
16
+ import {
17
+ buildRarebitForkPlan,
18
+ createRarebitForkEntries,
19
+ sessionFilenameFor,
20
+ writeRarebitForkFile,
21
+ } from "../src/rarebit-fork.mjs";
22
+ import {
23
+ getImportedRarebitEntryIds,
24
+ getRarebitForkLineageRecords,
25
+ } from "../src/rarebit-fork-lineage.mjs";
26
+ import { parseNativeSession, readPiSession } from "../src/pi-session.mjs";
27
+ import { parseRarebitCommand } from "../src/rarebit-command.mjs";
28
+ import { queryPiQ } from "../src/piq.mjs";
29
+ import registerPiRarebit from "../src/extension.mjs";
30
+
31
+ const packageRoot = resolve(dirname(new URL(import.meta.url).pathname), "..");
32
+ const childPiRoot = resolve(packageRoot, "node_modules/@earendil-works/pi-coding-agent");
33
+ const installedPiRoot = process.env.RAREBIT_INSTALLED_PI_ROOT
34
+ ? resolve(process.env.RAREBIT_INSTALLED_PI_ROOT)
35
+ : null;
36
+ if (!installedPiRoot)
37
+ throw new Error("RAREBIT_INSTALLED_PI_ROOT must name the installed Pi coding-agent package root for this canary");
38
+ const expectedChildVersion = process.env.RAREBIT_CHILD_PI_VERSION ?? "0.84.2";
39
+ const expectedInstalledVersion =
40
+ process.env.RAREBIT_INSTALLED_PI_VERSION ?? "0.86.1";
41
+
42
+ const SOURCE_CWD = "/synthetic/rarebit-source";
43
+ const TARGET_CWD = "/synthetic/rarebit-target";
44
+ const sourceHeader = {
45
+ type: "session",
46
+ version: 3,
47
+ id: "source-session-synthetic",
48
+ timestamp: "2026-09-21T00:00:00.000Z",
49
+ cwd: SOURCE_CWD,
50
+ };
51
+
52
+ function message(id, parentId, timestamp, value) {
53
+ return { type: "message", id, parentId, timestamp, message: value };
54
+ }
55
+
56
+ function syntheticSource() {
57
+ const entries = [
58
+ message("u-old", null, "2026-09-21T00:00:01.000Z", {
59
+ role: "user",
60
+ content: "old source request omitted by the bounded budget",
61
+ }),
62
+ message("a-tool", "u-old", "2026-09-21T00:00:02.000Z", {
63
+ role: "assistant",
64
+ content: [
65
+ { type: "text", text: "assistant continuation retained for recovery" },
66
+ {
67
+ type: "toolCall",
68
+ id: "tool-call-synthetic",
69
+ name: "lookup",
70
+ arguments: { query: "synthetic" },
71
+ },
72
+ ],
73
+ stopReason: "toolUse",
74
+ usage: { input: 19, output: 4, totalTokens: 23 },
75
+ }),
76
+ message("tool-result", "a-tool", "2026-09-21T00:00:03.000Z", {
77
+ role: "toolResult",
78
+ toolCallId: "tool-call-synthetic",
79
+ content: [{ type: "text", text: "synthetic omitted tool result" }],
80
+ }),
81
+ message("a-stop-1", "tool-result", "2026-09-21T00:00:04.000Z", {
82
+ role: "assistant",
83
+ content: [{ type: "text", text: "first consecutive assistant prose" }],
84
+ stopReason: "stop",
85
+ usage: { input: 21, output: 5, totalTokens: 26 },
86
+ }),
87
+ message("a-stop-2", "a-stop-1", "2026-09-21T00:00:05.000Z", {
88
+ role: "assistant",
89
+ content: [{ type: "text", text: "second consecutive assistant prose" }],
90
+ stopReason: "stop",
91
+ usage: { input: 22, output: 5, totalTokens: 27 },
92
+ }),
93
+ message("u-new", "a-stop-2", "2026-09-21T00:00:06.000Z", {
94
+ role: "user",
95
+ content: "newest retained user request",
96
+ }),
97
+ ];
98
+ return { header: sourceHeader, entries };
99
+ }
100
+
101
+ function serializedSession(session) {
102
+ return `${[session.header, ...session.entries]
103
+ .map((entry) => JSON.stringify(entry))
104
+ .join("\n")}\n`;
105
+ }
106
+
107
+ async function piPackage(root) {
108
+ const packagePath = join(root, "package.json");
109
+ const packageJson = JSON.parse(await readFile(packagePath, "utf8"));
110
+ const pi = await import(pathToFileURL(join(root, "dist/index.js")).href);
111
+ const faux = await import(
112
+ pathToFileURL(
113
+ join(root, "node_modules/@earendil-works/pi-ai/dist/providers/faux.js"),
114
+ ).href,
115
+ );
116
+ return { packageJson, pi, faux };
117
+ }
118
+
119
+ async function assertStaticForkContracts(root) {
120
+ assert.equal(parseRarebitCommand("fork").ok, true);
121
+ assert.equal(parseRarebitCommand("fork --max-token-length 64000").ok, true);
122
+ const source = syntheticSource();
123
+ const sourceFile = join(root, "source.jsonl");
124
+ const sourceBytes = serializedSession(source);
125
+ await writeFile(sourceFile, sourceBytes, { mode: 0o600 });
126
+
127
+ const sourceBranch = (await readPiSession(sourceFile)).branch;
128
+ const plan = buildRarebitForkPlan({
129
+ header: source.header,
130
+ sessionFile: sourceFile,
131
+ cwd: SOURCE_CWD,
132
+ branch: sourceBranch,
133
+ targetCwd: TARGET_CWD,
134
+ targetModel: { contextWindow: 128_000 },
135
+ maxTokenLength: 40,
136
+ reserveTokens: 4_096,
137
+ forkCreatedAt: "2026-09-21T01:00:00.000Z",
138
+ });
139
+ assert.equal(plan.selected.at(-1).sourceEntryId, "u-new");
140
+ assert.deepEqual(plan.selected.map(({ sourceEntryId }) => sourceEntryId), ["a-tool", "a-stop-1", "a-stop-2", "u-new"]);
141
+ assert.equal(plan.selected.some(({ sourceEntryId }) => sourceEntryId === "u-old"), false);
142
+ assert.equal(plan.selected.some(({ sourceEntryId }) => sourceEntryId === "a-tool"), true);
143
+ assert.equal(plan.selected.some(({ sourceEntryId }) => sourceEntryId === "a-stop-1"), true);
144
+ assert.equal(plan.selected.some(({ sourceEntryId }) => sourceEntryId === "a-stop-2"), true);
145
+ assert.equal(plan.selected.some(({ sourceEntryId }) => sourceEntryId === "tool-result"), false);
146
+
147
+ const fork = createRarebitForkEntries(plan, {
148
+ sessionId: "fork-session-synthetic",
149
+ });
150
+ const forkFile = join(root, "fork.jsonl");
151
+ await writeRarebitForkFile(forkFile, fork);
152
+ assert.equal(await readFile(sourceFile, "utf8"), sourceBytes, "fork must not mutate source");
153
+
154
+ const parsedFork = parseNativeSession(await readFile(forkFile, "utf8"), forkFile);
155
+ assert.deepEqual(
156
+ parsedFork.entries.map((entry) => entry.parentId),
157
+ parsedFork.entries.map((entry, index) => index === 0 ? null : parsedFork.entries[index - 1].id),
158
+ "fork must have one valid linear parent chain",
159
+ );
160
+ const importedIds = getImportedRarebitEntryIds(parsedFork.entries);
161
+ assert.equal(importedIds.size, plan.selected.length + 1, "seed and imports must be excluded from fresh activity");
162
+ assert.equal(getRarebitForkLineageRecords(parsedFork.entries).length, 1);
163
+
164
+ const imported = parsedFork.entries.filter((entry) => importedIds.has(entry.id));
165
+ assert.deepEqual(
166
+ imported.map((entry) => entry.timestamp),
167
+ imported.map(() => plan.forkCreatedAt),
168
+ "fork timestamps describe creation",
169
+ );
170
+ const mappings = getRarebitForkLineageRecords(parsedFork.entries)[0].mappings;
171
+ assert.ok(mappings.every((mapping) => typeof mapping.originalTimestamp === "string"));
172
+ assert.equal(
173
+ imported.find((entry) => entry.rarebitFork?.sourceEntryId === "a-tool")?.message.stopReason,
174
+ "toolUse",
175
+ );
176
+ assert.equal(
177
+ imported.filter((entry) => entry.message?.role === "assistant").length,
178
+ 3,
179
+ "assistant continuation and consecutive prose must remain assistant messages",
180
+ );
181
+ assert.ok(imported.every((entry) => !entry.message?.usage || Object.values(entry.message.usage).every((value) => value === 0 || (typeof value === "object" && Object.values(value).every((nested) => nested === 0)))));
182
+
183
+ const secondSource = await readPiSession(forkFile);
184
+ const secondPlan = buildRarebitForkPlan({
185
+ header: secondSource.parsed.header,
186
+ sessionFile: forkFile,
187
+ cwd: TARGET_CWD,
188
+ branch: secondSource.branch,
189
+ targetCwd: "/synthetic/rarebit-third",
190
+ targetModel: { contextWindow: 128_000 },
191
+ maxTokenLength: 80,
192
+ reserveTokens: 4_096,
193
+ forkCreatedAt: "2026-09-21T02:00:00.000Z",
194
+ });
195
+ const importedConversation = imported.filter((entry) => entry.rarebitFork?.kind === "import");
196
+ assert.equal(secondPlan.selected.some((item) => item.sourceEntryId === importedConversation[0].id), true);
197
+ assert.equal(secondPlan.selected.some((item) => item.sourceEntryId === parsedFork.entries[0].id), false, "generated opening notice must not be imported into a repeated fork");
198
+ const secondFork = createRarebitForkEntries(secondPlan, { sessionId: "fork-session-third-synthetic" });
199
+ assert.equal(secondFork.entries.filter((entry) => entry.rarebitFork?.kind === "seed").length, 1, "repeated forks must generate one opening notice");
200
+ assert.ok(secondFork.manifest.mappings.every((mapping) => mapping.ancestry.some((source) => source.sessionId === sourceHeader.id)), "repeated fork lineage must retain original source Session coordinates");
201
+ const thirdPlan = buildRarebitForkPlan({
202
+ header: secondFork.header,
203
+ sessionFile: "/synthetic/rarebit-third.jsonl",
204
+ cwd: "/synthetic/rarebit-third",
205
+ branch: secondFork.entries.slice(0, -1),
206
+ targetCwd: "/synthetic/rarebit-fourth",
207
+ targetModel: { contextWindow: 128_000 },
208
+ maxTokenLength: 80,
209
+ reserveTokens: 4_096,
210
+ forkCreatedAt: "2026-09-21T02:30:00.000Z",
211
+ });
212
+ const thirdFork = createRarebitForkEntries(thirdPlan, { sessionId: "fork-session-fourth-synthetic" });
213
+ assert.ok(thirdFork.manifest.mappings.every((mapping) => mapping.ancestry.length === 3), "depth-three repeated fork ancestry must remain explicit");
214
+
215
+ const v1File = join(root, "legacy-v1.jsonl");
216
+ const v1Bytes = `${JSON.stringify({ type: "session", version: 1, id: "legacy-v1", cwd: SOURCE_CWD })}\n${JSON.stringify({ type: "message", message: { role: "user", content: "legacy" } })}\n`;
217
+ await writeFile(v1File, v1Bytes);
218
+ assert.equal((await readPiSession(v1File)).branch.length, 1);
219
+ assert.equal(await readFile(v1File, "utf8"), v1Bytes, "v1 source must remain unchanged");
220
+ const v2File = join(root, "legacy-v2.jsonl");
221
+ const v2Records = [
222
+ { type: "session", version: 2, id: "legacy-v2", cwd: SOURCE_CWD },
223
+ { type: "message", id: "v2-root", parentId: null, message: { role: "user", content: "root" } },
224
+ { type: "message", id: "v2-leaf", parentId: "v2-root", message: { role: "assistant", stopReason: "stop", content: "leaf" } },
225
+ { type: "message", id: "v2-sibling", parentId: "v2-root", message: { role: "user", content: "sibling" } },
226
+ ];
227
+ const v2Bytes = `${v2Records.map(JSON.stringify).join("\n")}\n`;
228
+ await writeFile(v2File, v2Bytes);
229
+ assert.deepEqual((await readPiSession(v2File)).branch.map((entry) => entry.id), ["v2-root", "v2-sibling"]);
230
+ assert.equal(await readFile(v2File, "utf8"), v2Bytes, "v2 source must remain unchanged");
231
+
232
+ const atomicTarget = join(root, "atomic-failure.jsonl");
233
+ await assert.rejects(
234
+ writeRarebitForkFile(atomicTarget, fork, {
235
+ renameImpl: async () => { throw new Error("synthetic rename failure"); },
236
+ }),
237
+ /synthetic rename failure/,
238
+ );
239
+ await assert.rejects(readFile(atomicTarget, "utf8"));
240
+ assert.equal((await readdir(root)).some((name) => name.includes("atomic-failure") && name.endsWith(".tmp")), false, "failed setup must clean temporary output");
241
+ assert.match(sessionFilenameFor({ sessionDir: root, forkCreatedAt: plan.forkCreatedAt, sessionId: fork.sessionId }), /fork-session-synthetic\.jsonl$/);
242
+
243
+ const toolPlan = buildRarebitForkPlan({
244
+ header: source.header,
245
+ sessionFile: sourceFile,
246
+ cwd: SOURCE_CWD,
247
+ branch: sourceBranch.slice(0, 2),
248
+ targetCwd: TARGET_CWD,
249
+ targetModel: { contextWindow: 128_000 },
250
+ maxTokenLength: 20,
251
+ reserveTokens: 4_096,
252
+ forkCreatedAt: "2026-09-21T03:00:00.000Z",
253
+ });
254
+ assert.deepEqual(toolPlan.selected.map(({ sourceEntryId }) => sourceEntryId), ["a-tool"]);
255
+ const toolFork = createRarebitForkEntries(toolPlan, { sessionId: "fork-session-tool-synthetic" });
256
+ const toolForkFile = join(root, "fork-tool.jsonl");
257
+ await writeRarebitForkFile(toolForkFile, toolFork);
258
+
259
+ const assistantPlan = buildRarebitForkPlan({
260
+ header: source.header,
261
+ sessionFile: sourceFile,
262
+ cwd: SOURCE_CWD,
263
+ branch: sourceBranch.slice(0, 5),
264
+ targetCwd: TARGET_CWD,
265
+ targetModel: { contextWindow: 128_000 },
266
+ maxTokenLength: 39,
267
+ reserveTokens: 4_096,
268
+ forkCreatedAt: "2026-09-21T04:00:00.000Z",
269
+ });
270
+ assert.deepEqual(assistantPlan.selected.map(({ sourceEntryId }) => sourceEntryId), ["a-tool", "a-stop-1", "a-stop-2"]);
271
+ const assistantFork = createRarebitForkEntries(assistantPlan, { sessionId: "fork-session-assistant-synthetic" });
272
+ const assistantForkFile = join(root, "fork-assistant.jsonl");
273
+ await writeRarebitForkFile(assistantForkFile, assistantFork);
274
+
275
+ const sentinelPlan = buildRarebitForkPlan({
276
+ header: source.header,
277
+ sessionFile: sourceFile,
278
+ cwd: SOURCE_CWD,
279
+ branch: sourceBranch.slice(0, 5),
280
+ targetCwd: TARGET_CWD,
281
+ targetModel: { provider: "sentinel", id: "sentinel-model", contextWindow: 128_000 },
282
+ maxTokenLength: 40,
283
+ reserveTokens: 4_096,
284
+ forkCreatedAt: "2026-09-21T05:00:00.000Z",
285
+ });
286
+ const sentinelFork = createRarebitForkEntries(sentinelPlan, { sessionId: "fork-session-sentinel-synthetic" });
287
+ const sentinelForkFile = join(root, "fork-sentinel.jsonl");
288
+ await writeRarebitForkFile(sentinelForkFile, sentinelFork);
289
+
290
+ const truncated = join(root, "truncated.jsonl");
291
+ await writeFile(truncated, `${sourceBytes}{"type":"message","id":"partial"`);
292
+ await assert.rejects(readPiSession(truncated), /malformed JSON/);
293
+
294
+ await assert.rejects(
295
+ (async () => {
296
+ buildRarebitForkPlan({
297
+ header: source.header,
298
+ sessionFile: sourceFile,
299
+ cwd: SOURCE_CWD,
300
+ branch: sourceBranch,
301
+ targetCwd: TARGET_CWD,
302
+ targetModel: { contextWindow: 100 },
303
+ maxTokenLength: 40,
304
+ reserveTokens: 99,
305
+ forkCreatedAt: "2026-09-21T01:00:00.000Z",
306
+ });
307
+ })(),
308
+ /headroom|reserve/i,
309
+ );
310
+
311
+ return {
312
+ sourceFile,
313
+ forkFile,
314
+ sourceBytes,
315
+ fork,
316
+ toolForkFile,
317
+ assistantForkFile,
318
+ sentinelForkFile,
319
+ legacyFiles: [v1File, v2File],
320
+ };
321
+ }
322
+
323
+ async function runCliCanary(root, sourceFiles) {
324
+ const cliHome = join(root, "cli-home");
325
+ const cliAgent = join(root, "cli-agent");
326
+ const cliCwd = join(root, "cli-cwd");
327
+ await Promise.all([mkdir(cliHome, { recursive: true }), mkdir(cliAgent, { recursive: true }), mkdir(cliCwd, { recursive: true })]);
328
+ const { pi: childPi } = await piPackage(childPiRoot);
329
+ const cliRuntime = await childPi.ModelRuntime.create({ modelsPath: null });
330
+ const cliModel = cliRuntime
331
+ .getModels()
332
+ .find((model) => Number.isFinite(model.contextWindow) && model.contextWindow > 0);
333
+ assert.ok(cliModel, "child Pi must expose a context-bearing model for CLI verification");
334
+ await writeFile(join(cliAgent, "settings.json"), JSON.stringify({
335
+ defaultProvider: cliModel.provider,
336
+ defaultModel: cliModel.id,
337
+ }));
338
+ const outputs = [];
339
+ for (const [index, sourceFile] of sourceFiles.entries()) {
340
+ const sourceBytes = await readFile(sourceFile, "utf8");
341
+ const result = spawnSync(
342
+ process.execPath,
343
+ [join(packageRoot, "bin/rarebit.mjs"), "fork", sourceFile, "--max-token-length", "40", "--no-launch", "--json"],
344
+ { cwd: cliCwd, env: { ...process.env, HOME: cliHome, PI_CODING_AGENT_DIR: cliAgent }, encoding: "utf8" },
345
+ );
346
+ assert.equal(result.status, 0, result.stderr);
347
+ const payload = JSON.parse(result.stdout);
348
+ assert.equal(payload.operation, "fork");
349
+ assert.equal(payload.launched, false);
350
+ assert.match(payload.session, new RegExp(`^${cliAgent.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\\\$&")}/sessions/`));
351
+ assert.equal(await readFile(sourceFile, "utf8"), sourceBytes, `CLI fork ${index} must not mutate source`);
352
+ assert.ok((await readFile(payload.session, "utf8")).includes("rarebit_fork_lineage"));
353
+ outputs.push(payload);
354
+ }
355
+ const launcher = join(cliCwd, "controlled-pi-launch.sh");
356
+ const launchMarker = join(cliCwd, "launch-argv.txt");
357
+ await writeFile(launcher, `#!/bin/sh\nprintf '%s\\n' "$@" > ${launchMarker}\nexit 0\n`);
358
+ await chmod(launcher, 0o700);
359
+ const cliModule = pathToFileURL(resolve(packageRoot, "src/rarebit-cli.mjs")).href;
360
+ const launchCode = `import { parseRarebitCliArgs, runRarebitCli } from ${JSON.stringify(cliModule)};\nconst options = parseRarebitCliArgs(["fork", process.env.CANARY_SOURCE, "--max-token-length", "40", "--json"]);\nconst result = await runRarebitCli(options, { piCommand: process.env.CANARY_PI_COMMAND, resolveTarget: async () => ({ model: { provider: "synthetic", id: "controlled", contextWindow: 128000 }, reserveTokens: 4096, promptOverheadTokens: 0, toolOverheadTokens: 0 }) });\nprocess.stdout.write(JSON.stringify(result));`;
361
+ const launched = spawnSync(
362
+ process.execPath,
363
+ ["--input-type=module", "-e", launchCode],
364
+ { cwd: cliCwd, env: { ...process.env, HOME: cliHome, PI_CODING_AGENT_DIR: cliAgent, CANARY_SOURCE: sourceFiles[0], CANARY_PI_COMMAND: launcher }, encoding: "utf8" },
365
+ );
366
+ assert.equal(launched.status, 0, launched.stderr);
367
+ const launchPayload = JSON.parse(launched.stdout);
368
+ assert.equal(launchPayload.launched, true);
369
+ assert.equal(launchPayload.exitCode, 0);
370
+ assert.match(await readFile(launchMarker, "utf8"), /--session/);
371
+ assert.ok((await readFile(launchPayload.session, "utf8")).includes("rarebit_fork_lineage"));
372
+ return { session: outputs[0].session, cwd: cliCwd, legacyCount: outputs.length - 1, launched: true };
373
+ }
374
+
375
+ async function runSlashCanary(root, sourceFile, sourceBytes) {
376
+ const { packageJson, pi, faux } = await piPackage(root);
377
+ const provider = faux.fauxProvider({
378
+ provider: `rarebit-slash-${packageJson.version}`,
379
+ models: [{ id: "controlled", contextWindow: 128_000, maxTokens: 256, input: ["text"] }],
380
+ });
381
+ const settings = pi.SettingsManager.inMemory({ compaction: { enabled: false } });
382
+ const runtime = await pi.ModelRuntime.create({ modelsPath: null });
383
+ runtime.registerNativeProvider(provider.provider);
384
+ const slashSource = join(dirname(sourceFile), "slash-source-copy.jsonl");
385
+ await writeFile(slashSource, sourceBytes);
386
+ const manager = pi.SessionManager.open(slashSource, join(dirname(sourceFile), "slash-sessions"), TARGET_CWD);
387
+ const loader = new pi.DefaultResourceLoader({
388
+ cwd: TARGET_CWD,
389
+ agentDir: join(dirname(sourceFile), "slash-agent"),
390
+ settingsManager: settings,
391
+ noSkills: true,
392
+ noPromptTemplates: true,
393
+ noThemes: true,
394
+ noContextFiles: true,
395
+ extensionFactories: [
396
+ (extensionPi) => registerPiRarebit(extensionPi, {
397
+ settingsLoader: async () => ({ summaryPolicy: {}, autoTitle: false }),
398
+ }),
399
+ ],
400
+ });
401
+ await loader.reload();
402
+ const { session, extensionsResult } = await pi.createAgentSession({
403
+ cwd: TARGET_CWD,
404
+ agentDir: join(dirname(sourceFile), "slash-agent"),
405
+ model: provider.getModel(),
406
+ modelRuntime: runtime,
407
+ noTools: "all",
408
+ resourceLoader: loader,
409
+ sessionManager: manager,
410
+ settingsManager: settings,
411
+ });
412
+ let switchedPath;
413
+ const command = extensionsResult.extensions
414
+ .flatMap((extension) => [...extension.commands.values()])
415
+ .find((candidate) => candidate.name === "rarebit");
416
+ assert.ok(command, "real Pi SDK must register the Rarebit slash command");
417
+ try {
418
+ await command.handler("fork --max-token-length 40", {
419
+ cwd: TARGET_CWD,
420
+ model: provider.getModel(),
421
+ sessionManager: manager,
422
+ waitForIdle: async () => {},
423
+ getSystemPrompt: () => "synthetic system prompt",
424
+ hasUI: false,
425
+ ui: { notify: () => {} },
426
+ switchSession: async (destination, options) => {
427
+ switchedPath = destination;
428
+ await options?.withSession?.({ hasUI: false, ui: { notify: () => {} } });
429
+ return { cancelled: false };
430
+ },
431
+ });
432
+ assert.equal(provider.state.callCount, 0, "slash fork must not start a model turn");
433
+ assert.equal(typeof switchedPath, "string");
434
+ assert.ok((await readFile(switchedPath, "utf8")).includes("rarebit_fork_lineage"));
435
+ assert.equal(await readFile(sourceFile, "utf8"), sourceBytes, "slash fork must not mutate source");
436
+ await rm(switchedPath, { force: true });
437
+ switchedPath = undefined;
438
+ const refusalNotices = [];
439
+ await command.handler("fork --max-token-length 40", {
440
+ cwd: TARGET_CWD,
441
+ model: null,
442
+ sessionManager: manager,
443
+ waitForIdle: async () => {},
444
+ getSystemPrompt: () => "synthetic system prompt",
445
+ hasUI: true,
446
+ ui: { notify: (text) => refusalNotices.push(text) },
447
+ switchSession: async (destination) => {
448
+ switchedPath = destination;
449
+ return { cancelled: false };
450
+ },
451
+ });
452
+ assert.equal(switchedPath, undefined, "unknown model/window must refuse before switching");
453
+ assert.ok(refusalNotices.some((text) => /model|context|window|headroom/i.test(text)));
454
+ } finally {
455
+ if (switchedPath) await rm(switchedPath, { force: true });
456
+ session.dispose();
457
+ runtime.unregisterProvider(provider.provider);
458
+ }
459
+ return { host: packageJson.version };
460
+ }
461
+
462
+ async function runPiHost(
463
+ root,
464
+ expectedVersion,
465
+ forkFile,
466
+ sessionsRoot,
467
+ expectedTexts,
468
+ ) {
469
+ const { packageJson, pi, faux } = await piPackage(root);
470
+ assert.equal(packageJson.version, expectedVersion, `unexpected Pi host at ${root}`);
471
+ const provider = faux.fauxProvider({
472
+ provider: `rarebit-canary-${packageJson.version}`,
473
+ models: [
474
+ {
475
+ id: "controlled",
476
+ contextWindow: 128_000,
477
+ maxTokens: 256,
478
+ input: ["text"],
479
+ },
480
+ ],
481
+ });
482
+ const observedContexts = [];
483
+ provider.setResponses([
484
+ (context) => {
485
+ observedContexts.push(context);
486
+ return faux.fauxAssistantMessage("synthetic continuation");
487
+ },
488
+ ]);
489
+ await mkdir(sessionsRoot, { recursive: true });
490
+ const manager = pi.SessionManager.open(forkFile, sessionsRoot, TARGET_CWD);
491
+ const runtime = await pi.ModelRuntime.create({ modelsPath: null });
492
+ runtime.registerNativeProvider(provider.provider);
493
+ const settings = pi.SettingsManager.inMemory({ compaction: { enabled: false } });
494
+ const loader = new pi.DefaultResourceLoader({
495
+ cwd: TARGET_CWD,
496
+ agentDir: sessionsRoot,
497
+ settingsManager: settings,
498
+ noSkills: true,
499
+ noPromptTemplates: true,
500
+ noThemes: true,
501
+ noContextFiles: true,
502
+ });
503
+ await loader.reload();
504
+ const { session } = await pi.createAgentSession({
505
+ cwd: TARGET_CWD,
506
+ agentDir: sessionsRoot,
507
+ model: provider.getModel(),
508
+ modelRuntime: runtime,
509
+ noTools: "all",
510
+ resourceLoader: loader,
511
+ sessionManager: manager,
512
+ settingsManager: settings,
513
+ });
514
+ try {
515
+ assert.equal(provider.state.callCount, 0, "fork setup must not call a provider");
516
+ const before = session.getContextUsage();
517
+ assert.ok(before?.tokens > 0, "real Pi context occupancy must be positive");
518
+ const importedBranchEntries = manager
519
+ .getBranch()
520
+ .filter((entry) => entry.rarebitFork?.kind === "import");
521
+ assert.ok(
522
+ importedBranchEntries.every((entry) => {
523
+ const usage = entry.message?.usage;
524
+ return !usage || Object.values(usage).every((value) =>
525
+ typeof value === "object"
526
+ ? Object.values(value).every((nested) => nested === 0)
527
+ : value === 0,
528
+ );
529
+ }),
530
+ "imported messages must carry only zero usage",
531
+ );
532
+ await session.prompt("continue the synthetic imported evidence");
533
+ await session.agent.waitForIdle();
534
+ assert.equal(provider.state.callCount, 1, "continuation must make one provider call");
535
+ assert.equal(observedContexts.length, 1);
536
+ const contextText = JSON.stringify(observedContexts[0]);
537
+ for (const text of expectedTexts) assert.match(contextText, new RegExp(text));
538
+ assert.doesNotMatch(contextText, /synthetic omitted tool result/);
539
+ const after = session.getContextUsage();
540
+ assert.ok(after?.tokens > 0, "context occupancy must remain positive after continuation");
541
+ return { packageVersion: packageJson.version, before, after, manager, sourceToolId: "tool-result" };
542
+ } finally {
543
+ session.dispose();
544
+ runtime.unregisterProvider(provider.provider);
545
+ }
546
+ }
547
+
548
+ async function runSentinelRestoreHost(root, expectedVersion, forkFile, sessionsRoot) {
549
+ const { packageJson, pi, faux } = await piPackage(root);
550
+ assert.equal(packageJson.version, expectedVersion);
551
+ const provider = faux.fauxProvider({
552
+ provider: "sentinel",
553
+ models: [{ id: "sentinel-model", contextWindow: 128_000, maxTokens: 256, input: ["text"] }],
554
+ });
555
+ provider.setResponses([faux.fauxAssistantMessage("sentinel restored continuation")]);
556
+ const runtime = await pi.ModelRuntime.create({ modelsPath: null });
557
+ runtime.registerNativeProvider(provider.provider);
558
+ await runtime.setRuntimeApiKey(provider.provider, "synthetic");
559
+ // Faux has no native credential provider. Mark only this disposable runtime
560
+ // as configured so SDK restore exercises model_change resolution itself.
561
+ runtime.hasConfiguredAuth = () => true;
562
+ const manager = pi.SessionManager.open(forkFile, sessionsRoot, TARGET_CWD);
563
+ const settings = pi.SettingsManager.inMemory({ compaction: { enabled: false } });
564
+ const loader = new pi.DefaultResourceLoader({ cwd: TARGET_CWD, agentDir: sessionsRoot, settingsManager: settings, noSkills: true, noPromptTemplates: true, noThemes: true, noContextFiles: true });
565
+ await loader.reload();
566
+ const { session } = await pi.createAgentSession({
567
+ cwd: TARGET_CWD,
568
+ agentDir: sessionsRoot,
569
+ modelRuntime: runtime,
570
+ noTools: "all",
571
+ resourceLoader: loader,
572
+ sessionManager: manager,
573
+ settingsManager: settings,
574
+ });
575
+ try {
576
+ assert.equal(session.model?.provider, "sentinel", "native model_change must restore provider");
577
+ assert.equal(session.model?.id, "sentinel-model", "native model_change must restore model");
578
+ const imported = manager.getEntries().filter((entry) => entry.rarebitFork?.kind === "import");
579
+ assert.ok(imported.every((entry) => !entry.message.usage || Object.values(entry.message.usage).every((value) =>
580
+ typeof value === "object" ? Object.values(value).every((nested) => nested === 0) : value === 0,
581
+ )));
582
+ const stats = session.getSessionStats();
583
+ assert.equal(stats.cost, 0, "zero imported usage must aggregate without sentinel cost faults");
584
+ assert.ok(session.getContextUsage()?.tokens > 0, "restored sentinel context must remain occupied");
585
+ await session.prompt("continue after native model restoration");
586
+ await session.agent.waitForIdle();
587
+ assert.ok(provider.state.callCount >= 1, "restored model must support continuation");
588
+ assert.equal(session.getSessionStats().cost, 0);
589
+ return { packageVersion: packageJson.version, restored: true };
590
+ } finally {
591
+ session.dispose();
592
+ runtime.unregisterProvider(provider.provider);
593
+ }
594
+ }
595
+
596
+ async function makeRealColdFork(root, sourceFile) {
597
+ const configured = process.env.RAREBIT_REAL_PROVIDER;
598
+ if (!configured) return { status: "skipped", reason: "RAREBIT_REAL_PROVIDER_not_set" };
599
+ const separator = configured.indexOf("/");
600
+ if (separator <= 0 || separator === configured.length - 1)
601
+ return { status: "unavailable", reason: "invalid_provider_spec" };
602
+ const providerId = configured.slice(0, separator);
603
+ const modelId = configured.slice(separator + 1);
604
+ try {
605
+ const { pi } = await piPackage(installedPiRoot);
606
+ const runtime = await pi.ModelRuntime.create({ modelsPath: null });
607
+ const model = runtime.getModel(providerId, modelId);
608
+ if (!model) return { status: "unavailable", reason: "configured_model_not_found" };
609
+ if (!runtime.hasConfiguredAuth(providerId)) return { status: "unavailable", reason: "configured_model_auth_unavailable" };
610
+ const loaded = await readPiSession(sourceFile);
611
+ const plan = buildRarebitForkPlan({
612
+ header: loaded.parsed.header,
613
+ sessionFile: sourceFile,
614
+ cwd: SOURCE_CWD,
615
+ branch: loaded.branch.slice(0, 5),
616
+ targetCwd: TARGET_CWD,
617
+ targetModel: model,
618
+ maxTokenLength: 39,
619
+ reserveTokens: 4_096,
620
+ forkCreatedAt: "2026-09-21T06:00:00.000Z",
621
+ });
622
+ const fork = createRarebitForkEntries(plan, { sessionId: "fork-session-real-cold-synthetic" });
623
+ const forkFile = join(root, "fork-real-cold.jsonl");
624
+ await writeRarebitForkFile(forkFile, fork);
625
+ return { status: "ready", forkFile, model: { provider: model.provider, id: model.id } };
626
+ } catch (error) {
627
+ return { status: "unavailable", reason: error?.name === "AuthenticationError" ? "provider_auth" : "model_resolution" };
628
+ }
629
+ }
630
+
631
+ async function runInstalledRealColdRestore(cold, sourceFile, sourceBytes, sessionsRoot) {
632
+ if (cold.status !== "ready") return cold;
633
+ try {
634
+ const { packageJson, pi } = await piPackage(installedPiRoot);
635
+ const runtime = await pi.ModelRuntime.create({ modelsPath: null });
636
+ const model = runtime.getModel(cold.model.provider, cold.model.id);
637
+ if (!model || !runtime.hasConfiguredAuth(model.provider))
638
+ return { status: "unavailable", reason: "cold_restore_auth_unavailable" };
639
+ const manager = pi.SessionManager.open(cold.forkFile, sessionsRoot, TARGET_CWD);
640
+ const settings = pi.SettingsManager.inMemory({ compaction: { enabled: false } });
641
+ const loader = new pi.DefaultResourceLoader({ cwd: TARGET_CWD, agentDir: sessionsRoot, settingsManager: settings, noSkills: true, noPromptTemplates: true, noThemes: true, noContextFiles: true });
642
+ await loader.reload();
643
+ const { session } = await pi.createAgentSession({
644
+ cwd: TARGET_CWD,
645
+ agentDir: sessionsRoot,
646
+ modelRuntime: runtime,
647
+ noTools: "all",
648
+ resourceLoader: loader,
649
+ sessionManager: manager,
650
+ settingsManager: settings,
651
+ });
652
+ try {
653
+ assert.equal(session.model?.provider, cold.model.provider, "cold restore must use native model_change provider");
654
+ assert.equal(session.model?.id, cold.model.id, "cold restore must use native model_change model");
655
+ assert.ok(session.getContextUsage()?.tokens > 0);
656
+ await session.prompt("Reply with exactly CANARY_COLD_RESTORE_OK and no other text.");
657
+ await session.agent.waitForIdle();
658
+ assert.match(session.getLastAssistantText() ?? "", /CANARY_COLD_RESTORE_OK/);
659
+ assert.equal(await readFile(sourceFile, "utf8"), sourceBytes, "cold restore must not mutate source");
660
+ return { status: "passed", host: packageJson.version, model: cold.model };
661
+ } finally {
662
+ session.dispose();
663
+ }
664
+ } catch (error) {
665
+ return { status: "failed", reason: error?.name === "AuthenticationError" ? "provider_auth" : "cold_restore_request" };
666
+ }
667
+ }
668
+
669
+ async function runInstalledRealContinuation(
670
+ forkFile,
671
+ sourceFile,
672
+ sessionsRoot,
673
+ sourceBytes,
674
+ ) {
675
+ const configured = process.env.RAREBIT_REAL_PROVIDER;
676
+ if (!configured) return { status: "skipped", reason: "RAREBIT_REAL_PROVIDER_not_set" };
677
+ const separator = configured.indexOf("/");
678
+ if (separator <= 0 || separator === configured.length - 1)
679
+ return { status: "unavailable", reason: "invalid_provider_spec" };
680
+ const providerId = configured.slice(0, separator);
681
+ const modelId = configured.slice(separator + 1);
682
+ try {
683
+ const { packageJson, pi } = await piPackage(installedPiRoot);
684
+ assert.equal(packageJson.version, expectedInstalledVersion);
685
+ const runtime = await pi.ModelRuntime.create({ modelsPath: null });
686
+ const model = runtime.getModel(providerId, modelId);
687
+ if (!model) return { status: "unavailable", reason: "configured_model_not_found" };
688
+ const manager = pi.SessionManager.open(forkFile, sessionsRoot, TARGET_CWD);
689
+ const settings = pi.SettingsManager.inMemory({ compaction: { enabled: false } });
690
+ const loader = new pi.DefaultResourceLoader({
691
+ cwd: TARGET_CWD,
692
+ agentDir: sessionsRoot,
693
+ settingsManager: settings,
694
+ noSkills: true,
695
+ noPromptTemplates: true,
696
+ noThemes: true,
697
+ noContextFiles: true,
698
+ });
699
+ await loader.reload();
700
+ const { session } = await pi.createAgentSession({
701
+ cwd: TARGET_CWD,
702
+ agentDir: sessionsRoot,
703
+ model,
704
+ modelRuntime: runtime,
705
+ noTools: "all",
706
+ resourceLoader: loader,
707
+ sessionManager: manager,
708
+ settingsManager: settings,
709
+ });
710
+ try {
711
+ await session.prompt(
712
+ "Reply with exactly CANARY_CONTINUATION_OK and no other text.",
713
+ );
714
+ await session.agent.waitForIdle();
715
+ const reply = session.getLastAssistantText() ?? "";
716
+ assert.match(reply, /CANARY_CONTINUATION_OK/);
717
+ assert.equal(await readFile(sourceFile, "utf8"), sourceBytes, "real continuation must not mutate source");
718
+ return { status: "passed", host: packageJson.version };
719
+ } finally {
720
+ session.dispose();
721
+ }
722
+ } catch (error) {
723
+ // Keep provider/auth/network details out of the evidence artifact.
724
+ return {
725
+ status: "failed",
726
+ reason: error?.name === "AuthenticationError" ? "provider_auth" : "provider_request",
727
+ };
728
+ }
729
+ }
730
+
731
+ async function run() {
732
+ const root = await mkdtemp(join(tmpdir(), "rarebit-fork-canary-"));
733
+ try {
734
+ const { sourceFile, forkFile, sourceBytes, toolForkFile, assistantForkFile, sentinelForkFile, legacyFiles } = await assertStaticForkContracts(root);
735
+ const cli = await runCliCanary(root, [sourceFile, ...legacyFiles]);
736
+ const slash = await runSlashCanary(childPiRoot, sourceFile, sourceBytes);
737
+ const childUserFork = join(root, "child-user-fork.jsonl");
738
+ const childToolFork = join(root, "child-tool-fork.jsonl");
739
+ const childAssistantFork = join(root, "child-assistant-fork.jsonl");
740
+ const installedUserFork = join(root, "installed-user-fork.jsonl");
741
+ const installedToolFork = join(root, "installed-tool-fork.jsonl");
742
+ const installedAssistantFork = join(root, "installed-assistant-fork.jsonl");
743
+ for (const [from, to] of [
744
+ [forkFile, childUserFork],
745
+ [toolForkFile, childToolFork],
746
+ [assistantForkFile, childAssistantFork],
747
+ [forkFile, installedUserFork],
748
+ [toolForkFile, installedToolFork],
749
+ [assistantForkFile, installedAssistantFork],
750
+ ]) await writeFile(to, await readFile(from, "utf8"));
751
+ const userExpectations = ["assistant continuation retained for recovery", "newest retained user request", "first consecutive assistant prose", "second consecutive assistant prose"];
752
+ const toolExpectations = ["assistant continuation retained for recovery"];
753
+ const assistantExpectations = ["assistant continuation retained for recovery", "first consecutive assistant prose", "second consecutive assistant prose"];
754
+ const first = await runPiHost(childPiRoot, expectedChildVersion, childUserFork, join(root, "child-sessions"), userExpectations);
755
+ await runPiHost(childPiRoot, expectedChildVersion, childToolFork, join(root, "child-tool-sessions"), toolExpectations);
756
+ await runPiHost(childPiRoot, expectedChildVersion, childAssistantFork, join(root, "child-assistant-sessions"), assistantExpectations);
757
+ const second = await runPiHost(installedPiRoot, expectedInstalledVersion, installedUserFork, join(root, "installed-sessions"), userExpectations);
758
+ await runPiHost(installedPiRoot, expectedInstalledVersion, installedToolFork, join(root, "installed-tool-sessions"), toolExpectations);
759
+ await runPiHost(installedPiRoot, expectedInstalledVersion, installedAssistantFork, join(root, "installed-assistant-sessions"), assistantExpectations);
760
+ await runSentinelRestoreHost(childPiRoot, expectedChildVersion, sentinelForkFile, join(root, "child-sentinel-sessions"));
761
+ await runSentinelRestoreHost(installedPiRoot, expectedInstalledVersion, sentinelForkFile, join(root, "installed-sentinel-sessions"));
762
+ const real = await runInstalledRealContinuation(
763
+ installedUserFork,
764
+ sourceFile,
765
+ join(root, "real-sessions"),
766
+ sourceBytes,
767
+ );
768
+ if (real.status === "failed")
769
+ throw new Error(`configured provider continuation failed (${real.reason})`);
770
+ const coldPlan = await makeRealColdFork(root, sourceFile);
771
+ const realCold = await runInstalledRealColdRestore(
772
+ coldPlan,
773
+ sourceFile,
774
+ sourceBytes,
775
+ join(root, "real-cold-sessions"),
776
+ );
777
+ if (realCold.status === "failed")
778
+ throw new Error(`configured provider cold restore failed (${realCold.reason})`);
779
+ const recovered = await queryPiQ(sourceFile, {
780
+ kind: "tools",
781
+ entryId: "tool-result",
782
+ sessionRoot: join(root, "unused-session-root"),
783
+ });
784
+ assert.equal(recovered.length, 1, "PiQ must recover the omitted tool result");
785
+ assert.equal(recovered[0]._piq.kind, "tool-result");
786
+ console.log(
787
+ JSON.stringify({
788
+ canary: "rarebit-fork-pi",
789
+ status: "passed",
790
+ hosts: [first.packageVersion, second.packageVersion],
791
+ realProvider: real,
792
+ realColdRestore: realCold,
793
+ checks: [
794
+ "source-unchanged",
795
+ "linear-fork-lineage",
796
+ "fork-time-timestamps-with-original-lineage",
797
+ "toolUse-stop-and-consecutive-assistant-retention",
798
+ "zero-imported-usage-positive-context-occupancy",
799
+ "no-provider-call-before-continuation",
800
+ "piq-omitted-tool-result-recovery",
801
+ "partial-entry-rejection",
802
+ "headroom-refusal-before-switch",
803
+ "v1-v2-source-unchanged",
804
+ "atomic-setup-failure-cleanup",
805
+ "normal-storage-filename",
806
+ "slash-command-grammar",
807
+ "cli-cwd-storage-and-no-launch",
808
+ "cli-default-launch",
809
+ "sentinel-model-restore-zero-usage",
810
+ "depth-three-lineage",
811
+ ],
812
+ },
813
+ null,
814
+ 2,
815
+ ));
816
+ } finally {
817
+ await rm(root, { recursive: true, force: true });
818
+ }
819
+ }
820
+
821
+ run().catch((error) => {
822
+ process.stderr.write(`${error?.stack ?? error}\n`);
823
+ process.exitCode = 1;
824
+ });