@wenathlan/saddle 2.0.151 → 2.0.153
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 +3 -3
- package/biome.jsonc +12 -5
- package/dist/App.d.ts +5 -0
- package/dist/App.js +52 -0
- package/dist/Sol/Sol.d.ts +3 -0
- package/dist/Sol/Sol.js +46 -0
- package/dist/Sol/agentbrowser/agentbrowser.d.ts +9 -0
- package/dist/Sol/agentbrowser/agentbrowser.js +121 -0
- package/dist/Sol/architecture/architecture.d.ts +9 -0
- package/dist/Sol/architecture/architecture.js +93 -0
- package/dist/Sol/compute/compute.d.ts +9 -0
- package/dist/Sol/compute/compute.js +22 -0
- package/dist/Sol/console/console.d.ts +15 -0
- package/dist/Sol/console/console.js +292 -0
- package/dist/Sol/dashboard/dashboard.d.ts +10 -0
- package/dist/Sol/dashboard/dashboard.js +181 -0
- package/dist/Sol/docs/docs.d.ts +9 -0
- package/dist/Sol/docs/docs.js +28 -0
- package/dist/Sol/home/home.d.ts +35 -0
- package/dist/Sol/home/home.js +239 -0
- package/dist/Sol/integrations/integrations.d.ts +9 -0
- package/dist/Sol/integrations/integrations.js +83 -0
- package/dist/Sol/intro/intro.d.ts +7 -0
- package/dist/Sol/intro/intro.js +102 -0
- package/dist/Sol/login/login.d.ts +16 -0
- package/dist/Sol/login/login.js +49 -0
- package/dist/Sol/notfound/notfound.d.ts +9 -0
- package/dist/Sol/notfound/notfound.js +15 -0
- package/dist/Sol/onboarding/onboarding.d.ts +25 -0
- package/dist/Sol/onboarding/onboarding.js +60 -0
- package/dist/Sol/playground/playground.d.ts +9 -0
- package/dist/Sol/playground/playground.js +127 -0
- package/dist/Sol/register/register.d.ts +16 -0
- package/dist/Sol/register/register.js +193 -0
- package/dist/Sol/shell/Shell.d.ts +124 -0
- package/dist/Sol/shell/Shell.js +335 -0
- package/dist/Sol/wingets/ComputeBootladder.d.ts +6 -0
- package/dist/Sol/wingets/ComputeBootladder.js +71 -0
- package/dist/Sol/wingets/ComputeModalitycatalog.d.ts +7 -0
- package/dist/Sol/wingets/ComputeModalitycatalog.js +71 -0
- package/dist/Sol/wingets/ConsoleApibadge.d.ts +7 -0
- package/dist/Sol/wingets/ConsoleApibadge.js +5 -0
- package/dist/Sol/wingets/ConsoleEdgelaw.d.ts +6 -0
- package/dist/Sol/wingets/ConsoleEdgelaw.js +36 -0
- package/dist/Sol/wingets/ConsoleEventstimeline.d.ts +19 -0
- package/dist/Sol/wingets/ConsoleEventstimeline.js +12 -0
- package/dist/Sol/wingets/ConsoleSnapshotcontrols.d.ts +8 -0
- package/dist/Sol/wingets/ConsoleSnapshotcontrols.js +8 -0
- package/dist/Sol/wingets/ConsoleSpecpanel.d.ts +21 -0
- package/dist/Sol/wingets/ConsoleSpecpanel.js +75 -0
- package/dist/Sol/wingets/ConsoleTerminal.d.ts +22 -0
- package/dist/Sol/wingets/ConsoleTerminal.js +77 -0
- package/dist/Sol/wingets/DashboardAccountpanel.d.ts +18 -0
- package/dist/Sol/wingets/DashboardAccountpanel.js +65 -0
- package/dist/Sol/wingets/DashboardAdminpanel.d.ts +6 -0
- package/dist/Sol/wingets/DashboardAdminpanel.js +90 -0
- package/dist/Sol/wingets/DashboardAuditlog.d.ts +8 -0
- package/dist/Sol/wingets/DashboardAuditlog.js +50 -0
- package/dist/Sol/wingets/DashboardCreatesandboxform.d.ts +10 -0
- package/dist/Sol/wingets/DashboardCreatesandboxform.js +71 -0
- package/dist/Sol/wingets/DashboardEventstimeline.d.ts +8 -0
- package/dist/Sol/wingets/DashboardEventstimeline.js +83 -0
- package/dist/Sol/wingets/DashboardGlobalsandboxestable.d.ts +8 -0
- package/dist/Sol/wingets/DashboardGlobalsandboxestable.js +57 -0
- package/dist/Sol/wingets/DashboardMeshnodestable.d.ts +8 -0
- package/dist/Sol/wingets/DashboardMeshnodestable.js +103 -0
- package/dist/Sol/wingets/DashboardSandboxlist.d.ts +10 -0
- package/dist/Sol/wingets/DashboardSandboxlist.js +163 -0
- package/dist/Sol/wingets/DashboardUserstable.d.ts +8 -0
- package/dist/Sol/wingets/DashboardUserstable.js +78 -0
- package/dist/Sol/wingets/HomeEntrygate.d.ts +23 -0
- package/dist/Sol/wingets/HomeEntrygate.js +73 -0
- package/dist/Sol/wingets/HomeMetricstrip.d.ts +9 -0
- package/dist/Sol/wingets/HomeMetricstrip.js +4 -0
- package/dist/Sol/wingets/HomeRuntimediagram.d.ts +1 -0
- package/dist/Sol/wingets/HomeRuntimediagram.js +22 -0
- package/dist/Sol/wingets/LoginForm.d.ts +19 -0
- package/dist/Sol/wingets/LoginForm.js +141 -0
- package/dist/Sol/wingets/RegisterPasswordconfirm.d.ts +13 -0
- package/dist/Sol/wingets/RegisterPasswordconfirm.js +16 -0
- package/dist/Sol/wingets/RegisterPasswordmeter.d.ts +19 -0
- package/dist/Sol/wingets/RegisterPasswordmeter.js +65 -0
- package/dist/Sol/wingets/RegisterUsernamerules.d.ts +11 -0
- package/dist/Sol/wingets/RegisterUsernamerules.js +16 -0
- package/dist/Sol/wingets/index.d.ts +39 -0
- package/dist/Sol/wingets/index.js +39 -0
- package/dist/alternatives.d.ts +508 -0
- package/dist/alternatives.js +950 -0
- package/dist/api.d.ts +154 -0
- package/dist/api.js +174 -0
- package/dist/auth.d.ts +268 -0
- package/dist/auth.js +476 -0
- package/dist/automation.d.ts +425 -0
- package/dist/automation.js +566 -0
- package/dist/browser.d.ts +561 -0
- package/dist/browser.js +1058 -0
- package/dist/capacitor.config.d.ts +45 -0
- package/dist/capacitor.config.js +48 -0
- package/dist/catalog.d.ts +43 -0
- package/dist/catalog.js +44 -0
- package/dist/cli.d.ts +16 -0
- package/dist/cli.js +168 -0
- package/dist/communication.d.ts +181 -0
- package/dist/communication.js +376 -0
- package/dist/compute.d.ts +1160 -0
- package/dist/compute.js +2272 -0
- package/dist/dashboardlib.d.ts +58 -0
- package/dist/dashboardlib.js +161 -0
- package/dist/db.d.ts +569 -0
- package/dist/db.js +1080 -0
- package/dist/distribution.d.ts +528 -0
- package/dist/distribution.js +810 -0
- package/dist/drizzle.config.d.ts +20 -0
- package/dist/drizzle.config.js +18 -0
- package/dist/edgelandes.d.ts +70 -0
- package/dist/edgelandes.host.d.ts +26 -0
- package/dist/edgelandes.host.js +1089 -0
- package/dist/edgelandes.inproc.d.ts +27 -0
- package/dist/edgelandes.inproc.js +952 -0
- package/dist/edgelandes.js +1133 -0
- package/dist/edgelandes.ninety.d.ts +31 -0
- package/dist/edgelandes.ninety.js +174 -0
- package/dist/edgelandes.virtual.d.ts +25 -0
- package/dist/edgelandes.virtual.js +629 -0
- package/dist/edgeplane.d.ts +114 -0
- package/dist/edgeplane.js +305 -0
- package/dist/examplelocaljob.d.ts +1 -0
- package/{docs/example-localjob.ts → dist/examplelocaljob.js} +0 -1
- package/dist/execution.d.ts +529 -0
- package/dist/execution.js +760 -0
- package/dist/family.d.ts +16 -0
- package/dist/family.js +42 -0
- package/dist/familyurl.d.ts +21 -0
- package/dist/familyurl.js +24 -0
- package/dist/format.d.ts +18 -0
- package/dist/format.js +59 -0
- package/dist/foundation.d.ts +172 -0
- package/dist/foundation.js +312 -0
- package/dist/index.d.ts +735 -0
- package/dist/index.js +1020 -0
- package/dist/integration.d.ts +235 -0
- package/dist/integration.js +228 -0
- package/dist/intelligence.d.ts +93 -0
- package/dist/intelligence.js +144 -0
- package/dist/isolation.d.ts +128 -0
- package/dist/isolation.js +95 -0
- package/dist/labels.d.ts +665 -0
- package/dist/labels.js +751 -0
- package/dist/localauth.d.ts +127 -0
- package/dist/localauth.js +413 -0
- package/dist/media.d.ts +343 -0
- package/dist/media.js +985 -0
- package/dist/mesh.d.ts +177 -0
- package/dist/mesh.js +468 -0
- package/dist/modalityregistry.d.ts +109 -0
- package/dist/modalityregistry.js +1386 -0
- package/dist/modes.d.ts +358 -0
- package/dist/modes.js +133 -0
- package/dist/onboardingstate.d.ts +79 -0
- package/dist/onboardingstate.js +282 -0
- package/dist/orchestrator.d.ts +1338 -0
- package/dist/orchestrator.js +4310 -0
- package/dist/packager/targetcli.js +4 -0
- package/dist/paths.d.ts +2 -0
- package/dist/paths.js +14 -0
- package/dist/performance.d.ts +415 -0
- package/dist/performance.js +826 -0
- package/dist/quantum.d.ts +809 -0
- package/dist/quantum.js +2072 -0
- package/dist/render.d.ts +380 -0
- package/dist/render.js +1072 -0
- package/dist/runall.d.ts +2 -0
- package/dist/runall.js +39 -0
- package/dist/runtimeprobe.d.ts +4 -0
- package/dist/runtimeprobe.js +8 -0
- package/dist/sandbox.d.ts +355 -0
- package/dist/sandbox.js +1721 -0
- package/dist/sandboxenvprofile.d.ts +29 -0
- package/dist/sandboxenvprofile.js +89 -0
- package/dist/sandboxprofile.d.ts +137 -0
- package/dist/sandboxprofile.js +241 -0
- package/dist/sandboxqueue.d.ts +108 -0
- package/dist/sandboxqueue.js +201 -0
- package/dist/sandboxquota.d.ts +121 -0
- package/dist/sandboxquota.js +236 -0
- package/dist/scheduler.d.ts +595 -0
- package/dist/scheduler.js +1258 -0
- package/dist/scriptlanes.d.ts +182 -0
- package/dist/scriptlanes.js +660 -0
- package/dist/scripts.buildengine.d.ts +1 -0
- package/dist/scripts.buildengine.js +39 -0
- package/dist/scripts.securitygate.d.ts +1 -0
- package/dist/scripts.securitygate.js +68 -0
- package/dist/scripts.validateflatnative.d.ts +1 -0
- package/dist/scripts.validateflatnative.js +72 -0
- package/dist/security.d.ts +500 -0
- package/dist/security.js +1169 -0
- package/dist/seedcatalog.d.ts +13 -0
- package/dist/seedcatalog.js +46 -0
- package/dist/server.d.ts +20 -0
- package/dist/server.js +53 -0
- package/dist/speccatalog.d.ts +31 -0
- package/dist/speccatalog.js +163 -0
- package/dist/tiers.d.ts +1001 -0
- package/dist/tiers.js +2169 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +5 -0
- package/dist/vaulthandoff.d.ts +141 -0
- package/dist/vaulthandoff.js +444 -0
- package/dist/virtual.d.ts +640 -0
- package/dist/virtual.js +1284 -0
- package/dist/virtualcpu.d.ts +315 -0
- package/dist/virtualcpu.js +1180 -0
- package/dist/virtualgpu.d.ts +344 -0
- package/dist/virtualgpu.js +1000 -0
- package/dist/virtualization.d.ts +606 -0
- package/dist/virtualization.js +1186 -0
- package/dist/virtualmemory.d.ts +817 -0
- package/dist/virtualmemory.js +1795 -0
- package/dist/vite.config.d.ts +3 -0
- package/dist/vite.config.js +43 -0
- package/dist/vitest.config.d.ts +2 -0
- package/dist/vitest.config.js +25 -0
- package/dist/webserver.d.ts +166 -0
- package/dist/webserver.js +2226 -0
- package/dist/workloadlaw.d.ts +133 -0
- package/dist/workloadlaw.js +193 -0
- package/docs/artifactavailability.md +2 -2
- package/docs/{containerplatforms-2.1.0.md → containerplatforms.2.1.0.md} +1 -1
- package/docs/{documentmap-1.8.12.md → documentmap.1.8.12.md} +1 -1
- package/docs/ninety.modalities.md +108 -0
- package/docs/planning.ninety.md +391 -0
- package/docs/{releasenotes-1.8.13.md → releasenotes.1.8.13.md} +2 -2
- package/docs/{releasenotes-1.8.14.md → releasenotes.1.8.14.md} +1 -1
- package/docs/{repo-synthesis-1.8.16.md → repo.synthesis.1.8.16.md} +1 -1
- package/docs/{todo-1.8.15.md → todo.1.8.15.md} +1 -1
- package/docs/{todo-1.8.16.md → todo.1.8.16.md} +5 -5
- package/docs/todo.md +2 -2
- package/docs/workloadlaw.md +104 -0
- package/index.ts +1 -1
- package/manifest.json +1 -1
- package/modalityregistry.ts +1371 -34
- package/orchestrator.ts +1 -1
- package/package.json +78 -33
- package/docs/CONVERSA.txt +0 -1523
- package/docs/architecture-1.8.19.png +0 -0
- package/docs/assets/architecture.svg +0 -45
- package/docs/assets/saddle-browser-trace.webp +0 -0
- package/docs/assets/saddle-hero-bridge.webp +0 -0
- package/docs/assets/saddle-mark.webp +0 -0
- package/docs/assets/saddle-runtime-map.webp +0 -0
- package/docs/assets/saddlemark.svg +0 -13
- package/docs/saddle.archive.1.8.17.tar.gz.gpg +0 -0
- /package/docs/{architecture-1.8.19.md → architecture.1.8.19.md} +0 -0
- /package/docs/{architecture-1.8.19.mmd → architecture.1.8.19.mmd} +0 -0
- /package/docs/{artifactresearch-1.8.9.md → artifactresearch.1.8.9.md} +0 -0
- /package/docs/{batches-1816-1820.md → batches.1816.to.1820.md} +0 -0
- /package/docs/{brancharchive-2026-08-18.md → brancharchive.2026.08.18.md} +0 -0
- /package/docs/{containerplatforms-1.8.17.md → containerplatforms.1.8.17.md} +0 -0
- /package/docs/{e2ugh-engine.md → e2ugh.engine.md} +0 -0
- /package/docs/{flatbuildresearch-1.8.11.md → flatbuildresearch.1.8.11.md} +0 -0
- /package/docs/{licenseaudit-1.8.12.md → licenseaudit.1.8.12.md} +0 -0
- /package/docs/{mobileconversionresearch-1.8.10.md → mobileconversionresearch.1.8.10.md} +0 -0
- /package/docs/{native-wrappers.md → native.wrappers.md} +0 -0
- /package/docs/{nativeidentityresearch-1.8.11.md → nativeidentityresearch.1.8.11.md} +0 -0
- /package/docs/{releasenotes-1.8.12.md → releasenotes.1.8.12.md} +0 -0
- /package/docs/{releasenotes-1.8.15.md → releasenotes.1.8.15.md} +0 -0
- /package/docs/{releasenotes-1.8.16.md → releasenotes.1.8.16.md} +0 -0
- /package/docs/{releasenotes-1.8.17.md → releasenotes.1.8.17.md} +0 -0
- /package/docs/{releasenotes-1.8.18.md → releasenotes.1.8.18.md} +0 -0
- /package/docs/{releasenotes-1.8.19.md → releasenotes.1.8.19.md} +0 -0
- /package/docs/{reorganization-1.8.8.md → reorganization.1.8.8.md} +0 -0
- /package/docs/{reorganization-1.8.9.md → reorganization.1.8.9.md} +0 -0
- /package/docs/{repo-analysis-1.8.21.json → repo.analysis.1.8.21.json} +0 -0
- /package/docs/{repo-candidates-1.8.21.jsonl → repo.candidates.1.8.21.jsonl} +0 -0
- /package/docs/{repo-research-1.8.16.md → repo.research.1.8.16.md} +0 -0
- /package/docs/{repo-research-1.8.21.md → repo.research.1.8.21.md} +0 -0
- /package/docs/{repo-selected-1.8.21.json → repo.selected.1.8.21.json} +0 -0
- /package/docs/{repo-synthesis-1.8.21.md → repo.synthesis.1.8.21.md} +0 -0
- /package/docs/{research-1.8.18-isolation.md → research.1.8.18.isolation.md} +0 -0
- /package/docs/{research-1.8.19-virtual-browser.md → research.1.8.19.virtual.browser.md} +0 -0
- /package/docs/{scanningresearch-1.8.11.md → scanningresearch.1.8.11.md} +0 -0
- /package/docs/{securityaudit-1.8.7.md → securityaudit.1.8.7.md} +0 -0
- /package/docs/{securityreferences-1.8.11.md → securityreferences.1.8.11.md} +0 -0
- /package/docs/{securityresearch-1.8.11.md → securityresearch.1.8.11.md} +0 -0
- /package/docs/{signingresearch-1.8.12.md → signingresearch.1.8.12.md} +0 -0
- /package/docs/{tauri-glib-risk-1.8.11.md → tauri.glib.risk.1.8.11.md} +0 -0
- /package/docs/{todo-1.8.18.md → todo.1.8.18.md} +0 -0
- /package/docs/{todo-1.8.19.md → todo.1.8.19.md} +0 -0
- /package/docs/{toolchainresearch-1.8.9.md → toolchainresearch.1.8.9.md} +0 -0
- /package/docs/{web-duplicatecleanup.md → web.duplicatecleanup.md} +0 -0
- /package/docs/{web-ideas.md → web.ideas.md} +0 -0
- /package/docs/{workflowimprovements-2026-08-19.md → workflowimprovements.2026.08.19.md} +0 -0
|
@@ -0,0 +1,952 @@
|
|
|
1
|
+
// # edgelandes.inproc.ts — the in-process modality lanes of the ninety
|
|
2
|
+
// modalities wave (group b): the ram recipes, the delivery wire frames,
|
|
3
|
+
// the storage wire handshakes, the webcontainer seat and the local
|
|
4
|
+
// opt-in gate — nineteen registry modalities wired as honest pure
|
|
5
|
+
// typescript execution lanes that run inside the engine process itself.
|
|
6
|
+
// No host binary is spawned and no network socket is opened: the code
|
|
7
|
+
// field is the program or payload the lane interprets (a ram recipe
|
|
8
|
+
// manifest, a wire payload, a sql manifest, a javascript module) and the
|
|
9
|
+
// lane answers real computed output — mounted trees, framed bytes,
|
|
10
|
+
// handshake packets, captured exports. The laws every lane keeps:
|
|
11
|
+
// - the visitor law: everything runs server-side in the engine process,
|
|
12
|
+
// the visitor browser, gpu, cpu and storage are never touched (the
|
|
13
|
+
// local-optin lane is the only lane that may even name the visitor
|
|
14
|
+
// machine, and only under declared consent — and even then it answers
|
|
15
|
+
// a manifest, it never touches the machine);
|
|
16
|
+
// - the oom guard: every ram recipe reads its byte ceiling from
|
|
17
|
+
// inputs.budgetmb (the default 64 mb) and refuses to materialize a
|
|
18
|
+
// single byte over it (RAM_QUOTA_EXCEEDED), reading the declared size
|
|
19
|
+
// from the manifest numbers before any allocation rides;
|
|
20
|
+
// - the honest payload law: a lane whose manifest is not valid json or
|
|
21
|
+
// not the recipe's shape answers INVALID_LANE_PAYLOAD, a module that
|
|
22
|
+
// throws answers VM_MODULE_FAILED — no lane fakes a run;
|
|
23
|
+
// - the determinism law: fixed masks, fixed capability sets, fnv-1a
|
|
24
|
+
// hashes and stored-block zlib streams — the same payload answers the
|
|
25
|
+
// same bytes every run, no crypto, no randomness, no clock;
|
|
26
|
+
// - the artifact law: every lane answers one artifact under the mime its
|
|
27
|
+
// registry record declares (the wire, disk and frame bytes ride inside
|
|
28
|
+
// the artifact manifest as hex so the mime contract stays true).
|
|
29
|
+
import vm from "node:vm";
|
|
30
|
+
import { edgelaneerror } from "./edgelandes.ts";
|
|
31
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
32
|
+
/* Section 2: the shared recipe surface — encoders, hashes and the manifest readers. */
|
|
33
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
34
|
+
/** the artifact mime of the ram family (the registry record declares it). */
|
|
35
|
+
const rammime = "application/vnd.devthink.ram+json";
|
|
36
|
+
/** the artifact mime of the delivery family (the registry record declares it). */
|
|
37
|
+
const cdnmime = "application/vnd.devthink.cdn+json";
|
|
38
|
+
/** the artifact mime of the storage wire family (the registry record declares it). */
|
|
39
|
+
const storagemime = "application/vnd.devthink.storage+json";
|
|
40
|
+
/** the artifact mime of the browser family (the registry record declares it). */
|
|
41
|
+
const browsermime = "application/vnd.devthink.browser+json";
|
|
42
|
+
/** the artifact mime of the local family (the registry record declares it). */
|
|
43
|
+
const localmime = "application/vnd.devthink.local+json";
|
|
44
|
+
/** the default ram byte ceiling of the oom guard: 64 mb. */
|
|
45
|
+
const defaultbudgetmb = 64;
|
|
46
|
+
/** the bytes of one megabyte (the oom guard's unit). */
|
|
47
|
+
const bytespermib = 1048576;
|
|
48
|
+
/** the shared utf8 encoder of the lane surfaces. */
|
|
49
|
+
const utf8 = new TextEncoder();
|
|
50
|
+
/** the safe message of an unknown thrown value (no stack rides a lane answer). */
|
|
51
|
+
function errormessage(error) {
|
|
52
|
+
return error instanceof Error ? error.message : String(error ?? "unknown lane failure");
|
|
53
|
+
}
|
|
54
|
+
/** encodes one value as one stdout line (json when possible, string otherwise). */
|
|
55
|
+
function encodeline(value) {
|
|
56
|
+
try {
|
|
57
|
+
const encoded = JSON.stringify(value);
|
|
58
|
+
return encoded === undefined ? String(value) : encoded;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return String(value);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** encodes bytes as lowercase hex (the manifest's byte carrying form). */
|
|
65
|
+
function tohex(bytes) {
|
|
66
|
+
let hex = "";
|
|
67
|
+
for (let index = 0; index < bytes.byteLength; index += 1)
|
|
68
|
+
hex += bytes[index].toString(16).padStart(2, "0");
|
|
69
|
+
return hex;
|
|
70
|
+
}
|
|
71
|
+
/** decodes one hex string into bytes: an even-length hex string only —
|
|
72
|
+
* anything else answers INVALID_LANE_PAYLOAD naming the manifest field. */
|
|
73
|
+
function fromhex(lane, value, field) {
|
|
74
|
+
if (typeof value !== "string" || value.length % 2 !== 0 || /[^0-9a-fA-F]/.test(value)) {
|
|
75
|
+
throw edgelaneerror("INVALID_LANE_PAYLOAD", `the lane ${lane} requires hex bytes in the manifest field ${field}`);
|
|
76
|
+
}
|
|
77
|
+
const bytes = new Uint8Array(value.length / 2);
|
|
78
|
+
for (let index = 0; index < bytes.byteLength; index += 1) {
|
|
79
|
+
bytes[index] = Number.parseInt(value.slice(index * 2, index * 2 + 2), 16);
|
|
80
|
+
}
|
|
81
|
+
return bytes;
|
|
82
|
+
}
|
|
83
|
+
/** the fnv-1a 32 hash of one string, answered as 8 lowercase hex chars —
|
|
84
|
+
* the lane's deterministic content addressing (no crypto, no randomness). */
|
|
85
|
+
function fnv1a(text) {
|
|
86
|
+
let hash = 0x811c9dc5;
|
|
87
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
88
|
+
hash ^= text.charCodeAt(index);
|
|
89
|
+
hash = Math.imul(hash, 0x01000193);
|
|
90
|
+
}
|
|
91
|
+
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
92
|
+
}
|
|
93
|
+
/** the keyed fnv-1a 32 hash — the signer's key walks the offset basis
|
|
94
|
+
* before the message rides: the lane's documented deterministic signer,
|
|
95
|
+
* honest about never claiming aws sigv4. */
|
|
96
|
+
function keyedfnv1a(key, message) {
|
|
97
|
+
let hash = 0x811c9dc5;
|
|
98
|
+
for (let index = 0; index < key.length; index += 1) {
|
|
99
|
+
hash ^= key.charCodeAt(index);
|
|
100
|
+
hash = Math.imul(hash, 0x01000193);
|
|
101
|
+
}
|
|
102
|
+
for (let index = 0; index < message.length; index += 1) {
|
|
103
|
+
hash ^= message.charCodeAt(index);
|
|
104
|
+
hash = Math.imul(hash, 0x01000193);
|
|
105
|
+
}
|
|
106
|
+
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
107
|
+
}
|
|
108
|
+
/** parses the code field as one json object manifest: a non-json payload
|
|
109
|
+
* or a non-object document answers INVALID_LANE_PAYLOAD. */
|
|
110
|
+
function parsemanifest(lane, code) {
|
|
111
|
+
let parsed;
|
|
112
|
+
try {
|
|
113
|
+
parsed = JSON.parse(code);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
throw edgelaneerror("INVALID_LANE_PAYLOAD", `the lane ${lane} requires a json object manifest in the code field: ${errormessage(error)}`);
|
|
117
|
+
}
|
|
118
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
119
|
+
throw edgelaneerror("INVALID_LANE_PAYLOAD", `the lane ${lane} requires a json object manifest in the code field`);
|
|
120
|
+
}
|
|
121
|
+
return parsed;
|
|
122
|
+
}
|
|
123
|
+
/** reads one non-negative safe integer manifest field. */
|
|
124
|
+
function manifestuint(lane, value, field) {
|
|
125
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
|
|
126
|
+
throw edgelaneerror("INVALID_LANE_PAYLOAD", `the lane ${lane} requires a non-negative integer in the manifest field ${field}`);
|
|
127
|
+
}
|
|
128
|
+
return value;
|
|
129
|
+
}
|
|
130
|
+
/** reads one nonempty string manifest field. */
|
|
131
|
+
function manifesttext(lane, value, field) {
|
|
132
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
133
|
+
throw edgelaneerror("INVALID_LANE_PAYLOAD", `the lane ${lane} requires a nonempty string in the manifest field ${field}`);
|
|
134
|
+
}
|
|
135
|
+
return value;
|
|
136
|
+
}
|
|
137
|
+
/** reads one [offset, length] manifest pair of non-negative integers. */
|
|
138
|
+
function manifestwindow(lane, value, field) {
|
|
139
|
+
if (!Array.isArray(value) || value.length !== 2) {
|
|
140
|
+
throw edgelaneerror("INVALID_LANE_PAYLOAD", `the lane ${lane} requires an [offset, length] pair in the manifest field ${field}`);
|
|
141
|
+
}
|
|
142
|
+
return [manifestuint(lane, value[0], `${field}[0]`), manifestuint(lane, value[1], `${field}[1]`)];
|
|
143
|
+
}
|
|
144
|
+
/** reads one manifest file map (name → hex bytes), answering the decoded
|
|
145
|
+
* entries sorted by name (code point order — no locale walks the answer). */
|
|
146
|
+
function manifestfiles(lane, value, field) {
|
|
147
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
148
|
+
throw edgelaneerror("INVALID_LANE_PAYLOAD", `the lane ${lane} requires a name to hex file map in the manifest field ${field}`);
|
|
149
|
+
}
|
|
150
|
+
const entries = [];
|
|
151
|
+
for (const [name, hex] of Object.entries(value)) {
|
|
152
|
+
if (name.length === 0) {
|
|
153
|
+
throw edgelaneerror("INVALID_LANE_PAYLOAD", `the lane ${lane} refuses an empty file name in the manifest field ${field}`);
|
|
154
|
+
}
|
|
155
|
+
entries.push([name, fromhex(lane, hex, `${field}.${name}`)]);
|
|
156
|
+
}
|
|
157
|
+
return entries.sort((left, right) => (left[0] < right[0] ? -1 : left[0] > right[0] ? 1 : 0));
|
|
158
|
+
}
|
|
159
|
+
/** reads the oom guard ceiling in bytes: inputs.budgetmb (a non-negative
|
|
160
|
+
* finite number of megabytes) or the 64 mb default. */
|
|
161
|
+
function ramceiling(inputs) {
|
|
162
|
+
if (typeof inputs === "object" && inputs !== null) {
|
|
163
|
+
const budget = inputs.budgetmb;
|
|
164
|
+
if (typeof budget === "number" && Number.isFinite(budget) && budget >= 0)
|
|
165
|
+
return budget * bytespermib;
|
|
166
|
+
}
|
|
167
|
+
return defaultbudgetmb * bytespermib;
|
|
168
|
+
}
|
|
169
|
+
/** the oom guard of every ram lane: the declared bytes the manifest asks
|
|
170
|
+
* the lane to materialize are refused over the ceiling before a single
|
|
171
|
+
* allocation rides. */
|
|
172
|
+
function assertramquota(lane, declaredbytes, inputs) {
|
|
173
|
+
const ceiling = ramceiling(inputs);
|
|
174
|
+
if (declaredbytes > ceiling) {
|
|
175
|
+
throw edgelaneerror("RAM_QUOTA_EXCEEDED", `the ram lane ${lane} refuses to materialize ${declaredbytes} bytes under its ${ceiling} byte ceiling (inputs.budgetmb)`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/** answers one lane output whose stdout is the encodeline report and
|
|
179
|
+
* whose artifact is the json manifest under the lane mime. */
|
|
180
|
+
function lanereport(mime, report, manifest) {
|
|
181
|
+
return {
|
|
182
|
+
stdout: encodeline(report),
|
|
183
|
+
artifact: { mime, bytes: utf8.encode(JSON.stringify(manifest)) },
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
187
|
+
/* Section 3: the ram family — the memory ladder recipes under the oom guard. */
|
|
188
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
189
|
+
/** the rfc1950 adler32 checksum of one byte array (the stored-block
|
|
190
|
+
* zlib wrapper's closing word, big-endian in the stream). */
|
|
191
|
+
function adler32(bytes) {
|
|
192
|
+
let a = 1;
|
|
193
|
+
let b = 0;
|
|
194
|
+
for (let index = 0; index < bytes.byteLength; index += 1) {
|
|
195
|
+
a = (a + bytes[index]) % 65521;
|
|
196
|
+
b = (b + a) % 65521;
|
|
197
|
+
}
|
|
198
|
+
return ((b << 16) | a) >>> 0;
|
|
199
|
+
}
|
|
200
|
+
/** compresses one byte page with the lane's real stored-block zlib
|
|
201
|
+
* wrapper: the two byte rfc1950 header (0x78 0x01), the deflate stored
|
|
202
|
+
* blocks (bfinal on the last block, the len and one's-complement nlen
|
|
203
|
+
* little-endian, at most 65535 raw bytes per block) and the rfc1950
|
|
204
|
+
* adler32 checksum big-endian — a real, deterministic,
|
|
205
|
+
* decompressor-verifiable stream with no entropy coding. */
|
|
206
|
+
function storedzlib(page) {
|
|
207
|
+
const blockcount = Math.max(1, Math.ceil(page.byteLength / 65535));
|
|
208
|
+
const stream = new Uint8Array(2 + blockcount * 5 + page.byteLength + 4);
|
|
209
|
+
stream[0] = 0x78;
|
|
210
|
+
stream[1] = 0x01;
|
|
211
|
+
let offset = 2;
|
|
212
|
+
for (let index = 0; index < blockcount; index += 1) {
|
|
213
|
+
const start = index * 65535;
|
|
214
|
+
const chunk = page.subarray(start, Math.min(start + 65535, page.byteLength));
|
|
215
|
+
stream[offset] = index === blockcount - 1 ? 1 : 0;
|
|
216
|
+
offset += 1;
|
|
217
|
+
stream[offset] = chunk.byteLength & 0xff;
|
|
218
|
+
stream[offset + 1] = chunk.byteLength >> 8;
|
|
219
|
+
offset += 2;
|
|
220
|
+
const nlen = ~chunk.byteLength & 0xffff;
|
|
221
|
+
stream[offset] = nlen & 0xff;
|
|
222
|
+
stream[offset + 1] = nlen >> 8;
|
|
223
|
+
offset += 2;
|
|
224
|
+
stream.set(chunk, offset);
|
|
225
|
+
offset += chunk.byteLength;
|
|
226
|
+
}
|
|
227
|
+
const checksum = adler32(page);
|
|
228
|
+
stream[offset] = (checksum >>> 24) & 0xff;
|
|
229
|
+
stream[offset + 1] = (checksum >>> 16) & 0xff;
|
|
230
|
+
stream[offset + 2] = (checksum >>> 8) & 0xff;
|
|
231
|
+
stream[offset + 3] = checksum & 0xff;
|
|
232
|
+
return stream;
|
|
233
|
+
}
|
|
234
|
+
/** ram-tmpfs: the in-memory tmpfs lane. The code field is the mount
|
|
235
|
+
* manifest {"mount":"tmpfs","files":{"<name>":"<hex>"}}: the lane mounts
|
|
236
|
+
* the files into one in-memory tree (a map of path to bytes) under the
|
|
237
|
+
* oom guard and answers the mounted report — file count and total bytes
|
|
238
|
+
* on stdout, the serialized tree manifest as the artifact. */
|
|
239
|
+
async function ramtmpfslane(code, inputs, _deadline) {
|
|
240
|
+
const manifest = parsemanifest("ram-tmpfs", code);
|
|
241
|
+
if (manifest.mount !== "tmpfs") {
|
|
242
|
+
throw edgelaneerror("INVALID_LANE_PAYLOAD", 'the ram-tmpfs lane requires the manifest field mount "tmpfs"');
|
|
243
|
+
}
|
|
244
|
+
const entries = manifestfiles("ram-tmpfs", manifest.files, "files");
|
|
245
|
+
const totalbytes = entries.reduce((sum, entry) => sum + entry[1].byteLength, 0);
|
|
246
|
+
assertramquota("ram-tmpfs", totalbytes, inputs);
|
|
247
|
+
const tree = new Map(entries);
|
|
248
|
+
const files = {};
|
|
249
|
+
for (const [name, bytes] of tree)
|
|
250
|
+
files[name] = tohex(bytes);
|
|
251
|
+
return lanereport(rammime, { mount: "tmpfs", filecount: tree.size, totalbytes, files: [...tree.keys()] }, { mount: "tmpfs", files });
|
|
252
|
+
}
|
|
253
|
+
/** ram-mmap: the zero-copy byte window lane. The code field is the
|
|
254
|
+
* window manifest {"window":[offset,len],"bytes":"<hex>"}: the lane
|
|
255
|
+
* materializes one array buffer over the bytes (the whole mapping),
|
|
256
|
+
* answers the zero-copy subarray window view inside it and reports the
|
|
257
|
+
* window bounds — the window bytes ride the artifact. */
|
|
258
|
+
async function rammmaplane(code, inputs, _deadline) {
|
|
259
|
+
const manifest = parsemanifest("ram-mmap", code);
|
|
260
|
+
const buffer = fromhex("ram-mmap", manifest.bytes, "bytes");
|
|
261
|
+
const window = manifestwindow("ram-mmap", manifest.window, "window");
|
|
262
|
+
assertramquota("ram-mmap", buffer.byteLength, inputs);
|
|
263
|
+
const [offset, length] = window;
|
|
264
|
+
if (length > buffer.byteLength - offset) {
|
|
265
|
+
throw edgelaneerror("INVALID_LANE_PAYLOAD", `the ram-mmap window [${offset}, ${length}] escapes the ${buffer.byteLength} byte mapping`);
|
|
266
|
+
}
|
|
267
|
+
const view = buffer.subarray(offset, offset + length);
|
|
268
|
+
return lanereport(rammime, { bufferbytes: buffer.byteLength, window: [offset, offset + length], viewbytes: view.byteLength }, { window: [offset, length], bytes: tohex(view) });
|
|
269
|
+
}
|
|
270
|
+
/** ram-overlayfs: the layer union lane. The code field is the overlay
|
|
271
|
+
* manifest {"lower":{...},"upper":{...}}: the lane merges the two layers
|
|
272
|
+
* (the upper layer wins every shared name) and answers the union — the
|
|
273
|
+
* declared bytes are the merged tree only, never the double count. */
|
|
274
|
+
async function ramoverlayfslane(code, inputs, _deadline) {
|
|
275
|
+
const manifest = parsemanifest("ram-overlayfs", code);
|
|
276
|
+
const lower = manifestfiles("ram-overlayfs", manifest.lower, "lower");
|
|
277
|
+
const upper = manifestfiles("ram-overlayfs", manifest.upper, "upper");
|
|
278
|
+
const merged = new Map(lower);
|
|
279
|
+
for (const [name, bytes] of upper)
|
|
280
|
+
merged.set(name, bytes);
|
|
281
|
+
let totalbytes = 0;
|
|
282
|
+
const files = {};
|
|
283
|
+
for (const [name, bytes] of merged) {
|
|
284
|
+
totalbytes += bytes.byteLength;
|
|
285
|
+
files[name] = tohex(bytes);
|
|
286
|
+
}
|
|
287
|
+
assertramquota("ram-overlayfs", totalbytes, inputs);
|
|
288
|
+
return lanereport(rammime, { filecount: merged.size, totalbytes, lower: lower.length, upper: upper.length, merged: [...merged.keys()] }, { files });
|
|
289
|
+
}
|
|
290
|
+
/** reads the sector writes [sectorindex, hex] of the loop ram disk: the
|
|
291
|
+
* sector must sit inside the disk and the payload must fit one sector. */
|
|
292
|
+
function manifestwrites(lane, value, sectorsize, sectors) {
|
|
293
|
+
if (!Array.isArray(value)) {
|
|
294
|
+
throw edgelaneerror("INVALID_LANE_PAYLOAD", `the lane ${lane} requires a writes array in the manifest`);
|
|
295
|
+
}
|
|
296
|
+
const writes = [];
|
|
297
|
+
for (const entry of value) {
|
|
298
|
+
if (!Array.isArray(entry) || entry.length !== 2) {
|
|
299
|
+
throw edgelaneerror("INVALID_LANE_PAYLOAD", `the lane ${lane} requires [sector, hex] write pairs`);
|
|
300
|
+
}
|
|
301
|
+
const sector = manifestuint(lane, entry[0], "writes sector");
|
|
302
|
+
if (sector >= sectors) {
|
|
303
|
+
throw edgelaneerror("INVALID_LANE_PAYLOAD", `the lane ${lane} write names the sector ${sector} outside the ${sectors} sector disk`);
|
|
304
|
+
}
|
|
305
|
+
const bytes = fromhex(lane, entry[1], "writes bytes");
|
|
306
|
+
if (bytes.byteLength > sectorsize) {
|
|
307
|
+
throw edgelaneerror("INVALID_LANE_PAYLOAD", `the lane ${lane} write carries ${bytes.byteLength} bytes over the ${sectorsize} byte sector`);
|
|
308
|
+
}
|
|
309
|
+
writes.push([sector, bytes]);
|
|
310
|
+
}
|
|
311
|
+
return writes;
|
|
312
|
+
}
|
|
313
|
+
/** ram-loopram: the loop ram disk lane. The code field is the disk
|
|
314
|
+
* manifest {"sectorsize":n,"sectors":m,"writes":[[sector,"<hex>"],...]}:
|
|
315
|
+
* the lane materializes one sector-addressed zero-filled disk — the
|
|
316
|
+
* declared capacity (sectorsize * sectors) is what the oom guard reads
|
|
317
|
+
* from the manifest numbers before any allocation rides — applies the
|
|
318
|
+
* sector writes and answers the sector map with the disk bytes as the
|
|
319
|
+
* artifact. */
|
|
320
|
+
async function ramloopramlane(code, inputs, _deadline) {
|
|
321
|
+
const manifest = parsemanifest("ram-loopram", code);
|
|
322
|
+
const sectorsize = manifestuint("ram-loopram", manifest.sectorsize, "sectorsize");
|
|
323
|
+
const sectors = manifestuint("ram-loopram", manifest.sectors, "sectors");
|
|
324
|
+
if (sectorsize < 1) {
|
|
325
|
+
throw edgelaneerror("INVALID_LANE_PAYLOAD", "the ram-loopram lane requires a positive sectorsize");
|
|
326
|
+
}
|
|
327
|
+
const capacity = sectorsize * sectors;
|
|
328
|
+
if (!Number.isSafeInteger(capacity)) {
|
|
329
|
+
throw edgelaneerror("INVALID_LANE_PAYLOAD", "the ram-loopram disk exceeds the representable byte range");
|
|
330
|
+
}
|
|
331
|
+
assertramquota("ram-loopram", capacity, inputs);
|
|
332
|
+
const writes = manifestwrites("ram-loopram", manifest.writes ?? [], sectorsize, sectors);
|
|
333
|
+
const disk = new Uint8Array(capacity);
|
|
334
|
+
for (const [sector, bytes] of writes)
|
|
335
|
+
disk.set(bytes, sector * sectorsize);
|
|
336
|
+
const written = [...new Set(writes.map((write) => write[0]))].sort((left, right) => left - right);
|
|
337
|
+
return lanereport(rammime, { sectorsize, sectors, capacity, written }, { sectorsize, sectors, capacity, written, disk: tohex(disk) });
|
|
338
|
+
}
|
|
339
|
+
/** ram-gitram: the git object cache lane. The code field is the object
|
|
340
|
+
* manifest {"objects":[{"oid":"...","data":"<hex>"},...],"read":"oid"}:
|
|
341
|
+
* the lane loads the objects into one hash table keyed by oid (a
|
|
342
|
+
* repeated oid answers the last write) and answers the read object —
|
|
343
|
+
* the object bytes ride the artifact. */
|
|
344
|
+
async function ramgitramlane(code, inputs, _deadline) {
|
|
345
|
+
const manifest = parsemanifest("ram-gitram", code);
|
|
346
|
+
if (!Array.isArray(manifest.objects)) {
|
|
347
|
+
throw edgelaneerror("INVALID_LANE_PAYLOAD", "the ram-gitram lane requires an objects array in the manifest");
|
|
348
|
+
}
|
|
349
|
+
const objects = new Map();
|
|
350
|
+
for (const entry of manifest.objects) {
|
|
351
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
352
|
+
throw edgelaneerror("INVALID_LANE_PAYLOAD", "the ram-gitram lane requires {oid, data} object entries");
|
|
353
|
+
}
|
|
354
|
+
const record = entry;
|
|
355
|
+
const oid = manifesttext("ram-gitram", record.oid, "objects oid");
|
|
356
|
+
objects.set(oid, fromhex("ram-gitram", record.data, `objects ${oid} data`));
|
|
357
|
+
}
|
|
358
|
+
let totalbytes = 0;
|
|
359
|
+
for (const bytes of objects.values())
|
|
360
|
+
totalbytes += bytes.byteLength;
|
|
361
|
+
assertramquota("ram-gitram", totalbytes, inputs);
|
|
362
|
+
const read = manifesttext("ram-gitram", manifest.read, "read");
|
|
363
|
+
const object = objects.get(read);
|
|
364
|
+
if (object === undefined) {
|
|
365
|
+
throw edgelaneerror("INVALID_LANE_PAYLOAD", `the ram-gitram read names the unknown oid ${read}`);
|
|
366
|
+
}
|
|
367
|
+
return lanereport(rammime, { objects: objects.size, totalbytes, read, size: object.byteLength }, { oid: read, size: object.byteLength, bytes: tohex(object) });
|
|
368
|
+
}
|
|
369
|
+
/** ram-zram: the compressed page store lane. The code field is the page
|
|
370
|
+
* manifest {"pagesize":n,"pages":["<hex>",...]}: the lane compresses
|
|
371
|
+
* every page with the real stored-block zlib wrapper and answers the
|
|
372
|
+
* per page compression ratios — the compressed streams ride the artifact. */
|
|
373
|
+
async function ramzramlane(code, inputs, _deadline) {
|
|
374
|
+
const manifest = parsemanifest("ram-zram", code);
|
|
375
|
+
const pagesize = manifestuint("ram-zram", manifest.pagesize, "pagesize");
|
|
376
|
+
if (pagesize < 1) {
|
|
377
|
+
throw edgelaneerror("INVALID_LANE_PAYLOAD", "the ram-zram lane requires a positive pagesize");
|
|
378
|
+
}
|
|
379
|
+
if (!Array.isArray(manifest.pages)) {
|
|
380
|
+
throw edgelaneerror("INVALID_LANE_PAYLOAD", "the ram-zram lane requires a pages array in the manifest");
|
|
381
|
+
}
|
|
382
|
+
const pages = manifest.pages.map((page, index) => {
|
|
383
|
+
const bytes = fromhex("ram-zram", page, `pages ${index}`);
|
|
384
|
+
if (bytes.byteLength > pagesize) {
|
|
385
|
+
throw edgelaneerror("INVALID_LANE_PAYLOAD", `the ram-zram page ${index} carries ${bytes.byteLength} bytes over the ${pagesize} byte page`);
|
|
386
|
+
}
|
|
387
|
+
return bytes;
|
|
388
|
+
});
|
|
389
|
+
const rawbytes = pages.reduce((sum, page) => sum + page.byteLength, 0);
|
|
390
|
+
assertramquota("ram-zram", rawbytes, inputs);
|
|
391
|
+
const streams = pages.map((page) => storedzlib(page));
|
|
392
|
+
const compressedbytes = streams.reduce((sum, stream) => sum + stream.byteLength, 0);
|
|
393
|
+
const ratios = streams.map((stream, index) => pages[index].byteLength === 0 ? 0 : Math.round((stream.byteLength / pages[index].byteLength) * 1000) / 1000);
|
|
394
|
+
return lanereport(rammime, { pagesize, pages: pages.length, rawbytes, compressedbytes, ratios }, { pagesize, pages: pages.length, streams: streams.map((stream) => tohex(stream)) });
|
|
395
|
+
}
|
|
396
|
+
/** ram-zswap: the swap front pool lane. The code field is the pool
|
|
397
|
+
* manifest {"poolsize":n,"pages":["<hex>",...]}: the lane compresses
|
|
398
|
+
* every page with the same stored-block zlib wrapper and admits them
|
|
399
|
+
* into the LRU pool (arrival order, the oldest page evicted beyond
|
|
400
|
+
* poolsize) — the pool snapshot rides the artifact. */
|
|
401
|
+
async function ramzswaplane(code, inputs, _deadline) {
|
|
402
|
+
const manifest = parsemanifest("ram-zswap", code);
|
|
403
|
+
const poolsize = manifestuint("ram-zswap", manifest.poolsize, "poolsize");
|
|
404
|
+
if (!Array.isArray(manifest.pages)) {
|
|
405
|
+
throw edgelaneerror("INVALID_LANE_PAYLOAD", "the ram-zswap lane requires a pages array in the manifest");
|
|
406
|
+
}
|
|
407
|
+
const pages = manifest.pages.map((page, index) => fromhex("ram-zswap", page, `pages ${index}`));
|
|
408
|
+
const rawbytes = pages.reduce((sum, page) => sum + page.byteLength, 0);
|
|
409
|
+
assertramquota("ram-zswap", rawbytes, inputs);
|
|
410
|
+
const pool = new Map();
|
|
411
|
+
let evicted = 0;
|
|
412
|
+
pages.forEach((page, index) => {
|
|
413
|
+
pool.set(index, storedzlib(page));
|
|
414
|
+
while (pool.size > poolsize) {
|
|
415
|
+
const oldest = pool.keys().next();
|
|
416
|
+
if (oldest.done === true)
|
|
417
|
+
break;
|
|
418
|
+
pool.delete(oldest.value);
|
|
419
|
+
evicted += 1;
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
let compressedbytes = 0;
|
|
423
|
+
const snapshot = [];
|
|
424
|
+
for (const [index, stream] of pool) {
|
|
425
|
+
compressedbytes += stream.byteLength;
|
|
426
|
+
snapshot.push({ page: index, raw: pages[index].byteLength, compressed: tohex(stream) });
|
|
427
|
+
}
|
|
428
|
+
return lanereport(rammime, { poolsize, pages: pages.length, admitted: pool.size, evicted, compressedbytes }, { poolsize, pool: snapshot });
|
|
429
|
+
}
|
|
430
|
+
/** ram-swapfile: the swap extent file lane. The code field is the extent
|
|
431
|
+
* manifest {"extents":[[start,len,"<hex>"],...],"read":[start,len]}: the
|
|
432
|
+
* lane materializes the extent-addressed file (the extent table covers
|
|
433
|
+
* the sparse holes with zero bytes) and answers the read window — the
|
|
434
|
+
* extent table and the read bytes ride the artifact. */
|
|
435
|
+
async function ramswapfilelane(code, inputs, _deadline) {
|
|
436
|
+
const manifest = parsemanifest("ram-swapfile", code);
|
|
437
|
+
if (!Array.isArray(manifest.extents)) {
|
|
438
|
+
throw edgelaneerror("INVALID_LANE_PAYLOAD", "the ram-swapfile lane requires an extents array in the manifest");
|
|
439
|
+
}
|
|
440
|
+
const extents = [];
|
|
441
|
+
let filebytes = 0;
|
|
442
|
+
for (const entry of manifest.extents) {
|
|
443
|
+
if (!Array.isArray(entry) || entry.length !== 3) {
|
|
444
|
+
throw edgelaneerror("INVALID_LANE_PAYLOAD", "the ram-swapfile lane requires [start, length, hex] extent triples");
|
|
445
|
+
}
|
|
446
|
+
const start = manifestuint("ram-swapfile", entry[0], "extents start");
|
|
447
|
+
const length = manifestuint("ram-swapfile", entry[1], "extents length");
|
|
448
|
+
if (!Number.isSafeInteger(start + length)) {
|
|
449
|
+
throw edgelaneerror("INVALID_LANE_PAYLOAD", `the ram-swapfile extent at ${start} exceeds the representable byte range`);
|
|
450
|
+
}
|
|
451
|
+
const bytes = fromhex("ram-swapfile", entry[2], "extents bytes");
|
|
452
|
+
if (bytes.byteLength !== length) {
|
|
453
|
+
throw edgelaneerror("INVALID_LANE_PAYLOAD", `the ram-swapfile extent at ${start} declares ${length} bytes but carries ${bytes.byteLength}`);
|
|
454
|
+
}
|
|
455
|
+
extents.push({ start, length, bytes });
|
|
456
|
+
if (start + length > filebytes)
|
|
457
|
+
filebytes = start + length;
|
|
458
|
+
}
|
|
459
|
+
assertramquota("ram-swapfile", filebytes, inputs);
|
|
460
|
+
const [readstart, readlength] = manifestwindow("ram-swapfile", manifest.read, "read");
|
|
461
|
+
if (readlength > filebytes - readstart) {
|
|
462
|
+
throw edgelaneerror("INVALID_LANE_PAYLOAD", `the ram-swapfile read [${readstart}, ${readlength}] escapes the ${filebytes} byte file`);
|
|
463
|
+
}
|
|
464
|
+
const window = new Uint8Array(readlength);
|
|
465
|
+
const coveredmask = new Uint8Array(readlength);
|
|
466
|
+
let covered = 0;
|
|
467
|
+
for (const extent of extents) {
|
|
468
|
+
const from = Math.max(extent.start, readstart);
|
|
469
|
+
const to = Math.min(extent.start + extent.length, readstart + readlength);
|
|
470
|
+
for (let position = from; position < to; position += 1) {
|
|
471
|
+
if (coveredmask[position - readstart] === 0) {
|
|
472
|
+
coveredmask[position - readstart] = 1;
|
|
473
|
+
covered += 1;
|
|
474
|
+
}
|
|
475
|
+
window[position - readstart] = extent.bytes[position - extent.start];
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return lanereport(rammime, {
|
|
479
|
+
extents: extents.length,
|
|
480
|
+
filebytes,
|
|
481
|
+
read: [readstart, readstart + readlength],
|
|
482
|
+
covered,
|
|
483
|
+
holes: readlength - covered,
|
|
484
|
+
}, {
|
|
485
|
+
read: [readstart, readlength],
|
|
486
|
+
bytes: tohex(window),
|
|
487
|
+
extents: extents.map((extent) => [extent.start, extent.length]),
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
491
|
+
/* Section 4: the delivery family — the wire frame builders. */
|
|
492
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
493
|
+
/** the deterministic rfc6455 client mask of the wss lane. */
|
|
494
|
+
const wssmask = Uint8Array.of(0x01, 0x02, 0x03, 0x04);
|
|
495
|
+
/** builds one rfc6455 client text frame around the payload: fin and the
|
|
496
|
+
* text opcode 1, the mask bit, the deterministic four byte mask, the
|
|
497
|
+
* 7/16/64 bit payload length encoding and the masked payload bytes. */
|
|
498
|
+
function wssframe(payload) {
|
|
499
|
+
const length = payload.byteLength;
|
|
500
|
+
const headerbytes = length < 126 ? 2 : length < 65536 ? 4 : 10;
|
|
501
|
+
const frame = new Uint8Array(headerbytes + 4 + length);
|
|
502
|
+
frame[0] = 0x81;
|
|
503
|
+
if (length < 126) {
|
|
504
|
+
frame[1] = 0x80 | length;
|
|
505
|
+
}
|
|
506
|
+
else if (length < 65536) {
|
|
507
|
+
frame[1] = 0x80 | 126;
|
|
508
|
+
frame[2] = length >> 8;
|
|
509
|
+
frame[3] = length & 0xff;
|
|
510
|
+
}
|
|
511
|
+
else {
|
|
512
|
+
frame[1] = 0x80 | 127;
|
|
513
|
+
new DataView(frame.buffer).setBigUint64(2, BigInt(length));
|
|
514
|
+
}
|
|
515
|
+
frame.set(wssmask, headerbytes);
|
|
516
|
+
const body = headerbytes + 4;
|
|
517
|
+
for (let index = 0; index < length; index += 1)
|
|
518
|
+
frame[body + index] = payload[index] ^ wssmask[index % 4];
|
|
519
|
+
return frame;
|
|
520
|
+
}
|
|
521
|
+
/** encodes one number as a quic variable length integer (rfc 9000
|
|
522
|
+
* section 16): the two bit length prefix selects the 1, 2, 4 or 8 byte
|
|
523
|
+
* form of the datagram framing. */
|
|
524
|
+
function quicvarint(value) {
|
|
525
|
+
if (value < 64)
|
|
526
|
+
return [value];
|
|
527
|
+
if (value < 16384)
|
|
528
|
+
return [0x40 | (value >> 8), value & 0xff];
|
|
529
|
+
if (value < 1073741824) {
|
|
530
|
+
return [0x80 | ((value >> 24) & 0xff), (value >> 16) & 0xff, (value >> 8) & 0xff, value & 0xff];
|
|
531
|
+
}
|
|
532
|
+
const big = BigInt(value);
|
|
533
|
+
const bytes = [0xc0];
|
|
534
|
+
for (let shift = 56n; shift >= 0n; shift -= 8n)
|
|
535
|
+
bytes.push(Number((big >> shift) & 0xffn));
|
|
536
|
+
return bytes;
|
|
537
|
+
}
|
|
538
|
+
/** builds one grpc length-prefixed message: the one byte compressed flag
|
|
539
|
+
* 0 and the four byte big-endian payload length — exactly the bytes an
|
|
540
|
+
* http/2 DATA frame would carry. */
|
|
541
|
+
function grpcframe(payload) {
|
|
542
|
+
const frame = new Uint8Array(5 + payload.byteLength);
|
|
543
|
+
frame[0] = 0x00;
|
|
544
|
+
new DataView(frame.buffer).setUint32(1, payload.byteLength);
|
|
545
|
+
frame.set(payload, 5);
|
|
546
|
+
return frame;
|
|
547
|
+
}
|
|
548
|
+
/** builds the minimal one segment capnproto message around the payload:
|
|
549
|
+
* the little-endian 32-bit header pair (the segment count 0, then the
|
|
550
|
+
* segment word count) and the payload zero-padded to the 8 byte word
|
|
551
|
+
* boundary — the message length always answers a whole word count. */
|
|
552
|
+
function capnpmessage(payload) {
|
|
553
|
+
const words = Math.ceil(payload.byteLength / 8);
|
|
554
|
+
const message = new Uint8Array(8 + words * 8);
|
|
555
|
+
const view = new DataView(message.buffer);
|
|
556
|
+
view.setUint32(0, 0, true);
|
|
557
|
+
view.setUint32(4, words, true);
|
|
558
|
+
message.set(payload, 8);
|
|
559
|
+
return message;
|
|
560
|
+
}
|
|
561
|
+
/** delivery-cdn: the mirror manifest lane. The code field is the mirror
|
|
562
|
+
* manifest {"origin":"https://...","assets":["a.js",...]}: the lane is
|
|
563
|
+
* the mirror PLANNER — it answers the content addressed key of every
|
|
564
|
+
* asset (the fnv-1a 32 hash of the asset name, deterministic) and never
|
|
565
|
+
* fetches anything (the fetch rides the vfs seat inside the sandbox).
|
|
566
|
+
* The origin-pin law: a manifest without an origin answers
|
|
567
|
+
* INVALID_LANE_PAYLOAD. */
|
|
568
|
+
async function deliverycdnlane(code, _inputs, _deadline) {
|
|
569
|
+
const manifest = parsemanifest("delivery-cdn", code);
|
|
570
|
+
const origin = manifesttext("delivery-cdn", manifest.origin, "origin");
|
|
571
|
+
if (!Array.isArray(manifest.assets)) {
|
|
572
|
+
throw edgelaneerror("INVALID_LANE_PAYLOAD", "the delivery-cdn lane requires an assets array in the manifest");
|
|
573
|
+
}
|
|
574
|
+
const assets = manifest.assets.map((name) => manifesttext("delivery-cdn", name, "assets entry"));
|
|
575
|
+
const entries = assets.map((name) => ({ name, key: `assets/${fnv1a(name)}` }));
|
|
576
|
+
return lanereport(cdnmime, { origin, assets: entries.length, keys: entries.map((entry) => entry.key) }, { origin, assets: entries });
|
|
577
|
+
}
|
|
578
|
+
/** delivery-wss: the websocket frame writer lane. The code field is the
|
|
579
|
+
* text payload: the lane builds the real rfc6455 client frame — fin and
|
|
580
|
+
* the text opcode, the mask bit with the deterministic mask 0x01020304,
|
|
581
|
+
* the 7/16/64 bit length encoding and the masked payload — and answers
|
|
582
|
+
* the frame bytes (the first 64 ride stdout as hex, the full frame
|
|
583
|
+
* rides the artifact manifest). */
|
|
584
|
+
async function deliverywsslane(code, _inputs, _deadline) {
|
|
585
|
+
const payload = utf8.encode(code);
|
|
586
|
+
const frame = wssframe(payload);
|
|
587
|
+
const hex = tohex(frame.subarray(0, Math.min(64, frame.byteLength)));
|
|
588
|
+
return lanereport(cdnmime, {
|
|
589
|
+
opcode: 1,
|
|
590
|
+
fin: true,
|
|
591
|
+
masked: true,
|
|
592
|
+
mask: "01020304",
|
|
593
|
+
length: payload.byteLength,
|
|
594
|
+
framebytes: frame.byteLength,
|
|
595
|
+
hex,
|
|
596
|
+
}, { opcode: 1, fin: true, masked: true, mask: "01020304", length: payload.byteLength, framehex: tohex(frame) });
|
|
597
|
+
}
|
|
598
|
+
/** delivery-webtransport: the http3 datagram lane. The code field is the
|
|
599
|
+
* payload: the lane frames it as one quic-style DATAGRAM frame — the
|
|
600
|
+
* deterministic stream id 1 as a quic varint, the datagram frame type
|
|
601
|
+
* 0x40 and the varint payload length — the framed bytes ride the
|
|
602
|
+
* artifact manifest. */
|
|
603
|
+
async function deliverywebtransportlane(code, _inputs, _deadline) {
|
|
604
|
+
const payload = utf8.encode(code);
|
|
605
|
+
const streamvarint = quicvarint(1);
|
|
606
|
+
const lengthvarint = quicvarint(payload.byteLength);
|
|
607
|
+
const frame = new Uint8Array(streamvarint.length + 1 + lengthvarint.length + payload.byteLength);
|
|
608
|
+
frame.set(streamvarint, 0);
|
|
609
|
+
frame[streamvarint.length] = 0x40;
|
|
610
|
+
frame.set(lengthvarint, streamvarint.length + 1);
|
|
611
|
+
frame.set(payload, streamvarint.length + 1 + lengthvarint.length);
|
|
612
|
+
return lanereport(cdnmime, { type: "datagram", streamid: 1, length: payload.byteLength, framebytes: frame.byteLength }, { streamid: 1, type: 64, length: payload.byteLength, framehex: tohex(frame) });
|
|
613
|
+
}
|
|
614
|
+
/** delivery-grpc: the grpc frame lane. The code field is the text
|
|
615
|
+
* payload: the lane frames it as one grpc length-prefixed message — the
|
|
616
|
+
* one byte compressed flag 0 and the four byte big-endian length. */
|
|
617
|
+
async function deliverygrpclane(code, _inputs, _deadline) {
|
|
618
|
+
const payload = utf8.encode(code);
|
|
619
|
+
if (payload.byteLength > 0xffffffff) {
|
|
620
|
+
throw edgelaneerror("INVALID_LANE_PAYLOAD", "the delivery-grpc frame exceeds the four byte length ceiling");
|
|
621
|
+
}
|
|
622
|
+
const frame = grpcframe(payload);
|
|
623
|
+
return lanereport(cdnmime, { compressed: false, length: payload.byteLength, framebytes: frame.byteLength }, { compressedflag: 0, length: payload.byteLength, framehex: tohex(frame) });
|
|
624
|
+
}
|
|
625
|
+
/** delivery-capnproto: the capnproto segment lane. The code field is the
|
|
626
|
+
* payload: the lane builds the minimal one segment capnproto message —
|
|
627
|
+
* the little-endian 32-bit header pair (segment count 0, segment word
|
|
628
|
+
* count) and the payload zero-padded to the word boundary. */
|
|
629
|
+
async function deliverycapnprotolane(code, _inputs, _deadline) {
|
|
630
|
+
const payload = utf8.encode(code);
|
|
631
|
+
const message = capnpmessage(payload);
|
|
632
|
+
const words = (message.byteLength - 8) / 8;
|
|
633
|
+
return lanereport(cdnmime, { segments: 1, words, bytes: message.byteLength }, { segments: 1, words, messagehex: tohex(message) });
|
|
634
|
+
}
|
|
635
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
636
|
+
/* Section 5: the storage wire family — the handshake speakers and the signer. */
|
|
637
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
638
|
+
/** the mysql client capability set the lane speaks: long password, found
|
|
639
|
+
* rows, long flag, connect with db, protocol 41, transactions, secure
|
|
640
|
+
* connection and plugin auth — a fixed modern client set, deterministic. */
|
|
641
|
+
const mysqlclientflags = 0x0008a20f;
|
|
642
|
+
/** builds the mysql client handshake payload: the client flags and max
|
|
643
|
+
* packet (little-endian), the charset 33, the 23 byte zero filler, the
|
|
644
|
+
* null-terminated username, the sixteen byte auth response under its
|
|
645
|
+
* length byte and the null-terminated database "vault". */
|
|
646
|
+
function mysqlhandshakepayload(username, authresponse) {
|
|
647
|
+
const database = utf8.encode("vault");
|
|
648
|
+
const payload = new Uint8Array(32 + username.byteLength + 1 + 1 + 16 + database.byteLength + 1);
|
|
649
|
+
const view = new DataView(payload.buffer);
|
|
650
|
+
view.setUint32(0, mysqlclientflags, true);
|
|
651
|
+
view.setUint32(4, 0x01000000, true);
|
|
652
|
+
payload[8] = 33;
|
|
653
|
+
let offset = 32;
|
|
654
|
+
payload.set(username, offset);
|
|
655
|
+
offset += username.byteLength;
|
|
656
|
+
payload[offset] = 0;
|
|
657
|
+
offset += 1;
|
|
658
|
+
payload[offset] = 16;
|
|
659
|
+
offset += 1;
|
|
660
|
+
payload.set(authresponse, offset);
|
|
661
|
+
offset += 16;
|
|
662
|
+
payload.set(database, offset);
|
|
663
|
+
offset += database.byteLength;
|
|
664
|
+
payload[offset] = 0;
|
|
665
|
+
return payload;
|
|
666
|
+
}
|
|
667
|
+
/** the sixteen byte mysql auth response: the token's char codes (mod
|
|
668
|
+
* 256) truncated or zero-padded to the fixed sixteen byte slot — the
|
|
669
|
+
* auth-token law of the mysql lane. */
|
|
670
|
+
function mysqlauthresponse(token) {
|
|
671
|
+
const response = new Uint8Array(16);
|
|
672
|
+
if (typeof token === "string") {
|
|
673
|
+
for (let index = 0; index < Math.min(16, token.length); index += 1)
|
|
674
|
+
response[index] = token.charCodeAt(index) & 0xff;
|
|
675
|
+
}
|
|
676
|
+
return response;
|
|
677
|
+
}
|
|
678
|
+
/** builds the postgres startup message: the four byte big-endian length,
|
|
679
|
+
* the protocol 3.0 code 196608 and the null-terminated parameter list
|
|
680
|
+
* "user <name> database vault" closed by the empty parameter byte. */
|
|
681
|
+
function postgresstartup(username) {
|
|
682
|
+
const body = utf8.encode(`user\0${username}\0database\0vault\0\0`);
|
|
683
|
+
const message = new Uint8Array(8 + body.byteLength);
|
|
684
|
+
const view = new DataView(message.buffer);
|
|
685
|
+
view.setUint32(0, 8 + body.byteLength);
|
|
686
|
+
view.setUint32(4, 196608);
|
|
687
|
+
message.set(body, 8);
|
|
688
|
+
return message;
|
|
689
|
+
}
|
|
690
|
+
/** storage-libsql: the libsql seat lane. The code field is the query
|
|
691
|
+
* manifest {"sql":"select ..."}: the lane is the wire PLANNER — it
|
|
692
|
+
* composes the query envelope with the deterministic statement id (the
|
|
693
|
+
* fnv-1a 32 hash of the sql text); the daemon connection rides the host
|
|
694
|
+
* lane, this lane never opens a socket. */
|
|
695
|
+
async function storagelibsqllane(code, _inputs, _deadline) {
|
|
696
|
+
const manifest = parsemanifest("storage-libsql", code);
|
|
697
|
+
const sql = manifesttext("storage-libsql", manifest.sql, "sql");
|
|
698
|
+
const statementid = fnv1a(sql);
|
|
699
|
+
const envelope = { dialect: "libsql", sql, statementid };
|
|
700
|
+
return lanereport(storagemime, {
|
|
701
|
+
statementid,
|
|
702
|
+
sqlbytes: utf8.encode(sql).byteLength,
|
|
703
|
+
envelopebytes: utf8.encode(JSON.stringify(envelope)).byteLength,
|
|
704
|
+
}, envelope);
|
|
705
|
+
}
|
|
706
|
+
/** storage-mysql: the mysql wire handshake lane. The code field is the
|
|
707
|
+
* username (the default "saddle" when the field is empty): the lane
|
|
708
|
+
* composes the real mysql client handshake packet — the three byte
|
|
709
|
+
* little-endian payload length, the sequence byte 0 and the handshake
|
|
710
|
+
* payload. The auth-token law: inputs.token replaces the zero auth
|
|
711
|
+
* response with the token's char codes. */
|
|
712
|
+
async function storagemysqllane(code, inputs, _deadline) {
|
|
713
|
+
const username = code.length > 0 ? code : "saddle";
|
|
714
|
+
if (username.includes("\0")) {
|
|
715
|
+
throw edgelaneerror("INVALID_LANE_PAYLOAD", "the storage-mysql username must not carry a nul byte");
|
|
716
|
+
}
|
|
717
|
+
const token = typeof inputs === "object" && inputs !== null ? inputs.token : undefined;
|
|
718
|
+
const payload = mysqlhandshakepayload(utf8.encode(username), mysqlauthresponse(token));
|
|
719
|
+
if (payload.byteLength > 0xffffff) {
|
|
720
|
+
throw edgelaneerror("INVALID_LANE_PAYLOAD", "the storage-mysql handshake exceeds the three byte mysql length ceiling");
|
|
721
|
+
}
|
|
722
|
+
const packet = new Uint8Array(4 + payload.byteLength);
|
|
723
|
+
packet[0] = payload.byteLength & 0xff;
|
|
724
|
+
packet[1] = (payload.byteLength >> 8) & 0xff;
|
|
725
|
+
packet[2] = (payload.byteLength >> 16) & 0xff;
|
|
726
|
+
packet[3] = 0;
|
|
727
|
+
packet.set(payload, 4);
|
|
728
|
+
return lanereport(storagemime, { payloadbytes: payload.byteLength, packetbytes: packet.byteLength, sequence: 0, username }, { username, database: "vault", charset: 33, sequence: 0, length: payload.byteLength, packethex: tohex(packet) });
|
|
729
|
+
}
|
|
730
|
+
/** storage-postgres: the postgres wire startup lane. The code field is
|
|
731
|
+
* the username (the default "saddle"): the lane composes the real pg
|
|
732
|
+
* startup message — the big-endian length, the protocol 3.0 code and
|
|
733
|
+
* the null-terminated parameter list. */
|
|
734
|
+
async function storagepostgreslane(code, _inputs, _deadline) {
|
|
735
|
+
const username = code.length > 0 ? code : "saddle";
|
|
736
|
+
if (username.includes("\0")) {
|
|
737
|
+
throw edgelaneerror("INVALID_LANE_PAYLOAD", "the storage-postgres username must not carry a nul byte");
|
|
738
|
+
}
|
|
739
|
+
const message = postgresstartup(username);
|
|
740
|
+
return lanereport(storagemime, { length: message.byteLength, protocol: 196608, username, database: "vault" }, { length: message.byteLength, protocol: 196608, username, database: "vault", messagehex: tohex(message) });
|
|
741
|
+
}
|
|
742
|
+
/** storage-s3: the s3 request signer lane. The code field is the request
|
|
743
|
+
* manifest {"method":"GET","bucket":"name","key":"path","payload":"text"}:
|
|
744
|
+
* the lane composes the canonical request string (the method line, the
|
|
745
|
+
* /bucket/key path, the empty query line and the host header) and signs
|
|
746
|
+
* it with the lane's deterministic keyed fnv-1a signer — documented as
|
|
747
|
+
* the lane's own signer, never a claim of aws sigv4. The auth-token law:
|
|
748
|
+
* inputs.secretkey mixes the signer. */
|
|
749
|
+
async function storages3lane(code, inputs, _deadline) {
|
|
750
|
+
const manifest = parsemanifest("storage-s3", code);
|
|
751
|
+
const method = manifesttext("storage-s3", manifest.method, "method");
|
|
752
|
+
const bucket = manifesttext("storage-s3", manifest.bucket, "bucket");
|
|
753
|
+
if (bucket.includes("/")) {
|
|
754
|
+
throw edgelaneerror("INVALID_LANE_PAYLOAD", "the storage-s3 bucket must not carry a path separator");
|
|
755
|
+
}
|
|
756
|
+
const key = manifesttext("storage-s3", manifest.key, "key");
|
|
757
|
+
if (key.startsWith("/")) {
|
|
758
|
+
throw edgelaneerror("INVALID_LANE_PAYLOAD", "the storage-s3 key must be a relative path");
|
|
759
|
+
}
|
|
760
|
+
const payload = typeof manifest.payload === "string" ? manifest.payload : "";
|
|
761
|
+
const host = `${bucket}.s3.amazonaws.com`;
|
|
762
|
+
const path = `/${bucket}/${key}`;
|
|
763
|
+
const canonical = [method, path, "", "", `host:${host}`].join("\n");
|
|
764
|
+
const secret = typeof inputs === "object" && inputs !== null ? inputs.secretkey : undefined;
|
|
765
|
+
const signature = keyedfnv1a(typeof secret === "string" ? secret : "", `${canonical}\n${payload}`);
|
|
766
|
+
return lanereport(storagemime, { method, path, signature }, { method, bucket, key, host, canonical, signature, signer: "fnv1a-keyed" });
|
|
767
|
+
}
|
|
768
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
769
|
+
/* Section 6: the browser and local family — the webcontainer seat and the consent gate. */
|
|
770
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
771
|
+
/** rewrites the export declarations of one module into the wrapper's
|
|
772
|
+
* exports surface: export const/let/var/function/class NAME become the
|
|
773
|
+
* plain binding plus one trailing exports.NAME = NAME assignment, and
|
|
774
|
+
* export default EXPR becomes exports.default = EXPR. The pass is
|
|
775
|
+
* deliberately tiny and deterministic — anything beyond it reaches
|
|
776
|
+
* node:vm as written and the engine answers the honest syntax failure. */
|
|
777
|
+
function rewriteexports(code) {
|
|
778
|
+
const names = [];
|
|
779
|
+
const lines = code.split("\n").map((line) => {
|
|
780
|
+
const binding = /^(\s*)export\s+(const|let|var)\s+([A-Za-z_$][A-Za-z0-9_$]*)\b/.exec(line);
|
|
781
|
+
if (binding !== null) {
|
|
782
|
+
names.push(binding[3]);
|
|
783
|
+
return `${binding[1]}${binding[2]} ${binding[3]}${line.slice(binding[0].length)}`;
|
|
784
|
+
}
|
|
785
|
+
const callable = /^(\s*)export\s+(function\*?)\s+([A-Za-z_$][A-Za-z0-9_$]*)\b/.exec(line);
|
|
786
|
+
if (callable !== null) {
|
|
787
|
+
names.push(callable[3]);
|
|
788
|
+
return `${callable[1]}${callable[2]} ${callable[3]}${line.slice(callable[0].length)}`;
|
|
789
|
+
}
|
|
790
|
+
const klass = /^(\s*)export\s+class\s+([A-Za-z_$][A-Za-z0-9_$]*)\b/.exec(line);
|
|
791
|
+
if (klass !== null) {
|
|
792
|
+
names.push(klass[2]);
|
|
793
|
+
return `${klass[1]}class ${klass[2]}${line.slice(klass[0].length)}`;
|
|
794
|
+
}
|
|
795
|
+
const fallback = /^(\s*)export\s+default\s+(.+)$/.exec(line);
|
|
796
|
+
if (fallback !== null)
|
|
797
|
+
return `${fallback[1]}exports.default = ${fallback[2]}`;
|
|
798
|
+
return line;
|
|
799
|
+
});
|
|
800
|
+
const assignments = names.map((name) => `exports.${name} = ${name};`);
|
|
801
|
+
return [...lines, ...assignments].join("\n");
|
|
802
|
+
}
|
|
803
|
+
/** encodes one module export value for the exports report: the value
|
|
804
|
+
* rides its json form when json can carry it, the encodeline string form
|
|
805
|
+
* otherwise (a function answers its source text, an undefined answers
|
|
806
|
+
* "undefined" — the encodeline pattern, json when possible). */
|
|
807
|
+
function exportencoding(value) {
|
|
808
|
+
try {
|
|
809
|
+
if (JSON.stringify(value) !== undefined)
|
|
810
|
+
return value;
|
|
811
|
+
}
|
|
812
|
+
catch {
|
|
813
|
+
// the value is not json representable (a bigint, a circular
|
|
814
|
+
// reference): the encodeline string form answers below
|
|
815
|
+
}
|
|
816
|
+
return encodeline(value);
|
|
817
|
+
}
|
|
818
|
+
/** browser-webcontainer: the in-process webcontainer seat. The code field
|
|
819
|
+
* is a javascript module (like "export const answer = 42"): the lane
|
|
820
|
+
* rewrites the export declarations into the wrapper's exports surface
|
|
821
|
+
* and runs the module inside a fresh node:vm context that carries only
|
|
822
|
+
* the frozen console and inputs surfaces (no require, no process, no
|
|
823
|
+
* host globals — the server-side webcontainer law), then answers the
|
|
824
|
+
* exports report. A module that throws answers VM_MODULE_FAILED
|
|
825
|
+
* honestly — the vm error surfaces as the lane failure. */
|
|
826
|
+
async function browserwebcontainerlane(code, inputs, deadline) {
|
|
827
|
+
if (typeof vm.runInNewContext !== "function") {
|
|
828
|
+
throw edgelaneerror("HOST_RUNTIME_ABSENT", "the node:vm surface is absent from this engine host (the webcontainer seat never fakes a module run)");
|
|
829
|
+
}
|
|
830
|
+
const stdoutlines = [];
|
|
831
|
+
const stderrlines = [];
|
|
832
|
+
const push = (target) => (...values) => {
|
|
833
|
+
target.push(values.map((value) => (typeof value === "string" ? value : encodeline(value))).join(" "));
|
|
834
|
+
};
|
|
835
|
+
const consolesurface = Object.freeze({
|
|
836
|
+
log: push(stdoutlines),
|
|
837
|
+
info: push(stdoutlines),
|
|
838
|
+
debug: push(stdoutlines),
|
|
839
|
+
warn: push(stderrlines),
|
|
840
|
+
error: push(stderrlines),
|
|
841
|
+
});
|
|
842
|
+
const sandbox = { console: consolesurface, inputs: inputs === undefined ? null : Object.freeze(inputs) };
|
|
843
|
+
const script = [
|
|
844
|
+
'"use strict";',
|
|
845
|
+
"const module = Object.freeze({ exports: {} });",
|
|
846
|
+
"(function (module, exports) {",
|
|
847
|
+
rewriteexports(code),
|
|
848
|
+
"})(module, module.exports);",
|
|
849
|
+
"module.exports;",
|
|
850
|
+
].join("\n");
|
|
851
|
+
let exportsobject;
|
|
852
|
+
try {
|
|
853
|
+
exportsobject = vm.runInNewContext(script, sandbox, { timeout: deadline, displayErrors: true });
|
|
854
|
+
}
|
|
855
|
+
catch (error) {
|
|
856
|
+
throw edgelaneerror("VM_MODULE_FAILED", `the webcontainer module failed to run: ${errormessage(error)}`, {
|
|
857
|
+
stderr: stderrlines.join("\n"),
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
const exportsrecord = {};
|
|
861
|
+
if (typeof exportsobject === "object" && exportsobject !== null) {
|
|
862
|
+
for (const [name, value] of Object.entries(exportsobject))
|
|
863
|
+
exportsrecord[name] = exportencoding(value);
|
|
864
|
+
}
|
|
865
|
+
const report = { exports: exportsrecord };
|
|
866
|
+
const output = {
|
|
867
|
+
stdout: [...stdoutlines, encodeline(report)].join("\n"),
|
|
868
|
+
artifact: { mime: browsermime, bytes: utf8.encode(JSON.stringify(report)) },
|
|
869
|
+
};
|
|
870
|
+
if (stderrlines.length > 0)
|
|
871
|
+
output.stderr = stderrlines.join("\n");
|
|
872
|
+
return output;
|
|
873
|
+
}
|
|
874
|
+
/** local-optin: the consent gate lane — the only lane of the plane that
|
|
875
|
+
* may even name the visitor machine, and only under declared consent.
|
|
876
|
+
* The lane refuses every run whose inputs do not declare consent exactly
|
|
877
|
+
* true (LOCAL_CONSENT_REQUIRED), and even under consent the lane stays
|
|
878
|
+
* in-process: it answers the consent manifest, the visitor machine is
|
|
879
|
+
* never touched. */
|
|
880
|
+
async function localoptinlane(_code, inputs, _deadline) {
|
|
881
|
+
const consent = typeof inputs === "object" && inputs !== null && inputs.consent === true;
|
|
882
|
+
if (!consent) {
|
|
883
|
+
throw edgelaneerror("LOCAL_CONSENT_REQUIRED", "the local opt-in lane refuses to run without the declared consent (inputs.consent true)");
|
|
884
|
+
}
|
|
885
|
+
const manifest = { consent: true, scope: "declared", law: "the visitor machine stays untouched" };
|
|
886
|
+
return lanereport(localmime, manifest, manifest);
|
|
887
|
+
}
|
|
888
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
889
|
+
/* Section 7: the lane tables — the specs, the executors and the ids. */
|
|
890
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
891
|
+
/** the in-process lane specs: one row per wired modality, the source of
|
|
892
|
+
* truth of the lane order — every lane executes (live), the pure recipe
|
|
893
|
+
* and wire lanes ride the pure typescript engine surface and the
|
|
894
|
+
* webcontainer seat rides node:vm. */
|
|
895
|
+
export const inproclanespecs = Object.freeze([
|
|
896
|
+
// the ram family: the memory ladder recipes
|
|
897
|
+
{ modality: "ram-tmpfs", kind: "live", runtime: "pure-ts", present: () => true },
|
|
898
|
+
{ modality: "ram-mmap", kind: "live", runtime: "pure-ts", present: () => true },
|
|
899
|
+
{ modality: "ram-overlayfs", kind: "live", runtime: "pure-ts", present: () => true },
|
|
900
|
+
{ modality: "ram-loopram", kind: "live", runtime: "pure-ts", present: () => true },
|
|
901
|
+
{ modality: "ram-gitram", kind: "live", runtime: "pure-ts", present: () => true },
|
|
902
|
+
{ modality: "ram-zram", kind: "live", runtime: "pure-ts", present: () => true },
|
|
903
|
+
{ modality: "ram-zswap", kind: "live", runtime: "pure-ts", present: () => true },
|
|
904
|
+
{ modality: "ram-swapfile", kind: "live", runtime: "pure-ts", present: () => true },
|
|
905
|
+
// the delivery family: the wire frame builders
|
|
906
|
+
{ modality: "delivery-cdn", kind: "live", runtime: "pure-ts", present: () => true },
|
|
907
|
+
{ modality: "delivery-wss", kind: "live", runtime: "pure-ts", present: () => true },
|
|
908
|
+
{ modality: "delivery-webtransport", kind: "live", runtime: "pure-ts", present: () => true },
|
|
909
|
+
{ modality: "delivery-grpc", kind: "live", runtime: "pure-ts", present: () => true },
|
|
910
|
+
{ modality: "delivery-capnproto", kind: "live", runtime: "pure-ts", present: () => true },
|
|
911
|
+
// the storage wire family: the handshake speakers and the signer
|
|
912
|
+
{ modality: "storage-libsql", kind: "live", runtime: "pure-ts", present: () => true },
|
|
913
|
+
{ modality: "storage-mysql", kind: "live", runtime: "pure-ts", present: () => true },
|
|
914
|
+
{ modality: "storage-postgres", kind: "live", runtime: "pure-ts", present: () => true },
|
|
915
|
+
{ modality: "storage-s3", kind: "live", runtime: "pure-ts", present: () => true },
|
|
916
|
+
// the browser family: the server-side webcontainer seat (never the visitor's)
|
|
917
|
+
{
|
|
918
|
+
modality: "browser-webcontainer",
|
|
919
|
+
kind: "live",
|
|
920
|
+
runtime: "node:vm",
|
|
921
|
+
present: () => typeof vm.runInNewContext === "function",
|
|
922
|
+
},
|
|
923
|
+
// the local family: the explicit consent gate
|
|
924
|
+
{ modality: "local-optin", kind: "live", runtime: "pure-ts", present: () => true },
|
|
925
|
+
]);
|
|
926
|
+
/** the in-process executor table: every lane of the group b contract,
|
|
927
|
+
* keyed by modality id — the dispatcher refuses to boot over a lane it
|
|
928
|
+
* cannot wire. */
|
|
929
|
+
export const edgelaneexecutorsinproc = Object.freeze({
|
|
930
|
+
"ram-tmpfs": ramtmpfslane,
|
|
931
|
+
"ram-mmap": rammmaplane,
|
|
932
|
+
"ram-overlayfs": ramoverlayfslane,
|
|
933
|
+
"ram-loopram": ramloopramlane,
|
|
934
|
+
"ram-gitram": ramgitramlane,
|
|
935
|
+
"ram-zram": ramzramlane,
|
|
936
|
+
"ram-zswap": ramzswaplane,
|
|
937
|
+
"ram-swapfile": ramswapfilelane,
|
|
938
|
+
"delivery-cdn": deliverycdnlane,
|
|
939
|
+
"delivery-wss": deliverywsslane,
|
|
940
|
+
"delivery-webtransport": deliverywebtransportlane,
|
|
941
|
+
"delivery-grpc": deliverygrpclane,
|
|
942
|
+
"delivery-capnproto": deliverycapnprotolane,
|
|
943
|
+
"storage-libsql": storagelibsqllane,
|
|
944
|
+
"storage-mysql": storagemysqllane,
|
|
945
|
+
"storage-postgres": storagepostgreslane,
|
|
946
|
+
"storage-s3": storages3lane,
|
|
947
|
+
"browser-webcontainer": browserwebcontainerlane,
|
|
948
|
+
"local-optin": localoptinlane,
|
|
949
|
+
});
|
|
950
|
+
/** the wired lane ids in spec order — the pinned contract the parallel
|
|
951
|
+
* waves and the api routes read exactly. */
|
|
952
|
+
export const inproclaneids = Object.freeze(inproclanespecs.map((spec) => spec.modality));
|