@wenathlan/saddle 2.0.6 → 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.
Files changed (284) hide show
  1. package/Dockerfile +70 -24
  2. package/LICENSE +0 -0
  3. package/README.md +19 -3
  4. package/alternatives.ts +0 -0
  5. package/biome.jsonc +127 -0
  6. package/boards.json +2 -2
  7. package/compute.ts +4 -4
  8. package/cores.json +2 -2
  9. package/docker.config +16 -9
  10. package/docs/.gitkeep +0 -0
  11. package/docs/CONVERSA.txt +0 -0
  12. package/docs/actionrecorder.md +0 -0
  13. package/docs/actionsincident.md +0 -0
  14. package/docs/alternatives.md +0 -0
  15. package/docs/architecture-1.8.19.md +0 -0
  16. package/docs/architecture-1.8.19.mmd +0 -0
  17. package/docs/architecture-1.8.19.png +0 -0
  18. package/docs/architecture.md +3 -3
  19. package/docs/artifactavailability.md +0 -0
  20. package/docs/artifactresearch-1.8.9.md +0 -0
  21. package/docs/assets/architecture.svg +0 -0
  22. package/docs/assets/saddle-browser-trace.webp +0 -0
  23. package/docs/assets/saddle-hero-bridge.webp +0 -0
  24. package/docs/assets/saddle-mark.webp +0 -0
  25. package/docs/assets/saddle-runtime-map.webp +0 -0
  26. package/docs/assets/saddlemark.svg +0 -0
  27. package/docs/batches-1816-1820.md +0 -0
  28. package/docs/brancharchive-2026-08-18.md +0 -0
  29. package/docs/branchaudit.md +0 -0
  30. package/docs/browser.md +0 -0
  31. package/docs/capabilityreport.md +0 -0
  32. package/docs/comparativeaudit.md +0 -0
  33. package/docs/consolidation.md +0 -0
  34. package/docs/containerplatforms-1.8.17.md +0 -0
  35. package/docs/containerplatforms-2.1.0.md +73 -0
  36. package/docs/documentmap-1.8.12.md +0 -0
  37. package/docs/e2ugh-engine.md +0 -0
  38. package/docs/ecosystemplan.md +0 -0
  39. package/docs/enginearchitecture.md +0 -0
  40. package/docs/example-localjob.ts +0 -0
  41. package/docs/extension.md +0 -0
  42. package/docs/featureaudit.md +0 -0
  43. package/docs/flatbuildresearch-1.8.11.md +0 -0
  44. package/docs/gapmatrix.md +0 -0
  45. package/docs/hardware.md +0 -0
  46. package/docs/libraryapi.md +0 -0
  47. package/docs/licenseaudit-1.8.12.md +0 -0
  48. package/docs/logs/examplesession.json +54 -0
  49. package/docs/memoryengine.md +0 -0
  50. package/docs/mobileconversionresearch-1.8.10.md +0 -0
  51. package/docs/modes.md +0 -0
  52. package/docs/native-wrappers.md +38 -0
  53. package/docs/nativeidentityresearch-1.8.11.md +0 -0
  54. package/docs/optimization.md +0 -0
  55. package/docs/packageaudit185.md +0 -0
  56. package/docs/performance.md +0 -0
  57. package/docs/planning.1.8.18.md +0 -0
  58. package/docs/planning.1.8.19.md +0 -0
  59. package/docs/planning.engine.md +186 -0
  60. package/docs/platformpipelineaudit.md +0 -0
  61. package/docs/platformpipelines.md +0 -0
  62. package/docs/productindex.md +0 -0
  63. package/docs/registryresearch.md +0 -0
  64. package/docs/release.md +0 -0
  65. package/docs/release17notes.md +0 -0
  66. package/docs/release181notes.md +0 -0
  67. package/docs/release182notes.md +0 -0
  68. package/docs/release184notes.md +0 -0
  69. package/docs/release185notes.md +0 -0
  70. package/docs/release18notes.md +0 -0
  71. package/docs/releaseassets.md +0 -0
  72. package/docs/releasenotes-1.8.12.md +0 -0
  73. package/docs/releasenotes-1.8.13.md +0 -0
  74. package/docs/releasenotes-1.8.14.md +0 -0
  75. package/docs/releasenotes-1.8.15.md +0 -0
  76. package/docs/releasenotes-1.8.16.md +0 -0
  77. package/docs/releasenotes-1.8.17.md +0 -0
  78. package/docs/releasenotes-1.8.18.md +0 -0
  79. package/docs/releasenotes-1.8.19.md +0 -0
  80. package/docs/reorganization-1.8.8.md +0 -0
  81. package/docs/reorganization-1.8.9.md +0 -0
  82. package/docs/repo-analysis-1.8.21.json +0 -0
  83. package/docs/repo-candidates-1.8.21.jsonl +0 -0
  84. package/docs/repo-research-1.8.16.md +0 -0
  85. package/docs/repo-research-1.8.21.md +0 -0
  86. package/docs/repo-selected-1.8.21.json +0 -0
  87. package/docs/repo-synthesis-1.8.16.md +0 -0
  88. package/docs/repo-synthesis-1.8.21.md +0 -0
  89. package/docs/research-1.8.18-isolation.md +0 -0
  90. package/docs/research-1.8.19-virtual-browser.md +0 -0
  91. package/docs/roadmapp2p3.md +0 -0
  92. package/docs/s3compatible.md +0 -0
  93. package/docs/saddle.archive.1.8.17.tar.gz.gpg +0 -0
  94. package/docs/scanningresearch-1.8.11.md +0 -0
  95. package/docs/security.md +0 -0
  96. package/docs/securityaudit-1.8.7.md +0 -0
  97. package/docs/securityreferences-1.8.11.md +0 -0
  98. package/docs/securityresearch-1.8.11.md +0 -0
  99. package/docs/signingresearch-1.8.12.md +0 -0
  100. package/docs/signingwithoutcertificates.md +0 -0
  101. package/docs/signpathapplication.md +0 -0
  102. package/docs/sitemap.md +0 -0
  103. package/docs/tauri-glib-risk-1.8.11.md +0 -0
  104. package/docs/todo-1.8.15.md +0 -0
  105. package/docs/todo-1.8.16.md +0 -0
  106. package/docs/todo-1.8.18.md +0 -0
  107. package/docs/todo-1.8.19.md +0 -0
  108. package/docs/todo.md +0 -0
  109. package/docs/toolchainresearch-1.8.9.md +0 -0
  110. package/docs/toolchains.md +0 -0
  111. package/docs/usage.md +0 -0
  112. package/docs/viability.md +0 -0
  113. package/docs/virtualization.md +0 -0
  114. package/docs/web-duplicatecleanup.md +0 -0
  115. package/docs/web-ideas.md +0 -0
  116. package/docs/workflowimprovements-2026-08-19.md +0 -0
  117. package/docs/workflowinputs.md +0 -0
  118. package/docs/workflowoperations.md +0 -0
  119. package/gpumonitor.cpp +0 -0
  120. package/gpus.json +1 -1
  121. package/index.ts +15 -15
  122. package/manifest.json +20 -0
  123. package/media.ts +0 -0
  124. package/modalityregistry.ts +156 -0
  125. package/mttg.config +1 -1
  126. package/orchestrator.ts +5 -2
  127. package/package.json +147 -97
  128. package/passage.config +2 -2
  129. package/performance.ts +0 -0
  130. package/processors.json +1 -1
  131. package/qemu.config +2 -2
  132. package/qemubridge.py +0 -0
  133. package/quantum.ts +0 -0
  134. package/render.ts +0 -0
  135. package/scheduler.ts +0 -0
  136. package/security.ts +0 -0
  137. package/tiers.ts +9 -3
  138. package/tsconfig.json +22 -17
  139. package/virtualcpu.ts +1 -0
  140. package/virtualgpu.ts +1 -0
  141. package/virtualhardware.c +0 -0
  142. package/virtualhardware.json +2 -2
  143. package/virtualization.ts +0 -0
  144. package/virtualizationcore.cpp +0 -0
  145. package/virtualmemory.ts +0 -0
  146. package/vm.config.json +3 -3
  147. package/biome.json +0 -153
  148. package/dist/acquisition.d.ts +0 -359
  149. package/dist/acquisition.d.ts.map +0 -1
  150. package/dist/acquisition.js +0 -629
  151. package/dist/acquisition.js.map +0 -1
  152. package/dist/alternatives.d.ts +0 -509
  153. package/dist/alternatives.d.ts.map +0 -1
  154. package/dist/alternatives.js +0 -951
  155. package/dist/alternatives.js.map +0 -1
  156. package/dist/automation.d.ts +0 -426
  157. package/dist/automation.d.ts.map +0 -1
  158. package/dist/automation.js +0 -561
  159. package/dist/automation.js.map +0 -1
  160. package/dist/browser.d.ts +0 -562
  161. package/dist/browser.d.ts.map +0 -1
  162. package/dist/browser.js +0 -1057
  163. package/dist/browser.js.map +0 -1
  164. package/dist/capacitor.config.d.ts +0 -13
  165. package/dist/capacitor.config.d.ts.map +0 -1
  166. package/dist/capacitor.config.js +0 -26
  167. package/dist/capacitor.config.js.map +0 -1
  168. package/dist/cli.d.ts +0 -17
  169. package/dist/cli.d.ts.map +0 -1
  170. package/dist/cli.js +0 -111
  171. package/dist/cli.js.map +0 -1
  172. package/dist/communication.d.ts +0 -192
  173. package/dist/communication.d.ts.map +0 -1
  174. package/dist/communication.js +0 -418
  175. package/dist/communication.js.map +0 -1
  176. package/dist/compute.d.ts +0 -1161
  177. package/dist/compute.d.ts.map +0 -1
  178. package/dist/compute.js +0 -2273
  179. package/dist/compute.js.map +0 -1
  180. package/dist/distribution.d.ts +0 -529
  181. package/dist/distribution.d.ts.map +0 -1
  182. package/dist/distribution.js +0 -811
  183. package/dist/distribution.js.map +0 -1
  184. package/dist/execution.d.ts +0 -530
  185. package/dist/execution.d.ts.map +0 -1
  186. package/dist/execution.js +0 -761
  187. package/dist/execution.js.map +0 -1
  188. package/dist/format.d.ts +0 -19
  189. package/dist/format.d.ts.map +0 -1
  190. package/dist/format.js +0 -60
  191. package/dist/format.js.map +0 -1
  192. package/dist/foundation.d.ts +0 -176
  193. package/dist/foundation.d.ts.map +0 -1
  194. package/dist/foundation.js +0 -309
  195. package/dist/foundation.js.map +0 -1
  196. package/dist/index.d.ts +0 -735
  197. package/dist/index.d.ts.map +0 -1
  198. package/dist/index.js +0 -1020
  199. package/dist/index.js.map +0 -1
  200. package/dist/integration.d.ts +0 -236
  201. package/dist/integration.d.ts.map +0 -1
  202. package/dist/integration.js +0 -236
  203. package/dist/integration.js.map +0 -1
  204. package/dist/intelligence.d.ts +0 -94
  205. package/dist/intelligence.d.ts.map +0 -1
  206. package/dist/intelligence.js +0 -145
  207. package/dist/intelligence.js.map +0 -1
  208. package/dist/isolation.d.ts +0 -129
  209. package/dist/isolation.d.ts.map +0 -1
  210. package/dist/isolation.js +0 -96
  211. package/dist/isolation.js.map +0 -1
  212. package/dist/media.d.ts +0 -344
  213. package/dist/media.d.ts.map +0 -1
  214. package/dist/media.js +0 -986
  215. package/dist/media.js.map +0 -1
  216. package/dist/modes.d.ts +0 -359
  217. package/dist/modes.d.ts.map +0 -1
  218. package/dist/modes.js +0 -134
  219. package/dist/modes.js.map +0 -1
  220. package/dist/operations.d.ts +0 -67
  221. package/dist/operations.d.ts.map +0 -1
  222. package/dist/operations.js +0 -94
  223. package/dist/operations.js.map +0 -1
  224. package/dist/orchestrator.d.ts +0 -1339
  225. package/dist/orchestrator.d.ts.map +0 -1
  226. package/dist/orchestrator.js +0 -4308
  227. package/dist/orchestrator.js.map +0 -1
  228. package/dist/packager/targetcli.js +0 -4
  229. package/dist/performance.d.ts +0 -416
  230. package/dist/performance.d.ts.map +0 -1
  231. package/dist/performance.js +0 -827
  232. package/dist/performance.js.map +0 -1
  233. package/dist/quantum.d.ts +0 -810
  234. package/dist/quantum.d.ts.map +0 -1
  235. package/dist/quantum.js +0 -2073
  236. package/dist/quantum.js.map +0 -1
  237. package/dist/render.d.ts +0 -381
  238. package/dist/render.d.ts.map +0 -1
  239. package/dist/render.js +0 -1073
  240. package/dist/render.js.map +0 -1
  241. package/dist/scheduler.d.ts +0 -596
  242. package/dist/scheduler.d.ts.map +0 -1
  243. package/dist/scheduler.js +0 -1259
  244. package/dist/scheduler.js.map +0 -1
  245. package/dist/security.d.ts +0 -501
  246. package/dist/security.d.ts.map +0 -1
  247. package/dist/security.js +0 -1170
  248. package/dist/security.js.map +0 -1
  249. package/dist/server.d.ts +0 -21
  250. package/dist/server.d.ts.map +0 -1
  251. package/dist/server.js +0 -52
  252. package/dist/server.js.map +0 -1
  253. package/dist/tiers.d.ts +0 -1002
  254. package/dist/tiers.d.ts.map +0 -1
  255. package/dist/tiers.js +0 -2165
  256. package/dist/tiers.js.map +0 -1
  257. package/dist/virtual.d.ts +0 -641
  258. package/dist/virtual.d.ts.map +0 -1
  259. package/dist/virtual.js +0 -1285
  260. package/dist/virtual.js.map +0 -1
  261. package/dist/virtualcpu.d.ts +0 -316
  262. package/dist/virtualcpu.d.ts.map +0 -1
  263. package/dist/virtualcpu.js +0 -1180
  264. package/dist/virtualcpu.js.map +0 -1
  265. package/dist/virtualgpu.d.ts +0 -345
  266. package/dist/virtualgpu.d.ts.map +0 -1
  267. package/dist/virtualgpu.js +0 -1000
  268. package/dist/virtualgpu.js.map +0 -1
  269. package/dist/virtualization.d.ts +0 -607
  270. package/dist/virtualization.d.ts.map +0 -1
  271. package/dist/virtualization.js +0 -1187
  272. package/dist/virtualization.js.map +0 -1
  273. package/dist/virtualmemory.d.ts +0 -818
  274. package/dist/virtualmemory.d.ts.map +0 -1
  275. package/dist/virtualmemory.js +0 -1796
  276. package/dist/virtualmemory.js.map +0 -1
  277. package/dist/webscrape.d.ts +0 -720
  278. package/dist/webscrape.d.ts.map +0 -1
  279. package/dist/webscrape.js +0 -2697
  280. package/dist/webscrape.js.map +0 -1
  281. package/docs/example-publicapi.ts +0 -7
  282. package/web/extension/manifest.json +0 -23
  283. package/web/readme.md +0 -382
  284. package/web/tsconfig.json +0 -25
package/Dockerfile CHANGED
@@ -10,7 +10,10 @@
10
10
  # stages at the end of this file, folded from the former
11
11
  # dockerfile.saddle) — an explicit build target
12
12
  # (`docker build --target saddle-runtime .`), the node 26 storage and
13
- # compute service published as ghcr.io/wenathlan/saddle:<version>.
13
+ # compute service published as ghcr.io/wenathlan/saddle:<version>
14
+ # across the four-arch family standard (amd64, arm64, ppc64le, s390x
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/webserver.ts`).
14
17
  #
15
18
  # saddle v2 (grand merge) - virtual hardware engine, multi-stage, multi-arch image.
16
19
  #
@@ -91,7 +94,7 @@
91
94
  # -v vmdata:/data/vmdata -v webdata:/data/web \
92
95
  # -v ./vm.config.json:/engine/vm.config.json:ro \
93
96
  # -e PORT=8080 -e NODE_ENV=production -p 31280:8080 \
94
- # ghcr.io/wenathlan/saddle:vhe-2.0.6
97
+ # ghcr.io/wenathlan/saddle:vhe-2.1.6
95
98
  #
96
99
  # vheqemu (the guest runner; build it first with
97
100
  # docker build --target qemu-runtime -t saddle/qemu:11.1.0 . because the
@@ -125,7 +128,7 @@
125
128
  # -e VHE_GPU_PROFILE=b200 -e VHE_GPUS=8 -e VHE_MIG=1 \
126
129
  # -e VHE_SMI_DRIVER=575.57.08 -e VHE_SMI_CUDA=12.9 \
127
130
  # -e VHE_SMI_INTERVAL=30 -e VHE_SKIP_XVFB=1 -e VHE_SKIP_VALIDATE=1 \
128
- # ghcr.io/wenathlan/saddle:vhe-2.0.6 /bin/bash -c '
131
+ # ghcr.io/wenathlan/saddle:vhe-2.1.6 /bin/bash -c '
129
132
  # while true; do
130
133
  # /usr/local/bin/nvidia-smi "$VHE_GPU_PROFILE" "$VHE_GPUS" || true
131
134
  # sleep "$VHE_SMI_INTERVAL"
@@ -142,7 +145,7 @@
142
145
  # -v qemudata:/data/qemudata \
143
146
  # -v ./qemubridge.py:/engine/qemubridge.py:ro \
144
147
  # -e QMP_SOCKET=/run/vhe/vm.qmp -e PYTHONUNBUFFERED=1 \
145
- # ghcr.io/wenathlan/saddle:vhe-2.0.6 \
148
+ # ghcr.io/wenathlan/saddle:vhe-2.1.6 \
146
149
  # python3 /engine/qemubridge.py --socket /run/vhe/vm.qmp status
147
150
  #
148
151
  # saddle-node (the node-engine service, the former compose.yml
@@ -156,7 +159,7 @@
156
159
  # --log-driver json-file --log-opt max-size=50m --log-opt max-file=5 \
157
160
  # --tmpfs /tmp:size=2g,mode=1777 \
158
161
  # -e SADDLE_MEMORY_ENGINE=ram -e SBOT_PLATFORM= -e SBOT_CDN_URL= \
159
- # ghcr.io/wenathlan/saddle:2.0.6 \
162
+ # ghcr.io/wenathlan/saddle:2.1.6 \
160
163
  # node dist/cli.js plan
161
164
  #
162
165
  # observability (the former prometheus scraper of the full profile)
@@ -636,6 +639,44 @@ ENTRYPOINT ["/usr/bin/tini", "--", "qemu-system-x86_64"]
636
639
  # ---------------------------------------------------------------------------
637
640
  # stage 5: runtime (final, default target, the vhe/vhegpu docker run recipes)
638
641
  # ---------------------------------------------------------------------------
642
+ # saddle-build: the node-engine build stage (defined here, before the
643
+ # vhe runtime, so BOTH container surfaces can consume its output: the
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.
649
+
650
+ # the build stage pins to the native build platform ($BUILDPLATFORM): its
651
+ # outputs (the engine dist, pure js, and the web SPA, static assets) are
652
+ # architecture-independent, while the tailwind/lightningcss pipeline only
653
+ # ships native bindings for the builder platforms - the per-arch legs never
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
658
+
659
+ ARG SADDLE_VERSION
660
+ WORKDIR /app
661
+
662
+ ENV NPM_CONFIG_LEGACY_PEER_DEPS=true
663
+
664
+ COPY package.json package-lock.json ./
665
+ RUN npm ci --ignore-scripts --legacy-peer-deps
666
+ COPY . .
667
+ RUN npm run build:engine \
668
+ && node --check dist/cli.js \
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
679
+
639
680
  FROM ${UBUNTU} AS runtime
640
681
  ARG TARGETARCH
641
682
  ARG NODE_VERSION
@@ -866,15 +907,24 @@ COPY --from=python-bridge /bridge/qemubridge.py /engine/qemubridge.py
866
907
  # the engine sources and the specification files consumed at runtime
867
908
  # (docker.config "final" stage copies the whole tree; the flat layout
868
909
  # keeps that a single COPY layer).
869
- 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/
870
911
  COPY processors.json gpus.json cores.json boards.json vm.config.json virtualhardware.json /engine/
871
- COPY qemu.config mttg.config passage.config docker.config /engine/
912
+ COPY qemu.config mttg.config passage.config docker.config mime.types /engine/
872
913
 
873
914
  # the web node ships inside the main container: the mesh edition (auth,
874
915
  # sqlite database, dashboard and the browser sandbox pages) runs from the
875
916
  # same image, so one container serves the engine AND the web surface.
876
- # run it with: docker run ghcr.io/wenathlan/saddle node /engine/web/server.js
877
- COPY web /engine/web
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/
878
928
 
879
929
  RUN set -eux; \
880
930
  chmod 0755 /usr/local/bin/nvidia-smi /usr/local/bin/aetherforge; \
@@ -1238,7 +1288,7 @@ HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --start-interval=5s
1238
1288
  # OCI labels for registry introspection.
1239
1289
  LABEL org.opencontainers.image.title="saddle virtual-hardware engine (the grand merge)" \
1240
1290
  org.opencontainers.image.description="100% software virtual hardware: per-profile CPU/memory spoofing via LD_PRELOAD (max/balanced/lite), mesa 26.2.1 llvmpipe/lavapipe/rusticl, QEMU 11.1.0 TCG/MTTCG, virtual nvidia-smi adapter + NVML/CUDA shims, node 26.7.0, python bridge" \
1241
- org.opencontainers.image.version="2.0.6" \
1291
+ org.opencontainers.image.version="2.1.6" \
1242
1292
  org.opencontainers.image.licenses="MIT" \
1243
1293
  org.opencontainers.image.source="https://github.com/wenathlan/saddle" \
1244
1294
  org.opencontainers.image.documentation="https://github.com/wenathlan/saddle/blob/main/README.md" \
@@ -1277,20 +1327,7 @@ ENTRYPOINT ["/entrypoint.sh"]
1277
1327
  # docker build --target saddle-runtime -t saddle:<version> .
1278
1328
  # ══════════════════════════════════════════════════════════════════════
1279
1329
 
1280
- FROM node:26.8.1-bookworm-slim AS saddle-build
1281
-
1282
- ARG SADDLE_VERSION
1283
- WORKDIR /app
1284
-
1285
- ENV NPM_CONFIG_LEGACY_PEER_DEPS=true
1286
-
1287
- COPY package.json package-lock.json ./
1288
- RUN npm ci --ignore-scripts --legacy-peer-deps
1289
- COPY . .
1290
- RUN npm run build:engine \
1291
- && node --check dist/cli.js
1292
-
1293
- FROM node:26.8.1-bookworm-slim AS saddle-runtime
1330
+ FROM node:26.8.1-slim AS saddle-runtime
1294
1331
 
1295
1332
  ARG SADDLE_VERSION
1296
1333
  LABEL org.opencontainers.image.title="Saddle" \
@@ -1337,6 +1374,15 @@ RUN apt-get update \
1337
1374
  && rm -rf node_modules/cpu-features/deps \
1338
1375
  && find node_modules -type f -name Dockerfile -delete
1339
1376
  COPY --from=saddle-build /app/dist ./dist
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 ./
1340
1386
 
1341
1387
  USER node
1342
1388
 
package/LICENSE CHANGED
File without changes
package/README.md CHANGED
@@ -30,7 +30,9 @@ domain modules land beside the saddle domains:
30
30
  | `scheduler.ts`, `compute.ts`, `performance.ts` | job queues, batch compute and benchmark planning |
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
- | `web/` | the static console and the zero-dependency self-hosted API (`node web/server.js`) |
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.151 grand-merge envelope (`node server.ts`; the api answers its version at `/api/v1/health`) and the browser-pure engine port |
35
+ | `vercel.json`, `netlify.toml` | the platform deploy adapters beside the surface they publish (the caddy ghost stays out of the tree) |
34
36
  | `specs/*` package exports | processors, gpus, cores, boards and the qemu/mttg/passage/docker envelopes |
35
37
 
36
38
  The full pre-merge documentation of the engine lives in
@@ -57,7 +59,7 @@ console.log(context.summary);
57
59
  The deterministic examples and tests do not require network access or real credentials:
58
60
 
59
61
  ```bash
60
- node --import tsx examples/publicapi.ts
62
+ node --import tsx docs/example-publicapi.ts
61
63
  npm test
62
64
  ```
63
65
 
@@ -232,7 +234,7 @@ npm run web:check
232
234
  VITE_BASE_PATH=/saddle npm run web:build:pages
233
235
  ```
234
236
 
235
- Small public configuration and visual assets live under `web/public/`. The development collector is TypeScript source at `web/lib/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.
236
238
 
237
239
  ## Development
238
240
 
@@ -300,3 +302,17 @@ Version 1.8.19 organizes the existing engine into twenty documented, dependency-
300
302
  ## License
301
303
 
302
304
  Saddle is distributed under the [GNU General Public License v3.0 only](LICENSE).
305
+
306
+ ---
307
+
308
+ # The Sol theme
309
+
310
+ # Saddle Sol
311
+
312
+ Saddle is the 20-modality sandbox platform of the DevThink family: one engine spanning compute, virtualization, media, mesh, automation and the other modalities, exposed as a self-hosted site. This Sol theme is the platform's web surface — a Vite + React SPA (console, dashboard, playground, integrations) served from the same sqlite database that `schema.prisma`, `init.sql` and `drizzle.config.ts` describe.
313
+
314
+ ```bash
315
+ pnpm install
316
+ pnpm dev
317
+ ```
318
+
package/alternatives.ts CHANGED
File without changes
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/boards.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "date": "2026-08-22",
8
8
  "dateFirstDoctrine": "The observer validates all specifications against the latest stable releases available on 2026-08-22.",
9
9
  "toolchain": {
10
- "node": "26.7.0 Current",
10
+ "node": "26.12.0 Current",
11
11
  "nodeAlt": "24.19.0 LTS",
12
12
  "typescript": "7.0.2",
13
13
  "python": "3.14.7",
@@ -88,7 +88,7 @@
88
88
  }
89
89
  },
90
90
  "name": "boards.json",
91
- "version": "2.0.0",
91
+ "version": "2.0.127",
92
92
  "title": "saddle - Virtual Motherboard Database (retail boards + platform reference sockets)",
93
93
  "sources": [
94
94
  "pool:boards.json [S6, 20 retail boards]",
package/compute.ts CHANGED
@@ -2554,7 +2554,7 @@ export function buildvmcrdmanifest(opts?: {
2554
2554
  'metadata:',
2555
2555
  ` name: ${plural}.${group}`,
2556
2556
  'spec:',
2557
- ' group: ' + group,
2557
+ ` group: ${group}`,
2558
2558
  ' scope: Namespaced',
2559
2559
  ' names:',
2560
2560
  ` plural: ${plural}`,
@@ -2562,7 +2562,7 @@ export function buildvmcrdmanifest(opts?: {
2562
2562
  ` kind: ${kind}`,
2563
2563
  ' shortNames: [vm, evm]',
2564
2564
  ' versions:',
2565
- ' - name: ' + version,
2565
+ ` - name: ${version}`,
2566
2566
  ' served: true',
2567
2567
  ' storage: true',
2568
2568
  ' subresources:',
@@ -2618,8 +2618,8 @@ export function buildvmcrdmanifest(opts?: {
2618
2618
  '',
2619
2619
  ].join('\n');
2620
2620
  const sample = [
2621
- 'apiVersion: ' + group + '/' + version,
2622
- 'kind: ' + kind,
2621
+ `apiVersion: ${group}/${version}`,
2622
+ `kind: ${kind}`,
2623
2623
  'metadata:',
2624
2624
  ' name: demo-vm',
2625
2625
  ' namespace: saddle',
package/cores.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "metadata": {
3
3
  "id": "cores.json",
4
- "version": "2.0.0",
4
+ "version": "2.0.127",
5
5
  "date": "2026-08-22",
6
6
  "datestamp": "2026-08-22T00:00:00Z",
7
7
  "observer": "third person observer notes the hybrid core inventory reflects production silicon as of 2026-08-22",
@@ -36,7 +36,7 @@
36
36
  ]
37
37
  },
38
38
  "stack": {
39
- "node": "26.7.0 Current",
39
+ "node": "26.12.0 Current",
40
40
  "nodeAlt": "24.19.0 LTS",
41
41
  "typescript": "7.0.2",
42
42
  "python": "3.14.7",
package/docker.config CHANGED
@@ -2,7 +2,7 @@
2
2
  "meta": {
3
3
  "name": "docker.config",
4
4
  "title": "saddle - Docker Engine and Containerization Configuration",
5
- "version": "2.0.0",
5
+ "version": "2.1.6",
6
6
  "date": "2026-08-22",
7
7
  "language": "en",
8
8
  "sources": [
@@ -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",
@@ -618,7 +623,7 @@
618
623
  "labels": {
619
624
  "org.opencontainers.image.title": "saddle-v6",
620
625
  "org.opencontainers.image.description": "Saddle v6 virtualization orchestrator with QEMU 11.1.0, Node 22 LTS, Python 3.13.5, MTTG, Passage, OVMF",
621
- "org.opencontainers.image.version": "2.0.0",
626
+ "org.opencontainers.image.version": "2.1.6",
622
627
  "org.opencontainers.image.created": "2026-08-22T00:00:00Z",
623
628
  "org.opencontainers.image.authors": "Saddle Core <core@saddle.dev>",
624
629
  "org.opencontainers.image.url": "https://saddle.dev",
@@ -630,7 +635,7 @@
630
635
  "org.opencontainers.image.base.name": "debian:bookworm-slim",
631
636
  "org.opencontainers.image.base.digest": "sha256:bookworm-slim-pinned-2026-08-22",
632
637
  "saddle": {
633
- "com.saddle.version": "2.0.0",
638
+ "com.saddle.version": "2.1.6",
634
639
  "com.saddle.engine": "qemu-11.1.0",
635
640
  "com.saddle.modes": "32",
636
641
  "com.saddle.forge.mirrors": "github,gitea,forgejo",
@@ -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/docs/.gitkeep CHANGED
File without changes
package/docs/CONVERSA.txt CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -104,7 +104,7 @@ Line counts are the shipped files (v2 carryover plus the v3/v5/v6 additions); ev
104
104
  | `libs/gpumonitor.cpp` | 1543 | C++26 four-mode build: CLI smi-adapter, NVML/libcuda shim, N-API addon, forge mode (Plan/validate/qemu_argv, Mttg work-stealing mirror) | 12 hooks |
105
105
  | `libs/virtualhardware.c` | 1011 | C11 LD_PRELOAD core: open-family interception, vhe_translate, inotify regeneration thread, xorshift32 drift, constructor 101 | 12 hooks |
106
106
 
107
- Data files: `gpus.json` 2408, `processors.json` 2217, `vm.config.json` 1910, `cores.json` 1801, `mttg.config` 1548, `boards.json` 1517, `passage.config` 1330, `qemu.config` 1061, `virtualhardware.json` 739, `docker.config` 768. Web node (`web/`, the v6 edition plus the v7 wave): `sandbox.js` 1433 is the browser-pure engine port (bank, procfs payloads, the 89-char smi table, mesa summaries, boot dmesg and the command dispatcher), `server.js` 640 serves the console plus the `/api/v1` contract (health, spec catalogs, sandbox lifecycle, exec) over `node:http`, `index.html` 646 renders the spec panel, terminal and bus timeline, adapters `vercel.json`/`netlify.toml`/`caddyfile` (static edge, api reverse proxied) and `readme.md` 120; the v7 wave completes the node with `db.js` (sqlite persistence for users, sessions and sandboxes), `auth.js` (scrypt password hashing, timing-safe verification, cookie sessions), `mesh.js` (HMAC-SHA256 signed clone-to-main calls with replay windows), the `login.html`/`register.html`/`dashboard.html` pages, `package.json`/`Dockerfile` (image `ghcr.io/wenathlan/e2ugh (main image, web/ included)`; node pinned by the root `.nvmrc`), the database definition kept flat as `schema.prisma` plus `init.sql`, and `mime.types`. Total repository: about 62,200 lines across 83 tracked files (the v2 carryover plus the v3 domains, the v5 media/test waves and the v6 web edition; the `tests/` suite alone is 3,674 lines across eleven files).
107
+ Data files: `gpus.json` 2408, `processors.json` 2217, `vm.config.json` 1910, `cores.json` 1801, `mttg.config` 1548, `boards.json` 1517, `passage.config` 1330, `qemu.config` 1061, `virtualhardware.json` 739, `docker.config` 768. Web node (`web/`, the v6 edition plus the v7 wave): `sandbox.ts` 1433 is the browser-pure engine port (bank, procfs payloads, the 89-char smi table, mesa summaries, boot dmesg and the command dispatcher), `server.ts` 640 serves the console plus the `/api/v1` contract (health, spec catalogs, sandbox lifecycle, exec) over `node:http`, `index.html` 646 renders the spec panel, terminal and bus timeline, adapters `vercel.json`/`netlify.toml`/`caddyfile` (static edge, api reverse proxied) and `readme.md` 120; the v7 wave completes the node with `db.ts` (sqlite persistence for users, sessions and sandboxes), `auth.ts` (scrypt password hashing, timing-safe verification, cookie sessions), `mesh.ts` (HMAC-SHA256 signed clone-to-main calls with replay windows), the `login.html`/`register.html`/`dashboard.html` pages, `package.json`/`Dockerfile` (image `ghcr.io/wenathlan/e2ugh (main image, web/ included)`; node pinned by the root `.nvmrc`), the database definition kept flat as `schema.prisma` plus `init.sql`, and `mime.types`. Total repository: about 62,200 lines across 83 tracked files (the v2 carryover plus the v3 domains, the v5 media/test waves and the v6 web edition; the `tests/` suite alone is 3,674 lines across eleven files).
108
108
 
109
109
  ## 3a. v3 redistribution: features live in domains
110
110
 
@@ -129,7 +129,7 @@ Tests live in the `tests/` directory (one of the five allowed directories) and r
129
129
 
130
130
  ## 3c. Web node architecture (v7)
131
131
 
132
- The web node is a static edge plus one self-hosted process. Clones (Vercel and Netlify static deploys of the `web/` pages, redeployed from the `pages.yml` zip -9 bundle) ship only immutable assets: the console, the auth pages and `sandbox.js`, which runs the whole engine client-side and keeps working with no backend. The main node (`node web/server.js` behind the `caddyfile` on devthink.pro, packaged as `ghcr.io/wenathlan/e2ugh (main image, web/ included)`) owns everything stateful: `db.js` opens the sqlite file (`schema.prisma`/`init.sql` define it, kept flat per the two-level contract), `auth.js` is the auth authority - scrypt password hashing with per-user salts, timing-safe verification and server-side sessions delivered as `HttpOnly` cookies - and `mesh.js` verifies the HMAC-SHA256 signature (method, path, body digest, timestamp, bounded replay window) that every clone attaches to forwarded calls before routing. The design is deliberately anti-serverless: no function runs on the edge platforms, clones hold no secrets and no database, and unauthenticated clones receive 401 from the main node instead of any partial authority.
132
+ The web node is a static edge plus one self-hosted process. Clones (Vercel and Netlify static deploys of the `web/` pages, redeployed from the `pages.yml` zip -9 bundle) ship only immutable assets: the console, the auth pages and `sandbox.ts`, which runs the whole engine client-side and keeps working with no backend. The main node (`node web/server.ts` behind the `caddyfile` on devthink.pro, packaged as `ghcr.io/wenathlan/e2ugh (main image, web/ included)`) owns everything stateful: `db.ts` opens the sqlite file (`schema.prisma`/`init.sql` define it, kept flat per the two-level contract), `auth.ts` is the auth authority - scrypt password hashing with per-user salts, timing-safe verification and server-side sessions delivered as `HttpOnly` cookies - and `mesh.ts` verifies the HMAC-SHA256 signature (method, path, body digest, timestamp, bounded replay window) that every clone attaches to forwarded calls before routing. The design is deliberately anti-serverless: no function runs on the edge platforms, clones hold no secrets and no database, and unauthenticated clones receive 401 from the main node instead of any partial authority.
133
133
 
134
134
  ## 4. One bus, one state machine, one metrics store
135
135
 
@@ -209,7 +209,7 @@ Per-target delivery notes (one line each, from the collapsed topic docs):
209
209
  | Target | How the same library is consumed |
210
210
  | --- | --- |
211
211
  | desktop | EgUi lab UI over the in-process factory; every control honors the 44 px touch target |
212
- | web | browser bundle shipped as the `web/` console (v6): `sandbox.js` mirrors the verified bank, `server.js` serves it and `/api/spec`; zero dependencies keep the graph tree-shakeable |
212
+ | web | browser bundle shipped as the `web/` console (v6): `sandbox.ts` mirrors the verified bank, `server.ts` serves it and `/api/spec`; zero dependencies keep the graph tree-shakeable |
213
213
  | extension | browser extension service worker; endpoints bind a random port, never localhost |
214
214
  | cli | planner CLI: `node --experimental-strip-types index.ts` reading `vm.config.toml` |
215
215
  | android | container/wasm module surface through the JVM bridge; arm64 native runner images |
File without changes
File without changes
File without changes
Binary file
File without changes
File without changes
File without changes
File without changes
package/docs/browser.md CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,73 @@
1
+ # Container platform decision for 2.1.0
2
+
3
+ ## Scope
4
+
5
+ Version 2.1.0 expands the Saddle node-engine OCI image (the `saddle-runtime`
6
+ target of the one container file, published by the `publish ghcr` workflow)
7
+ from a three-platform Linux manifest index to the four-architecture family
8
+ surface: `linux/amd64`, `linux/arm64`, `linux/ppc64le`, and `linux/s390x`.
9
+ The selection unifies the container surfaces of the grand merge: the saddle
10
+ lineage shipped amd64/arm64/ppc64le (the 1.8.17 decision, see
11
+ [containerplatforms-1.8.17.md](containerplatforms-1.8.17.md)) and the e2ugh
12
+ lineage published all four architectures for the virtual-hardware engine
13
+ (`publish ghcr vhe`). One product, one registry, one architecture surface:
14
+ the s390x leg joins the node engine from the e2ugh side of the family.
15
+
16
+ ## Platform matrix
17
+
18
+ `linux/amd64` stays because it is the primary GitHub runner architecture and
19
+ the local smoke-test platform. `linux/arm64`, `linux/ppc64le` and
20
+ `linux/s390x` stay (or join) because the vhe engine of the same repository
21
+ already builds, publishes and verifies those architectures under QEMU
22
+ emulation - the publish ghcr vhe lane proves the build path - and the
23
+ node-engine build is a strict subset of that work (npm tooling, no mesa,
24
+ no qemu compilation).[1] [2]
25
+
26
+ `linux/arm/v7` and `linux/386` remain deferred because no inspected Node 26
27
+ base manifest exposes them. `windows/amd64` remains deferred (see the 1.8.17
28
+ decision): the Dockerfile uses Debian Linux stages and a Windows container
29
+ needs a Windows-specific base image, runner and smoke tests. `unknown/*`
30
+ remains rejected: OCI `unknown` descriptors are not a runnable target and
31
+ must not be advertised as a Saddle container platform.
32
+
33
+ ## Base image prerequisite (the s390x gap of the bookworm-slim variant)
34
+
35
+ The current node-engine stages build `FROM node:26.8.1-bookworm-slim`. The
36
+ manifest index of that tag (inspected through the Docker Hub registry API on
37
+ 2026-09-06, the same view `docker manifest inspect` reads) exposes exactly
38
+ `linux/amd64`, `linux/arm64` and `linux/ppc64le` - the bookworm-slim variant
39
+ does not publish an s390x entry. The Node 26 tags that DO carry s390x are
40
+ the default-distro (Debian trixie) variants: `node:26.8.1` and
41
+ `node:26.8.1-slim`. The four-arch publication therefore requires the
42
+ `saddle-build` and `saddle-runtime` FROM lines of the one container file to
43
+ move to a trixie-based variant (for example `node:26.8.1-slim`, keeping the
44
+ slim profile) before the first 2.1.0 publish run; until that base swap
45
+ lands, the buildx s390x leg cannot resolve its base manifest and the publish
46
+ job fails at the platform-resolution step. That Dockerfile change belongs to
47
+ the same 2.1.0 wave as this decision document (it is recorded here because
48
+ the container file is not part of this workflow-restoration change set).
49
+
50
+ ## Workflow design
51
+
52
+ The `publish ghcr` workflow registers QEMU (arm64, ppc64le, s390x emulation)
53
+ before Buildx, scans a loadable `linux/amd64` image (GitHub-hosted runners
54
+ cannot load a multi-platform image into the default local image store, so
55
+ the scan build stays single-platform while the publication is pushed
56
+ directly to GHCR), pushes all four Linux variants through one Buildx
57
+ invocation, and verifies the registry manifest index for the four
58
+ architectures before the amd64 label and CLI smoke test.[2] The publish lane
59
+ also carries the restored idempotency gates: the resolve job (tag ==
60
+ package.json == published release, plus the e2ugh "already shipped"
61
+ tag/HEAD guard) so a duplicate release firing no-ops instead of repushing.
62
+
63
+ ## Publication claims
64
+
65
+ The version 2.1.0 release notes and registry documentation may claim only
66
+ the four verified Linux variants after the pushed image index is inspected.
67
+ They must not claim Windows support, an `unknown` platform, or a universal
68
+ OS image.
69
+
70
+ ## References
71
+
72
+ [1]: https://docs.docker.com/build/building/multi-platform/ "Docker multi-platform builds"
73
+ [2]: https://docs.docker.com/build/ci/github-actions/multi-platform/ "Multi-platform image with GitHub Actions"