@frockbot/kernel-do 0.0.0 → 0.1.0

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,450 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import {
3
+ bootstrapGeneration,
4
+ type CompositionGenerationV1,
5
+ } from "@frockbot/kernel-composition/generation";
6
+ import type { SessionEvent, TurnTypeV1 } from "@frockbot/kernel-contracts";
7
+ import {
8
+ BotDurableAuthority,
9
+ type BotDurableAuthorityHooks,
10
+ type BotTurnExecutionInput,
11
+ } from "./authority.ts";
12
+ import { MemoryStorage } from "./memory-storage.fixture.ts";
13
+ import {
14
+ botTurnCommandFingerprintV1,
15
+ createStoredRunCodecV1,
16
+ storedRunAdmissionV1,
17
+ storedRunTurnTypeV1,
18
+ type StoredRunOriginV1,
19
+ type StoredRunV1,
20
+ } from "./run-records.ts";
21
+
22
+ const ROUTINE_ORIGIN: StoredRunOriginV1 = {
23
+ kind: "routine",
24
+ routineId: "morning-briefing",
25
+ fireId: "fire-1",
26
+ trigger: "cron",
27
+ };
28
+
29
+ /**
30
+ * A subagent Turn's origin (ADR 0017): recorded in the *child* Durable Object,
31
+ * naming the task it is and the parent run that asked for it.
32
+ */
33
+ const SUBAGENT_ORIGIN: StoredRunOriginV1 = {
34
+ kind: "subagent",
35
+ taskId: "tk-1",
36
+ parentRunId: "run-parent",
37
+ };
38
+
39
+ const codec = createStoredRunCodecV1<undefined>({
40
+ decodeRunId: (value) => value as string,
41
+ decodeConfigurationSnapshot: () => undefined,
42
+ });
43
+
44
+ /** A stored run exactly as it was written before turn admission existed. */
45
+ function legacyRun(
46
+ overrides: Partial<StoredRunV1<undefined>> = {},
47
+ ): Record<string, unknown> {
48
+ return {
49
+ runId: "run-1",
50
+ commandFingerprint: "bot-turn-command-v1:{}",
51
+ sessionId: "user-1:primary",
52
+ acceptedAt: "2026-08-31T01:00:00.000Z",
53
+ input: "hello",
54
+ events: [],
55
+ effectAdmissions: [],
56
+ status: "running",
57
+ phase: "executing",
58
+ compositionGenerationId: "generation-1",
59
+ configurationSnapshot: undefined,
60
+ previousEventCount: 0,
61
+ ...overrides,
62
+ };
63
+ }
64
+
65
+ describe("the stored run records the turn type it was admitted as", () => {
66
+ test("a record written before turn admission existed decodes as chat", () => {
67
+ const decoded = codec.require(legacyRun());
68
+
69
+ expect(decoded.admission).toBeUndefined();
70
+ expect(storedRunTurnTypeV1(decoded)).toBe("chat");
71
+ // Nothing is added on the way through: the bytes round-trip unchanged.
72
+ expect(Object.keys(decoded).sort()).toEqual(
73
+ Object.keys(legacyRun()).sort(),
74
+ );
75
+ });
76
+
77
+ test("round-trips a non-chat admission", () => {
78
+ const stored = legacyRun({
79
+ admission: { schemaVersion: 1, turnType: "automation" },
80
+ });
81
+
82
+ const decoded = codec.require(stored);
83
+
84
+ expect(decoded.admission).toEqual({
85
+ schemaVersion: 1,
86
+ turnType: "automation",
87
+ });
88
+ expect(storedRunTurnTypeV1(decoded)).toBe("automation");
89
+ expect(codec.require(structuredClone(decoded))).toEqual(decoded);
90
+ });
91
+
92
+ test("rejects an unknown turn type and a malformed admission", () => {
93
+ expect(() =>
94
+ codec.require(
95
+ legacyRun({
96
+ admission: { schemaVersion: 1, turnType: "routine" } as never,
97
+ }),
98
+ ),
99
+ ).toThrow(/invalid admission turn type/);
100
+ expect(() =>
101
+ codec.require(legacyRun({ admission: { schemaVersion: 2 } as never })),
102
+ ).toThrow(/invalid admission fields/);
103
+ expect(() =>
104
+ codec.require(legacyRun({ admission: "automation" as never })),
105
+ ).toThrow(/invalid admission/);
106
+ });
107
+
108
+ test("writes no admission field at all for a chat Turn", () => {
109
+ expect(storedRunAdmissionV1(undefined)).toEqual({});
110
+ expect(storedRunAdmissionV1("chat")).toEqual({});
111
+ expect(storedRunAdmissionV1("automation")).toEqual({
112
+ admission: { schemaVersion: 1, turnType: "automation" },
113
+ });
114
+ // A recorded origin is worth a record even on a chat Turn.
115
+ expect(storedRunAdmissionV1("chat", ROUTINE_ORIGIN)).toEqual({
116
+ admission: { schemaVersion: 1, turnType: "chat", origin: ROUTINE_ORIGIN },
117
+ });
118
+ });
119
+ });
120
+
121
+ describe("the admission record names what produced the Turn", () => {
122
+ test("round-trips a routine origin", () => {
123
+ const decoded = codec.require(
124
+ legacyRun({
125
+ admission: {
126
+ schemaVersion: 1,
127
+ turnType: "automation",
128
+ origin: ROUTINE_ORIGIN,
129
+ },
130
+ }),
131
+ );
132
+
133
+ expect(decoded.admission?.origin).toEqual(ROUTINE_ORIGIN);
134
+ expect(codec.require(structuredClone(decoded))).toEqual(decoded);
135
+ });
136
+
137
+ test("an admission with no origin decodes without the key", () => {
138
+ const decoded = codec.require(
139
+ legacyRun({ admission: { schemaVersion: 1, turnType: "automation" } }),
140
+ );
141
+
142
+ expect(Object.hasOwn(decoded.admission ?? {}, "origin")).toBe(false);
143
+ });
144
+
145
+ test("round-trips a subagent origin, and keeps it exact", () => {
146
+ const decoded = codec.require(
147
+ legacyRun({
148
+ admission: {
149
+ schemaVersion: 1,
150
+ turnType: "subagent",
151
+ origin: SUBAGENT_ORIGIN,
152
+ },
153
+ }),
154
+ );
155
+
156
+ expect(decoded.admission?.origin).toEqual(SUBAGENT_ORIGIN);
157
+ expect(codec.require(structuredClone(decoded))).toEqual(decoded);
158
+ });
159
+
160
+ test("each origin kind has its own exact fields, and cannot borrow another's", () => {
161
+ const withOrigin = (origin: unknown) =>
162
+ legacyRun({
163
+ admission: { schemaVersion: 1, turnType: "subagent", origin },
164
+ } as never);
165
+
166
+ // A subagent origin carrying a Routine's fields is not a record with a
167
+ // spare field; it is one this codec has never written.
168
+ expect(() =>
169
+ codec.require(
170
+ withOrigin({ ...SUBAGENT_ORIGIN, routineId: "morning-briefing" }),
171
+ ),
172
+ ).toThrow(/invalid admission origin fields/);
173
+ expect(() =>
174
+ codec.require(withOrigin({ kind: "subagent", taskId: "tk-1" })),
175
+ ).toThrow(/invalid admission origin fields/);
176
+ expect(() =>
177
+ codec.require(withOrigin({ ...SUBAGENT_ORIGIN, parentRunId: "" })),
178
+ ).toThrow(/invalid admission origin id/);
179
+ expect(() =>
180
+ codec.require(withOrigin({ ...ROUTINE_ORIGIN, kind: "subagent" })),
181
+ ).toThrow(/invalid admission origin fields/);
182
+ });
183
+
184
+ test("a subagent origin is part of the command identity", () => {
185
+ const withOrigin = botTurnCommandFingerprintV1({
186
+ userId: "user",
187
+ botId: "bot",
188
+ runId: "tk-1",
189
+ sessionId: "task:tk-1",
190
+ acceptedAt: "2026-09-01T00:00:00.000Z",
191
+ text: "do the thing",
192
+ turnType: "subagent",
193
+ origin: SUBAGENT_ORIGIN,
194
+ });
195
+
196
+ expect(withOrigin).toStartWith("bot-turn-command-v2:");
197
+ expect(withOrigin).toContain('"kind":"subagent"');
198
+ expect(withOrigin).toContain('"parentRunId":"run-parent"');
199
+ });
200
+
201
+ test("rejects an unknown origin kind, trigger, or extra field", () => {
202
+ const withOrigin = (origin: unknown) =>
203
+ legacyRun({
204
+ admission: { schemaVersion: 1, turnType: "automation", origin },
205
+ } as never);
206
+
207
+ expect(() =>
208
+ codec.require(withOrigin({ ...ROUTINE_ORIGIN, kind: "assignment" })),
209
+ ).toThrow(/invalid admission origin kind/);
210
+ expect(() =>
211
+ codec.require(withOrigin({ ...ROUTINE_ORIGIN, trigger: "alarm" })),
212
+ ).toThrow(/invalid admission origin trigger/);
213
+ expect(() =>
214
+ codec.require(withOrigin({ ...ROUTINE_ORIGIN, extra: 1 })),
215
+ ).toThrow(/invalid admission origin fields/);
216
+ expect(() =>
217
+ codec.require(withOrigin({ ...ROUTINE_ORIGIN, fireId: "" })),
218
+ ).toThrow(/invalid admission origin id/);
219
+ expect(() => codec.require(withOrigin("routine"))).toThrow(
220
+ /invalid admission origin/,
221
+ );
222
+ expect(() =>
223
+ codec.require(
224
+ legacyRun({
225
+ admission: {
226
+ schemaVersion: 1,
227
+ turnType: "automation",
228
+ unexpected: true,
229
+ },
230
+ } as never),
231
+ ),
232
+ ).toThrow(/invalid admission fields/);
233
+ });
234
+ });
235
+
236
+ describe("the command fingerprint stays byte-stable for chat", () => {
237
+ const command = {
238
+ userId: "user-1",
239
+ botId: "primary",
240
+ runId: "run-1",
241
+ sessionId: "user-1:primary",
242
+ acceptedAt: "2026-08-31T01:00:00.000Z",
243
+ text: "hello",
244
+ };
245
+
246
+ test("a chat command matches the exact bytes deployed idempotency records hold", () => {
247
+ // Pinned literal: an in-flight run admitted before this change must still
248
+ // replay against its stored fingerprint after deploy.
249
+ const pinned =
250
+ 'bot-turn-command-v1:{"userId":"user-1","botId":"primary","sessionId":"user-1:primary","text":"hello"}';
251
+
252
+ expect(botTurnCommandFingerprintV1(command)).toBe(pinned);
253
+ expect(botTurnCommandFingerprintV1({ ...command, turnType: "chat" })).toBe(
254
+ pinned,
255
+ );
256
+ });
257
+
258
+ test("only a non-chat command emits v2, and the type is part of its identity", () => {
259
+ const automation = botTurnCommandFingerprintV1({
260
+ ...command,
261
+ turnType: "automation",
262
+ });
263
+ const subagent = botTurnCommandFingerprintV1({
264
+ ...command,
265
+ turnType: "subagent",
266
+ });
267
+
268
+ expect(automation).toStartWith("bot-turn-command-v2:");
269
+ expect(automation).toContain('"turnType":"automation"');
270
+ expect(subagent).not.toBe(automation);
271
+ });
272
+
273
+ test("a recorded origin is part of the command identity", () => {
274
+ const withOrigin = botTurnCommandFingerprintV1({
275
+ ...command,
276
+ origin: ROUTINE_ORIGIN,
277
+ });
278
+
279
+ expect(withOrigin).toStartWith("bot-turn-command-v2:");
280
+ expect(withOrigin).toContain('"routineId":"morning-briefing"');
281
+ expect(withOrigin).not.toBe(botTurnCommandFingerprintV1(command));
282
+ expect(
283
+ botTurnCommandFingerprintV1({
284
+ ...command,
285
+ origin: { ...ROUTINE_ORIGIN, fireId: "fire-2" },
286
+ }),
287
+ ).not.toBe(withOrigin);
288
+ });
289
+ });
290
+
291
+ interface TurnProbe {
292
+ authority: BotDurableAuthority<undefined>;
293
+ observed: BotTurnExecutionInput<undefined>[];
294
+ }
295
+
296
+ function bootstrap(): Promise<CompositionGenerationV1> {
297
+ return bootstrapGeneration(
298
+ [
299
+ {
300
+ packageId: "shell",
301
+ specifier: "@frockbot/plugin-shell",
302
+ version: "0.0.1",
303
+ manifest: { id: "shell", version: "0.0.1" },
304
+ },
305
+ ],
306
+ { createdAt: "2026-08-31T00:00:00.000Z" },
307
+ );
308
+ }
309
+
310
+ /**
311
+ * An authority whose Package records `turn/admission` exactly as the Agent loop
312
+ * does, so the durable log shows the turn type the mounted Agent ran on.
313
+ */
314
+ function createAuthority(storage: MemoryStorage): TurnProbe {
315
+ const observed: BotTurnExecutionInput<undefined>[] = [];
316
+ const hooks: BotDurableAuthorityHooks<undefined> = {
317
+ resolveAdmissionSnapshot: () => Promise.resolve(undefined),
318
+ bootstrapComposition: () => bootstrap(),
319
+ admittedSnapshot: () => Promise.resolve(undefined),
320
+ executeTurn: async (input) => {
321
+ observed.push(input);
322
+ const events: SessionEvent[] = [
323
+ {
324
+ type: "turn/admission",
325
+ seq: input.previousEvents.length,
326
+ timestamp: "2026-08-31T01:00:01.000Z",
327
+ turn: 1,
328
+ turnType: input.command.turnType ?? "chat",
329
+ },
330
+ ];
331
+ await input.persistSessionEvents(input.command.sessionId, events);
332
+ return { runId: input.command.runId, text: "ok", events };
333
+ },
334
+ notification: () => undefined,
335
+ scheduledDeadlines: () => Promise.resolve([]),
336
+ scheduledWorkInFlight: () => false,
337
+ deferScheduledWork: () => Promise.resolve(),
338
+ settleScheduledWork: () => Promise.resolve(),
339
+ };
340
+ return {
341
+ authority: new BotDurableAuthority<undefined>({
342
+ state: { storage } as unknown as DurableObjectState,
343
+ codec,
344
+ hooks,
345
+ }),
346
+ observed,
347
+ };
348
+ }
349
+
350
+ function command(runId: string, turnType?: TurnTypeV1) {
351
+ return {
352
+ userId: "user-1",
353
+ botId: "primary",
354
+ runId,
355
+ sessionId: "user-1:primary",
356
+ acceptedAt: "2026-08-31T01:00:00.000Z",
357
+ text: "hello",
358
+ ...(turnType ? { turnType } : {}),
359
+ };
360
+ }
361
+
362
+ function admittedEvent(storage: MemoryStorage, runId: string) {
363
+ const run = storage.values.get(`run:${runId}`) as StoredRunV1<undefined>;
364
+ return run.events.find((event) => event.type === "turn/admission");
365
+ }
366
+
367
+ describe("an admitted Turn re-mounts on its recorded turn type", () => {
368
+ test("a chat Turn stores no admission and runs as chat", async () => {
369
+ const storage = new MemoryStorage();
370
+ const probe = createAuthority(storage);
371
+
372
+ await probe.authority.run(command("run-1"));
373
+
374
+ const stored = storage.values.get("run:run-1") as StoredRunV1<undefined>;
375
+ expect(stored.admission).toBeUndefined();
376
+ expect(Object.hasOwn(stored, "admission")).toBe(false);
377
+ expect(probe.observed[0]?.command.turnType).toBeUndefined();
378
+ });
379
+
380
+ test("an automation Turn stores the type it was admitted as", async () => {
381
+ const storage = new MemoryStorage();
382
+ const probe = createAuthority(storage);
383
+
384
+ await probe.authority.run(command("run-1", "automation"));
385
+
386
+ const stored = storage.values.get("run:run-1") as StoredRunV1<undefined>;
387
+ expect(stored.admission).toEqual({
388
+ schemaVersion: 1,
389
+ turnType: "automation",
390
+ });
391
+ expect(probe.observed[0]?.command.turnType).toBe("automation");
392
+ expect(admittedEvent(storage, "run-1")).toMatchObject({
393
+ turnType: "automation",
394
+ });
395
+ });
396
+
397
+ test("after eviction the resumed run re-mounts on the recorded type", async () => {
398
+ const storage = new MemoryStorage();
399
+ const probe = createAuthority(storage);
400
+ await probe.authority.run(command("run-1", "automation"));
401
+
402
+ // A Turn interrupted after admission and before any external intent: the
403
+ // durable record is all a reconstructed object has to re-mount from.
404
+ const stored = storage.values.get("run:run-1") as StoredRunV1<undefined>;
405
+ storage.values.set("run:run-1", {
406
+ ...stored,
407
+ status: "running",
408
+ phase: "executing",
409
+ responseText: undefined,
410
+ events: [],
411
+ });
412
+ storage.values.set("active-run", "run-1");
413
+ storage.values.set("identity", { userId: "user-1", botId: "primary" });
414
+ storage.values.set("latest-events", []);
415
+
416
+ const resumed = createAuthority(storage);
417
+ await resumed.authority.recoverActiveRun();
418
+
419
+ expect(resumed.observed.at(-1)?.command.turnType).toBe("automation");
420
+ expect(admittedEvent(storage, "run-1")).toMatchObject({
421
+ turnType: "automation",
422
+ });
423
+ });
424
+
425
+ test("a chat Turn recovered after eviction still re-mounts as chat", async () => {
426
+ const storage = new MemoryStorage();
427
+ const probe = createAuthority(storage);
428
+ await probe.authority.run(command("run-1"));
429
+
430
+ const stored = storage.values.get("run:run-1") as StoredRunV1<undefined>;
431
+ storage.values.set("run:run-1", {
432
+ ...stored,
433
+ status: "running",
434
+ phase: "executing",
435
+ responseText: undefined,
436
+ events: [],
437
+ });
438
+ storage.values.set("active-run", "run-1");
439
+ storage.values.set("identity", { userId: "user-1", botId: "primary" });
440
+ storage.values.set("latest-events", []);
441
+
442
+ const resumed = createAuthority(storage);
443
+ await resumed.authority.recoverActiveRun();
444
+
445
+ expect(resumed.observed.at(-1)?.command.turnType).toBe("chat");
446
+ expect(admittedEvent(storage, "run-1")).toMatchObject({
447
+ turnType: "chat",
448
+ });
449
+ });
450
+ });
@@ -0,0 +1,33 @@
1
+ import type { SessionEvent } from "@frockbot/kernel-contracts";
2
+
3
+ /**
4
+ * Terminal classification of an admitted Turn's failure. The kernel's cursor
5
+ * uses these to decide between failing, deferring, and requiring
6
+ * reconciliation; the Package that executes the Turn raises them.
7
+ */
8
+ export class BotTurnExecutionError extends Error {
9
+ constructor(
10
+ message: string,
11
+ readonly events: SessionEvent[],
12
+ ) {
13
+ super(message);
14
+ this.name = "BotTurnExecutionError";
15
+ }
16
+ }
17
+
18
+ export class BotTurnReconciliationRequiredError extends Error {
19
+ constructor(
20
+ message: string,
21
+ readonly events: SessionEvent[],
22
+ ) {
23
+ super(message);
24
+ this.name = "BotTurnReconciliationRequiredError";
25
+ }
26
+ }
27
+
28
+ export class BotTurnRecoveryRequiredError extends Error {
29
+ constructor(readonly events: SessionEvent[]) {
30
+ super("Bot turn has a durable outcome settlement pending");
31
+ this.name = "BotTurnRecoveryRequiredError";
32
+ }
33
+ }
@@ -0,0 +1,228 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import type {
3
+ WorkspaceGenerationRecordV1,
4
+ WorkspaceGenerationV1,
5
+ WorkspaceRootV1,
6
+ } from "@frockbot/kernel-contracts";
7
+ import { DurableWorkspaceGenerations } from "./workspace-generations.ts";
8
+ import {
9
+ WORKSPACE_CONFLICT_PREFIX,
10
+ WORKSPACE_GENERATION_PREFIX,
11
+ workspaceFileKeyTail,
12
+ } from "./storage-keys.ts";
13
+
14
+ class MemoryStorage {
15
+ readonly values = new Map<string, unknown>();
16
+
17
+ get<T>(key: string): Promise<T | undefined> {
18
+ return Promise.resolve(this.values.get(key) as T | undefined);
19
+ }
20
+
21
+ put(key: string, value: unknown): Promise<void> {
22
+ this.values.set(key, structuredClone(value));
23
+ return Promise.resolve();
24
+ }
25
+
26
+ list<T>(options: {
27
+ prefix?: string;
28
+ limit?: number;
29
+ }): Promise<Map<string, T>> {
30
+ const entries = [...this.values.entries()]
31
+ .filter(([key]) => key.startsWith(options.prefix ?? ""))
32
+ .sort(([left], [right]) => left.localeCompare(right));
33
+ return Promise.resolve(
34
+ new Map(entries.slice(0, options.limit) as Array<[string, T]>),
35
+ );
36
+ }
37
+ }
38
+
39
+ const ROOT: WorkspaceRootV1 = {
40
+ kind: "bot-instructions",
41
+ userId: "user-1",
42
+ botId: "bot-1",
43
+ };
44
+ const HASH = "a".repeat(64);
45
+
46
+ function store(storage = new MemoryStorage()): {
47
+ generations: DurableWorkspaceGenerations;
48
+ storage: MemoryStorage;
49
+ } {
50
+ return {
51
+ storage,
52
+ generations: new DurableWorkspaceGenerations({
53
+ state: { storage } as unknown as DurableObjectState,
54
+ }),
55
+ };
56
+ }
57
+
58
+ function generation(
59
+ generationId: string,
60
+ overrides: Partial<WorkspaceGenerationV1> = {},
61
+ ): WorkspaceGenerationV1 {
62
+ return {
63
+ schemaVersion: 1,
64
+ generationId,
65
+ contentHash: HASH,
66
+ size: 4,
67
+ writer: { kind: "user", userId: "user-1" },
68
+ writtenAt: "2026-08-31T00:00:00.000Z",
69
+ ...overrides,
70
+ };
71
+ }
72
+
73
+ function record(
74
+ generationId: string,
75
+ overrides: Partial<WorkspaceGenerationRecordV1> = {},
76
+ ): WorkspaceGenerationRecordV1 {
77
+ return {
78
+ schemaVersion: 1,
79
+ root: ROOT,
80
+ path: "skills/deploy/SKILL.md",
81
+ generation: generation(generationId),
82
+ etag: "etag-1",
83
+ ...overrides,
84
+ };
85
+ }
86
+
87
+ describe("minted generation ids", () => {
88
+ test("are sortable and strictly increasing", async () => {
89
+ const { generations } = store();
90
+ const ids = [
91
+ await generations.mint(new Date("2026-08-31T00:00:00.000Z")),
92
+ await generations.mint(new Date("2026-08-31T00:00:00.000Z")),
93
+ await generations.mint(new Date("2026-08-31T00:00:01.000Z")),
94
+ ];
95
+ expect([...ids].sort()).toEqual(ids);
96
+ expect(new Set(ids).size).toBe(3);
97
+ });
98
+
99
+ test("never move backwards when the clock does", async () => {
100
+ const { generations } = store();
101
+ const later = await generations.mint(new Date("2026-08-31T00:00:10.000Z"));
102
+ const earlier = await generations.mint(
103
+ new Date("2026-08-31T00:00:00.000Z"),
104
+ );
105
+ expect(earlier > later).toBe(true);
106
+ });
107
+
108
+ test("are distinct when two cold mints run concurrently", async () => {
109
+ // The cursor is only assigned after an `await` on storage, so two mints
110
+ // that begin before either has read it — the ordinary case on a freshly
111
+ // constructed object, where nothing is cached — must not both read the
112
+ // same cursor and hand two files one generation.
113
+ const { generations, storage } = store();
114
+ const at = new Date("2026-08-31T00:00:00.000Z");
115
+ const ids = await Promise.all([
116
+ generations.mint(at),
117
+ generations.mint(at),
118
+ generations.mint(at),
119
+ ]);
120
+ expect(new Set(ids).size).toBe(3);
121
+ // And the durable cursor still describes the last of them.
122
+ const highest = [...ids].sort().at(-1) ?? "";
123
+ expect((await store(storage).generations.mint(at)) > highest).toBe(true);
124
+ });
125
+
126
+ test("keep increasing across eviction, because the cursor is durable", async () => {
127
+ const storage = new MemoryStorage();
128
+ const first = await store(storage).generations.mint(
129
+ new Date("2026-08-31T00:00:00.000Z"),
130
+ );
131
+ // A fresh instance over the same storage is a reconstructed object.
132
+ const second = await store(storage).generations.mint(
133
+ new Date("2026-08-31T00:00:00.000Z"),
134
+ );
135
+ expect(second > first).toBe(true);
136
+ });
137
+ });
138
+
139
+ describe("the generation ledger", () => {
140
+ test("records a generation and reads it back decoded", async () => {
141
+ const { generations, storage } = store();
142
+ await generations.record(record("000000000000001-000000001"));
143
+
144
+ const current = await generations.current(ROOT, "skills/deploy/SKILL.md");
145
+ expect(current?.generation.generationId).toBe("000000000000001-000000001");
146
+ expect(current?.etag).toBe("etag-1");
147
+ expect([...storage.values.keys()]).toEqual([
148
+ `${WORKSPACE_GENERATION_PREFIX}${workspaceFileKeyTail(
149
+ "bot-instructions:user-1:bot-1",
150
+ "skills/deploy/SKILL.md",
151
+ )}`,
152
+ ]);
153
+ expect(await generations.current(ROOT, "absent.md")).toBe(undefined);
154
+ });
155
+
156
+ test("a tombstone survives eviction and names who deleted the file", async () => {
157
+ const storage = new MemoryStorage();
158
+ await store(storage).generations.tombstone(
159
+ record("000000000000002-000000001", {
160
+ etag: undefined,
161
+ generation: generation("000000000000002-000000001", {
162
+ contentHash:
163
+ "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
164
+ size: 0,
165
+ }),
166
+ }),
167
+ );
168
+
169
+ const recovered = await store(storage).generations.current(
170
+ ROOT,
171
+ "skills/deploy/SKILL.md",
172
+ );
173
+ expect(recovered?.deleted).toBe(true);
174
+ expect(recovered?.generation.writer).toEqual({
175
+ kind: "user",
176
+ userId: "user-1",
177
+ });
178
+ });
179
+
180
+ test("a conflict is stored beside the current record, never over it", async () => {
181
+ const { generations, storage } = store();
182
+ await generations.record(record("000000000000001-000000001"));
183
+ await generations.conflict(
184
+ record("000000000000003-000000001", {
185
+ etag: "etag-9",
186
+ conflictKey: "workspace/root/file.conflict/000000000000003-000000001",
187
+ generation: generation("000000000000003-000000001", {
188
+ conflictsWith: "000000000000001-000000001",
189
+ }),
190
+ }),
191
+ );
192
+
193
+ const current = await generations.current(ROOT, "skills/deploy/SKILL.md");
194
+ expect(current?.generation.generationId).toBe("000000000000001-000000001");
195
+ const conflicts = await generations.conflicts(
196
+ ROOT,
197
+ "skills/deploy/SKILL.md",
198
+ );
199
+ expect(conflicts).toHaveLength(1);
200
+ expect(conflicts[0]?.generation.conflictsWith).toBe(
201
+ "000000000000001-000000001",
202
+ );
203
+ expect(
204
+ [...storage.values.keys()].some((key) =>
205
+ key.startsWith(WORKSPACE_CONFLICT_PREFIX),
206
+ ),
207
+ ).toBe(true);
208
+ });
209
+
210
+ test("refuses to record a malformed generation record", async () => {
211
+ const { generations } = store();
212
+ await expect(
213
+ generations.record({
214
+ ...record("000000000000001-000000001"),
215
+ path: "../escape.md",
216
+ }),
217
+ ).rejects.toThrow();
218
+ });
219
+
220
+ test("a very long path still yields a bounded storage key", () => {
221
+ const tail = workspaceFileKeyTail(
222
+ "bot-instructions:user-1:bot-1",
223
+ `${"deep/".repeat(200)}file.md`,
224
+ );
225
+ expect(tail.length).toBeLessThanOrEqual(900);
226
+ expect(tail).toContain("#");
227
+ });
228
+ });