@fungi.computer/oyster 0.1.0 → 0.3.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.
- package/README.md +376 -72
- package/dist/agent-avatars.d.ts +103 -0
- package/dist/agent-avatars.js +66 -0
- package/dist/agents.d.ts +15 -4
- package/dist/agents.js +43 -4
- package/dist/app-builds.d.ts +81 -8
- package/dist/app-builds.js +210 -60
- package/dist/app-storage.d.ts +3 -3
- package/dist/approval-capability.d.ts +5 -0
- package/dist/approval-capability.js +46 -0
- package/dist/approval-home.d.ts +51 -0
- package/dist/approval-home.js +226 -0
- package/dist/apps/catalog.d.ts +335 -0
- package/dist/apps/catalog.js +46 -0
- package/dist/apps/gate.d.ts +26 -12
- package/dist/apps/gate.js +48 -22
- package/dist/apps/index.d.ts +3 -1
- package/dist/apps/index.js +3 -1
- package/dist/apps/installation.d.ts +147 -66
- package/dist/apps/installation.js +193 -86
- package/dist/apps/permissions.d.ts +247 -0
- package/dist/apps/permissions.js +255 -0
- package/dist/boot-capability-route.d.ts +15 -0
- package/dist/boot-capability-route.js +196 -0
- package/dist/boot-capability.d.ts +103 -0
- package/dist/boot-capability.js +141 -0
- package/dist/boot-construction.d.ts +12 -0
- package/dist/boot-construction.js +175 -0
- package/dist/boot-gate.d.ts +267 -0
- package/dist/boot-gate.js +236 -0
- package/dist/boot-host-script.d.ts +2 -0
- package/dist/boot-host-script.js +1 -0
- package/dist/boot-mount.d.ts +21 -0
- package/dist/boot-mount.js +65 -0
- package/dist/browser/boot.d.ts +73 -0
- package/dist/browser/boot.js +502 -0
- package/dist/browser/browser.d.ts +104 -0
- package/dist/browser/browser.js +374 -0
- package/dist/browser/capability-source.d.ts +76 -0
- package/dist/browser/capability-source.js +338 -0
- package/dist/browser/cdp-client.d.ts +56 -0
- package/dist/browser/cdp-client.js +184 -0
- package/dist/browser/errors.d.ts +79 -0
- package/dist/browser/errors.js +32 -0
- package/dist/browser/index.d.ts +16 -0
- package/dist/browser/index.js +14 -0
- package/dist/browser/lease-registry.d.ts +61 -0
- package/dist/browser/lease-registry.js +101 -0
- package/dist/browser/operations.d.ts +225 -0
- package/dist/browser/operations.js +137 -0
- package/dist/browser/page.d.ts +25 -0
- package/dist/browser/page.js +172 -0
- package/dist/browser/quick-reads.d.ts +29 -0
- package/dist/browser/quick-reads.js +160 -0
- package/dist/browser/sessions.d.ts +52 -0
- package/dist/browser/sessions.js +118 -0
- package/dist/browser/url-policy.d.ts +18 -0
- package/dist/browser/url-policy.js +58 -0
- package/dist/build/app-imports.d.ts +12 -0
- package/dist/build/app-imports.js +105 -0
- package/dist/build/build-policy.d.ts +5 -3
- package/dist/build/build-policy.js +5 -3
- package/dist/build/candidate-builder.d.ts +1 -1
- package/dist/build/candidate-cli.mjs +8428 -337
- package/dist/build/local-build.d.ts +2 -0
- package/dist/build/local-build.js +28 -6
- package/dist/build/snapshot-package-store.d.ts +1 -1
- package/dist/build/snapshot-package-store.js +7 -2
- package/dist/build/workspace-build.d.ts +92 -5
- package/dist/build/workspace-build.js +93 -19
- package/dist/build-egress.js +4 -4
- package/dist/build.d.ts +1 -1
- package/dist/build.js +1 -1
- package/dist/catalog-frame.js +1 -1
- package/dist/cloudflare.d.ts +13 -3
- package/dist/cloudflare.js +106 -8
- package/dist/frame-binding-wire-script.js +1 -1
- package/dist/frame-binding-wire.d.ts +1 -1
- package/dist/frame-binding-wire.js +5 -3
- package/dist/git-auth.d.ts +0 -2
- package/dist/git-auth.js +0 -2
- package/dist/git-journal.js +4 -4
- package/dist/git.d.ts +0 -1
- package/dist/git.js +0 -2
- package/dist/host.d.ts +39 -13
- package/dist/host.js +268 -142
- package/dist/index.d.ts +26 -15
- package/dist/index.js +40 -16
- package/dist/install-release-cli.d.ts +2 -0
- package/dist/install-release-cli.js +46 -0
- package/dist/installation-client.d.ts +43 -0
- package/dist/installation-client.js +178 -0
- package/dist/installation-execution.d.ts +11 -0
- package/dist/installation-execution.js +184 -0
- package/dist/installation-operations.d.ts +1437 -0
- package/dist/installation-operations.js +664 -0
- package/dist/installation-store-generation.d.ts +3 -0
- package/dist/installation-store-generation.js +16 -0
- package/dist/instance-id.d.ts +10 -0
- package/dist/instance-id.js +10 -0
- package/dist/instance-refusal.d.ts +11 -0
- package/dist/instance-refusal.js +21 -0
- package/dist/internal/delivery.js +4 -2
- package/dist/internal/host-gate.js +7 -1
- package/dist/internal/registry-installations.js +4 -4
- package/dist/keys.d.ts +5 -1
- package/dist/keys.js +10 -2
- package/dist/machine-link/authority.d.ts +89 -0
- package/dist/machine-link/authority.js +37 -0
- package/dist/machine-link/client.d.ts +66 -0
- package/dist/machine-link/client.js +94 -0
- package/dist/machine-link/delivery.d.ts +18 -0
- package/dist/machine-link/delivery.js +55 -0
- package/dist/machine-link/enrollment.d.ts +111 -0
- package/dist/machine-link/enrollment.js +286 -0
- package/dist/machine-link/fences.d.ts +37 -0
- package/dist/machine-link/fences.js +166 -0
- package/dist/machine-link/home.d.ts +20 -0
- package/dist/machine-link/home.js +24 -0
- package/dist/machine-link/host.d.ts +43 -0
- package/dist/machine-link/host.js +189 -0
- package/dist/machine-link/index.d.ts +15 -0
- package/dist/machine-link/index.js +15 -0
- package/dist/machine-link/link.d.ts +48 -0
- package/dist/machine-link/link.js +223 -0
- package/dist/machine-link/presence.d.ts +10 -0
- package/dist/machine-link/presence.js +25 -0
- package/dist/machine-link/workers.d.ts +6 -0
- package/dist/machine-link/workers.js +6 -0
- package/dist/mcp-owner.d.ts +2 -2
- package/dist/mcp-owner.js +41 -33
- package/dist/mcp.d.ts +15 -3
- package/dist/mcp.js +146 -25
- package/dist/pack.d.ts +14 -3
- package/dist/pack.js +44 -19
- package/dist/page/app-access.d.ts +7 -0
- package/dist/page/app-access.js +75 -0
- package/dist/page/boot-host.js +57 -0
- package/dist/page/capabilities.d.ts +3 -3
- package/dist/page/capabilities.js +16 -12
- package/dist/page/computer-handles.d.ts +3 -3
- package/dist/page/computer-target.d.ts +3 -0
- package/dist/page/computer-target.js +9 -0
- package/dist/page/mount.d.ts +23 -7
- package/dist/page/mount.js +152 -16
- package/dist/page-assets.d.ts +22 -0
- package/dist/page-assets.js +28 -0
- package/dist/page-script.d.ts +3 -0
- package/dist/page-script.js +5 -4
- package/dist/release.d.ts +13 -6
- package/dist/release.js +10 -7
- package/dist/root-data.d.ts +16 -6
- package/dist/root-data.js +2 -2
- package/dist/source/index.d.ts +1 -1
- package/dist/source/team-source.d.ts +20 -8
- package/dist/source/team-source.js +39 -10
- package/dist/source/template-seed.d.ts +2 -2
- package/dist/standalone/agent-mail.d.ts +24 -0
- package/dist/standalone/agent-mail.js +62 -0
- package/dist/standalone/agent-store.d.ts +9 -0
- package/dist/standalone/agent-store.js +30 -0
- package/dist/standalone/agents.d.ts +78 -12
- package/dist/standalone/agents.js +470 -59
- package/dist/standalone/app-builds-binding.d.ts +20 -2
- package/dist/standalone/app-builds-binding.js +151 -27
- package/dist/standalone/app-set.d.ts +5 -2
- package/dist/standalone/app-set.js +174 -143
- package/dist/standalone/browser.d.ts +11 -0
- package/dist/standalone/browser.js +72 -0
- package/dist/standalone/builds.d.ts +13 -4
- package/dist/standalone/builds.js +28 -44
- package/dist/standalone/computer-egress.d.ts +2 -0
- package/dist/standalone/computer-egress.js +13 -4
- package/dist/standalone/computer-fleet.d.ts +43 -0
- package/dist/standalone/computer-fleet.js +200 -0
- package/dist/standalone/computer.d.ts +44 -9
- package/dist/standalone/computer.js +132 -25
- package/dist/standalone/install-apps-cli.js +43 -6
- package/dist/standalone/instance.d.ts +2 -2
- package/dist/standalone/instance.js +8 -11
- package/dist/standalone/machine-link.d.ts +354 -0
- package/dist/standalone/machine-link.js +368 -0
- package/dist/standalone/media.d.ts +2 -2
- package/dist/standalone/media.js +19 -16
- package/dist/standalone/preview.d.ts +2 -2
- package/dist/standalone/preview.js +39 -12
- package/dist/standalone/registry.d.ts +149 -9
- package/dist/standalone/registry.js +299 -8
- package/dist/standalone/worker.d.ts +19 -5
- package/dist/standalone/worker.js +90 -54
- package/dist/standalone/workflow-binding.d.ts +28 -32
- package/dist/standalone/workflow-binding.js +20 -20
- package/dist/standalone/workflow-control.d.ts +4 -0
- package/dist/standalone/workflow-control.js +40 -0
- package/dist/standalone/workflow-events.d.ts +2 -2
- package/dist/standalone/workflow-events.js +9 -10
- package/dist/standalone/workflow-git-capture.js +4 -3
- package/dist/standalone/workflow-host.d.ts +177 -152
- package/dist/standalone/workflow-host.js +137 -30
- package/dist/standalone/workflow-node-capture.js +11 -4
- package/dist/standalone/workflow-schedules.d.ts +5 -2
- package/dist/standalone/workflow-schedules.js +9 -10
- package/dist/standalone/workflow-trigger-host.d.ts +88 -6
- package/dist/standalone/workflow-trigger-host.js +2 -2
- package/dist/standalone/workflow-webhooks.d.ts +22 -0
- package/dist/standalone/workflow-webhooks.js +39 -0
- package/dist/standalone/workflows.d.ts +203 -50
- package/dist/standalone/workflows.js +198 -39
- package/dist/storage-frame.d.ts +6 -6
- package/dist/testing/capabilities.js +14 -3
- package/dist/testing/installation.js +26 -2
- package/dist/testing/worker.js +1 -1
- package/dist/testing/workers-runtime.d.ts +8 -2
- package/dist/testing/workers-runtime.js +20 -2
- package/dist/webhook-abuse.d.ts +25 -0
- package/dist/webhook-abuse.js +54 -0
- package/dist/webhook-custody.d.ts +144 -0
- package/dist/webhook-custody.js +222 -0
- package/dist/webhook-ingress.d.ts +36 -0
- package/dist/webhook-ingress.js +146 -0
- package/dist/webhooks.d.ts +111 -0
- package/dist/webhooks.js +189 -0
- package/dist/workers-git.d.ts +12 -17
- package/dist/workers-git.js +60 -55
- package/dist/workers.d.ts +224 -13
- package/dist/workers.js +351 -51
- package/dist/workflow-agent-authority.d.ts +8 -0
- package/dist/workflow-agent-authority.js +42 -0
- package/dist/workflows.d.ts +66 -0
- package/dist/workflows.js +97 -0
- package/docs/api/README.md +63 -37
- package/docs/api/agents.md +17 -0
- package/docs/api/app-storage.md +1 -1
- package/docs/api/apps.md +1394 -543
- package/docs/api/browser/boot.md +132 -0
- package/docs/api/browser.md +1440 -0
- package/docs/api/build-contract.md +38 -25
- package/docs/api/build.md +263 -84
- package/docs/api/cloudflare.md +93 -7
- package/docs/api/git.md +3 -13
- package/docs/api/host.md +944 -118
- package/docs/api/index.md +320 -18
- package/docs/api/keys.md +24 -1
- package/docs/api/mcp.md +59 -12
- package/docs/api/modules.md +33 -40
- package/docs/api/pack.md +57 -8
- package/docs/api/page/mount.md +92 -4
- package/docs/api/workers-git.md +103 -24
- package/docs/api/workers.md +1581 -244
- package/docs/site/agent-quickstart.mdx +28 -14
- package/examples/standalone/worker.ts +3 -1
- package/examples/standalone/wrangler.jsonc +2 -0
- package/package.json +73 -69
- package/dist/installation-consent.d.ts +0 -79
- package/dist/installation-consent.js +0 -82
- package/dist/shell/app-directory.d.ts +0 -542
- package/dist/shell/app-directory.js +0 -199
- package/dist/shell/browser-actions.d.ts +0 -7
- package/dist/shell/browser-actions.js +0 -56
- package/dist/shell/command-host.d.ts +0 -112
- package/dist/shell/command-host.js +0 -502
- package/dist/shell/commands.d.ts +0 -25
- package/dist/shell/commands.js +0 -78
- package/dist/shell/index.d.ts +0 -8
- package/dist/shell/index.js +0 -8
- package/dist/shell/keys.d.ts +0 -41
- package/dist/shell/keys.js +0 -134
- package/dist/shell/shell-module.d.ts +0 -55
- package/dist/shell/shell-module.js +0 -86
- package/dist/shell/window-manager.d.ts +0 -124
- package/dist/shell/window-manager.js +0 -711
- package/dist/shell/workbench-contract.d.ts +0 -68
- package/dist/shell/workbench-directory.d.ts +0 -332
- package/dist/shell/workbench-directory.js +0 -35
- package/dist/shell/workbench-host-types.d.ts +0 -96
- package/dist/shell/workbench-host-types.js +0 -1
- package/dist/shell/workbench-host.d.ts +0 -34
- package/dist/shell/workbench-host.js +0 -347
- package/dist/shell/workbench-model.d.ts +0 -192
- package/dist/shell/workbench-model.js +0 -53
- package/dist/shell/workbench.d.ts +0 -4
- package/dist/shell/workbench.js +0 -2
- package/docs/api/shell/app-directory.md +0 -599
- package/docs/api/shell/command-host.md +0 -224
- package/docs/api/shell/commands.md +0 -197
- package/docs/api/shell/keys.md +0 -183
- package/docs/api/shell/shell-module.md +0 -204
- package/docs/api/shell/window-manager.md +0 -267
- package/docs/api/shell/workbench-directory.md +0 -119
- package/docs/api/shell/workbench.md +0 -205
- package/docs/api/shell.md +0 -1915
- /package/dist/{shell/workbench-contract.js → page/boot-host.d.ts} +0 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { parseReleaseId } from "@fungi.computer/puffball";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { BOOT_CAPABILITY_HEADER, } from "./boot-capability.js";
|
|
5
|
+
import {} from "./boot-gate.js";
|
|
6
|
+
import { defaultAssetPolicy } from "./release.js";
|
|
7
|
+
import { bootAssetUrl, bootHostScript, bootParentDocument, candidateAssetUrls, candidateSecurityHeaders, parentSecurityHeaders, } from "./boot-mount.js";
|
|
8
|
+
const tokenSchema = z.string().regex(/^[A-Za-z0-9_-]{43}$/);
|
|
9
|
+
const candidatePreflightPathSchema = z
|
|
10
|
+
.tuple([
|
|
11
|
+
z.literal("_boot"),
|
|
12
|
+
z.string(),
|
|
13
|
+
z.literal("candidate"),
|
|
14
|
+
z.string().min(1),
|
|
15
|
+
])
|
|
16
|
+
.rest(z.string().min(1));
|
|
17
|
+
const capabilityPreflightSchema = z.object({
|
|
18
|
+
method: z.literal("OPTIONS"),
|
|
19
|
+
search: z.literal(""),
|
|
20
|
+
origin: z.literal("null"),
|
|
21
|
+
requestedMethod: z.literal("GET"),
|
|
22
|
+
requestedHeaders: z
|
|
23
|
+
.string()
|
|
24
|
+
.trim()
|
|
25
|
+
.toLowerCase()
|
|
26
|
+
.pipe(z.literal(BOOT_CAPABILITY_HEADER)),
|
|
27
|
+
path: candidatePreflightPathSchema,
|
|
28
|
+
});
|
|
29
|
+
/** Worker-only HTTPS endpoint for a single digest-authorized boot lease. */
|
|
30
|
+
export async function serveBootCapability(request, options) {
|
|
31
|
+
const url = new URL(request.url);
|
|
32
|
+
if (!url.pathname.startsWith("/_boot/"))
|
|
33
|
+
return null;
|
|
34
|
+
const configured = new URL(options.configuredOrigin);
|
|
35
|
+
// The same origins checkBootPage opens: https, or http on loopback only.
|
|
36
|
+
const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(configured.hostname);
|
|
37
|
+
if ((configured.protocol !== "https:" &&
|
|
38
|
+
!(configured.protocol === "http:" && loopback)) ||
|
|
39
|
+
configured.pathname !== "/" ||
|
|
40
|
+
configured.search !== "" ||
|
|
41
|
+
configured.hash !== "" ||
|
|
42
|
+
configured.username !== "" ||
|
|
43
|
+
configured.password !== "" ||
|
|
44
|
+
url.origin !== configured.origin)
|
|
45
|
+
return refusal(404);
|
|
46
|
+
const segments = url.pathname.split("/");
|
|
47
|
+
if (isCapabilityPreflight(request, url, segments))
|
|
48
|
+
return capabilityPreflight();
|
|
49
|
+
const token = tokenSchema.safeParse(request.headers.get(BOOT_CAPABILITY_HEADER));
|
|
50
|
+
if (request.method !== "GET" ||
|
|
51
|
+
url.search !== "" ||
|
|
52
|
+
segments[2] === "" ||
|
|
53
|
+
!token.success ||
|
|
54
|
+
segments.length < 4 ||
|
|
55
|
+
segments[0] !== "" ||
|
|
56
|
+
segments[1] !== "_boot")
|
|
57
|
+
return refusal(404);
|
|
58
|
+
const tokenDigest = createHash("sha256")
|
|
59
|
+
.update(token.data, "utf8")
|
|
60
|
+
.digest("hex");
|
|
61
|
+
const lease = await options.capabilities.resolve(tokenDigest, Date.now());
|
|
62
|
+
if (lease === null)
|
|
63
|
+
return refusal(404);
|
|
64
|
+
// The capability store returns a lease already decoded at its persistence boundary.
|
|
65
|
+
const checkedLease = lease;
|
|
66
|
+
if (checkedLease.identity.releaseId !== segments[2])
|
|
67
|
+
return refusal(404);
|
|
68
|
+
const assets = checkedLease.assets.map(bootUiAsset);
|
|
69
|
+
const ui = {
|
|
70
|
+
identity: checkedLease.identity,
|
|
71
|
+
base: checkedLease.base,
|
|
72
|
+
assets,
|
|
73
|
+
};
|
|
74
|
+
const root = new URL(`/_boot/${checkedLease.identity.releaseId}/`, configured.origin);
|
|
75
|
+
const candidateBase = new URL("candidate/", root);
|
|
76
|
+
const urls = candidateAssetUrls(ui, candidateBase.href);
|
|
77
|
+
const entry = bootAssetUrl(candidateBase.href, checkedLease.base, "/index.html");
|
|
78
|
+
if (Date.now() >= checkedLease.expiresAt)
|
|
79
|
+
return refusal(404);
|
|
80
|
+
const parentPath = `${root.pathname}index.html`;
|
|
81
|
+
const bridgePath = `${root.pathname}bridge.js`;
|
|
82
|
+
if (url.pathname === parentPath) {
|
|
83
|
+
return new Response(bootParentDocument(checkedLease.identity, entry), {
|
|
84
|
+
status: 200,
|
|
85
|
+
headers: {
|
|
86
|
+
...parentSecurityHeaders(candidateBase.href),
|
|
87
|
+
"content-type": "text/html; charset=utf-8",
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
if (url.pathname === bridgePath) {
|
|
92
|
+
return new Response(bootHostScript, {
|
|
93
|
+
status: 200,
|
|
94
|
+
headers: {
|
|
95
|
+
...parentSecurityHeaders(candidateBase.href),
|
|
96
|
+
"content-type": "text/javascript; charset=utf-8",
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
const assetIndex = urls.findIndex((assetUrl) => {
|
|
101
|
+
const asset = new URL(assetUrl);
|
|
102
|
+
return asset.origin === url.origin && asset.pathname === url.pathname;
|
|
103
|
+
});
|
|
104
|
+
const asset = checkedLease.assets[assetIndex];
|
|
105
|
+
if (asset === undefined)
|
|
106
|
+
return refusal(404);
|
|
107
|
+
if (Date.now() >= checkedLease.expiresAt)
|
|
108
|
+
return refusal(404);
|
|
109
|
+
const object = await options.bucket.get(asset.artifactRef);
|
|
110
|
+
if (object?.size !== asset.byteLength)
|
|
111
|
+
return refusal(404);
|
|
112
|
+
const headers = new Headers({
|
|
113
|
+
...candidateSecurityHeaders(candidateBase.href),
|
|
114
|
+
"content-type": asset.contentType,
|
|
115
|
+
"x-content-type-options": "nosniff",
|
|
116
|
+
});
|
|
117
|
+
if (request.headers.get("origin") === "null") {
|
|
118
|
+
headers.set("access-control-allow-origin", "null");
|
|
119
|
+
headers.set("vary", "Origin");
|
|
120
|
+
}
|
|
121
|
+
const hash = createHash("sha256");
|
|
122
|
+
let byteLength = 0;
|
|
123
|
+
const verified = object.body.pipeThrough(new TransformStream({
|
|
124
|
+
transform(chunk, controller) {
|
|
125
|
+
if (Date.now() >= checkedLease.expiresAt) {
|
|
126
|
+
controller.error(new Error("Boot asset lease expired"));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
byteLength += chunk.byteLength;
|
|
130
|
+
if (byteLength > defaultAssetPolicy.maxObjectBytes) {
|
|
131
|
+
controller.error(new Error("Boot asset exceeds its size limit"));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
hash.update(chunk);
|
|
135
|
+
controller.enqueue(chunk);
|
|
136
|
+
},
|
|
137
|
+
flush(controller) {
|
|
138
|
+
if (byteLength !== asset.byteLength ||
|
|
139
|
+
`sha256:${hash.digest("hex")}` !== asset.digest)
|
|
140
|
+
controller.error(new Error("Boot asset verification failed"));
|
|
141
|
+
},
|
|
142
|
+
}));
|
|
143
|
+
return new Response(verified, { status: 200, headers });
|
|
144
|
+
}
|
|
145
|
+
function isCapabilityPreflight(request, url, segments) {
|
|
146
|
+
const preflight = capabilityPreflightSchema.safeParse({
|
|
147
|
+
method: request.method,
|
|
148
|
+
search: url.search,
|
|
149
|
+
origin: request.headers.get("origin"),
|
|
150
|
+
requestedMethod: request.headers.get("access-control-request-method"),
|
|
151
|
+
requestedHeaders: request.headers.get("access-control-request-headers"),
|
|
152
|
+
path: segments.slice(1),
|
|
153
|
+
});
|
|
154
|
+
if (!preflight.success)
|
|
155
|
+
return false;
|
|
156
|
+
try {
|
|
157
|
+
parseReleaseId(preflight.data.path[1]);
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function capabilityPreflight() {
|
|
165
|
+
return new Response(null, {
|
|
166
|
+
status: 204,
|
|
167
|
+
headers: {
|
|
168
|
+
"access-control-allow-origin": "null",
|
|
169
|
+
"access-control-allow-methods": "GET",
|
|
170
|
+
"access-control-allow-headers": BOOT_CAPABILITY_HEADER,
|
|
171
|
+
"cache-control": "no-store",
|
|
172
|
+
"referrer-policy": "no-referrer",
|
|
173
|
+
vary: "Origin, Access-Control-Request-Method, Access-Control-Request-Headers",
|
|
174
|
+
"x-content-type-options": "nosniff",
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
function bootUiAsset(asset) {
|
|
179
|
+
return {
|
|
180
|
+
path: asset.path,
|
|
181
|
+
contentType: asset.contentType,
|
|
182
|
+
artifactRef: asset.artifactRef,
|
|
183
|
+
digest: asset.digest,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
function refusal(status) {
|
|
187
|
+
return new Response("Boot resource unavailable", {
|
|
188
|
+
status,
|
|
189
|
+
headers: {
|
|
190
|
+
"cache-control": "no-store",
|
|
191
|
+
"content-type": "text/plain; charset=utf-8",
|
|
192
|
+
"referrer-policy": "no-referrer",
|
|
193
|
+
"x-content-type-options": "nosniff",
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { type BootUi, type UiBootCheckOutcome } from "./boot-gate.js";
|
|
3
|
+
import type { BootPageResult, BootPageTarget } from "./browser/boot.js";
|
|
4
|
+
export declare const BOOT_CAPABILITY_HEADER = "x-oyster-boot-capability";
|
|
5
|
+
declare const bootCapabilityAssetSchema: z.ZodObject<{
|
|
6
|
+
path: z.ZodString;
|
|
7
|
+
contentType: z.ZodString;
|
|
8
|
+
artifactRef: z.ZodPipe<z.ZodString, z.ZodTransform<import("@fungi.computer/puffball").ArtifactRef, string>>;
|
|
9
|
+
digest: z.ZodPipe<z.ZodString, z.ZodTransform<import("@fungi.computer/puffball").Sha256Digest, string>>;
|
|
10
|
+
byteLength: z.ZodNumber;
|
|
11
|
+
}, z.core.$strict>;
|
|
12
|
+
export declare const bootCapabilityLeaseSchema: z.ZodObject<{
|
|
13
|
+
id: z.ZodString;
|
|
14
|
+
identity: z.ZodObject<{
|
|
15
|
+
appId: z.ZodString;
|
|
16
|
+
releaseId: z.ZodPipe<z.ZodString, z.ZodTransform<import("@fungi.computer/puffball").ReleaseId, string>>;
|
|
17
|
+
surfaceId: z.ZodLiteral<"host-boot">;
|
|
18
|
+
}, z.core.$strict>;
|
|
19
|
+
base: z.ZodString;
|
|
20
|
+
expiresAt: z.ZodNumber;
|
|
21
|
+
assets: z.ZodArray<z.ZodObject<{
|
|
22
|
+
path: z.ZodString;
|
|
23
|
+
contentType: z.ZodString;
|
|
24
|
+
artifactRef: z.ZodPipe<z.ZodString, z.ZodTransform<import("@fungi.computer/puffball").ArtifactRef, string>>;
|
|
25
|
+
digest: z.ZodPipe<z.ZodString, z.ZodTransform<import("@fungi.computer/puffball").Sha256Digest, string>>;
|
|
26
|
+
byteLength: z.ZodNumber;
|
|
27
|
+
}, z.core.$strict>>;
|
|
28
|
+
}, z.core.$strict>;
|
|
29
|
+
export declare const bootCapabilityGrantSchema: z.ZodObject<{
|
|
30
|
+
lease: z.ZodObject<{
|
|
31
|
+
id: z.ZodString;
|
|
32
|
+
identity: z.ZodObject<{
|
|
33
|
+
appId: z.ZodString;
|
|
34
|
+
releaseId: z.ZodPipe<z.ZodString, z.ZodTransform<import("@fungi.computer/puffball").ReleaseId, string>>;
|
|
35
|
+
surfaceId: z.ZodLiteral<"host-boot">;
|
|
36
|
+
}, z.core.$strict>;
|
|
37
|
+
base: z.ZodString;
|
|
38
|
+
expiresAt: z.ZodNumber;
|
|
39
|
+
assets: z.ZodArray<z.ZodObject<{
|
|
40
|
+
path: z.ZodString;
|
|
41
|
+
contentType: z.ZodString;
|
|
42
|
+
artifactRef: z.ZodPipe<z.ZodString, z.ZodTransform<import("@fungi.computer/puffball").ArtifactRef, string>>;
|
|
43
|
+
digest: z.ZodPipe<z.ZodString, z.ZodTransform<import("@fungi.computer/puffball").Sha256Digest, string>>;
|
|
44
|
+
byteLength: z.ZodNumber;
|
|
45
|
+
}, z.core.$strict>>;
|
|
46
|
+
}, z.core.$strict>;
|
|
47
|
+
tokenDigest: z.ZodString;
|
|
48
|
+
}, z.core.$strict>;
|
|
49
|
+
export type BootCapabilityAsset = z.output<typeof bootCapabilityAssetSchema>;
|
|
50
|
+
export type BootCapabilityLease = z.output<typeof bootCapabilityLeaseSchema>;
|
|
51
|
+
export type BootCapabilityGrant = z.output<typeof bootCapabilityGrantSchema>;
|
|
52
|
+
/** Host-owned bounded Durable Object port. No raw token crosses this seam. */
|
|
53
|
+
export type BootCapabilityStore = Readonly<{
|
|
54
|
+
issue(grant: BootCapabilityGrant): Promise<void>;
|
|
55
|
+
resolve(tokenDigest: string, now: number): Promise<BootCapabilityLease | null>;
|
|
56
|
+
revoke(id: string): Promise<void>;
|
|
57
|
+
}>;
|
|
58
|
+
/** Build metadata retained by the lease must exactly match the verified boot UI. */
|
|
59
|
+
export declare function capabilityAssets(assets: BootUi["assets"], byteLengths: readonly number[]): BootCapabilityAsset[];
|
|
60
|
+
export declare function validateCapabilityAssetSet(lease: BootCapabilityLease): void;
|
|
61
|
+
/** The artifact bucket's metadata read; asset bytes are never read here. */
|
|
62
|
+
export type BootCapabilityBucket = Readonly<{
|
|
63
|
+
head(key: string): Promise<Readonly<{
|
|
64
|
+
size: number;
|
|
65
|
+
}> | null>;
|
|
66
|
+
}>;
|
|
67
|
+
/**
|
|
68
|
+
* Mint one boot capability for every host: a fresh 256-bit token for the
|
|
69
|
+
* caller's request header, only its SHA-256 digest in the store, and a lease
|
|
70
|
+
* whose asset sizes come from the bucket's own metadata. An asset the bucket
|
|
71
|
+
* does not hold, or one beyond the asset policy, refuses before anything is
|
|
72
|
+
* stored.
|
|
73
|
+
*/
|
|
74
|
+
export declare function issueBootCapability(input: Readonly<{
|
|
75
|
+
ui: BootUi;
|
|
76
|
+
bucket: BootCapabilityBucket;
|
|
77
|
+
capabilities: BootCapabilityStore;
|
|
78
|
+
expiresAt: number;
|
|
79
|
+
}>): Promise<Readonly<{
|
|
80
|
+
token: string;
|
|
81
|
+
leaseId: string;
|
|
82
|
+
}>>;
|
|
83
|
+
/** What one boot page report needs from its host. */
|
|
84
|
+
export type BootPageReportInput = Readonly<{
|
|
85
|
+
/** The host's configured origin for its `/_boot/` route, or null when unset. */
|
|
86
|
+
origin: string | null;
|
|
87
|
+
/** `checkBootPage` over the host's browser, or null without one. */
|
|
88
|
+
check: ((target: BootPageTarget) => Promise<BootPageResult>) | null;
|
|
89
|
+
/** The Release's UI as artifact references, or null when it has no UI. */
|
|
90
|
+
readUi: () => Promise<BootUi | null>;
|
|
91
|
+
bucket: BootCapabilityBucket;
|
|
92
|
+
capabilities: BootCapabilityStore;
|
|
93
|
+
now: () => number;
|
|
94
|
+
}>;
|
|
95
|
+
/**
|
|
96
|
+
* One UI page check for a Release, for every host, reading no UI bytes:
|
|
97
|
+
* issue a capability, open the pinned boot page, revoke the capability, and
|
|
98
|
+
* report in the boot gate's vocabulary. A page that did not mount fails,
|
|
99
|
+
* with diagnostics sanitized by the gate's schema; a timeout, unconfirmed
|
|
100
|
+
* cleanup, an unavailable runner, or no browser or origin is not checked.
|
|
101
|
+
*/
|
|
102
|
+
export declare function reportBootPage(input: BootPageReportInput): Promise<UiBootCheckOutcome>;
|
|
103
|
+
export {};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { defineIdFamily } from "@fungi.computer/cairn/id";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { parseArtifactRef, parseReleaseId, parseSha256Digest, } from "@fungi.computer/puffball";
|
|
5
|
+
import { BOOT_DEADLINE_MS, BOOT_TRANSPORT_GRACE_MS, BootGateError, bootMountDiagnosticsSchema, } from "./boot-gate.js";
|
|
6
|
+
import { defaultAssetPolicy } from "./release.js";
|
|
7
|
+
export const BOOT_CAPABILITY_HEADER = "x-oyster-boot-capability";
|
|
8
|
+
const bootCapabilityAssetSchema = z.strictObject({
|
|
9
|
+
path: z.string().min(1).max(4096),
|
|
10
|
+
contentType: z.string().min(1).max(256),
|
|
11
|
+
artifactRef: z.string().min(1).max(512).transform(parseArtifactRef),
|
|
12
|
+
digest: z.string().min(1).max(80).transform(parseSha256Digest),
|
|
13
|
+
byteLength: z
|
|
14
|
+
.number()
|
|
15
|
+
.int()
|
|
16
|
+
.nonnegative()
|
|
17
|
+
.max(defaultAssetPolicy.maxObjectBytes),
|
|
18
|
+
});
|
|
19
|
+
export const bootCapabilityLeaseSchema = z.strictObject({
|
|
20
|
+
id: z.string().min(1).max(128),
|
|
21
|
+
identity: z.strictObject({
|
|
22
|
+
appId: z.string().min(1).max(256),
|
|
23
|
+
releaseId: z.string().transform(parseReleaseId),
|
|
24
|
+
surfaceId: z.literal("host-boot"),
|
|
25
|
+
}),
|
|
26
|
+
base: z.string().min(1).max(2048),
|
|
27
|
+
expiresAt: z.number().int().positive(),
|
|
28
|
+
assets: z.array(bootCapabilityAssetSchema).max(defaultAssetPolicy.maxObjects),
|
|
29
|
+
});
|
|
30
|
+
export const bootCapabilityGrantSchema = z.strictObject({
|
|
31
|
+
lease: bootCapabilityLeaseSchema,
|
|
32
|
+
tokenDigest: z.string().regex(/^[0-9a-f]{64}$/),
|
|
33
|
+
});
|
|
34
|
+
/** Build metadata retained by the lease must exactly match the verified boot UI. */
|
|
35
|
+
export function capabilityAssets(assets, byteLengths) {
|
|
36
|
+
if (assets.length !== byteLengths.length)
|
|
37
|
+
throw new Error("Boot capability asset metadata is incomplete");
|
|
38
|
+
return assets.map((asset, index) => {
|
|
39
|
+
const byteLength = byteLengths[index];
|
|
40
|
+
if (byteLength === undefined)
|
|
41
|
+
throw new Error("Boot capability asset metadata is incomplete");
|
|
42
|
+
return { ...asset, byteLength };
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
export function validateCapabilityAssetSet(lease) {
|
|
46
|
+
const total = lease.assets.reduce((sum, asset) => sum + asset.byteLength, 0);
|
|
47
|
+
if (total > defaultAssetPolicy.maxTotalBytes ||
|
|
48
|
+
new Set(lease.assets.map((asset) => asset.path)).size !==
|
|
49
|
+
lease.assets.length)
|
|
50
|
+
throw new Error("Boot capability asset set is invalid");
|
|
51
|
+
}
|
|
52
|
+
const bootCapabilityIds = defineIdFamily({
|
|
53
|
+
family: "oyster-boot-capability",
|
|
54
|
+
version: 1,
|
|
55
|
+
kinds: { lease: { prefix: "bcap" } },
|
|
56
|
+
});
|
|
57
|
+
/**
|
|
58
|
+
* Mint one boot capability for every host: a fresh 256-bit token for the
|
|
59
|
+
* caller's request header, only its SHA-256 digest in the store, and a lease
|
|
60
|
+
* whose asset sizes come from the bucket's own metadata. An asset the bucket
|
|
61
|
+
* does not hold, or one beyond the asset policy, refuses before anything is
|
|
62
|
+
* stored.
|
|
63
|
+
*/
|
|
64
|
+
export async function issueBootCapability(input) {
|
|
65
|
+
const byteLengths = [];
|
|
66
|
+
let totalBytes = 0;
|
|
67
|
+
for (const asset of input.ui.assets) {
|
|
68
|
+
const head = await input.bucket.head(asset.artifactRef);
|
|
69
|
+
if (head === null || head.size > defaultAssetPolicy.maxObjectBytes)
|
|
70
|
+
throw new BootGateError("ui_mount_failed");
|
|
71
|
+
totalBytes += head.size;
|
|
72
|
+
if (totalBytes > defaultAssetPolicy.maxTotalBytes)
|
|
73
|
+
throw new BootGateError("ui_mount_failed");
|
|
74
|
+
byteLengths.push(head.size);
|
|
75
|
+
}
|
|
76
|
+
const lease = {
|
|
77
|
+
id: bootCapabilityIds.lease.generate(),
|
|
78
|
+
identity: input.ui.identity,
|
|
79
|
+
base: input.ui.base,
|
|
80
|
+
expiresAt: input.expiresAt,
|
|
81
|
+
assets: capabilityAssets(input.ui.assets, byteLengths),
|
|
82
|
+
};
|
|
83
|
+
validateCapabilityAssetSet(lease);
|
|
84
|
+
const token = btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(32))))
|
|
85
|
+
.replaceAll("+", "-")
|
|
86
|
+
.replaceAll("/", "_")
|
|
87
|
+
.replace(/=+$/u, "");
|
|
88
|
+
const tokenDigest = createHash("sha256").update(token, "utf8").digest("hex");
|
|
89
|
+
await input.capabilities.issue({ lease, tokenDigest });
|
|
90
|
+
return { token, leaseId: lease.id };
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* One UI page check for a Release, for every host, reading no UI bytes:
|
|
94
|
+
* issue a capability, open the pinned boot page, revoke the capability, and
|
|
95
|
+
* report in the boot gate's vocabulary. A page that did not mount fails,
|
|
96
|
+
* with diagnostics sanitized by the gate's schema; a timeout, unconfirmed
|
|
97
|
+
* cleanup, an unavailable runner, or no browser or origin is not checked.
|
|
98
|
+
*/
|
|
99
|
+
export async function reportBootPage(input) {
|
|
100
|
+
const ui = await input.readUi();
|
|
101
|
+
if (ui === null)
|
|
102
|
+
return { kind: "passed" };
|
|
103
|
+
if (input.check === null || input.origin === null)
|
|
104
|
+
return { kind: "not_checked", code: "boot_browser_binding_missing" };
|
|
105
|
+
const deadlineAt = input.now() + BOOT_DEADLINE_MS;
|
|
106
|
+
let issued;
|
|
107
|
+
try {
|
|
108
|
+
issued = await issueBootCapability({
|
|
109
|
+
ui,
|
|
110
|
+
bucket: input.bucket,
|
|
111
|
+
capabilities: input.capabilities,
|
|
112
|
+
expiresAt: deadlineAt + BOOT_TRANSPORT_GRACE_MS,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
catch (cause) {
|
|
116
|
+
if (cause instanceof BootGateError && cause.code === "ui_mount_failed")
|
|
117
|
+
return { kind: "failed", code: "ui_mount_failed" };
|
|
118
|
+
throw cause;
|
|
119
|
+
}
|
|
120
|
+
let result;
|
|
121
|
+
try {
|
|
122
|
+
result = await input.check({
|
|
123
|
+
origin: input.origin,
|
|
124
|
+
releaseId: ui.identity.releaseId,
|
|
125
|
+
capability: { header: BOOT_CAPABILITY_HEADER, token: issued.token },
|
|
126
|
+
deadlineAt,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
finally {
|
|
130
|
+
await input.capabilities.revoke(issued.leaseId);
|
|
131
|
+
}
|
|
132
|
+
if (result.kind === "mounted")
|
|
133
|
+
return { kind: "passed" };
|
|
134
|
+
if (result.code === "ui_mount_failed")
|
|
135
|
+
return {
|
|
136
|
+
kind: "failed",
|
|
137
|
+
code: "ui_mount_failed",
|
|
138
|
+
diagnostics: bootMountDiagnosticsSchema.parse(result.diagnostics),
|
|
139
|
+
};
|
|
140
|
+
return { kind: "not_checked", code: result.code };
|
|
141
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Backend, Instance } from "./index.js";
|
|
2
|
+
import type { AppInstanceContext, AppInstanceLoader } from "./cloudflare.js";
|
|
3
|
+
import type { GuestLimits } from "./guest-policy.js";
|
|
4
|
+
/** One bounded disposable sibling; it can never become the live App facet. */
|
|
5
|
+
export declare const BACKEND_CONSTRUCTION_FACET = "candidate";
|
|
6
|
+
/**
|
|
7
|
+
* The Instance owns the disposable facet and supplies its already-read prior
|
|
8
|
+
* state schema. Only initialized App storage is copied; supervisor state and
|
|
9
|
+
* authority are never copied. A unique Loader ID isolates candidate globals.
|
|
10
|
+
* No invocation frame or guestEnv factory is accepted.
|
|
11
|
+
*/
|
|
12
|
+
export declare function checkBackendConstruction<FacetClass>(ctx: Pick<AppInstanceContext<FacetClass>, "facets">, loader: AppInstanceLoader<FacetClass> | undefined, artifact: Backend, limits: GuestLimits, priorStateSchema: Instance["stateSchema"]): Promise<void>;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
2
|
+
if (value !== null && value !== void 0) {
|
|
3
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
4
|
+
var dispose, inner;
|
|
5
|
+
if (async) {
|
|
6
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
7
|
+
dispose = value[Symbol.asyncDispose];
|
|
8
|
+
}
|
|
9
|
+
if (dispose === void 0) {
|
|
10
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
11
|
+
dispose = value[Symbol.dispose];
|
|
12
|
+
if (async) inner = dispose;
|
|
13
|
+
}
|
|
14
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
15
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
16
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
17
|
+
}
|
|
18
|
+
else if (async) {
|
|
19
|
+
env.stack.push({ async: true });
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
};
|
|
23
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
24
|
+
return function (env) {
|
|
25
|
+
function fail(e) {
|
|
26
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
27
|
+
env.hasError = true;
|
|
28
|
+
}
|
|
29
|
+
var r, s = 0;
|
|
30
|
+
function next() {
|
|
31
|
+
while (r = env.stack.pop()) {
|
|
32
|
+
try {
|
|
33
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
34
|
+
if (r.dispose) {
|
|
35
|
+
var result = r.dispose.call(r.value);
|
|
36
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
37
|
+
}
|
|
38
|
+
else s |= 1;
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
fail(e);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
45
|
+
if (env.hasError) throw env.error;
|
|
46
|
+
}
|
|
47
|
+
return next();
|
|
48
|
+
};
|
|
49
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
50
|
+
var e = new Error(message);
|
|
51
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
|
+
});
|
|
53
|
+
import { rpcHandleSchema, rpcMethod } from "@fungi.computer/agar/rpc";
|
|
54
|
+
import { z } from "zod";
|
|
55
|
+
import * as Result from "effect/Result";
|
|
56
|
+
import { correlationKey } from "@fungi.computer/cairn";
|
|
57
|
+
import { BOOT_DEADLINE_MS, BootGateError } from "./boot-gate.js";
|
|
58
|
+
const probeSchema = rpcHandleSchema({
|
|
59
|
+
construct: rpcMethod(z.undefined()).input(z.tuple([])),
|
|
60
|
+
});
|
|
61
|
+
/** One bounded disposable sibling; it can never become the live App facet. */
|
|
62
|
+
export const BACKEND_CONSTRUCTION_FACET = "candidate";
|
|
63
|
+
/**
|
|
64
|
+
* The Instance owns the disposable facet and supplies its already-read prior
|
|
65
|
+
* state schema. Only initialized App storage is copied; supervisor state and
|
|
66
|
+
* authority are never copied. A unique Loader ID isolates candidate globals.
|
|
67
|
+
* No invocation frame or guestEnv factory is accepted.
|
|
68
|
+
*/
|
|
69
|
+
export async function checkBackendConstruction(ctx, loader, artifact, limits, priorStateSchema) {
|
|
70
|
+
if (loader === undefined)
|
|
71
|
+
throw new BootGateError("boot_runner_unavailable");
|
|
72
|
+
const name = BACKEND_CONSTRUCTION_FACET;
|
|
73
|
+
const loaderId = `boot:${correlationKey()}`;
|
|
74
|
+
let stage = "backend_load_failed";
|
|
75
|
+
let timeout;
|
|
76
|
+
const result = await (async () => {
|
|
77
|
+
try {
|
|
78
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
79
|
+
try {
|
|
80
|
+
const deadline = new Promise((_, reject) => {
|
|
81
|
+
timeout = setTimeout(() => {
|
|
82
|
+
reject(new BootGateError("boot_timeout"));
|
|
83
|
+
}, BOOT_DEADLINE_MS);
|
|
84
|
+
});
|
|
85
|
+
if (priorStateSchema !== null) {
|
|
86
|
+
if (ctx.facets.clone === undefined)
|
|
87
|
+
throw new BootGateError("boot_clone_unavailable");
|
|
88
|
+
ctx.facets.clone("app", name);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// A crash may have left the bounded sibling behind. Fresh means empty,
|
|
92
|
+
// not a retry against storage written by an earlier constructor.
|
|
93
|
+
ctx.facets.delete(name);
|
|
94
|
+
}
|
|
95
|
+
const stub = __addDisposableResource(env_1, probeSchema.parse(ctx.facets.get(name, () => ({
|
|
96
|
+
class: loader
|
|
97
|
+
.get(loaderId, () => ({
|
|
98
|
+
compatibilityDate: artifact.backend.compatibilityDate,
|
|
99
|
+
mainModule: "boot.js",
|
|
100
|
+
modules: {
|
|
101
|
+
"backend.js": artifact.backend.code,
|
|
102
|
+
"boot.js": constructionModule(artifact.backend.exportName),
|
|
103
|
+
},
|
|
104
|
+
env: {},
|
|
105
|
+
globalOutbound: null,
|
|
106
|
+
limits,
|
|
107
|
+
}))
|
|
108
|
+
.getDurableObjectClass("BootConstruction"),
|
|
109
|
+
}))), false);
|
|
110
|
+
stage = "backend_construction_failed";
|
|
111
|
+
await Promise.race([stub.construct(), deadline]);
|
|
112
|
+
return Result.succeed(undefined);
|
|
113
|
+
}
|
|
114
|
+
catch (e_1) {
|
|
115
|
+
env_1.error = e_1;
|
|
116
|
+
env_1.hasError = true;
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
__disposeResources(env_1);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch (cause) {
|
|
123
|
+
return Result.fail(cause instanceof BootGateError
|
|
124
|
+
? cause
|
|
125
|
+
: new BootGateError(stage, { cause }));
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
if (timeout !== undefined)
|
|
129
|
+
clearTimeout(timeout);
|
|
130
|
+
}
|
|
131
|
+
})();
|
|
132
|
+
// Cleanup uncertainty takes precedence over the captured probe result.
|
|
133
|
+
try {
|
|
134
|
+
try {
|
|
135
|
+
ctx.facets.abort(name, new Error("Candidate boot ended"));
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
ctx.facets.delete(name);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch (cause) {
|
|
142
|
+
throw new BootGateError("boot_cleanup_failed", { cause });
|
|
143
|
+
}
|
|
144
|
+
if (Result.isFailure(result))
|
|
145
|
+
throw result.failure;
|
|
146
|
+
}
|
|
147
|
+
function constructionModule(exportName) {
|
|
148
|
+
return `import { DurableObject } from "cloudflare:workers";
|
|
149
|
+
// Capture trusted operations BEFORE evaluating any candidate module.
|
|
150
|
+
const construct = Reflect.construct;
|
|
151
|
+
const freeze = Object.freeze;
|
|
152
|
+
// These are host test bindings, not invocation loopbacks. They deny all
|
|
153
|
+
// external operations. SQLite is the disposable facet's fresh or copied storage.
|
|
154
|
+
const unavailable = () => { throw new Error("Host test binding unavailable"); };
|
|
155
|
+
const env = freeze({
|
|
156
|
+
APP_STORAGE: freeze({
|
|
157
|
+
read: unavailable, write: unavailable, reset: unavailable
|
|
158
|
+
})
|
|
159
|
+
});
|
|
160
|
+
export class BootConstruction extends DurableObject {
|
|
161
|
+
constructor(ctx, bindings) {
|
|
162
|
+
super(ctx, bindings);
|
|
163
|
+
ctx.blockConcurrencyWhile(async () => {
|
|
164
|
+
const backend = await import("./backend.js");
|
|
165
|
+
construct(backend[${JSON.stringify(exportName)}], [ctx, env]);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
// Native constructor blocking must finish before RPC dispatch. No App-mutated
|
|
169
|
+
// Promise methods or candidate-supplied completion value decide readiness.
|
|
170
|
+
construct() {}
|
|
171
|
+
}
|
|
172
|
+
// A candidate can import this module, but cannot patch the host check.
|
|
173
|
+
freeze(BootConstruction.prototype);
|
|
174
|
+
freeze(BootConstruction);`;
|
|
175
|
+
}
|