@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 +43 -23
- package/README.md +2 -2
- package/biome.jsonc +127 -0
- package/docker.config +13 -6
- package/index.ts +3 -2
- package/manifest.json +1 -1
- package/modalityregistry.ts +156 -0
- package/package.json +9 -10
- package/docs/example-publicapi.ts +0 -7
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/
|
|
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
|
|
645
|
-
# the saddle-runtime stage copies the engine dist plus the
|
|
646
|
-
#
|
|
647
|
-
# QEMU): its outputs are platform-independent static/js
|
|
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
|
-
|
|
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.
|
|
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/
|
|
906
|
-
COPY
|
|
907
|
-
#
|
|
908
|
-
#
|
|
909
|
-
#
|
|
910
|
-
|
|
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
|
|
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
|
|
1361
|
-
#
|
|
1362
|
-
#
|
|
1363
|
-
# mime.types, init.sql, schema.prisma) - zero npm
|
|
1364
|
-
# node 26 runtime carries everything (node:sqlite,
|
|
1365
|
-
|
|
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.
|
|
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
|
|
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":
|
|
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
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
238
|
-
"docker:run": "docker run --rm --memory-swap=-1 --shm-size=2g saddle:2.0.
|
|
239
|
-
"docker:health": "docker run --rm saddle:2.0.
|
|
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));
|