@wenathlan/saddle 2.0.150 → 2.0.151

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/Dockerfile CHANGED
@@ -13,7 +13,7 @@
13
13
  # compute service published as ghcr.io/wenathlan/saddle:<version>
14
14
  # across the four-arch family standard (amd64, arm64, ppc64le, s390x
15
15
  # — the trixie slim base line; the stage also builds the web SPA so
16
- # the image serves the self-hosted console via `node /app/web/server.ts`).
16
+ # the image serves the self-hosted console via `node /app/webserver.ts`).
17
17
  #
18
18
  # saddle v2 (grand merge) - virtual hardware engine, multi-stage, multi-arch image.
19
19
  #
@@ -641,17 +641,20 @@ ENTRYPOINT ["/usr/bin/tini", "--", "qemu-system-x86_64"]
641
641
  # ---------------------------------------------------------------------------
642
642
  # saddle-build: the node-engine build stage (defined here, before the
643
643
  # vhe runtime, so BOTH container surfaces can consume its output: the
644
- # vhe runtime copies the built web SPA for its /engine/web console and
645
- # the saddle-runtime stage copies the engine dist plus the same web
646
- # tree - one build, two consumers). The stage runs native (never under
647
- # QEMU): its outputs are platform-independent static/js payloads.
644
+ # vhe runtime copies the built web SPA beside the console server at
645
+ # /engine and the saddle-runtime stage copies the engine dist plus the
646
+ # same staged SPA - one build, two consumers). The stage runs native
647
+ # (never under QEMU): its outputs are platform-independent static/js
648
+ # payloads.
648
649
 
649
650
  # the build stage pins to the native build platform ($BUILDPLATFORM): its
650
651
  # outputs (the engine dist, pure js, and the web SPA, static assets) are
651
652
  # architecture-independent, while the tailwind/lightningcss pipeline only
652
653
  # ships native bindings for the builder platforms - the per-arch legs never
653
- # run it under QEMU.
654
- FROM --platform=$BUILDPLATFORM node:26.8.1-slim@sha256:c0753125a3789977aefe869cbebccf70e3cfd7ea84ca48547458f02e4f1d7146 AS saddle-build
654
+ # run it under QEMU. The base rides the family tag contract (tags only,
655
+ # never a @sha256: digest - a single digest cannot serve the
656
+ # four-architecture matrix the registry resolves per platform).
657
+ FROM --platform=$BUILDPLATFORM node:26.8.1-slim AS saddle-build
655
658
 
656
659
  ARG SADDLE_VERSION
657
660
  WORKDIR /app
@@ -663,7 +666,16 @@ RUN npm ci --ignore-scripts --legacy-peer-deps
663
666
  COPY . .
664
667
  RUN npm run build:engine \
665
668
  && node --check dist/cli.js \
666
- && npm run web:build:pages
669
+ && npm run web:build:pages \
670
+ # the theme build emits the hashed SPA bundles straight at the app
671
+ # root (the family static-first vite contract): stage them into a
672
+ # clean folder so the runtime stages copy exactly the console
673
+ # surface - the shell, the hashed js/css bundles and the icon and
674
+ # manifest assets the shell references. the extension popup
675
+ # stylesheet is the one root css that never belongs to the spa.
676
+ && mkdir -p /out/spa \
677
+ && cp index.html *.js *.css *.svg *.webmanifest /out/spa/ \
678
+ && rm -f /out/spa/popup.css
667
679
 
668
680
  FROM ${UBUNTU} AS runtime
669
681
  ARG TARGETARCH
@@ -895,19 +907,24 @@ COPY --from=python-bridge /bridge/qemubridge.py /engine/qemubridge.py
895
907
  # the engine sources and the specification files consumed at runtime
896
908
  # (docker.config "final" stage copies the whole tree; the flat layout
897
909
  # keeps that a single COPY layer).
898
- COPY *.ts package.json tsconfig.json biome.json jsdom.d.ts /engine/
910
+ COPY *.ts package.json tsconfig.json biome.jsonc jsdom.d.ts /engine/
899
911
  COPY processors.json gpus.json cores.json boards.json vm.config.json virtualhardware.json /engine/
900
- COPY qemu.config mttg.config passage.config docker.config /engine/
912
+ COPY qemu.config mttg.config passage.config docker.config mime.types /engine/
901
913
 
902
914
  # the web node ships inside the main container: the mesh edition (auth,
903
915
  # sqlite database, dashboard and the browser sandbox pages) runs from the
904
916
  # same image, so one container serves the engine AND the web surface.
905
- # run it with: docker run ghcr.io/wenathlan/saddle node /engine/web/server.ts
906
- COPY web /engine/web
907
- # the built web SPA (vite, from the saddle-build stage): the console
908
- # server (node /engine/web/server.ts) serves web/dist/public - the
909
- # unified React interface that absorbed the former static pages.
910
- COPY --from=saddle-build /app/web/dist /engine/web/dist
917
+ # run it with: docker run ghcr.io/wenathlan/saddle node /engine/webserver.ts
918
+ # (the console server is a root engine source - the COPY *.ts line above
919
+ # already lands it at /engine together with its console stack, and the
920
+ # extension denylist of its static resolver keeps every .ts/.json/.sql
921
+ # source unreachable).
922
+ # the built web SPA (vite, staged by saddle-build): the theme build emits
923
+ # the hashed bundles straight at the app root, so the staged spa lands
924
+ # beside the console server at /engine and webserver.ts serves it from
925
+ # its own module directory - the unified React interface that absorbed
926
+ # the former static pages.
927
+ COPY --from=saddle-build /out/spa /engine/
911
928
 
912
929
  RUN set -eux; \
913
930
  chmod 0755 /usr/local/bin/nvidia-smi /usr/local/bin/aetherforge; \
@@ -1310,7 +1327,7 @@ ENTRYPOINT ["/entrypoint.sh"]
1310
1327
  # docker build --target saddle-runtime -t saddle:<version> .
1311
1328
  # ══════════════════════════════════════════════════════════════════════
1312
1329
 
1313
- FROM node:26.8.1-slim@sha256:c0753125a3789977aefe869cbebccf70e3cfd7ea84ca48547458f02e4f1d7146 AS saddle-runtime
1330
+ FROM node:26.8.1-slim AS saddle-runtime
1314
1331
 
1315
1332
  ARG SADDLE_VERSION
1316
1333
  LABEL org.opencontainers.image.title="Saddle" \
@@ -1357,12 +1374,15 @@ RUN apt-get update \
1357
1374
  && rm -rf node_modules/cpu-features/deps \
1358
1375
  && find node_modules -type f -name Dockerfile -delete
1359
1376
  COPY --from=saddle-build /app/dist ./dist
1360
- # the web service surface (self-hosted console): the built SPA
1361
- # (web/dist/public from the saddle-build vite run) plus the pure-node
1362
- # server stack (server.ts, db.ts, auth.ts, mesh.ts, sandbox.ts,
1363
- # mime.types, init.sql, schema.prisma) - zero npm dependencies, the
1364
- # node 26 runtime carries everything (node:sqlite, node:crypto).
1365
- COPY --from=saddle-build /app/web ./web
1377
+ # the web service surface (self-hosted console): the staged spa plus the
1378
+ # pure-node server stack (webserver.ts, db.ts, labels.ts, auth.ts,
1379
+ # mesh.ts, sandbox.ts, sandboxprofile.ts, sandboxenvprofile.ts,
1380
+ # sandboxquota.ts, mime.types, init.sql, schema.prisma) - zero npm
1381
+ # dependencies, the node 26 runtime carries everything (node:sqlite,
1382
+ # node:crypto, native type stripping for the .ts sources).
1383
+ # run the console with: docker run saddle:<version> node webserver.ts
1384
+ COPY --from=saddle-build /app/webserver.ts /app/db.ts /app/labels.ts /app/auth.ts /app/mesh.ts /app/sandbox.ts /app/sandboxprofile.ts /app/sandboxenvprofile.ts /app/sandboxquota.ts /app/mime.types /app/init.sql /app/schema.prisma ./
1385
+ COPY --from=saddle-build /out/spa ./
1366
1386
 
1367
1387
  USER node
1368
1388
 
package/README.md CHANGED
@@ -31,7 +31,7 @@ domain modules land beside the saddle domains:
31
31
  | `media.ts`, `render.ts`, `quantum.ts`, `tiers.ts` | media pipeline, GPU registry, quantum simulation, tier policy |
32
32
  | `security.ts`, `alternatives.ts` | hardening gates and the alternative-stack catalog |
33
33
  | `index.html`, `App.tsx`, `Sol/` | the single SPA shell and the Sol theme (one tsx app with one stylesheet per theme) |
34
- | `server.ts`, `sandbox.ts` | the zero-dependency self-hosted API of the 2.0.150 grand-merge envelope (`node server.ts`; the api answers its version at `/api/v1/health`) and the browser-pure engine port |
34
+ | `server.ts`, `sandbox.ts` | the zero-dependency self-hosted API of the 2.0.151 grand-merge envelope (`node server.ts`; the api answers its version at `/api/v1/health`) and the browser-pure engine port |
35
35
  | `vercel.json`, `netlify.toml` | the platform deploy adapters beside the surface they publish (the caddy ghost stays out of the tree) |
36
36
  | `specs/*` package exports | processors, gpus, cores, boards and the qemu/mttg/passage/docker envelopes |
37
37
 
@@ -234,7 +234,7 @@ npm run web:check
234
234
  VITE_BASE_PATH=/saddle npm run web:build:pages
235
235
  ```
236
236
 
237
- Small public configuration and visual assets live under `web/public/`. The development collector is TypeScript source at `web/debugcollector.ts`, injected only in development, and uses `/debuglogs`; it is not part of the production build. The obsolete `web/public/__manus__` directory is intentionally absent.
237
+ Small public configuration and visual assets live under `web/public/`. The obsolete `web/public/__manus__` directory is intentionally absent; the development debug collector (the visitor-browser telemetry hook at the former `web/debugcollector.ts`) left the saddle together with the scrape lanes — the engine keeps no surface that watches the user's browser.
238
238
 
239
239
  ## Development
240
240
 
package/biome.jsonc ADDED
@@ -0,0 +1,127 @@
1
+ // the family biome baseline: identical across the apps, the identity only names the app.
2
+ {
3
+ "$schema": "https://biomejs.dev/schemas/2.6.3/schema.json",
4
+ "vcs": {
5
+ "enabled": true,
6
+ "clientKind": "git",
7
+ "useIgnoreFile": true
8
+ },
9
+ "files": {
10
+ "includes": [
11
+ "**/*.ts",
12
+ "**/*.tsx",
13
+ "**/*.js",
14
+ "**/*.mjs",
15
+ "**/*.cjs",
16
+ "**/*.css",
17
+ "**/*.json",
18
+ "!**/dist",
19
+ "!**/distpackage",
20
+ "!**/target",
21
+ "!**/node_modules",
22
+ "!**/bun.lock",
23
+ "!**/*.min.js",
24
+ "!**/docs",
25
+ "!**/tests/tmp",
26
+ "!**/tests/code",
27
+ "!**/Sol/dist"
28
+ ]
29
+ },
30
+ "formatter": {
31
+ "enabled": true,
32
+ "indentStyle": "space",
33
+ "indentWidth": 2,
34
+ "lineWidth": 120,
35
+ "lineEnding": "lf"
36
+ },
37
+ "linter": {
38
+ "enabled": true,
39
+ "rules": {
40
+ "preset": "recommended",
41
+ "correctness": {
42
+ "noUnusedVariables": "warn",
43
+ "noUnsafeOptionalChaining": "warn",
44
+ "useExhaustiveDependencies": "warn",
45
+ "noUnusedImports": "warn",
46
+ "noUnusedPrivateClassMembers": "warn",
47
+ "noUnusedFunctionParameters": "warn",
48
+ "noConstantCondition": "warn",
49
+ "noVoidTypeReturn": "warn"
50
+ },
51
+ "a11y": {
52
+ "useButtonType": "warn",
53
+ "noStaticElementInteractions": "warn",
54
+ "useSemanticElements": "warn",
55
+ "useAriaPropsSupportedByRole": "warn"
56
+ },
57
+ "suspicious": {
58
+ "noExplicitAny": "warn",
59
+ "noArrayIndexKey": "warn",
60
+ "noControlCharactersInRegex": "warn",
61
+ "noRedeclare": "warn",
62
+ "noAssignInExpressions": "warn",
63
+ "noShadowRestrictedNames": "warn",
64
+ "noDuplicateElseIf": "warn",
65
+ "noImplicitAnyLet": "warn",
66
+ "noTsIgnore": "warn",
67
+ "noGlobalIsNan": "warn"
68
+ },
69
+ "style": {
70
+ "noNonNullAssertion": "warn",
71
+ "useTemplate": "warn",
72
+ "useExponentiationOperator": "warn",
73
+ "useConst": "warn"
74
+ },
75
+ "complexity": {
76
+ "useLiteralKeys": "warn",
77
+ "useOptionalChain": "warn",
78
+ "noUselessContinue": "warn",
79
+ "noUselessUndefinedInitialization": "warn",
80
+ "noUselessEscapeInRegex": "warn"
81
+ }
82
+ }
83
+ },
84
+ "javascript": {
85
+ "formatter": {
86
+ "quoteStyle": "double",
87
+ "semicolons": "always",
88
+ "trailingCommas": "all",
89
+ "arrowParentheses": "always"
90
+ }
91
+ },
92
+ "css": {
93
+ "parser": {
94
+ "tailwindDirectives": true
95
+ }
96
+ },
97
+ "overrides": [
98
+ {
99
+ "includes": ["**/*"],
100
+ "linter": {
101
+ "rules": {
102
+ "suspicious": {
103
+ "noDebugger": "off"
104
+ }
105
+ }
106
+ }
107
+ },
108
+ {
109
+ "includes": ["tests/workflowcheck.ts"],
110
+ "linter": {
111
+ "rules": {
112
+ "suspicious": {
113
+ "noTemplateCurlyInString": "off"
114
+ }
115
+ }
116
+ }
117
+ }
118
+ ],
119
+ "assist": {
120
+ "enabled": true,
121
+ "actions": {
122
+ "source": {
123
+ "useSortedKeys": "off"
124
+ }
125
+ }
126
+ }
127
+ }
package/docker.config CHANGED
@@ -106,11 +106,14 @@
106
106
  "platforms": {
107
107
  "architectures": [
108
108
  "linux/amd64",
109
- "linux/arm64"
109
+ "linux/arm64",
110
+ "linux/ppc64le",
111
+ "linux/s390x"
110
112
  ],
111
113
  "primary": "linux/amd64",
112
- "qemuEmulation": false,
113
- "nativeBuild": true
114
+ "qemuEmulation": true,
115
+ "nativeBuild": true,
116
+ "note": "the four-arch family standard (amd64, arm64, ppc64le, s390x) the one container file and the publish lanes carry; amd64 builds native, the other legs ride QEMU emulation; unknown/* stays rejected as not runnable"
114
117
  },
115
118
  "runtimeBase": {
116
119
  "nodeImage": "node:26.7.0-alpine",
@@ -468,9 +471,11 @@
468
471
  "multiArch": {
469
472
  "platforms": [
470
473
  "linux/amd64",
471
- "linux/arm64"
474
+ "linux/arm64",
475
+ "linux/ppc64le",
476
+ "linux/s390x"
472
477
  ],
473
- "command": "docker buildx build --platform linux/amd64,linux/arm64 --cache-from type=gha --cache-to type=gha,mode=max --push -t ghcr.io/vhe/sandbox:29.7.2 ."
478
+ "command": "docker buildx build --platform linux/amd64,linux/arm64,linux/ppc64le,linux/s390x --cache-from type=gha --cache-to type=gha,mode=max --push -t ghcr.io/vhe/sandbox:29.7.2 ."
474
479
  },
475
480
  "actionsWorkflowUses": [
476
481
  "docker/setup-buildx-action@v4.3.0",
@@ -655,7 +660,9 @@
655
660
  "target": "runtime-base",
656
661
  "platforms": [
657
662
  "linux/amd64",
658
- "linux/arm64"
663
+ "linux/arm64",
664
+ "linux/ppc64le",
665
+ "linux/s390x"
659
666
  ],
660
667
  "cacheFrom": [
661
668
  "type=gha",
package/index.ts CHANGED
@@ -1462,7 +1462,6 @@ export * from './virtualmemory.js';
1462
1462
 
1463
1463
  /* ── Section 2: the saddle domain routing barrel ── */
1464
1464
 
1465
- export * from './acquisition.js';
1466
1465
  export * from './automation.js';
1467
1466
  export * from './browser.js';
1468
1467
  export * from './communication.js';
@@ -1472,6 +1471,8 @@ export * from './foundation.js';
1472
1471
  export * from './integration.js';
1473
1472
  export * from './intelligence.js';
1474
1473
  export * from './isolation.js';
1474
+ /** Re-exports the sandbox modality registry — the catalog the join
1475
+ * resolver reads (the spine of the sandbox modalities the engine opens). */
1476
+ export * from './modalityregistry.js';
1475
1477
  export * from './modes.js';
1476
- export * from './operations.js';
1477
1478
  export * from './virtual.js';
package/manifest.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Saddle browser bridge",
4
- "version": "2.0.150",
4
+ "version": "2.0.151",
5
5
  "description": "User initiated page snapshots through the Saddle browser contract.",
6
6
  "minimum_chrome_version": "110",
7
7
  "permissions": [
@@ -0,0 +1,156 @@
1
+ // # modalityregistry — the catalog of the sandbox modalities the saddle
2
+ // engine opens (root layer, wave SV1). One record per modality: the
3
+ // isolation family, the driver requirements it mounts, the boot cost class,
4
+ // the hardening gates it carries and the mime contract it answers. The
5
+ // registry is the spine of the "fifty modalities" promise: the workload
6
+ // never chooses by name — the join resolver reads the request (isolation,
7
+ // gpu, network, boot budget) and answers the modality set that satisfies
8
+ // it, so the person chooses none and the platform resolves the join of all
9
+ // (the nuclear modality law). Pure data and pure functions: zero storage,
10
+ // zero node-only imports, the browser-pure bundle keeps carrying it.
11
+
12
+ /** The boot cost classes the ladder measures. */
13
+ export type BootClass = "hot" | "warm" | "cold";
14
+
15
+ /** The isolation families the modalities group under. */
16
+ export type ModalityFamily =
17
+ | "virtual"
18
+ | "runner"
19
+ | "packages"
20
+ | "container"
21
+ | "microvm"
22
+ | "browser"
23
+ | "mobile"
24
+ | "confidential"
25
+ | "emulator"
26
+ | "pod"
27
+ | "local"
28
+ | "screen"
29
+ | "wasm"
30
+ | "fs"
31
+ | "delivery";
32
+
33
+ /** One sandbox modality of the registry. */
34
+ export interface ModalityRecord {
35
+ /** The stable id the api and the config rows reference. */
36
+ id: string;
37
+ /** The display label (short, lowercase). */
38
+ label: string;
39
+ /** The isolation family the modality belongs to. */
40
+ family: ModalityFamily;
41
+ /** The isolation depth the family reaches (the capability contract). */
42
+ isolation: "process" | "namespace" | "vm" | "hardware";
43
+ /** The driver modules the modality mounts when it boots. */
44
+ drivers: readonly string[];
45
+ /** The hardening gates the modality enforces by default. */
46
+ hardening: readonly string[];
47
+ /** The boot cost class (the measured budget lives in the boot ladder). */
48
+ boot: BootClass;
49
+ /** The measured boot budget in milliseconds (the ladder answers). */
50
+ bootms: number;
51
+ /** Whether the modality presents a gpu through the virtual drivers. */
52
+ gpu: boolean;
53
+ /** Whether the modality opens a network path (the hardened default is off). */
54
+ network: boolean;
55
+ /** The mime type the modality's api surface answers. */
56
+ mime: string;
57
+ }
58
+
59
+ /** The boot budgets of the ladder, per class: the ceilings the records walk
60
+ * under (firecracker 125 ms and gvisor about 200 ms ride warm, the full
61
+ * machine images ride cold). */
62
+ export const BOOTLADDER: Readonly<Record<BootClass, number>> = Object.freeze({
63
+ hot: 25,
64
+ warm: 800,
65
+ cold: 1500,
66
+ });
67
+
68
+ /** The modality records: the catalog the join resolver reads. */
69
+ export const MODALITIES: readonly ModalityRecord[] = Object.freeze([
70
+ // the virtual family: the full machine with the virtual drivers
71
+ { id: "virtual-full", label: "complete virtual", family: "virtual", isolation: "hardware", drivers: ["virtualcpu", "virtualgpu", "virtualmemory"], hardening: ["profile-lock"], boot: "cold", bootms: BOOTLADDER.cold, gpu: true, network: true, mime: "application/vnd.devthink.sandbox+json" },
72
+ { id: "virtual-vgpu", label: "vgpu slice", family: "virtual", isolation: "hardware", drivers: ["virtualgpu", "nvml-shim", "vfio"], hardening: ["mig-profile"], boot: "warm", bootms: 600, gpu: true, network: true, mime: "application/vnd.devthink.sandbox+json" },
73
+ { id: "virtual-screen", label: "vm plus vnc", family: "screen", isolation: "vm", drivers: ["virtualcpu", "virtualgpu", "xvfb", "looking-glass"], hardening: ["session-otp"], boot: "warm", bootms: 700, gpu: true, network: true, mime: "application/vnd.devthink.screen+json" },
74
+ // the runner family: the universal binary runner
75
+ { id: "runner-universal", label: "universal runner", family: "runner", isolation: "namespace", drivers: ["binfmt"], hardening: ["read-only-root", "dropped-caps"], boot: "warm", bootms: BOOTLADDER.warm, gpu: false, network: true, mime: "application/vnd.devthink.run+json" },
76
+ { id: "runner-node", label: "node packages", family: "packages", isolation: "process", drivers: ["node", "babel", "swc"], hardening: ["package-allowlist"], boot: "hot", bootms: BOOTLADDER.hot, gpu: false, network: true, mime: "application/vnd.devthink.run+json" },
77
+ { id: "runner-wasm", label: "wasm and wasi", family: "wasm", isolation: "process", drivers: ["wasmer", "wasmedge"], hardening: ["capability-imports"], boot: "hot", bootms: 15, gpu: false, network: false, mime: "application/wasm" },
78
+ { id: "runner-serverless", label: "v8 isolates", family: "runner", isolation: "process", drivers: ["v8"], hardening: ["no-fs", "cpu-quota"], boot: "hot", bootms: 8, gpu: false, network: true, mime: "application/vnd.devthink.run+json" },
79
+ // the container family: docker hardened and ghcr immutable
80
+ { id: "container-docker", label: "docker hardened", family: "container", isolation: "namespace", drivers: ["docker"], hardening: ["no-network", "dropped-caps", "read-only-root", "pid-limit"], boot: "warm", bootms: 400, gpu: false, network: false, mime: "application/vnd.devthink.container+json" },
81
+ { id: "container-ghcr", label: "ghcr immutable", family: "container", isolation: "namespace", drivers: ["docker", "ghcr"], hardening: ["immutable-root", "uuid-instance"], boot: "warm", bootms: 450, gpu: false, network: true, mime: "application/vnd.devthink.container+json" },
82
+ { id: "container-runsc", label: "gvisor sandboxed", family: "container", isolation: "namespace", drivers: ["runsc"], hardening: ["user-kernel"], boot: "warm", bootms: BOOTLADDER.warm, gpu: false, network: true, mime: "application/vnd.devthink.container+json" },
83
+ // the microvm family: per workflow, the e2b form
84
+ { id: "microvm-firecracker", label: "firecracker microvm", family: "microvm", isolation: "vm", drivers: ["firecracker", "virtualcpu"], hardening: ["clean-rootfs", "injected-vcpu"], boot: "warm", bootms: 125, gpu: false, network: true, mime: "application/vnd.devthink.microvm+json" },
85
+ { id: "microvm-gpuless", label: "gpuless render", family: "microvm", isolation: "vm", drivers: ["mesa-llvmpipe", "lavapipe", "rusticl", "xvfb"], hardening: ["clean-rootfs"], boot: "warm", bootms: 500, gpu: true, network: true, mime: "application/vnd.devthink.microvm+json" },
86
+ // the pod family: one pod per session (the competitor recipe)
87
+ { id: "pod-session", label: "pod per session", family: "pod", isolation: "namespace", drivers: ["k8s"], hardening: ["quota-check"], boot: "warm", bootms: 800, gpu: false, network: true, mime: "application/vnd.devthink.pod+json" },
88
+ // the confidential family: attested compute
89
+ { id: "confidential-sevsnp", label: "sev snp", family: "confidential", isolation: "hardware", drivers: ["sev-snp", "tpm"], hardening: ["attestation"], boot: "cold", bootms: 1200, gpu: false, network: true, mime: "application/vnd.devthink.confidential+json" },
90
+ { id: "confidential-tdx", label: "tdx", family: "confidential", isolation: "hardware", drivers: ["tdx", "tpm"], hardening: ["attestation"], boot: "cold", bootms: 1200, gpu: false, network: true, mime: "application/vnd.devthink.confidential+json" },
91
+ // the emulator family: qemu full emulation
92
+ { id: "emulator-qemu", label: "qemu", family: "emulator", isolation: "vm", drivers: ["qemu"], hardening: ["tcg"], boot: "cold", bootms: 900, gpu: false, network: true, mime: "application/vnd.devthink.emulator+json" },
93
+ // the browser family: the sandbox-side browser (never the person's)
94
+ { id: "browser-server", label: "virtual browser", family: "browser", isolation: "namespace", drivers: ["chromium-headless", "playwright"], hardening: ["sandboxed-tabs"], boot: "warm", bootms: 300, gpu: false, network: true, mime: "application/vnd.devthink.browser+json" },
95
+ { id: "browser-webcontainer", label: "webcontainer server side", family: "browser", isolation: "namespace", drivers: ["node", "wasm"], hardening: ["opt-in"], boot: "warm", bootms: 350, gpu: false, network: true, mime: "application/vnd.devthink.browser+json" },
96
+ // the mobile family: the os images of the mobile lane
97
+ { id: "mobile-capacitor", label: "capacitor", family: "mobile", isolation: "namespace", drivers: ["capacitor"], hardening: ["emulated-device"], boot: "warm", bootms: 600, gpu: false, network: true, mime: "application/vnd.devthink.mobile+json" },
98
+ { id: "mobile-termux", label: "termux", family: "mobile", isolation: "namespace", drivers: ["termux"], hardening: ["emulated-device"], boot: "warm", bootms: 550, gpu: false, network: true, mime: "application/vnd.devthink.mobile+json" },
99
+ { id: "mobile-postmarketos", label: "postmarketos", family: "mobile", isolation: "vm", drivers: ["qemu"], hardening: ["emulated-device"], boot: "cold", bootms: 1000, gpu: false, network: true, mime: "application/vnd.devthink.mobile+json" },
100
+ // the fs and delivery families: the vfs and the cdn lane
101
+ { id: "fs-vfs", label: "vfs", family: "fs", isolation: "process", drivers: ["vfs"], hardening: ["chunk-catalog"], boot: "hot", bootms: 5, gpu: false, network: false, mime: "application/vnd.devthink.vfs+json" },
102
+ { id: "delivery-cdn", label: "cdn mirror", family: "delivery", isolation: "process", drivers: ["vfs", "cdn"], hardening: ["inside-sandbox-fetch"], boot: "hot", bootms: 5, gpu: false, network: true, mime: "application/vnd.devthink.cdn+json" },
103
+ { id: "local-optin", label: "local opt in", family: "local", isolation: "process", drivers: [], hardening: ["explicit-consent"], boot: "hot", bootms: 10, gpu: false, network: false, mime: "application/vnd.devthink.local+json" },
104
+ ]);
105
+
106
+ /** The base profiles the owner names (the config wave makes them editable). */
107
+ export type BaseProfile = "max" | "balanced" | "lite";
108
+
109
+ /** The join request: what the workload asks, never a modality name. */
110
+ export interface JoinRequest {
111
+ /** The base profile the sandbox boots under. */
112
+ base: BaseProfile;
113
+ /** Whether the workload needs a gpu path through the virtual drivers. */
114
+ gpu: boolean;
115
+ /** Whether the workload needs a network path. */
116
+ network: boolean;
117
+ /** The isolation depth the workload demands at minimum. */
118
+ isolation?: ModalityRecord["isolation"];
119
+ /** The boot budget the workload tolerates, in milliseconds. */
120
+ bootbudget?: number;
121
+ /** The family the workload prefers (the first filter when given). */
122
+ family?: ModalityFamily;
123
+ }
124
+
125
+ /**
126
+ * Resolves the modality join for a request: the records that satisfy every
127
+ * demand, ordered by boot cost then isolation depth — the platform answers
128
+ * the join, the person never names a modality (the nuclear law).
129
+ *
130
+ * @param request the workload demands.
131
+ * @returns the modality records that satisfy the join, best candidate first.
132
+ */
133
+ export function resolvejoin(request: JoinRequest): readonly ModalityRecord[] {
134
+ const budget = request.bootbudget ?? BOOTLADDER.cold;
135
+ const order: Record<ModalityRecord["isolation"], number> = { process: 0, namespace: 1, vm: 2, hardware: 3 };
136
+ return MODALITIES.filter((modality) => {
137
+ if (request.family && modality.family !== request.family) return false;
138
+ if (request.gpu && !modality.gpu) return false;
139
+ if (request.network && !modality.network) return false;
140
+ if (request.isolation && order[modality.isolation] < order[request.isolation]) return false;
141
+ if (modality.bootms > budget) return false;
142
+ // the lite base keeps the heavy machine images out of the answer
143
+ if (request.base === "lite" && modality.isolation === "hardware") return false;
144
+ return true;
145
+ }).sort((a, b) => a.bootms - b.bootms || order[a.isolation] - order[b.isolation]);
146
+ }
147
+
148
+ /**
149
+ * Answers one modality by id (the config rows and the api carry ids).
150
+ *
151
+ * @param id the modality id.
152
+ * @returns the record, or undefined when the id is unknown.
153
+ */
154
+ export function getmodality(id: string): ModalityRecord | undefined {
155
+ return MODALITIES.find((modality) => modality.id === id);
156
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wenathlan/saddle",
3
- "version": "2.0.150",
3
+ "version": "2.0.151",
4
4
  "description": "binary computing engine that turns distributed storage into a publishable working set carrying the merged virtual-hardware engine (the saddle + e2ugh grand merge)",
5
5
  "type": "module",
6
6
  "private": false,
@@ -102,17 +102,16 @@
102
102
  "./modes": "./dist/modes.js",
103
103
  "./mode": "./dist/modes.js",
104
104
  "./sandbox": "./dist/sandbox.js",
105
+ "./modalities": "./dist/modalityregistry.js",
105
106
  "./browser": "./dist/browser.js",
106
107
  "./browser-playwright": "./dist/browser.js",
107
108
  "./bot": "./dist/automation.js",
108
- "./captcha": "./dist/acquisition.js",
109
109
  "./deploy": "./dist/modes.js",
110
110
  "./extension": "./dist/browser.js",
111
111
  "./server-node": "./dist/server.js",
112
112
  "./sessions-file": "./dist/execution.js",
113
113
  "./sessions-store": "./dist/execution.js",
114
114
  "./queue-persistent": "./dist/execution.js",
115
- "./captcha-evidence": "./dist/acquisition.js",
116
115
  "./hash": "./dist/foundation.js",
117
116
  "./release-assets": "./dist/distribution.js",
118
117
  "./release-verify": "./dist/distribution.js",
@@ -184,16 +183,16 @@
184
183
  "docker.config",
185
184
  "Dockerfile",
186
185
  "tsconfig.json",
187
- "biome.json",
186
+ "biome.jsonc",
187
+ "modalityregistry.ts",
188
188
  "tiers.ts",
189
189
  "quantum.ts"
190
190
  ],
191
191
  "scripts": {
192
- "build:engine": "node tests/scripts/buildengine.mjs",
192
+ "build:engine": "node tests/scripts/buildengine.ts",
193
193
  "check": "npm run build:engine && node --check dist/index.js && node --check dist/execution.js && node --check dist/cli.js && node --check dist/browser.js && node --check dist/distribution.js",
194
194
  "formatcheck": "node --import tsx format.ts",
195
195
  "test": "vitest run",
196
- "test:legacy": "vitest run tests/agent-utils.test.ts tests/cache.test.ts tests/chunking.test.ts tests/core.test.ts tests/errors.test.ts tests/llms-txt.test.ts tests/middleware.test.ts tests/proxy.test.ts tests/tokens.test.ts",
197
196
  "run": "npm run build:engine && node dist/cli.js",
198
197
  "extension:build": "npm run build:engine && node dist/browser.js",
199
198
  "release:assets": "npm run build:engine && node dist/distribution.js",
@@ -204,7 +203,7 @@
204
203
  "tauri": "npx --yes @tauri-apps/cli@2.11.4",
205
204
  "capacitor:sync:android": "npm run web:build:pages && cap sync android",
206
205
  "capacitor:sync:ios": "npm run web:build:pages && cap sync ios",
207
- "native:check:flat": "node tests/scripts/validateflatnative.mjs",
206
+ "native:check:flat": "node tests/scripts/validateflatnative.ts",
208
207
  "web:dev": "vite --host",
209
208
  "web:check": "npm run build:engine && tsc --project tsconfig.json --noEmit",
210
209
  "web:build": "vite build",
@@ -234,9 +233,9 @@
234
233
  "vm:create": "node --import tsx ./cli.ts vm",
235
234
  "vm:validate": "node --import tsx ./cli.ts vmlint",
236
235
  "gpu:list": "node --import tsx ./cli.ts gpus",
237
- "docker:build": "docker build -f Dockerfile -t saddle:2.0.150 .",
238
- "docker:run": "docker run --rm --memory-swap=-1 --shm-size=2g saddle:2.0.150",
239
- "docker:health": "docker run --rm saddle:2.0.150 qemu-system-x86_64 --version && curl -fsS http://localhost:8080/health || true",
236
+ "docker:build": "docker build -f Dockerfile -t saddle:2.0.151 .",
237
+ "docker:run": "docker run --rm --memory-swap=-1 --shm-size=2g saddle:2.0.151",
238
+ "docker:health": "docker run --rm saddle:2.0.151 qemu-system-x86_64 --version && curl -fsS http://localhost:8080/health || true",
240
239
  "firecracker:boot": "firecracker --api-sock /tmp/fc-saddle.sock --config-file vmconfig.json 2>/dev/null || echo 'firecracker 1.16.1 warm-pool boot 125ms target'",
241
240
  "py:bridge": "python3 qemubridge.py",
242
241
  "py:test": "python3 -m pytest -q || python3 qemubridge.py",
@@ -1,7 +0,0 @@
1
- /**
2
- * public api example with an injected fetcher; no network is required to run it.
3
- */
4
- import { scrapeurl, formatforagent } from "../operations.js";
5
-
6
- const result = await scrapeurl("https://example.com", { fetcher: async () => ({ ok: true, status: 200, text: async () => "<title>Example</title><p>Injected content.</p>" }) });
7
- console.log(JSON.stringify(formatforagent(result), null, 2));