@frockbot/kernel-do 0.3.1 → 0.3.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frockbot/kernel-do",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "exports": {
@@ -12,8 +12,8 @@
12
12
  "typecheck": "tsc --noEmit -p tsconfig.json"
13
13
  },
14
14
  "dependencies": {
15
- "@frockbot/kernel-composition": "0.3.1",
16
- "@frockbot/kernel-contracts": "0.3.1",
15
+ "@frockbot/kernel-composition": "0.3.3",
16
+ "@frockbot/kernel-contracts": "0.3.3",
17
17
  "cordis": "4.0.0-rc.8"
18
18
  },
19
19
  "devDependencies": {
@@ -0,0 +1,285 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import {
3
+ appletBindingDigestV1,
4
+ appletDirectoryEntryKey,
5
+ appletFailureKey,
6
+ appletGenerationIdV1,
7
+ appletGenerationKey,
8
+ appletIdV1,
9
+ appletLoaderIdV1,
10
+ appletStateNameV1,
11
+ AppletViewerTokenError,
12
+ APPLET_CURRENT_KEY,
13
+ APPLET_DIRECTORY_REVISION_KEY,
14
+ APPLET_LAST_KNOWN_GOOD_KEY,
15
+ APPLET_MOUNT_INPUT_KEY,
16
+ APPLET_VIEWER_TOKEN_TTL_MS,
17
+ decodeAppletFailureV1,
18
+ decodeAppletHealthV1,
19
+ decodeAppletMountInputV1,
20
+ decodeAppletPointerV1,
21
+ decodeFocusedAppletV1,
22
+ mintAppletViewerTokenV1,
23
+ newAppletIdV1,
24
+ verifyAppletViewerTokenV1,
25
+ } from "./applets.js";
26
+
27
+ const SECRET = "applet-viewer-secret-0123456789abcdef";
28
+ const HASH_A = "a".repeat(64);
29
+ const HASH_B = "b".repeat(64);
30
+ const APPLET = `user-42.${"c".repeat(32)}`;
31
+
32
+ describe("Applet durable keys", () => {
33
+ test("the keys are exactly the ones the plan names", () => {
34
+ expect(appletDirectoryEntryKey(APPLET)).toBe(`applets:entry:${APPLET}`);
35
+ expect(APPLET_DIRECTORY_REVISION_KEY).toBe("applets:directory-revision");
36
+ expect(APPLET_CURRENT_KEY).toBe("applet:current");
37
+ expect(APPLET_LAST_KNOWN_GOOD_KEY).toBe("applet:last-known-good");
38
+ expect(APPLET_MOUNT_INPUT_KEY).toBe("applet:mount-input");
39
+ expect(appletGenerationKey("g1")).toBe("applet:generation:g1");
40
+ });
41
+
42
+ test("failure keys are attempt-ordered under one generation", () => {
43
+ expect(appletFailureKey("g1", 1)).toBe("applet:failure:g1:0001");
44
+ expect(
45
+ [appletFailureKey("g1", 10), appletFailureKey("g1", 2)].sort(),
46
+ ).toEqual([appletFailureKey("g1", 2), appletFailureKey("g1", 10)]);
47
+ });
48
+
49
+ test("the Durable Object name is `<userId>:<appletId>`", () => {
50
+ expect(appletStateNameV1("user-42", APPLET)).toBe(`user-42:${APPLET}`);
51
+ expect(() => appletStateNameV1("user:42", APPLET)).toThrow();
52
+ expect(() => appletStateNameV1("user-42", "not-an-applet")).toThrow();
53
+ });
54
+ });
55
+
56
+ describe("Applet ids", () => {
57
+ test("an id is the ADR 0015 share shape", () => {
58
+ expect(appletIdV1("user-42", "d".repeat(32))).toBe(
59
+ `user-42.${"d".repeat(32)}`,
60
+ );
61
+ expect(() => appletIdV1("User-42", "d".repeat(32))).toThrow();
62
+ expect(() => appletIdV1("user-42", "nope")).toThrow();
63
+ });
64
+
65
+ test("a minted id parses and is not guessable from the owner", () => {
66
+ const first = newAppletIdV1("user-42");
67
+ const second = newAppletIdV1("user-42");
68
+ expect(first.startsWith("user-42.")).toBe(true);
69
+ expect(first).not.toBe(second);
70
+ });
71
+ });
72
+
73
+ describe("Applet loader identity", () => {
74
+ test("the applet id is an input, so identical code never shares an env", async () => {
75
+ const bindingDigest = await appletBindingDigestV1({
76
+ userId: "user-42",
77
+ capabilities: ["scheduleAlarm", "invokeModel"],
78
+ contract: 1,
79
+ });
80
+ const first = await appletLoaderIdV1({
81
+ contract: 1,
82
+ appletId: `user-42.${"1".repeat(32)}`,
83
+ serverHash: HASH_A,
84
+ bindingDigest,
85
+ });
86
+ const second = await appletLoaderIdV1({
87
+ contract: 1,
88
+ appletId: `user-42.${"2".repeat(32)}`,
89
+ serverHash: HASH_A,
90
+ bindingDigest,
91
+ });
92
+ expect(first).not.toBe(second);
93
+ });
94
+
95
+ test("a changed server artifact or binding digest is a new loader id", async () => {
96
+ const base = {
97
+ contract: 1,
98
+ appletId: APPLET,
99
+ serverHash: HASH_A,
100
+ bindingDigest: HASH_A,
101
+ };
102
+ const id = await appletLoaderIdV1(base);
103
+ expect(await appletLoaderIdV1(base)).toBe(id);
104
+ expect(await appletLoaderIdV1({ ...base, serverHash: HASH_B })).not.toBe(
105
+ id,
106
+ );
107
+ expect(await appletLoaderIdV1({ ...base, bindingDigest: HASH_B })).not.toBe(
108
+ id,
109
+ );
110
+ });
111
+
112
+ test("the binding digest ignores capability order and follows the User", async () => {
113
+ const one = await appletBindingDigestV1({
114
+ userId: "user-42",
115
+ capabilities: ["invokeModel", "scheduleAlarm"],
116
+ contract: 1,
117
+ });
118
+ const two = await appletBindingDigestV1({
119
+ userId: "user-42",
120
+ capabilities: ["scheduleAlarm", "invokeModel"],
121
+ contract: 1,
122
+ });
123
+ const other = await appletBindingDigestV1({
124
+ userId: "user-43",
125
+ capabilities: ["scheduleAlarm", "invokeModel"],
126
+ contract: 1,
127
+ });
128
+ expect(one).toBe(two);
129
+ expect(one).not.toBe(other);
130
+ });
131
+
132
+ test("generation ids sort by their creation instant", () => {
133
+ const early = appletGenerationIdV1("2026-09-03T00:00:00.000Z", HASH_A);
134
+ const late = appletGenerationIdV1("2026-09-03T00:00:01.000Z", HASH_A);
135
+ expect([late, early].sort()).toEqual([early, late]);
136
+ });
137
+ });
138
+
139
+ describe("Applet durable records", () => {
140
+ test("a pointer decodes exactly", () => {
141
+ const pointer = {
142
+ schemaVersion: 1 as const,
143
+ generationId: "g1",
144
+ changedAt: "2026-09-03T00:00:00.000Z",
145
+ };
146
+ expect(decodeAppletPointerV1(pointer)).toEqual(pointer);
147
+ expect(() => decodeAppletPointerV1({ ...pointer, extra: 1 })).toThrow();
148
+ expect(() =>
149
+ decodeAppletPointerV1({ ...pointer, schemaVersion: 2 }),
150
+ ).toThrow(/schemaVersion/);
151
+ });
152
+
153
+ test("a failure record carries its phase, attempt, and diagnostics", () => {
154
+ const failure = {
155
+ schemaVersion: 1 as const,
156
+ appletId: APPLET,
157
+ generationId: "g1",
158
+ attempt: 2,
159
+ phase: "health",
160
+ message: "tools do not match",
161
+ diagnostics: ["declared:add_todo", "reported:"],
162
+ recordedAt: "2026-09-03T00:00:00.000Z",
163
+ };
164
+ expect(decodeAppletFailureV1(failure)).toEqual(failure as never);
165
+ expect(() =>
166
+ decodeAppletFailureV1({ ...failure, phase: "boom" }),
167
+ ).toThrow();
168
+ expect(() => decodeAppletFailureV1({ ...failure, attempt: 0 })).toThrow();
169
+ });
170
+
171
+ test("the mount input is everything the alarm handler needs", () => {
172
+ const input = {
173
+ schemaVersion: 1 as const,
174
+ userId: "user-42",
175
+ appletId: APPLET,
176
+ generationId: "g1",
177
+ loaderId: HASH_A,
178
+ serverHash: HASH_B,
179
+ contract: 1,
180
+ };
181
+ expect(decodeAppletMountInputV1(input)).toEqual(input as never);
182
+ expect(() =>
183
+ decodeAppletMountInputV1({ ...input, loaderId: "no" }),
184
+ ).toThrow();
185
+ });
186
+
187
+ test("a focused Applet may be null", () => {
188
+ const focused = {
189
+ schemaVersion: 1 as const,
190
+ appletId: null,
191
+ changedAt: "2026-09-03T00:00:00.000Z",
192
+ };
193
+ expect(decodeFocusedAppletV1(focused)).toEqual(focused);
194
+ expect(
195
+ decodeFocusedAppletV1({ ...focused, appletId: APPLET }).appletId,
196
+ ).toBe(APPLET);
197
+ expect(() =>
198
+ decodeFocusedAppletV1({ ...focused, appletId: "nope" }),
199
+ ).toThrow();
200
+ });
201
+
202
+ test("health is the contract, the tools, and the schema revision", () => {
203
+ expect(
204
+ decodeAppletHealthV1({ contract: 1, tools: ["a"], schemaRevision: 3 }),
205
+ ).toEqual({ contract: 1, tools: ["a"], schemaRevision: 3 });
206
+ expect(() =>
207
+ decodeAppletHealthV1({ contract: 2, tools: [], schemaRevision: 0 }),
208
+ ).toThrow(/contract/);
209
+ expect(() =>
210
+ decodeAppletHealthV1({
211
+ contract: 1,
212
+ tools: ["a", "a"],
213
+ schemaRevision: 0,
214
+ }),
215
+ ).toThrow(/duplicate/);
216
+ });
217
+ });
218
+
219
+ describe("Applet viewer tokens", () => {
220
+ const claims = {
221
+ u: "user-42",
222
+ a: APPLET,
223
+ g: "g1",
224
+ exp: Math.floor((Date.now() + APPLET_VIEWER_TOKEN_TTL_MS) / 1_000),
225
+ };
226
+
227
+ test("a minted token verifies and answers with its claims", async () => {
228
+ const token = await mintAppletViewerTokenV1(SECRET, claims);
229
+ expect(await verifyAppletViewerTokenV1(SECRET, token)).toEqual(claims);
230
+ });
231
+
232
+ test("a token minted under another secret is refused", async () => {
233
+ const token = await mintAppletViewerTokenV1(SECRET, claims);
234
+ await expect(
235
+ verifyAppletViewerTokenV1(`${SECRET}-other`, token),
236
+ ).rejects.toThrow(AppletViewerTokenError);
237
+ });
238
+
239
+ test("an expired token is refused", async () => {
240
+ const token = await mintAppletViewerTokenV1(SECRET, {
241
+ ...claims,
242
+ exp: Math.floor(Date.now() / 1_000) - 1,
243
+ });
244
+ await expect(verifyAppletViewerTokenV1(SECRET, token)).rejects.toThrow(
245
+ /invalid/,
246
+ );
247
+ });
248
+
249
+ test("a tampered payload is refused rather than re-signed", async () => {
250
+ const token = await mintAppletViewerTokenV1(SECRET, claims);
251
+ const [, signature] = token.split(".");
252
+ const forged = `${btoa(
253
+ JSON.stringify({ ...claims, a: `user-42.${"9".repeat(32)}` }),
254
+ )
255
+ .replace(/\+/g, "-")
256
+ .replace(/\//g, "_")
257
+ .replace(/=+$/, "")}.${signature}`;
258
+ await expect(verifyAppletViewerTokenV1(SECRET, forged)).rejects.toThrow(
259
+ /invalid/,
260
+ );
261
+ });
262
+
263
+ test("the token is scoped: the claims name the User, Applet, and generation", async () => {
264
+ const token = await mintAppletViewerTokenV1(SECRET, claims);
265
+ const verified = await verifyAppletViewerTokenV1(SECRET, token);
266
+ expect(verified.u).toBe("user-42");
267
+ expect(verified.a).toBe(APPLET);
268
+ expect(verified.g).toBe("g1");
269
+ });
270
+
271
+ test("a short secret is a deployment fault, not a 401", async () => {
272
+ await expect(mintAppletViewerTokenV1("short", claims)).rejects.toThrow(
273
+ /secret/,
274
+ );
275
+ });
276
+
277
+ test("garbage is refused without a signature check crash", async () => {
278
+ await expect(verifyAppletViewerTokenV1(SECRET, "nope")).rejects.toThrow(
279
+ /invalid/,
280
+ );
281
+ await expect(verifyAppletViewerTokenV1(SECRET, 7)).rejects.toThrow(
282
+ /invalid/,
283
+ );
284
+ });
285
+ });