@frockbot/kernel-do 0.3.25 → 0.3.26
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 +3 -3
- package/src/applets.test.ts +33 -0
- package/src/applets.ts +58 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frockbot/kernel-do",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.26",
|
|
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.
|
|
16
|
-
"@frockbot/kernel-contracts": "0.3.
|
|
15
|
+
"@frockbot/kernel-composition": "0.3.26",
|
|
16
|
+
"@frockbot/kernel-contracts": "0.3.26",
|
|
17
17
|
"cordis": "4.0.0-rc.8"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
package/src/applets.test.ts
CHANGED
|
@@ -18,7 +18,10 @@ import {
|
|
|
18
18
|
decodeAppletHealthV1,
|
|
19
19
|
decodeAppletMountInputV1,
|
|
20
20
|
decodeAppletPointerV1,
|
|
21
|
+
decodeAppletTrialV1,
|
|
21
22
|
decodeFocusedAppletV1,
|
|
23
|
+
APPLET_ROLLBACK_FACET_NAME_V1,
|
|
24
|
+
APPLET_TRIAL_KEY,
|
|
22
25
|
mintAppletViewerTokenV1,
|
|
23
26
|
newAppletIdV1,
|
|
24
27
|
verifyAppletViewerTokenV1,
|
|
@@ -189,6 +192,36 @@ describe("Applet durable records", () => {
|
|
|
189
192
|
).toThrow();
|
|
190
193
|
});
|
|
191
194
|
|
|
195
|
+
test("a trial names the candidate, and the previous generation only when there is one", () => {
|
|
196
|
+
const mountInput = {
|
|
197
|
+
schemaVersion: 1 as const,
|
|
198
|
+
userId: "user-42",
|
|
199
|
+
appletId: APPLET,
|
|
200
|
+
generationId: "g2",
|
|
201
|
+
loaderId: HASH_A,
|
|
202
|
+
serverHash: HASH_B,
|
|
203
|
+
contract: 1,
|
|
204
|
+
};
|
|
205
|
+
const first = { schemaVersion: 1 as const, candidate: mountInput };
|
|
206
|
+
expect(decodeAppletTrialV1(first)).toEqual(first as never);
|
|
207
|
+
const over = {
|
|
208
|
+
schemaVersion: 1 as const,
|
|
209
|
+
candidate: mountInput,
|
|
210
|
+
previous: { ...mountInput, generationId: "g1" },
|
|
211
|
+
};
|
|
212
|
+
expect(decodeAppletTrialV1(over)).toEqual(over as never);
|
|
213
|
+
expect(() =>
|
|
214
|
+
decodeAppletTrialV1({
|
|
215
|
+
...first,
|
|
216
|
+
candidate: { ...mountInput, contract: 2 },
|
|
217
|
+
}),
|
|
218
|
+
).toThrow();
|
|
219
|
+
// The rollback facet is a facet name, not a storage key: they must not
|
|
220
|
+
// collide with the live one.
|
|
221
|
+
expect(APPLET_ROLLBACK_FACET_NAME_V1).toBe("applet-rollback");
|
|
222
|
+
expect(APPLET_TRIAL_KEY).toBe("applet:trial");
|
|
223
|
+
});
|
|
224
|
+
|
|
192
225
|
test("a focused Applet may be null", () => {
|
|
193
226
|
const focused = {
|
|
194
227
|
schemaVersion: 1 as const,
|
package/src/applets.ts
CHANGED
|
@@ -65,6 +65,17 @@ export const APPLET_FAILURE_PREFIX = "applet:failure:";
|
|
|
65
65
|
* at mount time.
|
|
66
66
|
*/
|
|
67
67
|
export const APPLET_MOUNT_INPUT_KEY = "applet:mount-input";
|
|
68
|
+
/**
|
|
69
|
+
* Applet Durable Object: the open activation trial, if one is in flight.
|
|
70
|
+
*
|
|
71
|
+
* An activation is a commit boundary the kernel owns, not a mount the candidate
|
|
72
|
+
* is trusted to survive (ADR 0038). While this key exists the facet's storage is
|
|
73
|
+
* provisional: a byte copy of it is parked in the rollback facet, and whatever
|
|
74
|
+
* reads the Applet next either finds the trial committed — the key deleted — or
|
|
75
|
+
* rolls it back before answering. That is what makes an interrupted publish
|
|
76
|
+
* recoverable rather than half-migrated.
|
|
77
|
+
*/
|
|
78
|
+
export const APPLET_TRIAL_KEY = "applet:trial";
|
|
68
79
|
/** Bot Durable Object: the Session's focused Applet. */
|
|
69
80
|
export const APPLET_FOCUSED_KEY = "applets:focused";
|
|
70
81
|
|
|
@@ -72,6 +83,16 @@ export const APPLET_FOCUSED_KEY = "applets:focused";
|
|
|
72
83
|
export const APPLET_FAILURE_ATTEMPT_DIGITS = 4;
|
|
73
84
|
/** The one facet name the kernel mounts under an `AppletState` object. */
|
|
74
85
|
export const APPLET_FACET_NAME_V1 = "applet";
|
|
86
|
+
/**
|
|
87
|
+
* The second facet name an `AppletState` object uses, and it holds no code.
|
|
88
|
+
*
|
|
89
|
+
* The Durable Object facet API can clone a facet's whole storage, so the kernel
|
|
90
|
+
* parks a byte copy of the live Applet here for the length of one activation
|
|
91
|
+
* trial and clones it back if the candidate fails. Nothing is ever mounted
|
|
92
|
+
* against it; it exists only to be copied from. `AppletState` is the one place
|
|
93
|
+
* that call is made, and an architecture check keeps it that way.
|
|
94
|
+
*/
|
|
95
|
+
export const APPLET_ROLLBACK_FACET_NAME_V1 = "applet-rollback";
|
|
75
96
|
/** The Instance Contract version this kernel speaks. */
|
|
76
97
|
export const APPLET_CONTRACT_V1 = 1;
|
|
77
98
|
/** Most generations one Applet retains before the oldest are pruned. */
|
|
@@ -166,6 +187,20 @@ export interface AppletMountInputV1 {
|
|
|
166
187
|
contract: 1;
|
|
167
188
|
}
|
|
168
189
|
|
|
190
|
+
/**
|
|
191
|
+
* One activation in flight: which generation is being tried over the Applet's
|
|
192
|
+
* live storage, and which one the rollback facet holds the storage of.
|
|
193
|
+
*
|
|
194
|
+
* `previous` is absent for an Applet's first generation, where there is no data
|
|
195
|
+
* to protect and a failure discards the candidate's storage instead of
|
|
196
|
+
* restoring anything.
|
|
197
|
+
*/
|
|
198
|
+
export interface AppletTrialV1 {
|
|
199
|
+
schemaVersion: 1;
|
|
200
|
+
candidate: AppletMountInputV1;
|
|
201
|
+
previous?: AppletMountInputV1;
|
|
202
|
+
}
|
|
203
|
+
|
|
169
204
|
/** One Session's focused Applet. `null` closes the canvas. */
|
|
170
205
|
export interface FocusedAppletV1 {
|
|
171
206
|
schemaVersion: 1;
|
|
@@ -352,6 +387,29 @@ export function decodeAppletMountInputV1(
|
|
|
352
387
|
};
|
|
353
388
|
}
|
|
354
389
|
|
|
390
|
+
export function decodeAppletTrialV1(
|
|
391
|
+
input: unknown,
|
|
392
|
+
label = "Applet trial",
|
|
393
|
+
): AppletTrialV1 {
|
|
394
|
+
const value = record(input, label);
|
|
395
|
+
exactKeys(value, ["schemaVersion", "candidate"], ["previous"], label);
|
|
396
|
+
if (value.schemaVersion !== 1) {
|
|
397
|
+
throw new Error(`${label}.schemaVersion is unsupported`);
|
|
398
|
+
}
|
|
399
|
+
return {
|
|
400
|
+
schemaVersion: 1,
|
|
401
|
+
candidate: decodeAppletMountInputV1(value.candidate, `${label}.candidate`),
|
|
402
|
+
...(value.previous === undefined
|
|
403
|
+
? {}
|
|
404
|
+
: {
|
|
405
|
+
previous: decodeAppletMountInputV1(
|
|
406
|
+
value.previous,
|
|
407
|
+
`${label}.previous`,
|
|
408
|
+
),
|
|
409
|
+
}),
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
355
413
|
export function decodeFocusedAppletV1(
|
|
356
414
|
input: unknown,
|
|
357
415
|
label = "focused Applet",
|