@xemahq/xema 0.59.1 → 0.60.1

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 (850) hide show
  1. package/README.md +1 -2
  2. package/dist/appliance-owner.d.ts.map +1 -1
  3. package/dist/appliance-owner.js +6 -6
  4. package/dist/appliance-owner.js.map +1 -1
  5. package/dist/assets/appliance-base/authorization.lock.json +5186 -0
  6. package/dist/assets/appliance-base/distribution.lock.json +760 -279
  7. package/dist/assets/appliance-base/images.lock.json +576 -513
  8. package/dist/assets/appliance-lean/authorization.lock.json +4648 -0
  9. package/dist/assets/appliance-lean/distribution.lock.json +641 -186
  10. package/dist/assets/appliance-lean/images.lock.json +574 -512
  11. package/dist/assets/boot-graph.json +33 -14
  12. package/dist/assets/distribution-assets.manifest.json +23 -20
  13. package/dist/cli.d.ts.map +1 -1
  14. package/dist/cli.js +7 -9
  15. package/dist/cli.js.map +1 -1
  16. package/dist/commands/biome-dev.d.ts.map +1 -1
  17. package/dist/commands/biome-dev.js +41 -137
  18. package/dist/commands/biome-dev.js.map +1 -1
  19. package/dist/commands/biome-lifecycle.d.ts +24 -0
  20. package/dist/commands/biome-lifecycle.d.ts.map +1 -0
  21. package/dist/commands/biome-lifecycle.js +253 -0
  22. package/dist/commands/biome-lifecycle.js.map +1 -0
  23. package/dist/commands/biome.d.ts +2 -4
  24. package/dist/commands/biome.d.ts.map +1 -1
  25. package/dist/commands/biome.js +61 -46
  26. package/dist/commands/biome.js.map +1 -1
  27. package/dist/commands/config.d.ts.map +1 -1
  28. package/dist/commands/config.js +96 -8
  29. package/dist/commands/config.js.map +1 -1
  30. package/dist/commands/doctor.d.ts.map +1 -1
  31. package/dist/commands/doctor.js +13 -20
  32. package/dist/commands/doctor.js.map +1 -1
  33. package/dist/commands/down.d.ts.map +1 -1
  34. package/dist/commands/down.js +14 -21
  35. package/dist/commands/down.js.map +1 -1
  36. package/dist/commands/init.d.ts.map +1 -1
  37. package/dist/commands/init.js +6 -3
  38. package/dist/commands/init.js.map +1 -1
  39. package/dist/commands/installation-tenancy.d.ts +11 -0
  40. package/dist/commands/installation-tenancy.d.ts.map +1 -0
  41. package/dist/commands/installation-tenancy.js +308 -0
  42. package/dist/commands/installation-tenancy.js.map +1 -0
  43. package/dist/commands/installation.d.ts.map +1 -1
  44. package/dist/commands/installation.js +19 -5
  45. package/dist/commands/installation.js.map +1 -1
  46. package/dist/commands/logs.d.ts +2 -0
  47. package/dist/commands/logs.d.ts.map +1 -1
  48. package/dist/commands/logs.js +41 -8
  49. package/dist/commands/logs.js.map +1 -1
  50. package/dist/commands/{dev.d.ts → mirror.d.ts} +1 -1
  51. package/dist/commands/mirror.d.ts.map +1 -0
  52. package/dist/commands/mirror.js +132 -0
  53. package/dist/commands/mirror.js.map +1 -0
  54. package/dist/commands/source.d.ts.map +1 -1
  55. package/dist/commands/source.js +3 -2
  56. package/dist/commands/source.js.map +1 -1
  57. package/dist/commands/status.d.ts.map +1 -1
  58. package/dist/commands/status.js +40 -21
  59. package/dist/commands/status.js.map +1 -1
  60. package/dist/commands/up.d.ts.map +1 -1
  61. package/dist/commands/up.js +36 -4
  62. package/dist/commands/up.js.map +1 -1
  63. package/dist/dev/infra-preflight.d.ts.map +1 -1
  64. package/dist/dev/infra-preflight.js +0 -4
  65. package/dist/dev/infra-preflight.js.map +1 -1
  66. package/dist/lib/confirm.d.ts +3 -0
  67. package/dist/lib/confirm.d.ts.map +1 -0
  68. package/dist/lib/confirm.js +19 -0
  69. package/dist/lib/confirm.js.map +1 -0
  70. package/dist/lib/output-format.d.ts +6 -0
  71. package/dist/lib/output-format.d.ts.map +1 -0
  72. package/dist/lib/output-format.js +20 -0
  73. package/dist/lib/output-format.js.map +1 -0
  74. package/dist/mirror/config/mirror-config-document.d.ts +18 -0
  75. package/dist/mirror/config/mirror-config-document.d.ts.map +1 -0
  76. package/dist/mirror/config/mirror-config-document.js +57 -0
  77. package/dist/mirror/config/mirror-config-document.js.map +1 -0
  78. package/dist/mirror/config/mirror-config-schema.d.ts +54 -0
  79. package/dist/mirror/config/mirror-config-schema.d.ts.map +1 -0
  80. package/dist/mirror/config/mirror-config-schema.js +80 -0
  81. package/dist/mirror/config/mirror-config-schema.js.map +1 -0
  82. package/dist/mirror/kit/delta.d.ts +4 -0
  83. package/dist/mirror/kit/delta.d.ts.map +1 -0
  84. package/dist/mirror/kit/delta.js +41 -0
  85. package/dist/mirror/kit/delta.js.map +1 -0
  86. package/dist/mirror/kit/export-kit.d.ts +16 -0
  87. package/dist/mirror/kit/export-kit.d.ts.map +1 -0
  88. package/dist/mirror/kit/export-kit.js +112 -0
  89. package/dist/mirror/kit/export-kit.js.map +1 -0
  90. package/dist/mirror/kit/import-kit.d.ts +9 -0
  91. package/dist/mirror/kit/import-kit.d.ts.map +1 -0
  92. package/dist/mirror/kit/import-kit.js +166 -0
  93. package/dist/mirror/kit/import-kit.js.map +1 -0
  94. package/dist/mirror/kit/kit-archive.d.ts +4 -0
  95. package/dist/mirror/kit/kit-archive.d.ts.map +1 -0
  96. package/dist/mirror/kit/kit-archive.js +38 -0
  97. package/dist/mirror/kit/kit-archive.js.map +1 -0
  98. package/dist/mirror/kit/kit-layout.d.ts +10 -0
  99. package/dist/mirror/kit/kit-layout.d.ts.map +1 -0
  100. package/dist/mirror/kit/kit-layout.js +19 -0
  101. package/dist/mirror/kit/kit-layout.js.map +1 -0
  102. package/dist/mirror/kit/kit-sums.d.ts +5 -0
  103. package/dist/mirror/kit/kit-sums.d.ts.map +1 -0
  104. package/dist/mirror/kit/kit-sums.js +63 -0
  105. package/dist/mirror/kit/kit-sums.js.map +1 -0
  106. package/dist/mirror/kit/layout-sync.d.ts +8 -0
  107. package/dist/mirror/kit/layout-sync.d.ts.map +1 -0
  108. package/dist/mirror/kit/layout-sync.js +58 -0
  109. package/dist/mirror/kit/layout-sync.js.map +1 -0
  110. package/dist/mirror/plan/bundle-content.d.ts +10 -0
  111. package/dist/mirror/plan/bundle-content.d.ts.map +1 -0
  112. package/dist/mirror/plan/bundle-content.js +61 -0
  113. package/dist/mirror/plan/bundle-content.js.map +1 -0
  114. package/dist/mirror/plan/declaration-file.d.ts +9 -0
  115. package/dist/mirror/plan/declaration-file.d.ts.map +1 -0
  116. package/dist/mirror/plan/declaration-file.js +27 -0
  117. package/dist/mirror/plan/declaration-file.js.map +1 -0
  118. package/dist/mirror/plan/edition-inputs.d.ts +57 -0
  119. package/dist/mirror/plan/edition-inputs.d.ts.map +1 -0
  120. package/dist/mirror/plan/edition-inputs.js +44 -0
  121. package/dist/mirror/plan/edition-inputs.js.map +1 -0
  122. package/dist/mirror/plan/edition-reader.d.ts +9 -0
  123. package/dist/mirror/plan/edition-reader.d.ts.map +1 -0
  124. package/dist/mirror/plan/edition-reader.js +47 -0
  125. package/dist/mirror/plan/edition-reader.js.map +1 -0
  126. package/dist/mirror/plan/image-content.d.ts +30 -0
  127. package/dist/mirror/plan/image-content.d.ts.map +1 -0
  128. package/dist/mirror/plan/image-content.js +120 -0
  129. package/dist/mirror/plan/image-content.js.map +1 -0
  130. package/dist/mirror/plan/npm-lockfile.d.ts +12 -0
  131. package/dist/mirror/plan/npm-lockfile.d.ts.map +1 -0
  132. package/dist/mirror/plan/npm-lockfile.js +106 -0
  133. package/dist/mirror/plan/npm-lockfile.js.map +1 -0
  134. package/dist/mirror/plan/oci-reads.d.ts +22 -0
  135. package/dist/mirror/plan/oci-reads.d.ts.map +1 -0
  136. package/dist/mirror/plan/oci-reads.js +43 -0
  137. package/dist/mirror/plan/oci-reads.js.map +1 -0
  138. package/dist/mirror/plan/plan-command.d.ts +22 -0
  139. package/dist/mirror/plan/plan-command.d.ts.map +1 -0
  140. package/dist/mirror/plan/plan-command.js +81 -0
  141. package/dist/mirror/plan/plan-command.js.map +1 -0
  142. package/dist/mirror/plan/plan-file.d.ts +13 -0
  143. package/dist/mirror/plan/plan-file.d.ts.map +1 -0
  144. package/dist/mirror/plan/plan-file.js +63 -0
  145. package/dist/mirror/plan/plan-file.js.map +1 -0
  146. package/dist/mirror/plan/resolve-content-set.d.ts +16 -0
  147. package/dist/mirror/plan/resolve-content-set.d.ts.map +1 -0
  148. package/dist/mirror/plan/resolve-content-set.js +90 -0
  149. package/dist/mirror/plan/resolve-content-set.js.map +1 -0
  150. package/dist/mirror/preflight/glob.d.ts +2 -0
  151. package/dist/mirror/preflight/glob.d.ts.map +1 -0
  152. package/dist/mirror/preflight/glob.js +59 -0
  153. package/dist/mirror/preflight/glob.js.map +1 -0
  154. package/dist/mirror/preflight/harbor-api.d.ts +27 -0
  155. package/dist/mirror/preflight/harbor-api.d.ts.map +1 -0
  156. package/dist/mirror/preflight/harbor-api.js +39 -0
  157. package/dist/mirror/preflight/harbor-api.js.map +1 -0
  158. package/dist/mirror/preflight/preflight.d.ts +18 -0
  159. package/dist/mirror/preflight/preflight.d.ts.map +1 -0
  160. package/dist/mirror/preflight/preflight.js +41 -0
  161. package/dist/mirror/preflight/preflight.js.map +1 -0
  162. package/dist/mirror/preflight/tag-moves.d.ts +16 -0
  163. package/dist/mirror/preflight/tag-moves.d.ts.map +1 -0
  164. package/dist/mirror/preflight/tag-moves.js +28 -0
  165. package/dist/mirror/preflight/tag-moves.js.map +1 -0
  166. package/dist/mirror/preflight/write-probe.d.ts +10 -0
  167. package/dist/mirror/preflight/write-probe.d.ts.map +1 -0
  168. package/dist/mirror/preflight/write-probe.js +36 -0
  169. package/dist/mirror/preflight/write-probe.js.map +1 -0
  170. package/dist/mirror/target/extras.d.ts +16 -0
  171. package/dist/mirror/target/extras.d.ts.map +1 -0
  172. package/dist/mirror/target/extras.js +47 -0
  173. package/dist/mirror/target/extras.js.map +1 -0
  174. package/dist/mirror/target/npm-registry.d.ts +20 -0
  175. package/dist/mirror/target/npm-registry.d.ts.map +1 -0
  176. package/dist/mirror/target/npm-registry.js +67 -0
  177. package/dist/mirror/target/npm-registry.js.map +1 -0
  178. package/dist/mirror/target/npm-sync.d.ts +15 -0
  179. package/dist/mirror/target/npm-sync.d.ts.map +1 -0
  180. package/dist/mirror/target/npm-sync.js +44 -0
  181. package/dist/mirror/target/npm-sync.js.map +1 -0
  182. package/dist/mirror/target/oci-items.d.ts +27 -0
  183. package/dist/mirror/target/oci-items.d.ts.map +1 -0
  184. package/dist/mirror/target/oci-items.js +79 -0
  185. package/dist/mirror/target/oci-items.js.map +1 -0
  186. package/dist/mirror/target/oci-sync.d.ts +20 -0
  187. package/dist/mirror/target/oci-sync.d.ts.map +1 -0
  188. package/dist/mirror/target/oci-sync.js +93 -0
  189. package/dist/mirror/target/oci-sync.js.map +1 -0
  190. package/dist/mirror/target/receipt-store.d.ts +13 -0
  191. package/dist/mirror/target/receipt-store.d.ts.map +1 -0
  192. package/dist/mirror/target/receipt-store.js +61 -0
  193. package/dist/mirror/target/receipt-store.js.map +1 -0
  194. package/dist/mirror/target/receipt.d.ts +13 -0
  195. package/dist/mirror/target/receipt.d.ts.map +1 -0
  196. package/dist/mirror/target/receipt.js +25 -0
  197. package/dist/mirror/target/receipt.js.map +1 -0
  198. package/dist/mirror/target/seed-push.d.ts +15 -0
  199. package/dist/mirror/target/seed-push.d.ts.map +1 -0
  200. package/dist/mirror/target/seed-push.js +20 -0
  201. package/dist/mirror/target/seed-push.js.map +1 -0
  202. package/dist/mirror/target/target-access.d.ts +7 -0
  203. package/dist/mirror/target/target-access.d.ts.map +1 -0
  204. package/dist/mirror/target/target-access.js +17 -0
  205. package/dist/mirror/target/target-access.js.map +1 -0
  206. package/dist/mirror/target/target-commands.d.ts +21 -0
  207. package/dist/mirror/target/target-commands.d.ts.map +1 -0
  208. package/dist/mirror/target/target-commands.js +134 -0
  209. package/dist/mirror/target/target-commands.js.map +1 -0
  210. package/dist/mirror/target/target-context.d.ts +11 -0
  211. package/dist/mirror/target/target-context.d.ts.map +1 -0
  212. package/dist/mirror/target/target-context.js +31 -0
  213. package/dist/mirror/target/target-context.js.map +1 -0
  214. package/dist/mirror/target/target-ports.d.ts +18 -0
  215. package/dist/mirror/target/target-ports.d.ts.map +1 -0
  216. package/dist/mirror/target/target-ports.js +49 -0
  217. package/dist/mirror/target/target-ports.js.map +1 -0
  218. package/dist/mirror/target/target-state.d.ts +22 -0
  219. package/dist/mirror/target/target-state.d.ts.map +1 -0
  220. package/dist/mirror/target/target-state.js +41 -0
  221. package/dist/mirror/target/target-state.js.map +1 -0
  222. package/dist/runtime/biome-dev.d.ts +0 -16
  223. package/dist/runtime/biome-dev.d.ts.map +1 -1
  224. package/dist/runtime/biome-dev.js +0 -53
  225. package/dist/runtime/biome-dev.js.map +1 -1
  226. package/dist/runtime/biome-host/biome-directory-packer.d.ts +3 -0
  227. package/dist/runtime/biome-host/biome-directory-packer.d.ts.map +1 -0
  228. package/dist/runtime/biome-host/biome-directory-packer.js +12 -0
  229. package/dist/runtime/biome-host/biome-directory-packer.js.map +1 -0
  230. package/dist/runtime/biome-host/bundle-packer.d.ts +8 -0
  231. package/dist/runtime/biome-host/bundle-packer.d.ts.map +1 -0
  232. package/dist/runtime/biome-host/bundle-packer.js +3 -0
  233. package/dist/runtime/biome-host/bundle-packer.js.map +1 -0
  234. package/dist/runtime/biome-host/from-spec.d.ts +33 -0
  235. package/dist/runtime/biome-host/from-spec.d.ts.map +1 -0
  236. package/dist/runtime/biome-host/from-spec.js +70 -0
  237. package/dist/runtime/biome-host/from-spec.js.map +1 -0
  238. package/dist/runtime/biome-host/lifecycle-client.d.ts +20 -0
  239. package/dist/runtime/biome-host/lifecycle-client.d.ts.map +1 -0
  240. package/dist/runtime/biome-host/lifecycle-client.js +197 -0
  241. package/dist/runtime/biome-host/lifecycle-client.js.map +1 -0
  242. package/dist/runtime/biome-host/lifecycle-contract.d.ts +95 -0
  243. package/dist/runtime/biome-host/lifecycle-contract.d.ts.map +1 -0
  244. package/dist/runtime/biome-host/lifecycle-contract.js +61 -0
  245. package/dist/runtime/biome-host/lifecycle-contract.js.map +1 -0
  246. package/dist/runtime/biome-host/lifecycle-flow.d.ts +29 -0
  247. package/dist/runtime/biome-host/lifecycle-flow.d.ts.map +1 -0
  248. package/dist/runtime/biome-host/lifecycle-flow.js +106 -0
  249. package/dist/runtime/biome-host/lifecycle-flow.js.map +1 -0
  250. package/dist/runtime/biome-host/plan-sources.d.ts +4 -0
  251. package/dist/runtime/biome-host/plan-sources.d.ts.map +1 -0
  252. package/dist/runtime/biome-host/plan-sources.js +26 -0
  253. package/dist/runtime/biome-host/plan-sources.js.map +1 -0
  254. package/dist/runtime/biome-host/relay/bundle-fetch.d.ts +12 -0
  255. package/dist/runtime/biome-host/relay/bundle-fetch.d.ts.map +1 -0
  256. package/dist/runtime/biome-host/relay/bundle-fetch.js +76 -0
  257. package/dist/runtime/biome-host/relay/bundle-fetch.js.map +1 -0
  258. package/dist/runtime/biome-host/relay/declared-scheme.d.ts +3 -0
  259. package/dist/runtime/biome-host/relay/declared-scheme.d.ts.map +1 -0
  260. package/dist/runtime/biome-host/relay/declared-scheme.js +14 -0
  261. package/dist/runtime/biome-host/relay/declared-scheme.js.map +1 -0
  262. package/dist/runtime/biome-host/relay/npm-token.d.ts +4 -0
  263. package/dist/runtime/biome-host/relay/npm-token.d.ts.map +1 -0
  264. package/dist/runtime/biome-host/relay/npm-token.js +16 -0
  265. package/dist/runtime/biome-host/relay/npm-token.js.map +1 -0
  266. package/dist/runtime/biome-host/relay/relay-flow.d.ts +16 -0
  267. package/dist/runtime/biome-host/relay/relay-flow.d.ts.map +1 -0
  268. package/dist/runtime/biome-host/relay/relay-flow.js +48 -0
  269. package/dist/runtime/biome-host/relay/relay-flow.js.map +1 -0
  270. package/dist/runtime/biome-host/source-staging.d.ts +17 -0
  271. package/dist/runtime/biome-host/source-staging.d.ts.map +1 -0
  272. package/dist/runtime/biome-host/source-staging.js +80 -0
  273. package/dist/runtime/biome-host/source-staging.js.map +1 -0
  274. package/dist/runtime/biome-host/sse.d.ts +2 -0
  275. package/dist/runtime/biome-host/sse.d.ts.map +1 -0
  276. package/dist/runtime/biome-host/sse.js +33 -0
  277. package/dist/runtime/biome-host/sse.js.map +1 -0
  278. package/dist/runtime/biome-local-install.js +1 -1
  279. package/dist/runtime/biome-local-install.js.map +1 -1
  280. package/dist/runtime/browser-origins.d.ts +9 -0
  281. package/dist/runtime/browser-origins.d.ts.map +1 -1
  282. package/dist/runtime/browser-origins.js +10 -0
  283. package/dist/runtime/browser-origins.js.map +1 -1
  284. package/dist/runtime/config/config-document.d.ts +10 -0
  285. package/dist/runtime/config/config-document.d.ts.map +1 -0
  286. package/dist/runtime/config/config-document.js +56 -0
  287. package/dist/runtime/config/config-document.js.map +1 -0
  288. package/dist/runtime/config/config-migrations.d.ts +21 -0
  289. package/dist/runtime/config/config-migrations.d.ts.map +1 -0
  290. package/dist/runtime/config/config-migrations.js +102 -0
  291. package/dist/runtime/config/config-migrations.js.map +1 -0
  292. package/dist/runtime/config/config-paths.d.ts +3 -0
  293. package/dist/runtime/config/config-paths.d.ts.map +1 -0
  294. package/dist/runtime/config/config-paths.js +9 -0
  295. package/dist/runtime/config/config-paths.js.map +1 -0
  296. package/dist/runtime/config/dev-mode.d.ts +0 -1
  297. package/dist/runtime/config/dev-mode.d.ts.map +1 -1
  298. package/dist/runtime/config/dev-mode.js +11 -27
  299. package/dist/runtime/config/dev-mode.js.map +1 -1
  300. package/dist/runtime/config/init-presets.js +5 -9
  301. package/dist/runtime/config/init-presets.js.map +1 -1
  302. package/dist/runtime/config/launch-config-help.d.ts +4 -0
  303. package/dist/runtime/config/launch-config-help.d.ts.map +1 -0
  304. package/dist/runtime/config/launch-config-help.js +42 -0
  305. package/dist/runtime/config/launch-config-help.js.map +1 -0
  306. package/dist/runtime/config/launch-config.d.ts +60 -0
  307. package/dist/runtime/config/launch-config.d.ts.map +1 -0
  308. package/dist/runtime/config/launch-config.js +123 -0
  309. package/dist/runtime/config/launch-config.js.map +1 -0
  310. package/dist/runtime/config/launch-schema.d.ts +18 -0
  311. package/dist/runtime/config/launch-schema.d.ts.map +1 -0
  312. package/dist/runtime/config/launch-schema.js +31 -0
  313. package/dist/runtime/config/launch-schema.js.map +1 -0
  314. package/dist/runtime/config/loader.d.ts.map +1 -1
  315. package/dist/runtime/config/loader.js +4 -14
  316. package/dist/runtime/config/loader.js.map +1 -1
  317. package/dist/runtime/config/schema.d.ts +43 -38
  318. package/dist/runtime/config/schema.d.ts.map +1 -1
  319. package/dist/runtime/config/schema.js +17 -46
  320. package/dist/runtime/config/schema.js.map +1 -1
  321. package/dist/runtime/config/sources-config.d.ts +0 -2
  322. package/dist/runtime/config/sources-config.d.ts.map +1 -1
  323. package/dist/runtime/config/sources-config.js +10 -29
  324. package/dist/runtime/config/sources-config.js.map +1 -1
  325. package/dist/runtime/config/versioned-document.d.ts +26 -0
  326. package/dist/runtime/config/versioned-document.d.ts.map +1 -0
  327. package/dist/runtime/config/versioned-document.js +87 -0
  328. package/dist/runtime/config/versioned-document.js.map +1 -0
  329. package/dist/runtime/directory-changes.d.ts +12 -0
  330. package/dist/runtime/directory-changes.d.ts.map +1 -0
  331. package/dist/runtime/directory-changes.js +62 -0
  332. package/dist/runtime/directory-changes.js.map +1 -0
  333. package/dist/runtime/foreground.d.ts +2 -0
  334. package/dist/runtime/foreground.d.ts.map +1 -1
  335. package/dist/runtime/foreground.js +5 -20
  336. package/dist/runtime/foreground.js.map +1 -1
  337. package/dist/runtime/infra/infra-runner.js +5 -5
  338. package/dist/runtime/infra/infra-runner.js.map +1 -1
  339. package/dist/runtime/infra/keycloak-realm.d.ts +3 -19
  340. package/dist/runtime/infra/keycloak-realm.d.ts.map +1 -1
  341. package/dist/runtime/infra/keycloak-realm.js +12 -133
  342. package/dist/runtime/infra/keycloak-realm.js.map +1 -1
  343. package/dist/runtime/installation/current-launcher-renderer.js.map +1 -1
  344. package/dist/runtime/installation/from-current-config.d.ts.map +1 -1
  345. package/dist/runtime/installation/from-current-config.js +24 -47
  346. package/dist/runtime/installation/from-current-config.js.map +1 -1
  347. package/dist/runtime/installation/index.d.ts +1 -0
  348. package/dist/runtime/installation/index.d.ts.map +1 -1
  349. package/dist/runtime/installation/index.js +1 -0
  350. package/dist/runtime/installation/index.js.map +1 -1
  351. package/dist/runtime/installation/installation-file.d.ts +3 -0
  352. package/dist/runtime/installation/installation-file.d.ts.map +1 -0
  353. package/dist/runtime/installation/installation-file.js +9 -0
  354. package/dist/runtime/installation/installation-file.js.map +1 -0
  355. package/dist/runtime/installation/readiness.d.ts +6 -7
  356. package/dist/runtime/installation/readiness.d.ts.map +1 -1
  357. package/dist/runtime/installation/readiness.js +82 -132
  358. package/dist/runtime/installation/readiness.js.map +1 -1
  359. package/dist/runtime/installation/state-store.d.ts +4 -0
  360. package/dist/runtime/installation/state-store.d.ts.map +1 -1
  361. package/dist/runtime/installation/state-store.js +22 -1
  362. package/dist/runtime/installation/state-store.js.map +1 -1
  363. package/dist/runtime/installation/to-current-config.d.ts +4 -1
  364. package/dist/runtime/installation/to-current-config.d.ts.map +1 -1
  365. package/dist/runtime/installation/to-current-config.js +10 -36
  366. package/dist/runtime/installation/to-current-config.js.map +1 -1
  367. package/dist/runtime/launch-plan-format.d.ts.map +1 -1
  368. package/dist/runtime/launch-plan-format.js +31 -6
  369. package/dist/runtime/launch-plan-format.js.map +1 -1
  370. package/dist/runtime/launch-result.d.ts +15 -0
  371. package/dist/runtime/launch-result.d.ts.map +1 -0
  372. package/dist/runtime/launch-result.js +3 -0
  373. package/dist/runtime/launch-result.js.map +1 -0
  374. package/dist/runtime/launch.d.ts +16 -5
  375. package/dist/runtime/launch.d.ts.map +1 -1
  376. package/dist/runtime/launch.js +151 -315
  377. package/dist/runtime/launch.js.map +1 -1
  378. package/dist/runtime/license/helm-pull-secret.d.ts +1 -1
  379. package/dist/runtime/license/helm-pull-secret.d.ts.map +1 -1
  380. package/dist/runtime/license/helm-pull-secret.js +2 -2
  381. package/dist/runtime/license/helm-pull-secret.js.map +1 -1
  382. package/dist/runtime/lifecycle-progress/index.d.ts +9 -0
  383. package/dist/runtime/lifecycle-progress/index.d.ts.map +1 -0
  384. package/dist/runtime/lifecycle-progress/index.js +30 -0
  385. package/dist/runtime/lifecycle-progress/index.js.map +1 -0
  386. package/dist/runtime/lifecycle-progress/json-renderer.d.ts +3 -0
  387. package/dist/runtime/lifecycle-progress/json-renderer.d.ts.map +1 -0
  388. package/dist/runtime/lifecycle-progress/json-renderer.js +27 -0
  389. package/dist/runtime/lifecycle-progress/json-renderer.js.map +1 -0
  390. package/dist/runtime/lifecycle-progress/text-renderer.d.ts +5 -0
  391. package/dist/runtime/lifecycle-progress/text-renderer.d.ts.map +1 -0
  392. package/dist/runtime/lifecycle-progress/text-renderer.js +83 -0
  393. package/dist/runtime/lifecycle-progress/text-renderer.js.map +1 -0
  394. package/dist/runtime/lifecycle-progress/types.d.ts +79 -0
  395. package/dist/runtime/lifecycle-progress/types.d.ts.map +1 -0
  396. package/dist/runtime/lifecycle-progress/types.js +32 -0
  397. package/dist/runtime/lifecycle-progress/types.js.map +1 -0
  398. package/dist/runtime/lock-loader.d.ts +13 -3
  399. package/dist/runtime/lock-loader.d.ts.map +1 -1
  400. package/dist/runtime/lock-loader.js +28 -8
  401. package/dist/runtime/lock-loader.js.map +1 -1
  402. package/dist/runtime/paths.d.ts +2 -0
  403. package/dist/runtime/paths.d.ts.map +1 -1
  404. package/dist/runtime/paths.js +17 -4
  405. package/dist/runtime/paths.js.map +1 -1
  406. package/dist/runtime/roster.d.ts +6 -5
  407. package/dist/runtime/roster.d.ts.map +1 -1
  408. package/dist/runtime/roster.js +0 -21
  409. package/dist/runtime/roster.js.map +1 -1
  410. package/dist/runtime/runner/container-preflight.d.ts +3 -2
  411. package/dist/runtime/runner/container-preflight.d.ts.map +1 -1
  412. package/dist/runtime/runner/container-preflight.js +5 -5
  413. package/dist/runtime/runner/container-preflight.js.map +1 -1
  414. package/dist/runtime/runner/container-runner.d.ts +0 -22
  415. package/dist/runtime/runner/container-runner.d.ts.map +1 -1
  416. package/dist/runtime/runner/container-runner.js +0 -112
  417. package/dist/runtime/runner/container-runner.js.map +1 -1
  418. package/dist/runtime/runner/docker-cli.d.ts +1 -0
  419. package/dist/runtime/runner/docker-cli.d.ts.map +1 -1
  420. package/dist/runtime/runner/docker-cli.js +19 -0
  421. package/dist/runtime/runner/docker-cli.js.map +1 -1
  422. package/dist/runtime/runner/helm-cli.d.ts +1 -0
  423. package/dist/runtime/runner/helm-cli.d.ts.map +1 -1
  424. package/dist/runtime/runner/helm-cli.js +5 -1
  425. package/dist/runtime/runner/helm-cli.js.map +1 -1
  426. package/dist/runtime/runner/helm-pre-install.d.ts +3 -2
  427. package/dist/runtime/runner/helm-pre-install.d.ts.map +1 -1
  428. package/dist/runtime/runner/helm-pre-install.js +3 -3
  429. package/dist/runtime/runner/helm-pre-install.js.map +1 -1
  430. package/dist/runtime/runner/helm-runner.d.ts +29 -4
  431. package/dist/runtime/runner/helm-runner.d.ts.map +1 -1
  432. package/dist/runtime/runner/helm-runner.js +44 -14
  433. package/dist/runtime/runner/helm-runner.js.map +1 -1
  434. package/dist/runtime/runner/kube-cli.d.ts +1 -0
  435. package/dist/runtime/runner/kube-cli.d.ts.map +1 -1
  436. package/dist/runtime/runner/kube-cli.js +24 -2
  437. package/dist/runtime/runner/kube-cli.js.map +1 -1
  438. package/dist/runtime/secrets/managed-local-key-provider.d.ts +2 -0
  439. package/dist/runtime/secrets/managed-local-key-provider.d.ts.map +1 -1
  440. package/dist/runtime/secrets/managed-local-key-provider.js +17 -8
  441. package/dist/runtime/secrets/managed-local-key-provider.js.map +1 -1
  442. package/dist/runtime/seed/closure-container.d.ts +8 -0
  443. package/dist/runtime/seed/closure-container.d.ts.map +1 -0
  444. package/dist/runtime/seed/closure-container.js +23 -0
  445. package/dist/runtime/seed/closure-container.js.map +1 -0
  446. package/dist/runtime/seed/closure-helm.d.ts +9 -0
  447. package/dist/runtime/seed/closure-helm.d.ts.map +1 -0
  448. package/dist/runtime/seed/closure-helm.js +16 -0
  449. package/dist/runtime/seed/closure-helm.js.map +1 -0
  450. package/dist/runtime/seed/closure-names.d.ts +4 -0
  451. package/dist/runtime/seed/closure-names.d.ts.map +1 -0
  452. package/dist/runtime/seed/closure-names.js +29 -0
  453. package/dist/runtime/seed/closure-names.js.map +1 -0
  454. package/dist/runtime/seed/closure-native.d.ts +3 -0
  455. package/dist/runtime/seed/closure-native.d.ts.map +1 -0
  456. package/dist/runtime/seed/closure-native.js +18 -0
  457. package/dist/runtime/seed/closure-native.js.map +1 -0
  458. package/dist/runtime/seed/installation-tar.d.ts +5 -0
  459. package/dist/runtime/seed/installation-tar.d.ts.map +1 -0
  460. package/dist/runtime/seed/installation-tar.js +30 -0
  461. package/dist/runtime/seed/installation-tar.js.map +1 -0
  462. package/dist/runtime/seed/launch-env.d.ts +42 -0
  463. package/dist/runtime/seed/launch-env.d.ts.map +1 -0
  464. package/dist/runtime/seed/launch-env.js +150 -0
  465. package/dist/runtime/seed/launch-env.js.map +1 -0
  466. package/dist/runtime/seed/seed-container.d.ts +13 -0
  467. package/dist/runtime/seed/seed-container.d.ts.map +1 -0
  468. package/dist/runtime/seed/seed-container.js +175 -0
  469. package/dist/runtime/seed/seed-container.js.map +1 -0
  470. package/dist/runtime/seed/seed-delivery-env.d.ts +39 -0
  471. package/dist/runtime/seed/seed-delivery-env.d.ts.map +1 -0
  472. package/dist/runtime/seed/seed-delivery-env.js +47 -0
  473. package/dist/runtime/seed/seed-delivery-env.js.map +1 -0
  474. package/dist/runtime/seed/seed-down.d.ts +2 -0
  475. package/dist/runtime/seed/seed-down.d.ts.map +1 -0
  476. package/dist/runtime/seed/seed-down.js +52 -0
  477. package/dist/runtime/seed/seed-down.js.map +1 -0
  478. package/dist/runtime/seed/seed-env-file.d.ts +3 -0
  479. package/dist/runtime/seed/seed-env-file.d.ts.map +1 -0
  480. package/dist/runtime/seed/seed-env-file.js +24 -0
  481. package/dist/runtime/seed/seed-env-file.js.map +1 -0
  482. package/dist/runtime/seed/seed-env-secret.d.ts +18 -0
  483. package/dist/runtime/seed/seed-env-secret.d.ts.map +1 -0
  484. package/dist/runtime/seed/seed-env-secret.js +62 -0
  485. package/dist/runtime/seed/seed-env-secret.js.map +1 -0
  486. package/dist/runtime/seed/seed-environment.d.ts +12 -0
  487. package/dist/runtime/seed/seed-environment.d.ts.map +1 -0
  488. package/dist/runtime/seed/seed-environment.js +66 -0
  489. package/dist/runtime/seed/seed-environment.js.map +1 -0
  490. package/dist/runtime/seed/seed-facts.d.ts +22 -0
  491. package/dist/runtime/seed/seed-facts.d.ts.map +1 -0
  492. package/dist/runtime/seed/seed-facts.js +40 -0
  493. package/dist/runtime/seed/seed-facts.js.map +1 -0
  494. package/dist/runtime/seed/seed-helm.d.ts +10 -0
  495. package/dist/runtime/seed/seed-helm.d.ts.map +1 -0
  496. package/dist/runtime/seed/seed-helm.js +74 -0
  497. package/dist/runtime/seed/seed-helm.js.map +1 -0
  498. package/dist/runtime/seed/seed-image-user.d.ts +6 -0
  499. package/dist/runtime/seed/seed-image-user.d.ts.map +1 -0
  500. package/dist/runtime/seed/seed-image-user.js +14 -0
  501. package/dist/runtime/seed/seed-image-user.js.map +1 -0
  502. package/dist/runtime/seed/seed-installation-files.d.ts +23 -0
  503. package/dist/runtime/seed/seed-installation-files.d.ts.map +1 -0
  504. package/dist/runtime/seed/seed-installation-files.js +51 -0
  505. package/dist/runtime/seed/seed-installation-files.js.map +1 -0
  506. package/dist/runtime/seed/seed-installation.d.ts +20 -0
  507. package/dist/runtime/seed/seed-installation.d.ts.map +1 -0
  508. package/dist/runtime/seed/seed-installation.js +36 -0
  509. package/dist/runtime/seed/seed-installation.js.map +1 -0
  510. package/dist/runtime/seed/seed-launcher.d.ts +14 -0
  511. package/dist/runtime/seed/seed-launcher.d.ts.map +1 -0
  512. package/dist/runtime/seed/seed-launcher.js +155 -0
  513. package/dist/runtime/seed/seed-launcher.js.map +1 -0
  514. package/dist/runtime/seed/seed-options.d.ts +22 -0
  515. package/dist/runtime/seed/seed-options.d.ts.map +1 -0
  516. package/dist/runtime/seed/seed-options.js +57 -0
  517. package/dist/runtime/seed/seed-options.js.map +1 -0
  518. package/dist/runtime/seed/seed-platform-endpoints.d.ts +8 -0
  519. package/dist/runtime/seed/seed-platform-endpoints.d.ts.map +1 -0
  520. package/dist/runtime/seed/seed-platform-endpoints.js +55 -0
  521. package/dist/runtime/seed/seed-platform-endpoints.js.map +1 -0
  522. package/dist/runtime/seed/seed-process.d.ts +22 -0
  523. package/dist/runtime/seed/seed-process.d.ts.map +1 -0
  524. package/dist/runtime/seed/seed-process.js +79 -0
  525. package/dist/runtime/seed/seed-process.js.map +1 -0
  526. package/dist/runtime/seed/seed-record.d.ts +101 -0
  527. package/dist/runtime/seed/seed-record.d.ts.map +1 -0
  528. package/dist/runtime/seed/seed-record.js +141 -0
  529. package/dist/runtime/seed/seed-record.js.map +1 -0
  530. package/dist/runtime/seed/seed-resolved-deployment.d.ts +14 -0
  531. package/dist/runtime/seed/seed-resolved-deployment.d.ts.map +1 -0
  532. package/dist/runtime/seed/seed-resolved-deployment.js +52 -0
  533. package/dist/runtime/seed/seed-resolved-deployment.js.map +1 -0
  534. package/dist/runtime/seed/substrate-facts.d.ts +52 -0
  535. package/dist/runtime/seed/substrate-facts.d.ts.map +1 -0
  536. package/dist/runtime/seed/substrate-facts.js +92 -0
  537. package/dist/runtime/seed/substrate-facts.js.map +1 -0
  538. package/dist/runtime/seed/supervisor-contract.d.ts +9 -0
  539. package/dist/runtime/seed/supervisor-contract.d.ts.map +1 -0
  540. package/dist/runtime/seed/supervisor-contract.js +68 -0
  541. package/dist/runtime/seed/supervisor-contract.js.map +1 -0
  542. package/dist/runtime/seed/workload-identity-facts.d.ts +17 -0
  543. package/dist/runtime/seed/workload-identity-facts.d.ts.map +1 -0
  544. package/dist/runtime/seed/workload-identity-facts.js +53 -0
  545. package/dist/runtime/seed/workload-identity-facts.js.map +1 -0
  546. package/dist/runtime/sources/schema.d.ts +2 -0
  547. package/dist/runtime/sources/schema.d.ts.map +1 -1
  548. package/dist/runtime/sources/schema.js +10 -9
  549. package/dist/runtime/sources/schema.js.map +1 -1
  550. package/dist/runtime/store-client.d.ts +0 -10
  551. package/dist/runtime/store-client.d.ts.map +1 -1
  552. package/dist/runtime/store-client.js +0 -5
  553. package/dist/runtime/store-client.js.map +1 -1
  554. package/dist/runtime/tenancy/identity-installer.d.ts +11 -0
  555. package/dist/runtime/tenancy/identity-installer.d.ts.map +1 -0
  556. package/dist/runtime/tenancy/identity-installer.js +39 -0
  557. package/dist/runtime/tenancy/identity-installer.js.map +1 -0
  558. package/dist/runtime/tenancy/import-cache.d.ts +5 -0
  559. package/dist/runtime/tenancy/import-cache.d.ts.map +1 -0
  560. package/dist/runtime/tenancy/import-cache.js +27 -0
  561. package/dist/runtime/tenancy/import-cache.js.map +1 -0
  562. package/dist/runtime/tenancy/openfga.d.ts +4 -0
  563. package/dist/runtime/tenancy/openfga.d.ts.map +1 -0
  564. package/dist/runtime/tenancy/openfga.js +29 -0
  565. package/dist/runtime/tenancy/openfga.js.map +1 -0
  566. package/dist/runtime/tenancy/servers.d.ts +18 -0
  567. package/dist/runtime/tenancy/servers.d.ts.map +1 -0
  568. package/dist/runtime/tenancy/servers.js +102 -0
  569. package/dist/runtime/tenancy/servers.js.map +1 -0
  570. package/dist/runtime/tenancy/session.d.ts +19 -0
  571. package/dist/runtime/tenancy/session.d.ts.map +1 -0
  572. package/dist/runtime/tenancy/session.js +79 -0
  573. package/dist/runtime/tenancy/session.js.map +1 -0
  574. package/dist/runtime/tenancy/specs.d.ts +27 -0
  575. package/dist/runtime/tenancy/specs.d.ts.map +1 -0
  576. package/dist/runtime/tenancy/specs.js +129 -0
  577. package/dist/runtime/tenancy/specs.js.map +1 -0
  578. package/examples/01-local-native.yaml +1 -3
  579. package/examples/02-local-container-full.yaml +0 -2
  580. package/examples/03-byo-infra.yaml +1 -5
  581. package/examples/06-biome-sources.yaml +1 -3
  582. package/package.json +32 -37
  583. package/xema.config.example.yaml +1 -10
  584. package/dist/assets/keycloak/realm-xema.json +0 -397
  585. package/dist/commands/dev.d.ts.map +0 -1
  586. package/dist/commands/dev.js +0 -196
  587. package/dist/commands/dev.js.map +0 -1
  588. package/dist/commands/edition.d.ts +0 -3
  589. package/dist/commands/edition.d.ts.map +0 -1
  590. package/dist/commands/edition.js +0 -54
  591. package/dist/commands/edition.js.map +0 -1
  592. package/dist/commands/serve.d.ts +0 -3
  593. package/dist/commands/serve.d.ts.map +0 -1
  594. package/dist/commands/serve.js +0 -114
  595. package/dist/commands/serve.js.map +0 -1
  596. package/dist/dev/carved-installs.d.ts +0 -3
  597. package/dist/dev/carved-installs.d.ts.map +0 -1
  598. package/dist/dev/carved-installs.js +0 -63
  599. package/dist/dev/carved-installs.js.map +0 -1
  600. package/dist/dev/env-defaults.d.ts +0 -15
  601. package/dist/dev/env-defaults.d.ts.map +0 -1
  602. package/dist/dev/env-defaults.js +0 -178
  603. package/dist/dev/env-defaults.js.map +0 -1
  604. package/dist/dev/forked-worker-bootstrap-protocol.d.ts +0 -17
  605. package/dist/dev/forked-worker-bootstrap-protocol.d.ts.map +0 -1
  606. package/dist/dev/forked-worker-bootstrap-protocol.js +0 -35
  607. package/dist/dev/forked-worker-bootstrap-protocol.js.map +0 -1
  608. package/dist/dev/forked-worker-bootstrap.d.ts +0 -18
  609. package/dist/dev/forked-worker-bootstrap.d.ts.map +0 -1
  610. package/dist/dev/forked-worker-bootstrap.js +0 -84
  611. package/dist/dev/forked-worker-bootstrap.js.map +0 -1
  612. package/dist/dev/forked-worker.d.ts +0 -43
  613. package/dist/dev/forked-worker.d.ts.map +0 -1
  614. package/dist/dev/forked-worker.js +0 -358
  615. package/dist/dev/forked-worker.js.map +0 -1
  616. package/dist/edition/verify-image-evidence.bundle.mjs +0 -70456
  617. package/dist/edition/verify-image-evidence.bundle.provenance.json +0 -3957
  618. package/dist/runtime/backing-registration.d.ts +0 -11
  619. package/dist/runtime/backing-registration.d.ts.map +0 -1
  620. package/dist/runtime/backing-registration.js +0 -94
  621. package/dist/runtime/backing-registration.js.map +0 -1
  622. package/dist/runtime/edition/run-entry.d.ts +0 -9
  623. package/dist/runtime/edition/run-entry.d.ts.map +0 -1
  624. package/dist/runtime/edition/run-entry.js +0 -36
  625. package/dist/runtime/edition/run-entry.js.map +0 -1
  626. package/dist/runtime/edition/verb-map.d.ts +0 -70
  627. package/dist/runtime/edition/verb-map.d.ts.map +0 -1
  628. package/dist/runtime/edition/verb-map.js +0 -166
  629. package/dist/runtime/edition/verb-map.js.map +0 -1
  630. package/dist/runtime/env-seeder.d.ts +0 -26
  631. package/dist/runtime/env-seeder.d.ts.map +0 -1
  632. package/dist/runtime/env-seeder.js +0 -340
  633. package/dist/runtime/env-seeder.js.map +0 -1
  634. package/dist/runtime/port-map.d.ts +0 -3
  635. package/dist/runtime/port-map.d.ts.map +0 -1
  636. package/dist/runtime/port-map.js +0 -25
  637. package/dist/runtime/port-map.js.map +0 -1
  638. package/dist/runtime/run-manifest.d.ts +0 -52
  639. package/dist/runtime/run-manifest.d.ts.map +0 -1
  640. package/dist/runtime/run-manifest.js +0 -585
  641. package/dist/runtime/run-manifest.js.map +0 -1
  642. package/dist/runtime/runner/native-runner.d.ts +0 -13
  643. package/dist/runtime/runner/native-runner.d.ts.map +0 -1
  644. package/dist/runtime/runner/native-runner.js +0 -94
  645. package/dist/runtime/runner/native-runner.js.map +0 -1
  646. package/dist/runtime/runner/service-runner.d.ts +0 -28
  647. package/dist/runtime/runner/service-runner.d.ts.map +0 -1
  648. package/dist/runtime/runner/service-runner.js +0 -10
  649. package/dist/runtime/runner/service-runner.js.map +0 -1
  650. package/dist/runtime/secrets/generated-platform-secrets.d.ts +0 -16
  651. package/dist/runtime/secrets/generated-platform-secrets.d.ts.map +0 -1
  652. package/dist/runtime/secrets/generated-platform-secrets.js +0 -115
  653. package/dist/runtime/secrets/generated-platform-secrets.js.map +0 -1
  654. package/dist/runtime/secrets/resolver.d.ts +0 -23
  655. package/dist/runtime/secrets/resolver.d.ts.map +0 -1
  656. package/dist/runtime/secrets/resolver.js +0 -266
  657. package/dist/runtime/secrets/resolver.js.map +0 -1
  658. package/dist/runtime/supervisor.d.ts +0 -67
  659. package/dist/runtime/supervisor.d.ts.map +0 -1
  660. package/dist/runtime/supervisor.js +0 -227
  661. package/dist/runtime/supervisor.js.map +0 -1
  662. package/edition/README.md +0 -293
  663. package/edition/adapter-bundled-versions.mjs +0 -108
  664. package/edition/adapter-host-versions.mjs +0 -41
  665. package/edition/assemble-producer-input.mjs +0 -1423
  666. package/edition/assemble-third-party-input.mjs +0 -336
  667. package/edition/authored-source-check.mjs +0 -135
  668. package/edition/authorization-base.mjs +0 -357
  669. package/edition/biome-content.mjs +0 -327
  670. package/edition/build-biome-index.mjs +0 -180
  671. package/edition/build-ledger-completeness.mjs +0 -67
  672. package/edition/build-ledger.mjs +0 -107
  673. package/edition/build-platform-service-index.mjs +0 -292
  674. package/edition/build-runtime-image-source-index.mjs +0 -1187
  675. package/edition/built-verifier.mjs +0 -55
  676. package/edition/candidate-edition.mjs +0 -697
  677. package/edition/canonical-evidence-json.mjs +0 -82
  678. package/edition/canonical-repository-url.mjs +0 -47
  679. package/edition/catalogue-edition.mjs +0 -49
  680. package/edition/catalogue-evidence.mjs +0 -228
  681. package/edition/catalogue-fetch.mjs +0 -124
  682. package/edition/catalogue-lock-field.mjs +0 -41
  683. package/edition/catalogue-pin.mjs +0 -71
  684. package/edition/catalogue.mjs +0 -223
  685. package/edition/chart-set-source-commit.mjs +0 -97
  686. package/edition/check-committed-image-evidence.mjs +0 -412
  687. package/edition/checkout-indexed-source-repositories.mjs +0 -283
  688. package/edition/collected-refusals.mjs +0 -59
  689. package/edition/comment-blanker.mjs +0 -75
  690. package/edition/connector-source-authority.mjs +0 -60
  691. package/edition/contract-authority.mjs +0 -122
  692. package/edition/cosign-certificate-constraints.mjs +0 -50
  693. package/edition/cross-repo-build-inputs.mjs +0 -158
  694. package/edition/customer-definition.mjs +0 -174
  695. package/edition/customer-edition-verbs.mjs +0 -617
  696. package/edition/customer-flags.mjs +0 -15
  697. package/edition/develop-catalogue-free-resolve.mjs +0 -280
  698. package/edition/develop-chart-roster.mjs +0 -96
  699. package/edition/develop-chart-set.mjs +0 -147
  700. package/edition/develop-coverage-readers.mjs +0 -217
  701. package/edition/develop-coverage-run.mjs +0 -50
  702. package/edition/develop-coverage-verdict.mjs +0 -153
  703. package/edition/develop-coverage.mjs +0 -131
  704. package/edition/develop-current-resolution.mjs +0 -29
  705. package/edition/develop-edition-invariants.mjs +0 -142
  706. package/edition/develop-edition-resolve.mjs +0 -211
  707. package/edition/develop-edition.mjs +0 -379
  708. package/edition/develop-engine-image.mjs +0 -95
  709. package/edition/develop-follow.mjs +0 -491
  710. package/edition/develop-freeze.mjs +0 -289
  711. package/edition/develop-git-bundles.mjs +0 -255
  712. package/edition/develop-graph.mjs +0 -28
  713. package/edition/develop-image-coordinate.mjs +0 -54
  714. package/edition/develop-image-probe.mjs +0 -120
  715. package/edition/develop-image-record.mjs +0 -194
  716. package/edition/develop-record-ancestry.mjs +0 -144
  717. package/edition/develop-record-verify.mjs +0 -216
  718. package/edition/develop-resolve-report.mjs +0 -159
  719. package/edition/develop-select-records.mjs +0 -172
  720. package/edition/develop-selection-line.mjs +0 -30
  721. package/edition/develop-selection-pins.mjs +0 -36
  722. package/edition/develop-snapshot-inputs.mjs +0 -93
  723. package/edition/develop-verify-image-record.mjs +0 -109
  724. package/edition/dispatched-runtime-components.mjs +0 -172
  725. package/edition/dist-check.mjs +0 -1137
  726. package/edition/distribution-evidence-set.mjs +0 -1064
  727. package/edition/distribution-paths.mjs +0 -191
  728. package/edition/edition-artifact.mjs +0 -529
  729. package/edition/edition-biome-manifests.mjs +0 -85
  730. package/edition/edition-ceiling.mjs +0 -180
  731. package/edition/edition-channel.mjs +0 -20
  732. package/edition/edition-chart-set.mjs +0 -104
  733. package/edition/edition-config.mjs +0 -374
  734. package/edition/edition-constants.mjs +0 -110
  735. package/edition/edition-contract-parse.mjs +0 -179
  736. package/edition/edition-contract.json +0 -100
  737. package/edition/edition-image-seal.mjs +0 -110
  738. package/edition/edition-kind.mjs +0 -10
  739. package/edition/edition-list.mjs +0 -35
  740. package/edition/edition-locks.mjs +0 -79
  741. package/edition/edition-pin-verbs.mjs +0 -183
  742. package/edition/edition-pin.mjs +0 -17
  743. package/edition/edition-pinned-inputs.mjs +0 -116
  744. package/edition/edition-protocol.mjs +0 -277
  745. package/edition/edition-publisher.mjs +0 -21
  746. package/edition/edition-seal.mjs +0 -257
  747. package/edition/edition-source-pins.mjs +0 -211
  748. package/edition/edition-unpin.mjs +0 -16
  749. package/edition/edition-workspace.mjs +0 -204
  750. package/edition/evidence-checkout.mjs +0 -439
  751. package/edition/evidence-grammars.mjs +0 -18
  752. package/edition/evidence-input-mode.mjs +0 -136
  753. package/edition/evidence-trust-pin.mjs +0 -380
  754. package/edition/evidence-trust-policy.mjs +0 -374
  755. package/edition/external-source-walk.mjs +0 -485
  756. package/edition/first-party-source-ids.mjs +0 -62
  757. package/edition/fleet-coordinate-finality.mjs +0 -363
  758. package/edition/fulcio-certificate-extensions.mjs +0 -231
  759. package/edition/git-json.mjs +0 -54
  760. package/edition/git-source-auth.mjs +0 -131
  761. package/edition/image-delivery-entitlement-descriptor.mjs +0 -106
  762. package/edition/image-evidence-replay-state.mjs +0 -242
  763. package/edition/image-evidence.mjs +0 -1404
  764. package/edition/image-lock-service-rows.mjs +0 -26
  765. package/edition/image-subject-identity.mjs +0 -24
  766. package/edition/images-from-record.mjs +0 -298
  767. package/edition/indexes-at-pins.mjs +0 -13
  768. package/edition/kernel-capability-domain.mjs +0 -30
  769. package/edition/kernel-contract-authority.mjs +0 -517
  770. package/edition/kernel-contract-source-path-set.mjs +0 -94
  771. package/edition/kernel-package.mjs +0 -206
  772. package/edition/kernel-protocol-choice.mjs +0 -239
  773. package/edition/kernel-signer-kind.mjs +0 -23
  774. package/edition/lib.mjs +0 -3303
  775. package/edition/locked-bundle-signer.mjs +0 -297
  776. package/edition/map-with-concurrency.mjs +0 -19
  777. package/edition/no-follow-file.mjs +0 -94
  778. package/edition/oci-evidence-verbs.mjs +0 -419
  779. package/edition/oci-image-source-evidence.mjs +0 -364
  780. package/edition/operation-grant-admission.mjs +0 -285
  781. package/edition/operation-requirements.mjs +0 -282
  782. package/edition/oras-process.mjs +0 -35
  783. package/edition/oras.mjs +0 -742
  784. package/edition/pin-source.mjs +0 -242
  785. package/edition/pinned-cosign.mjs +0 -70
  786. package/edition/plan-image-evidence-refresh.mjs +0 -427
  787. package/edition/platform-lock-readability.mjs +0 -901
  788. package/edition/platform-service-class.mjs +0 -85
  789. package/edition/platform-service-ownership.mjs +0 -88
  790. package/edition/platform-service-source-authority.mjs +0 -105
  791. package/edition/positive-closure.mjs +0 -162
  792. package/edition/previous-seal-registry.mjs +0 -149
  793. package/edition/produce-distribution-completeness-evidence.mjs +0 -318
  794. package/edition/produce-edition-catalogue-evidence.mjs +0 -252
  795. package/edition/produce-image-delivery-entitlement-evidence.mjs +0 -304
  796. package/edition/produce-image-source-evidence.mjs +0 -788
  797. package/edition/producer-evidence-bundle.mjs +0 -201
  798. package/edition/producer-evidence-index-file.mjs +0 -4
  799. package/edition/record-refused.mjs +0 -12
  800. package/edition/registry-credentials.mjs +0 -225
  801. package/edition/release-catalogue.mjs +0 -47
  802. package/edition/release-channel-guard.mjs +0 -108
  803. package/edition/release-edition-artifact.mjs +0 -152
  804. package/edition/release-edition.mjs +0 -54
  805. package/edition/remedy-command.mjs +0 -89
  806. package/edition/repin-evidence-trust.mjs +0 -109
  807. package/edition/required-resource-provider.mjs +0 -58
  808. package/edition/resolve-chart-set.mjs +0 -376
  809. package/edition/resolve-customer-edition.mjs +0 -374
  810. package/edition/resolve-distribution.mjs +0 -2639
  811. package/edition/resolve-edition.mjs +0 -192
  812. package/edition/resolve-images.mjs +0 -2351
  813. package/edition/seal-image-digests.mjs +0 -954
  814. package/edition/sealed-distribution-lock.mjs +0 -22
  815. package/edition/sealed-evidence-accept.mjs +0 -82
  816. package/edition/shallow-checkout.mjs +0 -18
  817. package/edition/sigstore-bundle-media-type.mjs +0 -4
  818. package/edition/source-authority.mjs +0 -713
  819. package/edition/source-closure.mjs +0 -115
  820. package/edition/store-publisher-trust.mjs +0 -40
  821. package/edition/strict-json.mjs +0 -121
  822. package/edition/string-order.mjs +0 -5
  823. package/edition/third-party-admission.mjs +0 -91
  824. package/edition/third-party-bundle-registry.mjs +0 -242
  825. package/edition/third-party-certificate-extensions.mjs +0 -21
  826. package/edition/third-party-image-pins.mjs +0 -41
  827. package/edition/third-party-pins-trusted.mjs +0 -24
  828. package/edition/third-party-producer-evidence.mjs +0 -76
  829. package/edition/third-party-source-check.mjs +0 -28
  830. package/edition/third-party-source-context.mjs +0 -41
  831. package/edition/third-party-source-evidence.mjs +0 -287
  832. package/edition/third-party-source-flags.mjs +0 -134
  833. package/edition/third-party-source-pins.mjs +0 -259
  834. package/edition/third-party-source-uniqueness.mjs +0 -54
  835. package/edition/third-party-source-walk.mjs +0 -115
  836. package/edition/third-party-source-writer.mjs +0 -213
  837. package/edition/third-party-subjects.mjs +0 -128
  838. package/edition/trust-policy-arming-verb.mjs +0 -60
  839. package/edition/trust-policy-arming.mjs +0 -144
  840. package/edition/trust-policy-image-ownership.mjs +0 -85
  841. package/edition/trusted-executable.mjs +0 -166
  842. package/edition/verifier-binding.mjs +0 -45
  843. package/edition/verifier-files.mjs +0 -8
  844. package/edition/verifier-kernel-schemas.mjs +0 -22
  845. package/edition/verifier-provenance.mjs +0 -64
  846. package/edition/verify-catalogue-evidence.mjs +0 -247
  847. package/edition/verify-image-evidence.mjs +0 -475
  848. package/edition/verify-image-platforms.mjs +0 -523
  849. package/edition/why.mjs +0 -56
  850. package/edition/workspace-sections.mjs +0 -276
@@ -1,2351 +0,0 @@
1
- #!/usr/bin/env node
2
- // Resolve a distribution into schema-v3 actual runtime image subjects.
3
- // Services, the host shell, and independently sourced platform images are
4
- // subjects. Biomes remain selection/ownership units and never become fictitious
5
- // one-container-per-Biome build outputs.
6
- //
7
- // ── "REGENERATE AND DIFF" IS NOT A VALID CHECK ON THIS ARTIFACT ─────────────
8
- //
9
- // This resolver emits NO `imageDigest`. The digests in a committed
10
- // `images.lock.json` are written afterwards, by `seal-image-digests.mjs`, and
11
- // nothing here reproduces them. So the obvious verification — regenerate the
12
- // lock and diff it against the committed one — reports drift that is purely the
13
- // absence of a seal, on every subject, every time.
14
- //
15
- // Measured 2026-09-23 on `origin/main`, running this script unmodified against a
16
- // pristine checkout with two editions left alone as controls:
17
- //
18
- // core 54 of 54 subjects carried a digest -> 0 after; `grep -c imageDigest` = 0
19
- // cloud 66 of 66 -> 0 after
20
- // minimal / oss (not re-resolved) -> 45 and 63, untouched
21
- //
22
- // The field is ABSENT rather than emptied, on `subjects`, `platformServiceBindings`
23
- // and `webBiomes` alike, and no merge path carries a subset forward. Two
24
- // consequences, and the second is the one that costs a night:
25
- //
26
- // - to verify a lock, compare a fresh resolve against ANOTHER fresh resolve
27
- // (which cancels both the seal and the wall-clock `resolvedAt`), never
28
- // against the committed file;
29
- // - a re-resolve DISARMS the sealer's moved-tag refusal, which fires only when
30
- // the lock being sealed already carries a digest for that subject. That is
31
- // not a bypass to reach for — it is why "re-resolve then seal" and "re-seal
32
- // the committed lock" are different operations with different verdicts, and
33
- // anyone comparing their outcomes needs to know which one they ran.
34
-
35
- import { execFileSync } from 'node:child_process';
36
- import { createHash } from 'node:crypto';
37
- import { existsSync, readFileSync, writeFileSync } from 'node:fs';
38
- import { join, relative, sep } from 'node:path';
39
- import { setImmediate as yieldToEventLoop } from 'node:timers/promises';
40
-
41
- import { assertBuildLedgerComplete } from './build-ledger-completeness.mjs';
42
- import { BUILD_LEDGER_FILE, readBuildLedger } from './build-ledger.mjs';
43
- import { CROSS_REPO_BUILD_INPUTS_PATH, crossRepoInputKey, resolveCrossRepoVendoredInputs } from './cross-repo-build-inputs.mjs';
44
- import {
45
- resolveDispatchedRuntimeComponents,
46
- UndeclaredSupplementalRuntimeComponentError,
47
- } from './dispatched-runtime-components.mjs';
48
- import {
49
- assertCanonicalDistributionId,
50
- distributionOutputPath,
51
- readBoundDistributionFile,
52
- } from './distribution-paths.mjs';
53
- import { editionWorkspaceConfig, workspaceSection } from './edition-config.mjs';
54
- import { editionPinnedInputs } from './edition-pinned-inputs.mjs';
55
- import { dataRoot, editionsRoot, WorkspaceRoot, configureEditionWorkspaceFromProcess, isEntryModule, sourcePath, sourcesRoot } from './edition-workspace.mjs';
56
- import {
57
- externalSourceCacheDir,
58
- materializeGitSource,
59
- } from './external-source-walk.mjs';
60
- import { assertDeclaredSourceId, firstPartySourceId } from './first-party-source-ids.mjs';
61
- import {
62
- compareStrings,
63
- hashImageBuildInputs,
64
- imageInputSourceHashAlgorithm,
65
- imageLockWriteSchemaVersion,
66
- loadKernelDistributionSchemas,
67
- parseResolvedDistributionLock,
68
- readGitJsonAtCommit,
69
- serializeJson,
70
- sha256Hex,
71
- validateImmutableDistributionLock,
72
- validateImmutableImageLock,
73
- } from './lib.mjs';
74
- import { assertReleaseContent } from './release-channel-guard.mjs';
75
- import { composeEdition } from './resolve-distribution.mjs';
76
- import { parseStrictJson, readStrictJsonFile } from './strict-json.mjs';
77
- import { loadThirdPartySourcePins } from './third-party-source-pins.mjs';
78
- import {
79
- assertOciBundleSourceOrigin,
80
- attestedContentHash,
81
- isThirdPartyOciRow,
82
- thirdPartyPinForRow,
83
- thirdPartySubjectCandidates,
84
- } from './third-party-subjects.mjs';
85
-
86
- if (isEntryModule(import.meta.url)) {
87
- configureEditionWorkspaceFromProcess([WorkspaceRoot.Data, WorkspaceRoot.Editions, WorkspaceRoot.Sources, WorkspaceRoot.Kernel, WorkspaceRoot.Cache]);
88
- }
89
-
90
- const firstPartyImageRegistry = () => editionWorkspaceConfig().imageRegistry;
91
-
92
- /**
93
- * WHERE A LOCKED BIOME'S SOURCE TREE IS ON DISK — the one answer this plane
94
- * needs, and the one thing every read in this file used to assume.
95
- *
96
- * Every Git read here was `sourcePath(<repo>)`, which encodes an
97
- * assumption that held while every biome came from a sibling repository in the
98
- * aggregator's layout: that a `repo` HANDLE IS A PATH under `sourcesRoot()`. A
99
- * biome drawn from an EXTERNAL source breaks it — its `repo` is that source's
100
- * own LOGICAL name (`sources[].name`), deliberately not a `repos/<x>` path,
101
- * because a hosting repository's name is source-management detail and must not
102
- * become a biome's provenance. `<sourcesRoot()>/<logical name>` is a directory that
103
- * does not exist, so the content hash could not be computed at all.
104
- *
105
- * The fix is ONE locator rather than a special case per read site: a `repo`
106
- * handle resolves to a checkout DIRECTORY plus the BASE its `path` is relative
107
- * to. Both kinds answer the same shape — a first-party repo's paths are
108
- * `sourcesRoot()`-relative, an external source's are relative to the cache the walk
109
- * materialised it into — so every call site reads the same two fields and none
110
- * of them knows which kind it has.
111
- *
112
- * WHAT THE HASH THEN COVERS IS UNCHANGED, and that is the point of routing the
113
- * DIRECTORY rather than teaching the hash about sources. `hashImageBuildInputs`
114
- * reads the service's own source tree, the transitive workspace-package
115
- * closure, the exact Dockerfile blob the build resolves to, every file the
116
- * Dockerfile copies out of the context, the filtered pnpm lock graph, the
117
- * context-directory residue and any vendored cross-repo tree — all out of the
118
- * Git objects of whatever repository it is handed, at an exact commit. Handing
119
- * it an external checkout therefore covers that repository's FULL build input
120
- * by the same construction it already covers a first-party one, with no second
121
- * definition of what an image is built from.
122
- *
123
- * MATERIALISATION IS AT THE LOCKED COMMIT, never at the declared ref. The lock
124
- * already recorded the exact commit its manifests were read at; re-resolving a
125
- * ref here would let a hash be computed over a different revision than the one
126
- * the edition locked, silently and in the direction nothing detects.
127
- * `materializeGitSource` short-circuits an exact 40-hex ref instead of sending
128
- * it to `ls-remote`, so passing the commit AS the ref is the pin rather than a
129
- * re-resolution.
130
- */
131
- export function externalSourceTrees(
132
- lock,
133
- { cacheRoot = externalSourceCacheDir(), materialize = materializeGitSource } = {},
134
- ) {
135
- const byRepo = new Map();
136
- for (const biome of lock.biomes ?? []) {
137
- if (biome.source === undefined) {continue;}
138
- const name = biome.source.name;
139
- const incumbent = byRepo.get(name);
140
- if (incumbent) {
141
- if (incumbent.commit !== biome.source.commit) {
142
- throw new Error(
143
- `distribution "${lock.distributionId}" pins two commits for external source ` +
144
- `"${name}" (${incumbent.commit} and ${biome.source.commit}) — one source is one ` +
145
- 'revision, so an image built from it cannot be addressed',
146
- );
147
- }
148
- continue;
149
- }
150
- byRepo.set(name, { source: biome.source, commit: biome.source.commit });
151
- }
152
- const trees = new Map();
153
- for (const [name, { source, commit }] of byRepo) {
154
- const { directory } = materialize({ source: { ...source, ref: commit }, cacheRoot });
155
- trees.set(name, { dir: directory, base: cacheRoot });
156
- }
157
- return trees;
158
- }
159
-
160
- /**
161
- * `repo` handle -> `{ dir, base }`.
162
- *
163
- * An unknown handle is FIRST-PARTY, which is the only default that can be
164
- * correct: the external set is CLOSED by the lock's own `sources[]`, so
165
- * anything outside it came from this fleet's layout. A handle that is neither
166
- * resolves to a directory that does not exist and fails at the first Git read,
167
- * naming the path — a loud failure rather than a silent fall back to an empty
168
- * tree, which would hash to a coordinate nothing ever published.
169
- */
170
- export function sourceTreeLocator(externalTrees) {
171
- return (repo) =>
172
- externalTrees.get(repo) ?? { dir: sourcePath(repo), base: sourcesRoot() };
173
- }
174
-
175
- /** The first-party-only locator — for callers that hold no lock to read. */
176
- const FIRST_PARTY_TREES = (repo) => ({ dir: sourcePath(repo), base: sourcesRoot() });
177
- const imageBuildInputHashByIdentity = new Map();
178
- const sourceManifestHashByIdentity = new Map();
179
-
180
- /**
181
- * Memoized image build-input closure hash.
182
- *
183
- * Every distribution that ships a service resolves the SAME closure for it, and
184
- * the closure reads immutable Git objects at an exact commit, so one digest per
185
- * (service, service path, commit, vendored inputs) is exact — not a cache with
186
- * an invalidation story.
187
- */
188
- function exactImageBuildInputHash(request, algorithm) {
189
- const identity = serializeJson({ request, algorithm });
190
- const cached = imageBuildInputHashByIdentity.get(identity);
191
- if (cached) {return cached;}
192
- const hash = hashImageBuildInputs(request, algorithm);
193
- imageBuildInputHashByIdentity.set(identity, hash);
194
- return hash;
195
- }
196
-
197
- function exactSourceManifestHash(
198
- repoDirectory,
199
- repoRelativePath,
200
- sourceCommit,
201
- ) {
202
- const identity = serializeJson({
203
- repoDirectory,
204
- repoRelativePath,
205
- sourceCommit,
206
- });
207
- const cached = sourceManifestHashByIdentity.get(identity);
208
- if (cached) {return cached;}
209
- const hash = sha256Hex(
210
- serializeJson(
211
- readGitJsonAtCommit(repoDirectory, repoRelativePath, sourceCommit),
212
- ),
213
- );
214
- sourceManifestHashByIdentity.set(identity, hash);
215
- return hash;
216
- }
217
-
218
- /**
219
- * Key of one vendored input WITHIN one consumer repository.
220
- *
221
- * Both the store and the lookup go through this, so the two sides cannot spell
222
- * the separator differently — which they did, and every lookup missed.
223
- */
224
- function consumerInputKey(consumerRepo, input) {
225
- return `${consumerRepo}\u0000${crossRepoInputKey(input)}`;
226
- }
227
-
228
- /**
229
- * Compose one image ref: WHERE the artifact lives, and WHAT it is called.
230
- *
231
- * `registry` defaults to the vendor namespace because that is where everything
232
- * this organisation BUILDS is published, and every caller but one wants it. The
233
- * exception is a biome drawn from an external source, whose coordinate is a
234
- * DECLARED fact carried on the lock (`biome.source.imageRegistry`) rather than a
235
- * constant — see the biomes loop in `resolveImageStructure`.
236
- *
237
- * `namePrefix` is the second half of that same declared coordinate, and it is
238
- * what keeps a subject's LOGICAL id separate from its PUBLISHED name. Those two
239
- * are the same string for every first-party subject — measured 2026-09-15, 67
240
- * of 67 deploy-manifest entries and 60 of 60 `oci-image` component keys — but
241
- * that is a property of owning the whole of the first-party registry namespace, not a law. A
242
- * customer organisation's namespace is flat and carries every product that
243
- * customer ships, so a bare component name there identifies nothing in
244
- * particular and the first collision silently overwrites somebody's tags.
245
- *
246
- * DEFAULTING TO THE EMPTY STRING IS THE IDENTITY ELEMENT OF CONCATENATION, not
247
- * a fallback: an unqualified name is what every existing subject already gets,
248
- * so the absent case reproduces today's behaviour exactly rather than
249
- * substituting something. Contrast `registry`, whose absence on an external
250
- * source is REFUSED in `resolveImageStructure` — there the only available
251
- * default names the wrong ORG, which is a misdirection rather than a preference.
252
- *
253
- * Both parameters are deliberately not module-level mutables: two editions are
254
- * resolved in one process by `dist:check`, and a coordinate held in module scope
255
- * would leak one edition's customer namespace into the next one's subjects.
256
- */
257
- export function imageRefForSubject(
258
- subjectId,
259
- registry = firstPartyImageRegistry(),
260
- namePrefix = '',
261
- ) {
262
- return `${registry}/${namePrefix}${subjectId}`;
263
- }
264
-
265
- /**
266
- * The OCI platforms one distribution requires every runtime image to ship.
267
- *
268
- * Read from that edition's OWN `xema-distribution.json`
269
- * and projected onto image-lock platform
270
- * coordinates by the kernel. This deliberately replaced a global frozen
271
- * `[{linux, amd64}]` constant: with one constant, every subject of every
272
- * edition declared amd64 — including `appliance-lean`/`appliance-base`, whose
273
- * installer (`xema-appliance/os/dgx/install-k3s.sh`, `ansible/site.yml`) refuses
274
- * any host that is not ARM64. The image plane therefore declared the one
275
- * architecture the appliance could not run, and nothing in the chain could say
276
- * so.
277
- *
278
- * The kernel owns the vocabulary and the fail-fast: an absent, empty, unknown,
279
- * duplicated or misordered declaration throws here rather than resolving to a
280
- * default. There is no default.
281
- */
282
- export function resolveDistributionRequiredPlatforms(schemas, id) {
283
- assertCanonicalDistributionId(id);
284
- const { requiredPlatforms } = composeEdition(schemas.parseDistribution, id);
285
- return schemas.resolveRuntimeImagePlatforms(requiredPlatforms);
286
- }
287
-
288
- /**
289
- * The edition's DECLARED `sources[]`,
290
- *
291
- * WHY THIS IS A PARAMETER AND NOT A LOCK FIELD. `imageNamePrefix` is producer
292
- * INPUT: it composes the image NAME and is then spent, because the lock carries
293
- * the fully-qualified `imageRef`. It cannot travel in the lock to reach the
294
- * resolver — `DistributionLockSchema` is `.strict()` and learned that key only
295
- * in `@xemahq/contracts` 0.10.0, so a pinned image resolving `^0.9.x` refuses
296
- * the whole lock at boot. Persisting it took a customer environment's three lock
297
- * consumers into CrashLoopBackOff on 2026-09-17.
298
- *
299
- * So the producer reads its OWN declaration, and `requiredPlatforms` one
300
- * function up is the precedent: a declared fact the pure structure resolver
301
- * needs arrives as an argument the impure caller composes. That keeps
302
- * `resolveImageStructure` pure over the committed lock and runtime index, which
303
- * is what lets `dist-check` grade an images lock offline.
304
- */
305
- export function resolveDistributionDeclaredSources(schemas, id) {
306
- assertCanonicalDistributionId(id);
307
- const { declaredSources } = composeEdition(schemas.parseDistribution, id);
308
- return declaredSources;
309
- }
310
-
311
- function subjectKey(subject) {
312
- return `${subject.kind}\0${subject.id}`;
313
- }
314
-
315
- function bindingKey(binding) {
316
- return `${binding.subjectKind}\0${binding.subjectId}`;
317
- }
318
-
319
- function compareSubject(left, right) {
320
- return (
321
- compareStrings(left.kind, right.kind) || compareStrings(left.id, right.id)
322
- );
323
- }
324
-
325
- function compareOwner(left, right) {
326
- return compareStrings(serializeJson(left), serializeJson(right));
327
- }
328
-
329
- function addSubject(subjects, candidate) {
330
- const key = subjectKey(candidate);
331
- const existing = subjects.get(key);
332
- if (!existing) {
333
- subjects.set(key, {
334
- ...candidate,
335
- owners: [...candidate.owners],
336
- });
337
- return;
338
- }
339
- const withoutOwners = ({ owners: _owners, ...value }) => value;
340
- if (
341
- serializeJson(withoutOwners(existing)) !==
342
- serializeJson(withoutOwners(candidate))
343
- ) {
344
- throw new Error(
345
- `runtime image subject "${candidate.kind}:${candidate.id}" has conflicting source identities`,
346
- );
347
- }
348
- const ownerKeys = new Set(existing.owners.map(serializeJson));
349
- for (const owner of candidate.owners) {
350
- if (!ownerKeys.has(serializeJson(owner))) {existing.owners.push(owner);}
351
- }
352
- }
353
-
354
- /**
355
- * The refusal for a SELECTED platform service whose declared artifact
356
- * dependency no selected biome produces.
357
- *
358
- * The edge is DECLARED, never inferred from a name: a platform service's
359
- * `runtimeBinding` (platform-service-source-authority.mjs, projected into
360
- * `runtime-image-source-index.json#platformServiceBindings`) says which image
361
- * subject carries it — `opencode-image-prepuller` → `runtime-component:
362
- * opencode-worker`. When that subject is not supplemental it is produced by a
363
- * biome, and the biome that produces it is the one whose OWN manifest declares
364
- * it (`services` / `runtimeComponents` in the biome index). So excluding the
365
- * provider biome while keeping the dependent service is refused here, naming
366
- * both ends; excluding both leaves no edge and resolves.
367
- */
368
- function missingArtifactDependencyMessage(lock, serviceName, binding, biomeIndex) {
369
- const field =
370
- binding.subjectKind === 'runtime-component' ? 'runtimeComponents' : 'services';
371
- const providers = Array.isArray(biomeIndex)
372
- ? [
373
- ...new Set(
374
- biomeIndex
375
- .filter((entry) => (entry[field] ?? []).includes(binding.subjectId))
376
- .map((entry) => entry.id),
377
- ),
378
- ].sort(compareStrings)
379
- : [];
380
- const providerText =
381
- providers.length > 0
382
- ? `, which is declared by biome ${providers.map((id) => `"${id}"`).join(' / ')}` +
383
- ' — and this edition does not select it'
384
- : ', and no biome this edition selects declares it';
385
- return (
386
- `distribution "${lock.distributionId}" ships platform service "${serviceName}", ` +
387
- `whose declared runtime binding requires image subject ` +
388
- `"${binding.subjectKind}:${binding.subjectId}"${providerText}. ` +
389
- `Either keep the providing biome, or exclude "${serviceName}" as well — ` +
390
- 'refusing rather than resolving an edition whose service has no image.'
391
- );
392
- }
393
-
394
- /**
395
- * Offline roster derivation. It reads only committed locks/indexes and performs
396
- * no registry, GitHub, DigitalOcean, or source-worktree access.
397
- *
398
- * `requiredPlatforms` is supplied by the caller (see
399
- * {@link resolveDistributionRequiredPlatforms}) rather than read here, so this
400
- * function stays a pure projection of its inputs — and so a caller that forgets
401
- * the distribution's declaration gets a hard error instead of a default.
402
- *
403
- * THE ROSTER IS THE ARTIFACT CLOSURE OF THE LOGICAL CLOSURE, and nothing else.
404
- * `runtime-image-source-index.json` is the FLEET's catalogue — every platform
405
- * service binding, every supplemental subject — and an edition selects from it.
406
- * A binding for a platform service this edition does not ship is simply not
407
- * part of this edition's artifact graph and is dropped; it is never a reason to
408
- * refuse. What DOES refuse is a SELECTED service whose declared artifact
409
- * dependency is absent from the closure (see `missingArtifactDependencyMessage`).
410
- *
411
- * `options.biomeIndex` is the first-party biome index. It names, in a refusal,
412
- * which biome declares a missing artifact, and it carries each rostered biome's
413
- * `dispatchesRuntimeComponents`: the runtime components that biome launches,
414
- * whose images enter this roster as runtime-component rows although their
415
- * biomes are not rostered (see `dispatched-runtime-components.mjs`).
416
- *
417
- * `options.supplementalSources` is the edition workspace's
418
- * `runtimeImageSources.supplementalSources`: the declarations the source
419
- * index's supplemental subjects were built from, carrying each one's
420
- * `launchedBy`. A subject launched by a biome ships, owned by that biome, in
421
- * every edition whose roster contains it; one a platform service binds ships
422
- * with that service. A declared subject with neither is refused by name: it
423
- * could never enter any edition, and it used to be dropped silently.
424
- */
425
- export function resolveImageStructure(
426
- lock,
427
- runtimeSourceIndex,
428
- enums,
429
- requiredPlatforms,
430
- declaredSources,
431
- { biomeIndex, supplementalSources, thirdPartyPins } = {},
432
- ) {
433
- if (!Array.isArray(requiredPlatforms) || requiredPlatforms.length === 0) {
434
- throw new Error(
435
- `distribution "${lock.distributionId}" resolved no required image platforms — ` +
436
- `every distribution declares requiredPlatforms in its xema-distribution.json ` +
437
- `and there is no default; a silent default is the bug this replaced`,
438
- );
439
- }
440
- if (!Array.isArray(biomeIndex)) {
441
- throw new Error(
442
- `distribution "${lock.distributionId}" resolved its images without the biome index — the index carries ` +
443
- 'the runtime components a rostered biome dispatches, so a roster without it can omit images',
444
- );
445
- }
446
- if (!Array.isArray(supplementalSources)) {
447
- throw new Error(
448
- `distribution "${lock.distributionId}" resolved its images without the supplemental source declarations — ` +
449
- 'they say which biome launches a supplemental subject, so a roster without them can omit images',
450
- );
451
- }
452
- const subjects = new Map();
453
- // Third-party OCI subjects come from their pin and are never built, hashed or read from a checkout, so they are kept
454
- // apart from every git-backed subject (see `third-party-subjects.mjs`).
455
- const thirdPartySubjects = new Map();
456
- const webBiomes = [];
457
- const platformServiceBindings = [];
458
- const repositoryPins = new Map(
459
- runtimeSourceIndex.repositoryPins.map((repository) => [
460
- repository.repo,
461
- repository,
462
- ]),
463
- );
464
- const requireRepositoryPin = (repo) => {
465
- const pin = repositoryPins.get(repo);
466
- if (!pin) {
467
- throw new Error(`runtime image source "${repo}" has no repository pin`);
468
- }
469
- return pin;
470
- };
471
- // A supplemental subject of the runtime-image source index, built at its
472
- // repository pin, owned by whatever brings it into this edition.
473
- const addSupplementalSubject = (supplemental, owners) => {
474
- const repository = requireRepositoryPin(supplemental.repo);
475
- if (supplemental.sourceCommit !== repository.sourceCommit) {
476
- throw new Error(
477
- `runtime subject "${supplemental.kind}:${supplemental.id}" source commit does not match its repository pin`,
478
- );
479
- }
480
- addSubject(subjects, {
481
- kind: supplemental.kind,
482
- id: supplemental.id,
483
- imageRef: supplemental.imageRef,
484
- repo: supplemental.repo,
485
- path: supplemental.path,
486
- sourceCommit: repository.sourceCommit,
487
- sourceManifestHash: supplemental.sourceManifestHash,
488
- sourceManifestPath: supplemental.sourceManifestPath,
489
- requiredPlatforms,
490
- owners,
491
- });
492
- };
493
-
494
- for (const biome of lock.biomes) {
495
- const biomeLabel = `distribution "${lock.distributionId}" biome "${biome.id}"`;
496
- assertOciBundleSourceOrigin(biome, biomeLabel);
497
- if (isThirdPartyOciRow(biome)) {
498
- const pin = thirdPartyPinForRow({ row: biome, pins: thirdPartyPins, declaredSources, label: biomeLabel });
499
- for (const candidate of thirdPartySubjectCandidates({ row: biome, pin, enums, requiredPlatforms, label: biomeLabel })) {
500
- addSubject(thirdPartySubjects, candidate);
501
- }
502
- continue;
503
- }
504
- // WHERE THIS BIOME'S IMAGES LIVE, and WHICH COMMIT THEY ARE BUILT FROM.
505
- // Both answers move together when a biome is drawn from an external source,
506
- // because both are facts about that source rather than about this fleet.
507
- //
508
- // ── THE REGISTRY ──
509
- //
510
- // A first-party biome is published to the vendor namespace. A biome drawn
511
- // from an EXTERNAL source is not, and its coordinate is DECLARED — on the
512
- // `sources[]` entry as `imageRegistry`, carried into the lock verbatim by
513
- // `external-source-walk.mjs` and read HERE. This used to refuse
514
- // unconditionally, including when the coordinate WAS declared, because
515
- // reading it was not implemented; that was an honest partial and it is now
516
- // implemented.
517
- //
518
- // THE REFUSAL SURVIVES, NARROWED TO THE CASE IT WAS ALWAYS ABOUT: an
519
- // external source that declares NO registry. The kernel legislates that on
520
- // the field itself — *"a plane that needs one and finds it absent must
521
- // REFUSE rather than substitute the first-party registry, which would
522
- // publish a customer's build into the vendor org."* Absent must never
523
- // become a fallback to the first-party registry. That substitution is the entire
524
- // defect; narrowing the refusal must not soften it into a default.
525
- //
526
- // ── THE COMMIT ──
527
- //
528
- // A first-party biome's commit comes from `runtime-image-source-index.json`
529
- // via `requireRepositoryPin`. An external biome has no entry there and
530
- // never will: its `repo` is the source's own LOGICAL name as declared in
531
- // `sources[]`, not a `repos/<x>` path, so the pin lookup would fail with "has no
532
- // repository pin" — a true sentence about the wrong plane. Its pin is the
533
- // commit the WALK resolved and the lock recorded, which is the same fact
534
- // held by the authority that actually resolved it.
535
- //
536
- // ── PLACEMENT ──
537
- //
538
- // Both are resolved BEFORE `requireRepositoryPin` and before the
539
- // `target === 'web'` branch, so services, runtime components and web biomes
540
- // are covered by one statement rather than leaving the web branch's
541
- // `continue` as a hole.
542
- //
543
- // KNOWN NARROWING, stated rather than discovered: a web biome composes no
544
- // image ref at all, so an external source shipping ONLY web biomes needs no
545
- // registry and is nonetheless refused here. That is deliberately left
546
- // fail-closed — it has no subject today (both externally-sourced biomes in
547
- // this plane declare `target: "server"`), and loosening a fence for a case
548
- // that does not exist is how a fence stops meaning anything.
549
- let imageRegistry = firstPartyImageRegistry();
550
- // The identity element, not a fallback — see `imageRefForSubject`. A
551
- // first-party subject is published under its own id, which is exactly what
552
- // an empty prefix composes.
553
- let imageNamePrefix = '';
554
- let sourceCommit;
555
- if (biome.source === undefined) {
556
- sourceCommit = requireRepositoryPin(biome.repo).sourceCommit;
557
- } else {
558
- if (biome.source.imageRegistry === undefined) {
559
- throw new Error(
560
- `distribution "${lock.distributionId}" biome "${biome.id}" was drawn from external ` +
561
- `source "${biome.source.name}" (${biome.source.url}@${biome.source.commit}), which ` +
562
- 'declares no imageRegistry, so this plane cannot address its images. Composing ' +
563
- `them under "${firstPartyImageRegistry()}" would address a build this organisation does not ` +
564
- 'produce, inside the vendor namespace. Declare `imageRegistry` on that sources[] ' +
565
- 'entry — refusing rather than substituting the first-party registry.',
566
- );
567
- }
568
- imageRegistry = biome.source.imageRegistry;
569
- // THE PREFIX COMES FROM THE DECLARATION, NEVER FROM THE LOCK.
570
- //
571
- // This read was `biome.source.imageNamePrefix`, which required the lock to
572
- // carry the key — and a lock carrying it is refused outright by any
573
- // consumer on `@xemahq/contracts` `^0.9.x`, whose DistributionLockSchema
574
- // is `.strict()` and learned the key only in 0.10.0.
575
- //
576
- // Removing it from the lock WITHOUT moving this read is what composed
577
- // `<customer-registry>/pai-core-api` instead of `<customer-registry>/<customer>-pai-core-api`:
578
- // the `?? ''` swallowed the absence, so an unqualified name in a shared
579
- // organisation namespace resolved silently. That is worse than a 404 —
580
- // the first collision overwrites another product's tags.
581
- //
582
- // A MISSING DECLARED ENTRY IS THEREFORE REFUSED, never defaulted. An entry
583
- // that declares NO prefix is a different fact and is honoured: the empty
584
- // prefix is the identity element, composing a subject under its bare id.
585
- if (!Array.isArray(declaredSources)) {
586
- throw new Error(
587
- `distribution "${lock.distributionId}" biome "${biome.id}" was drawn from ` +
588
- `external source "${biome.source.name}", but this resolve was given no ` +
589
- 'declared sources[] to read its imageNamePrefix from. That prefix is ' +
590
- 'producer input and is deliberately not carried in the lock; pass ' +
591
- '`resolveDistributionDeclaredSources(schemas, id)` — refusing rather ' +
592
- 'than composing an unprefixed name.',
593
- );
594
- }
595
- const declaredSource = declaredSources.find(
596
- (candidate) => candidate.name === biome.source.name,
597
- );
598
- if (declaredSource === undefined) {
599
- throw new Error(
600
- `distribution "${lock.distributionId}" biome "${biome.id}" was drawn from ` +
601
- `external source "${biome.source.name}", which this distribution's ` +
602
- `composed sources[] does not declare (declared: ${
603
- declaredSources.map((candidate) => candidate.name).join(', ') ||
604
- 'none'
605
- }). The image name cannot be composed without its declaration — ` +
606
- 'refusing rather than publishing under an unprefixed name in a shared ' +
607
- "organisation namespace, where the first collision overwrites another " +
608
- "product's tags.",
609
- );
610
- }
611
- imageNamePrefix = declaredSource.imageNamePrefix ?? '';
612
- sourceCommit = biome.source.commit;
613
- }
614
- if (biome.target === 'web') {
615
- webBiomes.push({
616
- biomeId: biome.id,
617
- repo: biome.repo,
618
- path: biome.path,
619
- sourceCommit,
620
- sourceManifestHash: biome.sourceManifestHash,
621
- });
622
- continue;
623
- }
624
- // A worker that runs a sibling service's image builds none of its own: the
625
- // service's subject is the one sealed coordinate, and the deploy plane
626
- // reaches it through `workerImages[worker].imageOf`. Minting a subject for
627
- // the worker would name an image nothing builds.
628
- const sharedImageWorkers = new Set(Object.keys(biome.workerImages ?? {}));
629
- for (const service of biome.services ?? []) {
630
- if (sharedImageWorkers.has(service)) {
631
- continue;
632
- }
633
- addSubject(subjects, {
634
- kind: enums.RuntimeImageSubjectKind.Service,
635
- id: service,
636
- imageRef: imageRefForSubject(service, imageRegistry, imageNamePrefix),
637
- repo: biome.repo,
638
- path: biome.path,
639
- sourceCommit,
640
- sourceManifestHash: biome.sourceManifestHash,
641
- requiredPlatforms,
642
- owners: [
643
- {
644
- kind: enums.RuntimeImageOwnerKind.Biome,
645
- biomeId: biome.id,
646
- },
647
- ],
648
- });
649
- }
650
- // Runtime components are FULL delivery subjects — built, content-addressed,
651
- // signed, mirrored and air-gapped exactly like a service image. They differ
652
- // only in that nothing schedules them, which the deploy plane reads off the
653
- // subject KIND. Their source path resolves through the same
654
- // `ships.apis[].path` SSOT, because a runtime component IS a `ships.apis[]`
655
- // entry — it just carries `workload: "runtime-component"`.
656
- for (const component of biome.runtimeComponents ?? []) {
657
- addSubject(subjects, {
658
- kind: enums.RuntimeImageSubjectKind.RuntimeComponent,
659
- id: component,
660
- imageRef: imageRefForSubject(component, imageRegistry, imageNamePrefix),
661
- repo: biome.repo,
662
- path: biome.path,
663
- sourceCommit,
664
- sourceManifestHash: biome.sourceManifestHash,
665
- requiredPlatforms,
666
- owners: [
667
- {
668
- kind: enums.RuntimeImageOwnerKind.Biome,
669
- biomeId: biome.id,
670
- },
671
- ],
672
- });
673
- }
674
- }
675
-
676
- // Runtime components a rostered biome DISPATCHES enter the roster as
677
- // runtime-component rows built from the TARGET biome's index entry, at its
678
- // repository pin, exactly like a rostered biome's own runtime component. The
679
- // owner is the rostered biome that dispatches it: the selection that brings
680
- // the image into this edition. The target biome stays unrostered.
681
- //
682
- // A rostered biome's edges are read from ITS index entry, which must describe
683
- // the very biome the lock pins; one from a declared source is refused at
684
- // distribution resolve if it dispatches, and the first-party index carries
685
- // none, so it is not looked up here.
686
- const dispatchers = lock.biomes
687
- .filter((biome) => biome.source === undefined && !isThirdPartyOciRow(biome) && biome.target === 'server')
688
- .map((biome) => {
689
- const entries = biomeIndex.filter(
690
- (entry) =>
691
- entry.id === biome.id &&
692
- entry.target === biome.target &&
693
- entry.repo === biome.repo &&
694
- entry.source === undefined,
695
- );
696
- if (entries.length !== 1 || entries[0].sourceManifestHash !== biome.sourceManifestHash) {
697
- throw new Error(
698
- `distribution "${lock.distributionId}" biome "${biome.id}" (${biome.repo}, ${biome.sourceManifestHash}) ` +
699
- `has ${entries.length === 1 ? 'a stale' : `${entries.length}`} biome index ${entries.length === 1 ? 'entry' : 'entries'}, ` +
700
- 'so the runtime components it dispatches cannot be read — re-resolve the edition from the current index',
701
- );
702
- }
703
- return entries[0];
704
- });
705
- for (const dispatched of resolveDispatchedRuntimeComponents(
706
- `distribution "${lock.distributionId}"`,
707
- dispatchers,
708
- biomeIndex,
709
- )) {
710
- if (dispatched.supplementalRuntimeComponentId !== undefined) {
711
- // THE REFUSAL: a dispatched supplemental runtime component must be one the
712
- // runtime image source index declares as a runtime-component subject.
713
- // Every image lock is produced here, so this is the one place it is checked.
714
- const declared = runtimeSourceIndex.supplementalSubjects.find(
715
- (subject) =>
716
- subject.kind === enums.RuntimeImageSubjectKind.RuntimeComponent &&
717
- subject.id === dispatched.supplementalRuntimeComponentId,
718
- );
719
- if (declared === undefined) {
720
- throw new UndeclaredSupplementalRuntimeComponentError(
721
- dispatched.supplementalRuntimeComponentId,
722
- dispatched.consumers,
723
- `distribution "${lock.distributionId}"`,
724
- );
725
- }
726
- addSupplementalSubject(
727
- declared,
728
- dispatched.consumers.map((biomeId) => ({ kind: enums.RuntimeImageOwnerKind.Biome, biomeId })),
729
- );
730
- continue;
731
- }
732
- const { target } = dispatched;
733
- addSubject(subjects, {
734
- kind: enums.RuntimeImageSubjectKind.RuntimeComponent,
735
- id: dispatched.image,
736
- imageRef: imageRefForSubject(dispatched.image, firstPartyImageRegistry(), ''),
737
- repo: target.repo,
738
- path: target.path,
739
- sourceCommit: requireRepositoryPin(target.repo).sourceCommit,
740
- sourceManifestHash: target.sourceManifestHash,
741
- requiredPlatforms,
742
- owners: dispatched.consumers.map((biomeId) => ({
743
- kind: enums.RuntimeImageOwnerKind.Biome,
744
- biomeId,
745
- })),
746
- });
747
- }
748
-
749
- const runtimeBindings = new Map(
750
- runtimeSourceIndex.platformServiceBindings.map((binding) => [
751
- binding.platformService,
752
- binding,
753
- ]),
754
- );
755
- const supplementalSubjects = new Map(
756
- runtimeSourceIndex.supplementalSubjects.map((subject) => [
757
- subjectKey(subject),
758
- subject,
759
- ]),
760
- );
761
-
762
- // Supplemental subjects a BIOME launches (`launchedBy`): sealed, owned by
763
- // that biome, in every edition whose roster contains it. The catalogue
764
- // checks run for every edition, rostered launcher or not.
765
- const boundSubjects = new Set(
766
- runtimeSourceIndex.platformServiceBindings.map((binding) => bindingKey(binding)),
767
- );
768
- // A biome's `supplemental:<id>` dispatch edge also carries the subject: the
769
- // catalogue check reads the whole first-party index, not this roster, so a
770
- // subject reached only by a dispatch is not "unreachable" in any edition.
771
- const dispatchedSubjects = new Set(
772
- biomeIndex
773
- .filter((entry) => entry.source === undefined)
774
- .flatMap((entry) => entry.dispatchesRuntimeComponents ?? [])
775
- .filter((edge) => edge.supplementalRuntimeComponentId !== undefined)
776
- .map((edge) => `${enums.RuntimeImageSubjectKind.RuntimeComponent}\0${edge.supplementalRuntimeComponentId}`),
777
- );
778
- const launchers = new Map(
779
- supplementalSources
780
- .filter((source) => source.launchedBy !== undefined)
781
- .map((source) => [subjectKey(source), source.launchedBy]),
782
- );
783
- for (const key of launchers.keys()) {
784
- if (!supplementalSubjects.has(key)) {
785
- throw new Error(
786
- `supplemental source "${key.replace('\0', ':')}" declares a launcher, but the runtime image source index ` +
787
- 'has no such supplemental subject — rebuild the index from the current declarations',
788
- );
789
- }
790
- }
791
- for (const [key, supplemental] of supplementalSubjects) {
792
- const launcher = launchers.get(key);
793
- if (launcher === undefined) {
794
- if (!boundSubjects.has(key) && !dispatchedSubjects.has(key)) {
795
- throw new Error(
796
- `supplemental subject "${supplemental.kind}:${supplemental.id}" has neither a launchedBy, a ` +
797
- 'platform-service binding nor a biome dispatching it, so no edition could ever carry it — declare what launches it ' +
798
- '(runtimeImageSources.supplementalSources[].launchedBy), bind it to a platform service or dispatch it from a biome',
799
- );
800
- }
801
- continue;
802
- }
803
- if (!biomeIndex.some((entry) => entry.id === launcher.biomeId && entry.source === undefined)) {
804
- throw new Error(
805
- `supplemental subject "${supplemental.kind}:${supplemental.id}" is launched by biome ` +
806
- `"${launcher.biomeId}", which the first-party biome index does not carry`,
807
- );
808
- }
809
- if (lock.biomes.some((biome) => biome.id === launcher.biomeId)) {
810
- addSupplementalSubject(supplemental, [
811
- { kind: enums.RuntimeImageOwnerKind.Biome, biomeId: launcher.biomeId },
812
- ]);
813
- }
814
- }
815
-
816
- for (const service of lock.platformServices ?? []) {
817
- let binding;
818
- // The discriminator is the DECLARED runtime binding, never "does the lock
819
- // carry a source". Every platform service carries an exact source identity
820
- // now (`LockedPlatformService.source` is required at lock schemaVersion 2),
821
- // so `if (service.source)` became unconditionally true — and it would have
822
- // minted a repository-ROOT `platform` subject for `xema-kernel-server`,
823
- // `docs-api`, `xema-host-web` and `opencode-image-prepuller`, hashing the
824
- // wrong closure for three of them and inventing an image that has never
825
- // existed for the fourth. A binding names the subject that really carries
826
- // the service; its absence means the service IS its own repository image.
827
- const runtimeBinding = runtimeBindings.get(service.name);
828
- if (!runtimeBinding) {
829
- if (!service.source) {
830
- throw new Error(
831
- `platform service "${service.name}" has no declared runtime image binding ` +
832
- 'and no exact source identity, so no image subject can be resolved for it',
833
- );
834
- }
835
- const repository = requireRepositoryPin(service.source.repo);
836
- if (service.source.sourceCommit !== repository.sourceCommit) {
837
- throw new Error(
838
- `platform service "${service.name}" source commit does not match its repository pin`,
839
- );
840
- }
841
- binding = {
842
- platformService: service.name,
843
- subjectKind: enums.RuntimeImageSubjectKind.Platform,
844
- subjectId: service.source.imageKey,
845
- };
846
- addSubject(subjects, {
847
- kind: binding.subjectKind,
848
- id: binding.subjectId,
849
- imageRef: imageRefForSubject(binding.subjectId),
850
- repo: service.source.repo,
851
- path: service.source.repo,
852
- sourceCommit: repository.sourceCommit,
853
- sourceManifestHash: service.source.sourceManifestHash,
854
- sourceManifestPath: service.source.path,
855
- requiredPlatforms,
856
- owners: [
857
- {
858
- kind: enums.RuntimeImageOwnerKind.PlatformService,
859
- platformService: service.name,
860
- },
861
- ],
862
- });
863
- } else {
864
- binding = runtimeBinding;
865
- const key = bindingKey(binding);
866
- const supplemental = supplementalSubjects.get(key);
867
- if (supplemental) {
868
- addSupplementalSubject(supplemental, [
869
- {
870
- kind: enums.RuntimeImageOwnerKind.PlatformService,
871
- platformService: service.name,
872
- },
873
- ]);
874
- } else {
875
- const target = subjects.get(key);
876
- if (!target) {
877
- throw new Error(
878
- missingArtifactDependencyMessage(lock, service.name, binding, biomeIndex),
879
- );
880
- }
881
- addSubject(subjects, {
882
- ...target,
883
- owners: [
884
- {
885
- kind: enums.RuntimeImageOwnerKind.PlatformService,
886
- platformService: service.name,
887
- },
888
- ],
889
- });
890
- }
891
- }
892
- platformServiceBindings.push(binding);
893
- }
894
-
895
- // A binding for a service this edition does not ship is outside its artifact
896
- // graph. It was REFUSED here ("runtime image source index contains unselected
897
- // platform service"), which made every image-bearing platform service
898
- // unexcludable: `billing-api` could be dropped only because it happens to be
899
- // independently sourced and carries no binding, while `docs-api`,
900
- // `xema-host-web` and `opencode-image-prepuller` could not. The index is the
901
- // fleet catalogue, not this edition's roster; the roster is derived from the
902
- // lock's `platformServices`, above. A binding naming a service the COMPOSED
903
- // catalogue does not know at all is still a declaration defect, and is
904
- // refused by `build-runtime-image-source-index.mjs --check`, which owns the
905
- // index — not by every edition that happens to omit the service.
906
-
907
- const orderedSubjects = [...subjects.values()]
908
- .map((subject) => ({
909
- ...subject,
910
- owners: subject.owners.sort(compareOwner),
911
- }))
912
- .sort(compareSubject);
913
- webBiomes.sort((left, right) => compareStrings(left.biomeId, right.biomeId));
914
- platformServiceBindings.sort((left, right) =>
915
- compareStrings(left.platformService, right.platformService),
916
- );
917
- const orderedThirdPartySubjects = [...thirdPartySubjects.values()]
918
- .map((subject) => ({ ...subject, owners: subject.owners.sort(compareOwner) }))
919
- .sort(compareSubject);
920
- for (const subject of orderedThirdPartySubjects) {
921
- if (subjects.has(subjectKey(subject))) {
922
- throw new Error(
923
- `third-party runtime image subject "${subject.kind}:${subject.id}" has the identity of another subject of distribution "${lock.distributionId}"`,
924
- );
925
- }
926
- }
927
- return {
928
- subjects: orderedSubjects,
929
- thirdPartySubjects: orderedThirdPartySubjects,
930
- webBiomes,
931
- platformServiceBindings,
932
- };
933
- }
934
-
935
- /** Every image subject of a structure, first-party and third-party, in the lock's order. */
936
- export function structureSubjects(structure) {
937
- return [...structure.subjects, ...structure.thirdPartySubjects].sort(compareSubject);
938
- }
939
-
940
- /**
941
- * THE COMPOSED-PROCESS IMAGE OF ONE BUILD — where a producing repository
942
- * declares it, and how an edition addresses it.
943
- *
944
- * The kernel (`RuntimeImageSubjectKind.ComposedProcess`) fixes WHAT the subject
945
- * is: one repository at one commit, carrying every platform-scheduled service
946
- * of that build and `@xemahq/xema-service-nest`'s `xema-composed-process`
947
- * entrypoint, owned by the biomes whose services it carries. Neither the kernel
948
- * nor the SDK fixes WHERE a repository builds it, so this plane does, once:
949
- *
950
- * - the image directory is the per-class directory
951
- * `infra/composed-process/<class>/` at the repository root (`<class>` a
952
- * kernel `ComposedImageClass`; an edition addresses one composed subject
953
- * per repository, of the kernel's `DEFAULT_COMPOSED_IMAGE_CLASS`). A commit
954
- * that predates the per-class layout declares it at the legacy root
955
- * `infra/composed-process/`, which stays readable because committed release
956
- * locks still pin such commits. Both at once, or a declaration in another
957
- * class directory, is refused (`composedProcessDeclarationDir`). The
958
- * directory holds its own `Dockerfile` and an `image-build-inputs.json` whose
959
- * `workspacePackages` lists every member service's workspace package
960
- * directory — the Dockerfile-only image shape `hashImageBuildInputs`
961
- * already reads (`opencode-worker` is the other one). Its tag is therefore
962
- * an ordinary `xema-image-input-sha256-v2` key, computed by the one
963
- * implementation every producer uses, with `SERVICE` = the subject id and
964
- * `SERVICE_PATH` = this directory;
965
- * - the DECLARATION is that `image-build-inputs.json` existing in that
966
- * directory at the pinned commit. A repository that declares none (and has
967
- * no `infra/composed-process/` tree at all) has no composed image, and an
968
- * edition carries no composed subject for it — which the deploy plane
969
- * reports as "the producer builds no composed-process image at that
970
- * commit". That is an honest absence, not a skipped subject: a DECLARED
971
- * image that has not been built is still emitted, carries no
972
- * `imageDigest`, and is undeliverable until it is sealed;
973
- * - the subject id is `<declared source id>-composed-process`. The source
974
- * id is DECLARED, never derived from where the repository is hosted: an
975
- * external source's is the edition's `sources[].name` (which the walk
976
- * also stamps as the biome's `repo`), a first-party repository's is
977
- * declared in `first-party-source-ids.mjs`. A rename or host move changes
978
- * `repositoryUrl` — provenance — and no subject id, `imageRef` or
979
- * `contentHash` (the id is the `SERVICE` input of the tag). It cannot be
980
- * a constant: two first-party repositories publish into one registry
981
- * namespace, and the image lock refuses two subjects sharing an
982
- * `imageRef`. The producing build publishes under this same name;
983
- * - `imageRef` is composed exactly as the repository's service subjects are
984
- * (`imageRefForSubject`, with the same registry and declared prefix).
985
- */
986
- export const COMPOSED_PROCESS_IMAGE_PATH = 'infra/composed-process';
987
- export const COMPOSED_PROCESS_DECLARATION_FILE = 'image-build-inputs.json';
988
- const COMPOSED_PROCESS_ID_SUFFIX = '-composed-process';
989
-
990
- export function composedProcessSubjectId(sourceId) {
991
- return `${assertDeclaredSourceId(sourceId, 'composed-process subject')}${COMPOSED_PROCESS_ID_SUFFIX}`;
992
- }
993
-
994
- /**
995
- * The DECLARED source id of every repository this edition builds a service
996
- * image from, keyed by the subject's `repo` handle — the edition's own
997
- * `sources[].name` for a source-walked biome, the first-party declaration
998
- * otherwise. Never read off a URL.
999
- */
1000
- export function editionSourceIds(lock) {
1001
- const ids = new Map();
1002
- for (const biome of lock.biomes ?? []) {
1003
- if (biome.source === undefined) {continue;}
1004
- if (biome.repo !== biome.source.name) {
1005
- throw new Error(
1006
- `lock biome "${biome.id}" is drawn from source "${biome.source.name}" but its repo ` +
1007
- `handle is "${biome.repo}" — a source-walked biome's repo IS its declared source id`,
1008
- );
1009
- }
1010
- ids.set(biome.repo, assertDeclaredSourceId(biome.source.name, `lock biome "${biome.id}" source`));
1011
- }
1012
- return { get: (repo) => ids.get(repo) ?? firstPartySourceId(repo) };
1013
- }
1014
-
1015
- /**
1016
- * A composed-process declaration the build cannot be read from. Named so a
1017
- * caller can tell "this image's inputs are unknown" from any other refusal:
1018
- * the carried set is evidence, and with none there is no owner to derive.
1019
- */
1020
- export class ComposedProcessWorkerError extends Error {
1021
- constructor(message) {
1022
- super(message);
1023
- this.name = 'ComposedProcessWorkerError';
1024
- }
1025
- }
1026
-
1027
- export class ComposedProcessInputsError extends Error {
1028
- constructor(message) {
1029
- super(message);
1030
- this.name = 'ComposedProcessInputsError';
1031
- }
1032
- }
1033
-
1034
- /**
1035
- * Which of a repository's biome services the composed image CARRIES, read from
1036
- * the composed build's own inputs at the sealed commit.
1037
- *
1038
- * `image-build-inputs.json` lists the member package directories the image is
1039
- * built from (its hash is the subject's `sourceManifestHash`). A service is
1040
- * carried when its manifest-declared source directory (`subjectServicePath`,
1041
- * the `components[].artifact.path` mapping) is one of them. Returns one row
1042
- * per biome: the service ids carried and the ones not.
1043
- */
1044
- /** A component's declared `imageClass` validated with the kernel's own schema; absent stays undefined (the default applies). */
1045
- function declaredImageClass(declared, subject, enums) {
1046
- if (declared === undefined) {return undefined;}
1047
- const parsed = enums.ComposedImageClassSchema.safeParse(declared);
1048
- if (!parsed.success) {
1049
- throw new ComposedProcessInputsError(
1050
- `service "${subject.id}" of ${subject.repo} declares components[].imageClass ${JSON.stringify(declared)}, which is not an image class the kernel knows`,
1051
- );
1052
- }
1053
- return parsed.data;
1054
- }
1055
-
1056
- function composedProcessCarriage(structure, enums, repo, sourceCommit, locate, declarationDir) {
1057
- const inputsPath = `${declarationDir}/${COMPOSED_PROCESS_DECLARATION_FILE}`;
1058
- const { dir } = locate(repo);
1059
- let members;
1060
- try {
1061
- members = readGitJsonAtCommit(dir, inputsPath, sourceCommit)?.workspacePackages;
1062
- } catch (cause) {
1063
- throw new ComposedProcessInputsError(
1064
- `composed-process inputs "${inputsPath}" of "${repo}" are unreadable at ${sourceCommit} ` +
1065
- `(${cause.message}) — the biomes the image carries cannot be derived`,
1066
- );
1067
- }
1068
- if (!Array.isArray(members) || members.some((member) => typeof member !== 'string')) {
1069
- throw new ComposedProcessInputsError(
1070
- `composed-process inputs "${inputsPath}" of "${repo}" at ${sourceCommit} declare no ` +
1071
- '"workspacePackages" array of directories — the biomes the image carries cannot be derived',
1072
- );
1073
- }
1074
- const carried = new Set(members);
1075
- const byBiome = new Map();
1076
- for (const subject of structure.subjects) {
1077
- if (subject.kind !== enums.RuntimeImageSubjectKind.Service || subject.repo !== repo) {continue;}
1078
- const { path: servicePath, componentKind, imageClass: declaredClass } = subjectServiceSource(subject, sourceCommit, locate);
1079
- const imageClass = declaredImageClass(declaredClass, subject, enums);
1080
- for (const owner of subject.owners) {
1081
- if (owner.kind !== enums.RuntimeImageOwnerKind.Biome) {continue;}
1082
- const row = byBiome.get(owner.biomeId) ??
1083
- { biomeId: owner.biomeId, carried: [], uncarried: [], carriedWorkers: [], packageDirs: {}, imageClasses: {} };
1084
- if (!carried.has(servicePath)) {
1085
- row.uncarried.push(subject.id);
1086
- } else if (componentKind === 'worker') {
1087
- // A worker is never a member: it runs as its own Deployment.
1088
- row.carriedWorkers.push(subject.id);
1089
- } else {
1090
- row.carried.push(subject.id);
1091
- // The member's package directory IS the composed build's own input entry.
1092
- row.packageDirs[subject.id] = servicePath;
1093
- // The class its manifest declares (`components[].imageClass`); absent stays undefined (the kernel default applies).
1094
- row.imageClasses[subject.id] = imageClass;
1095
- }
1096
- byBiome.set(owner.biomeId, row);
1097
- }
1098
- }
1099
- return [...byBiome.values()]
1100
- .map((row) => ({
1101
- ...row,
1102
- carried: row.carried.sort(compareStrings),
1103
- uncarried: row.uncarried.sort(compareStrings),
1104
- carriedWorkers: row.carriedWorkers.sort(compareStrings),
1105
- }))
1106
- .sort((a, b) => compareStrings(a.biomeId, b.biomeId));
1107
- }
1108
-
1109
- function gitPathExistsAtCommit(dir, sourceCommit, repoPath) {
1110
- try {
1111
- execFileSync('git', ['-C', dir, 'cat-file', '-e', `${sourceCommit}:${repoPath}`], {
1112
- stdio: ['ignore', 'ignore', 'ignore'],
1113
- });
1114
- return true;
1115
- } catch {
1116
- return false;
1117
- }
1118
- }
1119
-
1120
- /**
1121
- * The repo-relative directory holding a repository's composed-process
1122
- * DECLARATION at one commit, or null when it declares none. The default class's
1123
- * directory and the legacy root are the two places an edition reads; a
1124
- * declaration it cannot address (both present, another class, an
1125
- * `infra/composed-process/` tree without a recognised one) is refused, because
1126
- * a declaration the resolver cannot find must never read as "declares nothing".
1127
- */
1128
- function composedProcessDeclarationDir(dir, sourceCommit, enums) {
1129
- const defaultClass = enums.DEFAULT_COMPOSED_IMAGE_CLASS;
1130
- const classDir = `${COMPOSED_PROCESS_IMAGE_PATH}/${defaultClass}`;
1131
- const declares = (candidate) =>
1132
- gitPathExistsAtCommit(dir, sourceCommit, `${candidate}/${COMPOSED_PROCESS_DECLARATION_FILE}`);
1133
- const inClassDir = declares(classDir);
1134
- const atLegacyRoot = declares(COMPOSED_PROCESS_IMAGE_PATH);
1135
- if (inClassDir && atLegacyRoot) {
1136
- throw new ComposedProcessInputsError(
1137
- `ambiguous composed-process declaration at ${sourceCommit}: both "${classDir}/${COMPOSED_PROCESS_DECLARATION_FILE}" ` +
1138
- `and the legacy "${COMPOSED_PROCESS_IMAGE_PATH}/${COMPOSED_PROCESS_DECLARATION_FILE}" exist`,
1139
- );
1140
- }
1141
- const otherClasses = enums.ComposedImageClassSchema.options
1142
- .filter((candidate) => candidate !== defaultClass)
1143
- .filter((candidate) => declares(`${COMPOSED_PROCESS_IMAGE_PATH}/${candidate}`));
1144
- if (otherClasses.length > 0) {
1145
- throw new ComposedProcessInputsError(
1146
- `composed-process declaration at ${sourceCommit} is in the "${otherClasses.join('", "')}" class ` +
1147
- `director${otherClasses.length > 1 ? 'ies' : 'y'}, but an edition addresses one composed-process subject per ` +
1148
- `repository, of the default class "${defaultClass}"; a "${otherClasses[0]}" image has no subject naming yet`,
1149
- );
1150
- }
1151
- if (inClassDir) {return classDir;}
1152
- if (atLegacyRoot) {return COMPOSED_PROCESS_IMAGE_PATH;}
1153
- if (gitPathExistsAtCommit(dir, sourceCommit, COMPOSED_PROCESS_IMAGE_PATH)) {
1154
- throw new ComposedProcessInputsError(
1155
- `"${COMPOSED_PROCESS_IMAGE_PATH}" exists at ${sourceCommit} but declares no composed-process image the edition can ` +
1156
- `read: looked at "${classDir}/${COMPOSED_PROCESS_DECLARATION_FILE}" and ` +
1157
- `"${COMPOSED_PROCESS_IMAGE_PATH}/${COMPOSED_PROCESS_DECLARATION_FILE}"`,
1158
- );
1159
- }
1160
- return null;
1161
- }
1162
-
1163
- /**
1164
- * Read each repository's composed-process DECLARATION at the commit the
1165
- * edition builds its services from. Only repositories that contribute a
1166
- * service subject are asked: a composed image carries services, so a
1167
- * repository shipping none has nothing to compose.
1168
- */
1169
- export function readComposedProcessDeclarations(
1170
- structure,
1171
- enums,
1172
- sourceIds,
1173
- locate = FIRST_PARTY_TREES,
1174
- ) {
1175
- const commits = new Map();
1176
- for (const subject of structure.subjects) {
1177
- if (subject.kind !== enums.RuntimeImageSubjectKind.Service) {continue;}
1178
- if (!subject.owners.some((owner) => owner.kind === enums.RuntimeImageOwnerKind.Biome)) {continue;}
1179
- commits.set(subject.repo, subject.sourceCommit);
1180
- }
1181
- const declarations = [];
1182
- for (const [repo, sourceCommit] of [...commits].sort(([a], [b]) => compareStrings(a, b))) {
1183
- const { dir, base } = locate(repo);
1184
- // ABSENCE IS ONLY AN ANSWER ABOUT A COMMIT THAT WAS READ. A repository
1185
- // missing from this layout, or one lacking the pinned commit, would
1186
- // otherwise read as "declares nothing", and `dist-check` would grade a lock
1187
- // that dropped the composed subject as up to date.
1188
- try {
1189
- execFileSync('git', ['-C', dir, 'cat-file', '-e', `${sourceCommit}^{commit}`], {
1190
- stdio: ['ignore', 'ignore', 'ignore'],
1191
- });
1192
- } catch {
1193
- throw new Error(
1194
- `runtime image source "${repo}" pins commit "${sourceCommit}", which is absent from ` +
1195
- `${dir} — its composed-process declaration cannot be read; fetch the repository ` +
1196
- 'before resolving',
1197
- );
1198
- }
1199
- const declarationDir = composedProcessDeclarationDir(dir, sourceCommit, enums);
1200
- if (declarationDir === null) {continue;} // declares no composed-process image at this commit
1201
- const declarationPath = `${declarationDir}/${COMPOSED_PROCESS_DECLARATION_FILE}`;
1202
- const sourceId = sourceIds.get(repo);
1203
- const repoRelative = relative(base, dir).split(sep).join('/');
1204
- declarations.push({
1205
- repo,
1206
- sourceId,
1207
- sourceCommit,
1208
- carriage: composedProcessCarriage(structure, enums, repo, sourceCommit, locate, declarationDir),
1209
- path: `${repoRelative}/${declarationDir}`,
1210
- sourceManifestPath: `${repoRelative}/${declarationPath}`,
1211
- sourceManifestHash: exactSourceManifestHash(dir, declarationPath, sourceCommit),
1212
- });
1213
- }
1214
- return declarations;
1215
- }
1216
-
1217
- /**
1218
- * The image class of one composed artifact, DERIVED from its members' manifests: each member's `components[].imageClass`,
1219
- * absent meaning the kernel's `DEFAULT_COMPOSED_IMAGE_CLASS`. All members must agree; a mixed artifact is refused naming
1220
- * the artifact and the members of each class.
1221
- */
1222
- export function composedArtifactImageClass(artifactId, memberRows, declaredClassOf, enums) {
1223
- const byClass = new Map();
1224
- for (const { service } of memberRows) {
1225
- const declared = declaredClassOf.get(service);
1226
- const imageClass = declared ?? enums.DEFAULT_COMPOSED_IMAGE_CLASS;
1227
- byClass.set(imageClass, [...(byClass.get(imageClass) ?? []), service]);
1228
- }
1229
- if (byClass.size > 1) {
1230
- const detail = [...byClass].sort(([a], [b]) => compareStrings(a, b)).map(([imageClass, services]) => `${imageClass}: ${services.join(', ')}`).join('; ');
1231
- throw new ComposedProcessInputsError(
1232
- `composed-process artifact "${artifactId}" mixes image classes across its members (${detail}) — ` +
1233
- 'an artifact has one class; split the members or align their components[].imageClass',
1234
- );
1235
- }
1236
- return [...byClass.keys()][0];
1237
- }
1238
-
1239
- /** The services the edition SELECTS, read off its distribution lock: every service of every non-web biome whose image is its own (a shared-image worker is not one). */
1240
- export function editionRosterServices(distributionLock) {
1241
- return distributionLock.biomes
1242
- .filter((biome) => biome.target !== 'web')
1243
- .flatMap((biome) => {
1244
- const shared = new Set(Object.keys(biome.workerImages ?? {}));
1245
- return (biome.services ?? []).filter((service) => !shared.has(service));
1246
- });
1247
- }
1248
-
1249
- /**
1250
- * Refuse a build that does not cover every service of the edition roster: each must be a dedicated service subject or a
1251
- * member of a built composed artifact. A service neither is never dropped from the lock silently, and the carry-forward
1252
- * (which sees only what was built) is never the one to lose it.
1253
- */
1254
- export function assertBuiltCoversEditionRoster({ rosterServices, serviceSubjects, composedBuilt }) {
1255
- const covered = new Set([
1256
- ...serviceSubjects.map((subject) => subject.id),
1257
- ...composedBuilt.flatMap((artifact) => (artifact.members ?? []).map((member) => member.service)),
1258
- ]);
1259
- const missing = [...new Set(rosterServices)].filter((service) => !covered.has(service)).sort(compareStrings);
1260
- if (missing.length > 0) {
1261
- throw new ComposedProcessInputsError(
1262
- `the build covers no image for ${missing.map((service) => `"${service}"`).join(', ')} of the edition roster — ` +
1263
- 'a service of the edition is never dropped from the lock',
1264
- );
1265
- }
1266
- }
1267
-
1268
- /** The members of a composed subject with the key of each: the member service's own image-input hash (the one function the packager's tag is emitted from). */
1269
- export function composedMemberKeys({ subject, repoDirectory, sourceCommit, crossRepoInputsByConsumer, buildStepDirectories, hash = exactImageBuildInputHash }) {
1270
- return subject.members.map((member) => ({
1271
- ...member,
1272
- key: hash(
1273
- {
1274
- repoDir: repoDirectory,
1275
- service: member.service,
1276
- servicePath: member.packageDir,
1277
- commit: sourceCommit,
1278
- crossRepoInputs: [
1279
- ...(crossRepoInputsByConsumer.get(`${subject.repo}\u0000`) ?? []),
1280
- ...(crossRepoInputsByConsumer.get(`${subject.repo}\u0000${member.service}`) ?? []),
1281
- ],
1282
- buildStepDirectories,
1283
- },
1284
- imageInputSourceHashAlgorithm(),
1285
- ),
1286
- }));
1287
- }
1288
-
1289
- /**
1290
- * Add ONE `composed-process` subject per declaring repository to an edition's
1291
- * structure. Pure over its inputs, so the structural check and the source
1292
- * resolve derive the same subject from the same declarations.
1293
- *
1294
- * Owners are the biomes the image CARRIES, derived from the composed build's
1295
- * inputs at the sealed commit (`declaration.carriage`): every biome at least one
1296
- * of whose services is a member, never every biome with a service in the
1297
- * repository. The members leave the structure and are sealed on the subject
1298
- * as `members[{ service, packageDir }]` — the per-service membership truth: a
1299
- * service runs from the composed digest if and only if it is a row (owners
1300
- * never resolve a service). The edition carries the composed subject plus the
1301
- * services that are no row (the specials, which keep their own subjects even
1302
- * inside an owner biome), and nothing addresses a member's own image. A worker
1303
- * is never a row; an own-image worker the composed build carries is refused.
1304
- */
1305
- export function withComposedProcessSubjects(
1306
- structure,
1307
- declarations,
1308
- enums,
1309
- requiredPlatforms,
1310
- { onNotice = (message) => console.warn(`[dist:images] ${message}`) } = {},
1311
- ) {
1312
- if (declarations.length === 0) {return structure;}
1313
- const subjects = new Map(structure.subjects.map((subject) => [subjectKey(subject), subject]));
1314
- // A biome's members run from ONE composed image: a biome owning two composed
1315
- // subjects would leave its pods two images to choose between.
1316
- const composedOwnerOf = new Map();
1317
- for (const declaration of declarations) {
1318
- const services = structure.subjects.filter(
1319
- (subject) =>
1320
- subject.kind === enums.RuntimeImageSubjectKind.Service && subject.repo === declaration.repo,
1321
- );
1322
- if (!Array.isArray(declaration.carriage)) {
1323
- throw new ComposedProcessInputsError(
1324
- `composed-process declaration for "${declaration.repo}" carries no evidence of the ` +
1325
- 'biomes the image carries — refusing to guess its owners',
1326
- );
1327
- }
1328
- // Owners are the biomes ANY of whose services the image carries. A biome
1329
- // with only some carried is an owner too; its services the image does not
1330
- // carry are specials and keep their own subjects (named here). A biome with
1331
- // none carried is simply not one.
1332
- const biomeOwners = [];
1333
- const members = new Set();
1334
- const packageDirOf = new Map();
1335
- const imageClassOf = new Map();
1336
- for (const row of declaration.carriage) {
1337
- // A worker is never a member. One the composed build carries must run
1338
- // from a carried sibling's image (`workerImages`, declared in the
1339
- // manifest), never be dropped into the composed process or fall back.
1340
- for (const worker of row.carriedWorkers ?? []) {
1341
- throw new ComposedProcessWorkerError(
1342
- row.carried.length === 0
1343
- ? `composed-process "${declaration.repo}" carries worker "${worker}" of biome "${row.biomeId}", ` +
1344
- 'and that biome has no carried service whose image it could run — a worker is never a ' +
1345
- 'composed member; carry a service of its biome or take the worker out of the composed inputs'
1346
- : `composed-process "${declaration.repo}" carries worker "${worker}" of biome "${row.biomeId}" as ` +
1347
- 'its own oci-image — a worker is never a composed member; declare it `artifact.kind: ' +
1348
- `shared-oci-image\` of a carried service (${row.carried.join(', ')}) or take it out of the composed inputs`,
1349
- );
1350
- }
1351
- if (row.carried.length === 0) {continue;}
1352
- if (row.uncarried.length > 0) {
1353
- onNotice(
1354
- `composed-process "${declaration.repo}": biome "${row.biomeId}" is partly carried ` +
1355
- `(carried: ${row.carried.join(', ')}; not carried: ${row.uncarried.join(', ')}) — ` +
1356
- 'its uncarried services keep their own subjects',
1357
- );
1358
- }
1359
- const other = composedOwnerOf.get(row.biomeId);
1360
- if (other !== undefined) {
1361
- throw new Error(
1362
- `biome "${row.biomeId}" is carried by the composed-process images of both "${other}" and ` +
1363
- `"${declaration.repo}" — a biome is owned by exactly one composed subject`,
1364
- );
1365
- }
1366
- composedOwnerOf.set(row.biomeId, declaration.repo);
1367
- biomeOwners.push({ kind: enums.RuntimeImageOwnerKind.Biome, biomeId: row.biomeId });
1368
- for (const id of row.carried) {members.add(id);}
1369
- for (const [id, packageDir] of Object.entries(row.packageDirs ?? {})) {packageDirOf.set(id, packageDir);}
1370
- for (const [id, imageClass] of Object.entries(row.imageClasses ?? {})) {imageClassOf.set(id, imageClass);}
1371
- }
1372
- if (biomeOwners.length === 0) {
1373
- throw new Error(
1374
- `composed-process declaration for "${declaration.repo}" carries no biome's services in ` +
1375
- 'this edition — a composed image carries services, so it has no owner',
1376
- );
1377
- }
1378
- for (const subject of services) {
1379
- if (subject.sourceCommit !== declaration.sourceCommit) {
1380
- throw new Error(
1381
- `runtime subject "${subject.kind}:${subject.id}" is resolved at ${subject.sourceCommit}, ` +
1382
- `but "${declaration.repo}"'s composed-process image is declared at ` +
1383
- `${declaration.sourceCommit} — one composed image is ONE build`,
1384
- );
1385
- }
1386
- }
1387
- // The registry and declared prefix the repository's services are published
1388
- // under: `imageRefForSubject` is `<registry>/<prefix><id>`, so stripping
1389
- // the service id recovers it exactly. Two services disagreeing would mean
1390
- // one build published under two coordinates, which nothing can address.
1391
- const namespaces = new Set(
1392
- services.map((subject) => {
1393
- if (!subject.imageRef.endsWith(subject.id)) {
1394
- throw new Error(
1395
- `runtime subject "${subject.kind}:${subject.id}" imageRef "${subject.imageRef}" ` +
1396
- 'does not end with its id, so its namespace cannot be recovered',
1397
- );
1398
- }
1399
- return subject.imageRef.slice(0, -subject.id.length);
1400
- }),
1401
- );
1402
- if (namespaces.size !== 1) {
1403
- throw new Error(
1404
- `"${declaration.repo}"'s services publish under ${namespaces.size} namespaces ` +
1405
- `(${[...namespaces].join(', ')}) — a composed image of one build has one`,
1406
- );
1407
- }
1408
- // A MEMBER reaches its pods through this one subject (every member runs as a
1409
- // group-of-one of the composed image), so it carries no subject of its own:
1410
- // no `<imageRef>:<contentHash>` of its own is probed, verified or sealed. A
1411
- // member is a service of this build the composed build's inputs carry
1412
- // (`declaration.carriage`), every owner of which is a biome owning the
1413
- // composed image. Any other service (not carried, or owned by anything but
1414
- // a biome, e.g. shared with a platform service) keeps its own.
1415
- const composedOwners = new Set(biomeOwners.map((owner) => owner.biomeId));
1416
- const memberRows = [];
1417
- for (const subject of services) {
1418
- if (
1419
- members.has(subject.id) &&
1420
- subject.owners.length > 0 &&
1421
- subject.owners.every(
1422
- (owner) => owner.kind === enums.RuntimeImageOwnerKind.Biome && composedOwners.has(owner.biomeId),
1423
- )
1424
- ) {
1425
- subjects.delete(subjectKey(subject));
1426
- const packageDir = packageDirOf.get(subject.id);
1427
- if (packageDir === undefined) {
1428
- throw new ComposedProcessInputsError(
1429
- `composed-process "${declaration.repo}": member "${subject.id}" has no package directory in ` +
1430
- 'the composed build inputs — its shared-image workers would have nowhere to run from',
1431
- );
1432
- }
1433
- memberRows.push({ service: subject.id, packageDir });
1434
- }
1435
- }
1436
- if (memberRows.length === 0) {
1437
- throw new ComposedProcessInputsError(
1438
- `composed-process "${declaration.repo}" has no member: every service its build carries ` +
1439
- `(${[...members].sort(compareStrings).join(', ')}) is also owned by something other than a biome ` +
1440
- 'it owns (e.g. a platform service), so each keeps its own subject — a composed image with no ' +
1441
- 'member runs nothing; take it out of the edition or carry a biome-owned service',
1442
- );
1443
- }
1444
- memberRows.sort((a, b) => compareStrings(a.service, b.service));
1445
- const id = composedProcessSubjectId(declaration.sourceId);
1446
- const imageClass = composedArtifactImageClass(id, memberRows, imageClassOf, enums);
1447
- addSubject(subjects, {
1448
- kind: enums.RuntimeImageSubjectKind.ComposedProcess,
1449
- id,
1450
- imageRef: `${[...namespaces][0]}${id}`,
1451
- repo: declaration.repo,
1452
- path: declaration.path,
1453
- sourceCommit: declaration.sourceCommit,
1454
- sourceManifestHash: declaration.sourceManifestHash,
1455
- sourceManifestPath: declaration.sourceManifestPath,
1456
- requiredPlatforms,
1457
- owners: biomeOwners,
1458
- members: memberRows,
1459
- imageClass,
1460
- });
1461
- }
1462
- return {
1463
- ...structure,
1464
- subjects: [...subjects.values()]
1465
- .map((subject) => ({ ...subject, owners: [...subject.owners].sort(compareOwner) }))
1466
- .sort(compareSubject),
1467
- };
1468
- }
1469
-
1470
- /**
1471
- * The edition structure INCLUDING its composed-process subjects — the one
1472
- * entry point the source resolve and the structural check share, so neither
1473
- * can carry a subject the other does not. A repository that declares no
1474
- * composed image at its pinned commit contributes no subject.
1475
- */
1476
- export function resolveComposedProcessStructure(
1477
- lock,
1478
- structure,
1479
- enums,
1480
- requiredPlatforms,
1481
- { locate, onNotice } = {},
1482
- ) {
1483
- return withComposedProcessSubjects(
1484
- structure,
1485
- readComposedProcessDeclarations(
1486
- structure,
1487
- enums,
1488
- editionSourceIds(lock),
1489
- // Materialised only when something will be read through it.
1490
- locate ?? sourceTreeLocator(externalSourceTrees(lock)),
1491
- ),
1492
- enums,
1493
- requiredPlatforms,
1494
- onNotice === undefined ? undefined : { onNotice },
1495
- );
1496
- }
1497
-
1498
- /**
1499
- * The exact commit every image source repository is resolved at.
1500
- *
1501
- * The PIN is the authority, not the local checkout. Requiring each submodule's
1502
- * `HEAD` to equal its pin made regeneration impossible in any working tree that
1503
- * had moved on — which is every working tree — and bought nothing: the hashes
1504
- * read immutable Git objects at the pinned commit, so what is checked out
1505
- * cannot change a digest. What DOES have to hold is that the pinned commit is
1506
- * present in the local object database, which is asserted here.
1507
- */
1508
- function sourceCommitMap(structure, locate = FIRST_PARTY_TREES) {
1509
- const commitByRepo = new Map();
1510
- for (const source of [...structure.subjects, ...structure.webBiomes]) {
1511
- if (!source.sourceCommit) {continue;}
1512
- const incumbent = commitByRepo.get(source.repo);
1513
- if (incumbent && incumbent !== source.sourceCommit) {
1514
- throw new Error(
1515
- `runtime image roster pins multiple commits for "${source.repo}"`,
1516
- );
1517
- }
1518
- commitByRepo.set(source.repo, source.sourceCommit);
1519
- }
1520
- const repositories = [
1521
- ...new Set([
1522
- ...structure.subjects.map((subject) => subject.repo),
1523
- ...structure.webBiomes.map((biome) => biome.repo),
1524
- ]),
1525
- ].sort(compareStrings);
1526
- for (const repo of repositories) {
1527
- const commit = commitByRepo.get(repo);
1528
- if (!commit) {
1529
- throw new Error(`runtime image source "${repo}" has no pinned commit`);
1530
- }
1531
- try {
1532
- execFileSync(
1533
- 'git',
1534
- ['-C', locate(repo).dir, 'cat-file', '-e', `${commit}^{commit}`],
1535
- { stdio: ['ignore', 'ignore', 'pipe'] },
1536
- );
1537
- } catch {
1538
- throw new Error(
1539
- `runtime image source "${repo}" pins commit "${commit}", which is absent ` +
1540
- `from the local object database — fetch the repository before resolving`,
1541
- );
1542
- }
1543
- }
1544
- return commitByRepo;
1545
- }
1546
-
1547
- /**
1548
- * The SERVICE directory an image is built from, repository-relative.
1549
- *
1550
- * A biome subject's `path` is the BIOME directory, and a biome can ship several
1551
- * services — `agent-runtime` ships four. Hashing the biome directory would give
1552
- * all four the same closure and would move all four when any one of them
1553
- * changes, so the service directory is read from the manifest SSOT
1554
- * (`xema.ships.apis[].path`) rather than synthesized. Non-biome subjects
1555
- * (`sourceManifestPath` present) already carry their own service directory.
1556
- *
1557
- * Exported for `assemble-producer-input.mjs`, which must derive the SAME
1558
- * service directory to hash the SAME build-input closure the lock names.
1559
- */
1560
- export function subjectServicePath(subject, sourceCommit, locate = FIRST_PARTY_TREES) {
1561
- return subjectServiceSource(subject, sourceCommit, locate).path;
1562
- }
1563
-
1564
- /**
1565
- * The repository-relative source directory of a subject and the kind of the
1566
- * manifest component it is built from (`worker` for a worker component,
1567
- * `service` otherwise — a legacy `ships.apis[]` entry or a subject with its own
1568
- * source manifest is not a worker).
1569
- */
1570
- function subjectServiceSource(subject, sourceCommit, locate = FIRST_PARTY_TREES) {
1571
- const { dir: repoDirectory, base } = locate(subject.repo);
1572
- const toRepoRelative = (absolute) =>
1573
- relative(repoDirectory, absolute).split(sep).join('/') || '.';
1574
- if (subject.sourceManifestPath) {
1575
- return { path: toRepoRelative(join(base, subject.path)), componentKind: 'service', imageClass: undefined };
1576
- }
1577
- const biomePath = toRepoRelative(join(base, subject.path));
1578
- const manifestPath =
1579
- biomePath === '.' ? 'xema-biome.json' : `${biomePath}/xema-biome.json`;
1580
- const manifest = readGitJsonAtCommit(
1581
- repoDirectory,
1582
- manifestPath,
1583
- sourceCommit,
1584
- );
1585
- const xema = manifest?.xema;
1586
- const apis = xema?.ships?.apis;
1587
- const legacyApis = Array.isArray(apis) ? apis : [];
1588
- // component-contract-v5: a component's source directory is declared by its
1589
- // own `artifact.path`.
1590
- //
1591
- // Both `service` AND `worker` kinds qualify. Restricting this to `service`
1592
- // was correct only while a runtime component was spelled in the legacy
1593
- // `ships.apis[]` block; v5 dropped that field, so `opencode-worker` —
1594
- // `kind: "worker"`, `scheduler: "runtime"` — fell through to the legacy
1595
- // branch and failed with "ships no apis" even though its manifest declares
1596
- // everything needed. What identifies the subject is
1597
- // `protocol.serviceName`, not the component kind.
1598
- const components = Array.isArray(xema?.components) ? xema.components : [];
1599
- const v4ServiceComponents = components.filter(
1600
- (component) =>
1601
- component !== null &&
1602
- typeof component === 'object' &&
1603
- (component.kind === 'service' || component.kind === 'worker'),
1604
- );
1605
- if (legacyApis.length > 0 && v4ServiceComponents.length > 0) {
1606
- throw new Error(
1607
- `${subject.repo}@${sourceCommit}:${manifestPath} declares services in BOTH ` +
1608
- `xema.ships.apis[] (legacy) and xema.components[] kind "service" ` +
1609
- `(component-contract-v4) — a biome must use exactly one shape`,
1610
- );
1611
- }
1612
- let servicePath;
1613
- let componentKind = 'service';
1614
- let imageClass;
1615
- if (v4ServiceComponents.length > 0) {
1616
- // A SERVICE is identified by `protocol.serviceName`; a WORKER has no
1617
- // service protocol and is identified by its component `key`. Matching only
1618
- // on `serviceName` silently found nothing for every worker subject and
1619
- // reported it as an undeclared service directory.
1620
- const component = v4ServiceComponents.find(
1621
- (entry) =>
1622
- entry?.protocol?.serviceName === subject.id || entry?.key === subject.id,
1623
- );
1624
- if (
1625
- !component ||
1626
- typeof component?.artifact?.path !== 'string' ||
1627
- component.artifact.path.length === 0
1628
- ) {
1629
- throw new Error(
1630
- `${subject.repo}@${sourceCommit}:${manifestPath} declares no xema.components[] ` +
1631
- `kind "service"/"worker" entry identified as "${subject.id}" (by ` +
1632
- `protocol.serviceName or component key) with an ` +
1633
- `artifact.path — the service source directory is the manifest's to declare ` +
1634
- `and must never be synthesized`,
1635
- );
1636
- }
1637
- servicePath = component.artifact.path;
1638
- componentKind = component.kind;
1639
- imageClass = component.imageClass;
1640
- } else {
1641
- if (legacyApis.length === 0) {
1642
- throw new Error(
1643
- `${subject.repo}@${sourceCommit}:${manifestPath} ships no apis, but runtime ` +
1644
- `subject "${subject.kind}:${subject.id}" claims to be one of them`,
1645
- );
1646
- }
1647
- const api = legacyApis.find((entry) => entry?.name === subject.id);
1648
- if (!api || typeof api.path !== 'string' || api.path.length === 0) {
1649
- throw new Error(
1650
- `${subject.repo}@${sourceCommit}:${manifestPath} declares no ships.apis[] entry ` +
1651
- `named "${subject.id}" with a "path" — the service source directory is the ` +
1652
- `manifest's to declare and must never be synthesized`,
1653
- );
1654
- }
1655
- servicePath = api.path;
1656
- }
1657
- return { path: toRepoRelative(join(base, subject.path, servicePath)), componentKind, imageClass };
1658
- }
1659
-
1660
- function assertSourceManifestParity(structure, commitByRepo, locate = FIRST_PARTY_TREES) {
1661
- for (const source of structure.subjects) {
1662
- const { dir: repoDirectory, base } = locate(source.repo);
1663
- const sourceCommit = commitByRepo.get(source.repo);
1664
- const manifestPath = source.sourceManifestPath
1665
- ? relative(repoDirectory, join(base, source.sourceManifestPath))
1666
- .split(sep)
1667
- .join('/')
1668
- : relative(repoDirectory, join(base, source.path, 'xema-biome.json'))
1669
- .split(sep)
1670
- .join('/');
1671
- const actual = exactSourceManifestHash(
1672
- repoDirectory,
1673
- manifestPath,
1674
- sourceCommit,
1675
- );
1676
- if (actual !== source.sourceManifestHash) {
1677
- throw new Error(
1678
- `runtime subject "${source.kind}:${source.id}" sourceManifestHash does not match ${source.repo}@${sourceCommit}:${manifestPath}`,
1679
- );
1680
- }
1681
- }
1682
- for (const biome of structure.webBiomes) {
1683
- const { dir: repoDirectory, base } = locate(biome.repo);
1684
- const sourceCommit = commitByRepo.get(biome.repo);
1685
- const manifestPath = relative(
1686
- repoDirectory,
1687
- join(base, biome.path, 'xema-biome.json'),
1688
- )
1689
- .split(sep)
1690
- .join('/');
1691
- const actual = exactSourceManifestHash(
1692
- repoDirectory,
1693
- manifestPath,
1694
- sourceCommit,
1695
- );
1696
- if (actual !== biome.sourceManifestHash) {
1697
- throw new Error(
1698
- `web biome "${biome.biomeId}" sourceManifestHash does not match ${biome.repo}@${sourceCommit}:${manifestPath}`,
1699
- );
1700
- }
1701
- }
1702
- }
1703
-
1704
- /**
1705
- * AN EXTERNAL SOURCE THAT VENDORS CROSS-REPOSITORY CONTENT IS REFUSED, LOUDLY.
1706
- *
1707
- * `runtime-image-source-index.json` declares the vendoring this fleet's own
1708
- * repositories do, and an external source has no entry there — correctly, since
1709
- * the index is the vendor's view of the vendor's repositories. Reading that
1710
- * absence as "this repository vendors nothing" is the silent half: a Dockerfile
1711
- * that copies a tree in from elsewhere would have those bytes drop out of the
1712
- * closure, and the hash would name an image whose content it does not describe.
1713
- *
1714
- * So the ABSENCE is checked rather than assumed. The producing repository's own
1715
- * committed record — the same file `vendor-cross-repo-inputs.mjs` reads to emit
1716
- * the `--vendored` arguments CI hands the tag computation — is read at the
1717
- * locked commit. Absent means genuinely nothing to vendor, which is the honest
1718
- * empty. PRESENT means this plane cannot pin the other side (it has no checkout
1719
- * of a repository the customer vendors from, and no authority to resolve one),
1720
- * and an undeclared input must fail rather than silently drop out.
1721
- */
1722
- function assertExternalSourcesDeclareNoVendoring(lock, locate, commitByRepo) {
1723
- const external = new Set(
1724
- (lock.biomes ?? [])
1725
- .filter((biome) => biome.source !== undefined)
1726
- .map((biome) => biome.repo),
1727
- );
1728
- for (const repo of [...external].sort(compareStrings)) {
1729
- const { dir } = locate(repo);
1730
- const commit = commitByRepo.get(repo);
1731
- if (!commit) {continue;}
1732
- // Existence is probed with `cat-file -e` rather than by catching the read:
1733
- // a failed read writes `fatal: path ... does not exist` to stderr, and a
1734
- // `fatal:` printed during a successful run is a false signal a reader has
1735
- // to spend time discounting.
1736
- let present;
1737
- try {
1738
- execFileSync('git', ['-C', dir, 'cat-file', '-e', `${commit}:${CROSS_REPO_BUILD_INPUTS_PATH}`], {
1739
- stdio: ['ignore', 'ignore', 'ignore'],
1740
- });
1741
- present = true;
1742
- } catch {
1743
- present = false;
1744
- }
1745
- if (!present) {continue;} // absent — nothing vendored, which is the honest empty
1746
- const record = readGitJsonAtCommit(dir, CROSS_REPO_BUILD_INPUTS_PATH, commit);
1747
- const inputs = Array.isArray(record?.inputs) ? record.inputs : [];
1748
- if (inputs.length === 0) {continue;}
1749
- throw new Error(
1750
- `external source "${repo}" declares ${inputs.length} cross-repository build ` +
1751
- `input(s) in ${CROSS_REPO_BUILD_INPUTS_PATH} at ${commit}. This plane cannot pin ` +
1752
- 'the other side of them — it holds no checkout of the repositories they vendor ' +
1753
- 'from — so the image content hash would omit bytes the build bakes in and name a ' +
1754
- 'coordinate it does not describe. Refusing rather than hashing an incomplete ' +
1755
- 'closure.',
1756
- );
1757
- }
1758
- }
1759
-
1760
- /**
1761
- * The vendored cross-repository build inputs of THIS roster's images, keyed
1762
- * `${consumerRepo}\0${consumerService ?? ''}`.
1763
- *
1764
- * EVERY COMMIT HERE COMES FROM A REPOSITORY PIN, NEVER FROM ROSTER MEMBERSHIP.
1765
- *
1766
- * The consumer commit — the commit whose committed
1767
- * `tooling/release/cross-repo-build-inputs.json` names the vendored revisions —
1768
- * is the consumer repository's `repositoryPins` entry. It used to be read out
1769
- * of the edition's resolved commit map, which knows a repository only if some
1770
- * SELECTED biome happens to come from it. That made an image's identity depend
1771
- * on unrelated membership, and made a trimmed edition unaddressable: an edition
1772
- * shipping no `repos/xema-community` biome refused with "cross-repo build inputs
1773
- * are declared for repos/xema-community, but this distribution resolves no
1774
- * commit for it" — although it shipped no xema-community image at all. For a
1775
- * first-party subject the two are the same commit by construction (a subject's
1776
- * `sourceCommit` IS its repository pin), so every edition that resolved before
1777
- * resolves to byte-identical hashes now.
1778
- *
1779
- * Only consumers the roster actually BUILDS FROM are resolved: a consumer with
1780
- * no selected subject contributes no input to any hash here, so reading its
1781
- * record would be work that grades nothing. The vendored revision itself is the
1782
- * consumer's own record at its pin (see the reasoning above the call site): the
1783
- * pin fixes the record, and the record fixes the bytes. A consumer, or a
1784
- * repository it vendors from, with NO repository pin is refused by name — the
1785
- * image cannot be addressed without one.
1786
- */
1787
- export function resolveCrossRepoInputsForRoster({
1788
- runtimeSourceIndex,
1789
- subjects,
1790
- resolveVendored = resolveCrossRepoVendoredInputs,
1791
- repoRoot = sourcesRoot(),
1792
- // The subject kind that builds EVERY input of its repository (the composed
1793
- // process image carries every service of its build). Undefined: none does.
1794
- composedProcessKind = undefined,
1795
- }) {
1796
- const pins = new Map(
1797
- runtimeSourceIndex.repositoryPins.map((pin) => [pin.repo, pin]),
1798
- );
1799
- const requirePin = (repo, role) => {
1800
- const pin = pins.get(repo);
1801
- if (!pin) {
1802
- throw new Error(
1803
- `cross-repo build inputs need a repository pin for "${repo}" (${role}), ` +
1804
- 'and runtime-image-source-index.json declares none — the vendored content ' +
1805
- 'cannot be pinned, so the image cannot be addressed',
1806
- );
1807
- }
1808
- return pin;
1809
- };
1810
- const builds = (input) =>
1811
- subjects.some(
1812
- (subject) =>
1813
- subject.repo === input.consumerRepo &&
1814
- (input.consumerService === undefined || input.consumerService === null ||
1815
- input.consumerService === subject.id ||
1816
- (composedProcessKind !== undefined && subject.kind === composedProcessKind)),
1817
- );
1818
- const needed = runtimeSourceIndex.crossRepoBuildInputs.filter(builds);
1819
- const vendoredCommitByInputKey = new Map();
1820
- for (const consumerRepo of [
1821
- ...new Set(needed.map((input) => input.consumerRepo)),
1822
- ].sort(compareStrings)) {
1823
- const consumerCommit = requirePin(consumerRepo, 'the consuming repository').sourceCommit;
1824
- for (const subject of subjects) {
1825
- if (subject.repo === consumerRepo && subject.sourceCommit !== undefined &&
1826
- subject.sourceCommit !== consumerCommit) {
1827
- throw new Error(
1828
- `runtime subject "${subject.kind}:${subject.id}" is resolved at ` +
1829
- `${subject.sourceCommit}, but "${consumerRepo}" is pinned at ${consumerCommit}`,
1830
- );
1831
- }
1832
- }
1833
- // The record is read against EVERY input the index declares for this
1834
- // consumer, not the roster-filtered subset: set equality with the
1835
- // consumer's record is the fence against a drifted declaration, and it must
1836
- // not weaken because an edition ships fewer services.
1837
- for (const resolved of resolveVendored({
1838
- indexInputs: runtimeSourceIndex.crossRepoBuildInputs,
1839
- consumerRepo,
1840
- consumerRepoDir: join(repoRoot, consumerRepo),
1841
- consumerCommit,
1842
- })) {
1843
- vendoredCommitByInputKey.set(
1844
- consumerInputKey(consumerRepo, {
1845
- sourceRepo: relative(repoRoot, resolved.sourceRepoDir),
1846
- sourcePath: resolved.sourcePath,
1847
- destinationPath: resolved.destinationPath,
1848
- }),
1849
- resolved.sourceCommit,
1850
- );
1851
- }
1852
- }
1853
- const crossRepoInputsByConsumer = new Map();
1854
- for (const input of needed) {
1855
- requirePin(input.sourceRepo, `vendored into "${input.consumerRepo}"`);
1856
- // A null `consumerService` means the vendoring step runs for EVERY service
1857
- // built from that repository; it is keyed on the repository alone.
1858
- const key = `${input.consumerRepo}\u0000${input.consumerService ?? ''}`;
1859
- const sourceCommit = vendoredCommitByInputKey.get(
1860
- consumerInputKey(input.consumerRepo, input),
1861
- );
1862
- if (!sourceCommit) {
1863
- throw new Error(
1864
- `cross-repo build input for "${input.consumerService}" vendoring ` +
1865
- `"${input.sourceRepo}:${input.sourcePath}" has no commit in ` +
1866
- `"${input.consumerRepo}"'s own cross-repo build-input record — its ` +
1867
- `vendored content cannot be pinned, so the image cannot be addressed`,
1868
- );
1869
- }
1870
- const entries = crossRepoInputsByConsumer.get(key) ?? [];
1871
- entries.push({
1872
- destinationPath: input.destinationPath,
1873
- sourceRepoDir: join(repoRoot, input.sourceRepo),
1874
- sourcePath: input.sourcePath,
1875
- sourceCommit,
1876
- });
1877
- crossRepoInputsByConsumer.set(key, entries);
1878
- }
1879
- return crossRepoInputsByConsumer;
1880
- }
1881
-
1882
- /**
1883
- * The release's inputs: the COMMITTED lock, and the edition's own indexes
1884
- * (`editionPinnedInputs`: the committed ones, or rebuilt at the repositories the
1885
- * edition holds).
1886
- */
1887
- async function releaseImageInputs(schemas, id) {
1888
- const pinned = await editionPinnedInputs(id, { schemas });
1889
- return {
1890
- lock: validateImmutableDistributionLock(
1891
- readBoundDistributionFile({
1892
- distributionsRoot: editionsRoot(),
1893
- distributionId: id,
1894
- fileName: 'distribution.lock.json',
1895
- // A pure READ of the committed lock: the tolerant reader, never the writer.
1896
- parse: (raw) => parseResolvedDistributionLock(schemas, raw),
1897
- }).value,
1898
- ),
1899
- runtimeSourceIndex: pinned.runtimeImageSourceIndex,
1900
- biomeIndex: pinned.biomeIndex,
1901
- requiredPlatforms: resolveDistributionRequiredPlatforms(schemas, id),
1902
- declaredSources: resolveDistributionDeclaredSources(schemas, id),
1903
- };
1904
- }
1905
-
1906
- /**
1907
- * A develop snapshot's inputs: the SNAPSHOT distribution lock and the
1908
- * snapshot indexes and chain (`develop-snapshot-inputs.mjs`), all already
1909
- * resolved at the snapshot's pins. The runtime index carries the snapshot pins
1910
- * already and is not re-pinned here.
1911
- */
1912
- function snapshotImageInputs(schemas, { inputs, lock }) {
1913
- return {
1914
- lock: validateImmutableDistributionLock(lock),
1915
- runtimeSourceIndex: inputs.runtimeImageSourceIndex,
1916
- biomeIndex: inputs.biomeIndex,
1917
- requiredPlatforms: schemas.resolveRuntimeImagePlatforms(inputs.composed.requiredPlatforms),
1918
- declaredSources: inputs.composed.declaredSources,
1919
- };
1920
- }
1921
-
1922
- /**
1923
- * The supplemental source declarations an image resolve reads beside
1924
- * `runtimeSourceIndex`. An index with no supplemental subjects (a customer
1925
- * edition's own rows) needs none, and its workspace need not declare the
1926
- * section; any other reads the edition workspace's.
1927
- */
1928
- export function supplementalSourcesFor(runtimeSourceIndex) {
1929
- return runtimeSourceIndex.supplementalSubjects.length === 0
1930
- ? []
1931
- : workspaceSection('runtimeImageSources', 'resolving supplemental image subjects').supplementalSources;
1932
- }
1933
-
1934
- /**
1935
- * The image ROWS of one edition: its subjects (hashed at the commits the lock
1936
- * pins), its platform-service bindings and its bundled web biomes, after every
1937
- * check that needs no parsed lock. Rows are not a lock: the kernel's image-lock
1938
- * grammar is the grammar of a COMPLETE lock, and a caller that holds only part
1939
- * of one (a customer's own rows, merged with the catalogue's) parses the lock
1940
- * it completes. `resolveImages` is these rows parsed as the whole lock.
1941
- */
1942
- export async function resolveImageRows(id, { inputs, lock, onSubjectHashed, recordedImages, previousLock = null } = {}) {
1943
- assertCanonicalDistributionId(id);
1944
- if ((inputs === undefined) !== (lock === undefined)) {
1945
- throw new Error('resolveImages takes the snapshot `inputs` and its distribution `lock` together, or neither');
1946
- }
1947
- // A build record (images-from-record.mjs) replaces the hashing on the snapshot lane only, and never beside a probe.
1948
- if (recordedImages !== undefined && (inputs === undefined || onSubjectHashed !== undefined)) {
1949
- throw new Error('resolveImages takes recorded images only on the snapshot lane, and never with onSubjectHashed');
1950
- }
1951
- const schemas = await loadKernelDistributionSchemas();
1952
- const {
1953
- lock: distributionLock,
1954
- runtimeSourceIndex,
1955
- biomeIndex,
1956
- requiredPlatforms,
1957
- declaredSources,
1958
- } = inputs === undefined ? await releaseImageInputs(schemas, id) : snapshotImageInputs(schemas, { inputs, lock });
1959
- // Materialise every EXTERNAL source at the commit the lock pins, and resolve
1960
- // every `repo` handle through one locator from here down.
1961
- const locate = sourceTreeLocator(externalSourceTrees(distributionLock));
1962
- const structure = resolveComposedProcessStructure(
1963
- distributionLock,
1964
- resolveImageStructure(
1965
- distributionLock,
1966
- runtimeSourceIndex,
1967
- schemas,
1968
- requiredPlatforms,
1969
- declaredSources,
1970
- {
1971
- biomeIndex,
1972
- supplementalSources: supplementalSourcesFor(runtimeSourceIndex),
1973
- thirdPartyPins: loadThirdPartySourcePins(dataRoot()),
1974
- },
1975
- ),
1976
- schemas,
1977
- requiredPlatforms,
1978
- { locate },
1979
- );
1980
- const commitsBefore = sourceCommitMap(structure, locate);
1981
- assertSourceManifestParity(structure, commitsBefore, locate);
1982
- assertExternalSourcesDeclareNoVendoring(distributionLock, locate, commitsBefore);
1983
-
1984
- const buildStepDirectoriesByRepo = new Map();
1985
- for (const directory of runtimeSourceIndex.buildStepContextDirectories) {
1986
- const entries = buildStepDirectoriesByRepo.get(directory.repo) ?? [];
1987
- entries.push(directory.path);
1988
- buildStepDirectoriesByRepo.set(directory.repo, entries);
1989
- }
1990
- // Vendored content is pinned from the CONSUMER REPOSITORY'S OWN committed
1991
- // record of what it vendored (`resolveCrossRepoVendoredInputs`) — never from
1992
- // the index's `repositoryPins` mirror of the vendored axis, and never from this
1993
- // edition's resolved commit map. The record is read at the CONSUMER's
1994
- // repository pin, which is what makes the result roster-independent (see
1995
- // `resolveCrossRepoInputsForRoster`).
1996
- //
1997
- // An image's identity is edition-INDEPENDENT, and that still holds: the record
1998
- // is a property of the consumer's COMMIT, not of any edition, so every edition
1999
- // shipping a subject derives the same `contentHash` for it.
2000
- // The first-party `biome-host-api` image is built once, by the producing
2001
- // repository's CI, which has no notion of an edition and vendors every
2002
- // declared cross-repo input. Reading the vendored commit from `commitsBefore`
2003
- // made that edition-independent quantity depend on the edition, which failed
2004
- // in both directions at once:
2005
- // • it is UNCOMPUTABLE for an edition that does not itself resolve the
2006
- // source repo — `appliance-lean` does not ship the xema-store
2007
- // biome, so re-resolving it threw here and their locks could not
2008
- // be regenerated at all, even though the image they name is the very one
2009
- // the other six editions resolve fine;
2010
- // • had it not thrown it would have been WORSE, silently minting a second
2011
- // content hash for one image — a coordinate no CI ever pushes, which is
2012
- // the exact "the tag the lock names does not exist" failure the delivery
2013
- // chain exists to prevent (.claude/rules/image-release-coordinate.md).
2014
- //
2015
- // Reading `index.repositoryPins` instead was the SAME failure by a different
2016
- // route, and it shipped. The mirror is generated from the index and committed
2017
- // separately, so lagging it by a pin is its normal resting state — and the
2018
- // vendored commit is framed into the image-input hash's component KEY, so a
2019
- // one-commit lag mints a `contentHash` for byte-identical bytes that no CI run
2020
- // has ever pushed. Measured 2026-09-07 on `origin/main`: five of sixty-eight
2021
- // coordinates 404'd for exactly this, while the tags CI DID push were served
2022
- // on both platforms. The gate that was supposed to catch it,
2023
- // `tooling/release/check-image-tag-parity.mjs` in the AGGREGATOR (a path that
2024
- // resolves in no producing repository, though two of them cite it as if it
2025
- // did), read `index.repositoryPins` too — so both of its halves consumed the
2026
- // one input that had diverged, and it reported 474/474 verified while the
2027
- // delivery plane was undeliverable. It now reads the consumer's record, as
2028
- // this does.
2029
- const crossRepoInputsByConsumer = resolveCrossRepoInputsForRoster({
2030
- runtimeSourceIndex,
2031
- subjects: structure.subjects,
2032
- composedProcessKind: schemas.RuntimeImageSubjectKind.ComposedProcess,
2033
- });
2034
- // A composed image carries EVERY service of its build, so it vendors every
2035
- // cross-repository input its repository declares — repository-wide and
2036
- // per-service alike. The set is a property of the repository at its commit,
2037
- // never of the edition, so every edition derives the same tag for it.
2038
- const crossRepoInputsFor = (subject) =>
2039
- subject.kind === schemas.RuntimeImageSubjectKind.ComposedProcess
2040
- ? [...crossRepoInputsByConsumer]
2041
- .filter(([key]) => key.startsWith(`${subject.repo}\u0000`))
2042
- .sort(([a], [b]) => compareStrings(a, b))
2043
- .flatMap(([, entries]) => entries)
2044
- : [
2045
- ...(crossRepoInputsByConsumer.get(`${subject.repo}\u0000`) ?? []),
2046
- ...(crossRepoInputsByConsumer.get(`${subject.repo}\u0000${subject.id}`) ?? []),
2047
- ];
2048
- const subjects = [];
2049
- for (const subject of structure.subjects) {
2050
- const sourceCommit = commitsBefore.get(subject.repo);
2051
- const repoDirectory = locate(subject.repo).dir;
2052
- const servicePath = subjectServicePath(subject, sourceCommit, locate);
2053
- const { sourceManifestPath: ignoredSourceManifestPath, ...lockedSubject } =
2054
- subject;
2055
- void ignoredSourceManifestPath;
2056
- // With a build record the content hash and source commit are the record's, carried and never recomputed.
2057
- const isComposed = subject.kind === schemas.RuntimeImageSubjectKind.ComposedProcess;
2058
- const hashed = recordedImages === undefined ? {
2059
- ...lockedSubject,
2060
- sourceCommit,
2061
- // The hashing lane computes each member's key itself: the member service's own image-input hash.
2062
- ...(isComposed ? {
2063
- members: composedMemberKeys({
2064
- subject,
2065
- repoDirectory,
2066
- sourceCommit,
2067
- crossRepoInputsByConsumer,
2068
- buildStepDirectories: buildStepDirectoriesByRepo.get(subject.repo) ?? [],
2069
- }),
2070
- } : {}),
2071
- contentHash: exactImageBuildInputHash(
2072
- {
2073
- repoDir: repoDirectory,
2074
- service: subject.id,
2075
- servicePath,
2076
- commit: sourceCommit,
2077
- crossRepoInputs: crossRepoInputsFor(subject),
2078
- buildStepDirectories:
2079
- buildStepDirectoriesByRepo.get(subject.repo) ?? [],
2080
- },
2081
- imageInputSourceHashAlgorithm(),
2082
- ),
2083
- } : { ...lockedSubject, ...recordedImages.take({ ...lockedSubject, sourceCommit }) };
2084
- subjects.push(hashed);
2085
- // The hash is the last thing a registry probe waits for: hand the subject
2086
- // over now, and let the event loop run so the probes of the subjects
2087
- // already hashed progress while the rest hash (hashing is synchronous).
2088
- if (onSubjectHashed !== undefined) {
2089
- onSubjectHashed(hashed);
2090
- await yieldToEventLoop();
2091
- }
2092
- }
2093
- // Every subject missing from, contradicted by or extra in the build record, refused at once.
2094
- recordedImages?.settle();
2095
- // The composed artifacts are sealed with their generation: the roster's composed services must all be built, then the
2096
- // kernel carries the previous lock's still-pinned artifacts forward (`previousLock` null or a v4 lock: generation 1).
2097
- const ComposedProcess = schemas.RuntimeImageSubjectKind.ComposedProcess;
2098
- const composedBuilt = subjects.filter((subject) => subject.kind === ComposedProcess);
2099
- assertBuiltCoversEditionRoster({
2100
- rosterServices: editionRosterServices(distributionLock),
2101
- serviceSubjects: [...subjects, ...structure.thirdPartySubjects].filter((subject) => subject.kind === schemas.RuntimeImageSubjectKind.Service),
2102
- composedBuilt,
2103
- });
2104
- if (composedBuilt.length > 0 || previousLock !== null) {
2105
- const sealed = schemas.carryForwardComposedArtifacts({ previous: previousLock, built: composedBuilt });
2106
- subjects.splice(0, subjects.length, ...subjects.filter((subject) => subject.kind !== ComposedProcess), ...sealed);
2107
- subjects.sort(compareSubject);
2108
- }
2109
- // Release lane only: a develop snapshot or customer overlay is not covered by
2110
- // the committed ledger.
2111
- if (inputs === undefined) {
2112
- assertBuildLedgerComplete({
2113
- ledger: readBuildLedger(),
2114
- distributionId: id,
2115
- subjects,
2116
- requiredPlatforms,
2117
- expectedAt: join(dataRoot(), BUILD_LEDGER_FILE),
2118
- });
2119
- }
2120
- const webBiomes = structure.webBiomes.map((biome) => ({
2121
- ...biome,
2122
- sourceCommit: commitsBefore.get(biome.repo),
2123
- }));
2124
- // The SAME locator the hash used. The default locator resolves every repo
2125
- // handle to a first-party tree under the aggregator root, so for an edition
2126
- // with an external source it looked for `<sourcesRoot()>/<source name>`, found no
2127
- // repository there, and refused with "absent from the local object database"
2128
- // — after every hash had already been computed correctly through `locate`.
2129
- // Measured 2026-09-16 resolving a customer edition against a customer root.
2130
- const commitsAfter = sourceCommitMap(structure, locate);
2131
- if (serializeJson([...commitsBefore]) !== serializeJson([...commitsAfter])) {
2132
- throw new Error(
2133
- 'refusing image-lock write: a source repository commit changed while hashing',
2134
- );
2135
- }
2136
- // After the ledger check and the hashing: a third-party subject has no ledger row and no checkout, and its
2137
- // `contentHash` is the publisher's statement, so none of the above read it.
2138
- return {
2139
- subjects: structure.thirdPartySubjects.length === 0 ? subjects : [...subjects, ...structure.thirdPartySubjects].sort(compareSubject),
2140
- thirdPartySubjects: structure.thirdPartySubjects,
2141
- platformServiceBindings: structure.platformServiceBindings,
2142
- webBiomes,
2143
- contractAuthority: runtimeSourceIndex.contractAuthority,
2144
- commits: [...commitsBefore],
2145
- };
2146
- }
2147
-
2148
- export const PREVIOUS_SEAL_USAGE =
2149
- 'exactly one of `--previous-seal-signer <certificate identity>` (the CLI reads the images.lock layer of the latest edition ' +
2150
- 'signed by that identity in this edition\'s registry repository; run under the per-edition concurrency group), ' +
2151
- '`--previous-lock <images.lock.json> --previous-lock-sha256 <hex>` (the same layer, taken by the caller) or `--first-seal` ' +
2152
- '(only for a distribution that has never had an edition)';
2153
-
2154
- /**
2155
- * The previous seal an images-lock write carries composed artifacts forward from, as an EXPLICIT input of exactly one kind:
2156
- * - `registry` (`--previous-seal-signer`): `{ find }`, where `find()` returns the latest signed edition's images.lock bytes
2157
- * and their sha256 (previous-seal-registry.mjs);
2158
- * - the previous lock with the sha256 of its bytes (taken by the caller from the signed, published edition's images.lock
2159
- * layer);
2160
- * - `--first-seal` (null: generation 1).
2161
- * None, more than one kind, or half of the pair is refused; the bytes must hash to the given digest, and the lock is parsed
2162
- * with the kernel's `parseImageLock` and must be this edition's own (`distributionId`).
2163
- */
2164
- export async function resolvePreviousSeal({ distributionId, previousLockPath, previousLockSha256, firstSeal = false, registry = undefined }) {
2165
- const given = [previousLockPath, previousLockSha256].filter((value) => value !== undefined);
2166
- const kinds = [registry !== undefined, given.length > 0, firstSeal].filter(Boolean).length;
2167
- if (kinds > 1) {
2168
- throw new Error(`more than one previous seal is given (--previous-seal-signer, --previous-lock/--previous-lock-sha256, --first-seal); give ${PREVIOUS_SEAL_USAGE}`);
2169
- }
2170
- if (firstSeal) {return null;}
2171
- if (registry !== undefined) {
2172
- const { bytes, imagesLockSha256, edition } = await registry.find();
2173
- const actual = createHash('sha256').update(bytes).digest('hex');
2174
- if (actual !== imagesLockSha256) {
2175
- throw new Error(`the images lock of ${edition} has sha256 ${actual}, not its layer digest ${imagesLockSha256}`);
2176
- }
2177
- return parsePreviousLock(distributionId, parseStrictJson(bytes.toString('utf8'), `${edition} images.lock.json`), `the images lock of ${edition}`);
2178
- }
2179
- if (given.length !== 2) {
2180
- throw new Error(
2181
- given.length === 0
2182
- ? `no previous seal is given; give ${PREVIOUS_SEAL_USAGE}`
2183
- : `--previous-lock and --previous-lock-sha256 go together (one was given); give ${PREVIOUS_SEAL_USAGE}`,
2184
- );
2185
- }
2186
- const bytes = readPreviousLockBytes(previousLockPath, previousLockSha256);
2187
- return parsePreviousLock(distributionId, parseStrictJson(bytes.toString('utf8'), previousLockPath), '--previous-lock');
2188
- }
2189
-
2190
- /**
2191
- * The bytes of a previous lock file, held to the sha256 (hex) they were given with: the one check of the pair
2192
- * (`flags` names the pair in the refusal). The parse, with the kernel and against the edition's id, is the caller's.
2193
- */
2194
- export function readPreviousLockBytes(path, sha256, flags = { lock: '--previous-lock', sha256: '--previous-lock-sha256' }) {
2195
- const bytes = readFileSync(path);
2196
- const actual = createHash('sha256').update(bytes).digest('hex');
2197
- if (actual !== sha256) {
2198
- throw new Error(`${flags.lock} ${path} has sha256 ${actual}, not the given ${flags.sha256} ${sha256}`);
2199
- }
2200
- return bytes;
2201
- }
2202
-
2203
- /** The in-repo lane's previous lock: the committed file at `outPath`, parsed before it is overwritten; null (first seal) when there is none. */
2204
- export async function readCommittedPreviousLock(distributionId, outPath) {
2205
- return existsSync(outPath) ? parsePreviousLock(distributionId, readStrictJsonFile(outPath), outPath) : null;
2206
- }
2207
-
2208
- async function parsePreviousLock(distributionId, document, label) {
2209
- const previous = (await loadKernelDistributionSchemas()).parseImageLock(document);
2210
- if (previous.distributionId !== distributionId) {
2211
- throw new Error(`${label} is the images lock of "${previous.distributionId}", not of "${distributionId}"`);
2212
- }
2213
- return previous;
2214
- }
2215
-
2216
- /**
2217
- * The composed artifacts of a STRUCTURAL view (no source hashing), carried forward from `previousLock` through the kernel's own
2218
- * `carryForwardComposedArtifacts`, exactly as a reseal does. A structural subject has no content hash or member keys; each
2219
- * composed id takes them from its newest previous artifact (a structure the previous lock does not cover keeps none, and the
2220
- * kernel refuses it: the caller reports that as out of date). Source drift is `--check-sources`' job, through `resolveImages`.
2221
- */
2222
- export function carryComposedForwardStructurally({ subjects, previousLock, schemas }) {
2223
- const ComposedProcess = schemas.RuntimeImageSubjectKind.ComposedProcess;
2224
- const newest = new Map();
2225
- for (const subject of previousLock?.subjects ?? []) {
2226
- if (subject.kind === ComposedProcess && (newest.get(subject.id)?.generation ?? 0) <= (subject.generation ?? 0)) {newest.set(subject.id, subject);}
2227
- }
2228
- const built = subjects.filter((subject) => subject.kind === ComposedProcess).map((subject) => {
2229
- const previous = newest.get(subject.id);
2230
- if (previous === undefined) {return subject;}
2231
- const keyOf = new Map((previous.members ?? []).map((member) => [member.service, member.key]));
2232
- return {
2233
- ...subject,
2234
- contentHash: previous.contentHash,
2235
- members: (subject.members ?? []).map((member) => (keyOf.has(member.service) ? { ...member, key: keyOf.get(member.service) } : member)),
2236
- };
2237
- });
2238
- if (built.length === 0 && previousLock === null) {return subjects;}
2239
- const sealed = schemas.carryForwardComposedArtifacts({ previous: previousLock, built });
2240
- return [...subjects.filter((subject) => subject.kind !== ComposedProcess), ...sealed].sort(compareSubject);
2241
- }
2242
-
2243
- /** One edition's COMPLETE image lock: its rows, parsed and validated as the kernel's image lock. */
2244
- export async function resolveImages(id, { inputs, lock, onSubjectHashed, recordedImages, previousLock = null } = {}) {
2245
- const { subjects, thirdPartySubjects, platformServiceBindings, webBiomes, contractAuthority, commits } = await resolveImageRows(id, { inputs, lock, onSubjectHashed, recordedImages, previousLock });
2246
- const schemas = await loadKernelDistributionSchemas();
2247
- const imageLock = validateImmutableImageLock(
2248
- schemas.parseImageLock({
2249
- schemaVersion: imageLockWriteSchemaVersion(schemas, subjects, `distribution "${id}"`),
2250
- sourceHashAlgorithm: schemas.ImageSourceHashAlgorithm.ImageInputV2,
2251
- distributionId: id,
2252
- contractAuthority,
2253
- subjects,
2254
- platformServiceBindings,
2255
- webBiomes,
2256
- }),
2257
- schemas,
2258
- );
2259
- return { imageLock, commits, thirdPartySubjects };
2260
- }
2261
-
2262
- /**
2263
- * Resolve and persist one schema-v3 image lock. Keeping the explicit trusted
2264
- * write acknowledgement on this exported boundary means the in-process plane
2265
- * refresh has the same safety contract as the standalone CLI.
2266
- */
2267
- export async function writeResolvedImages(
2268
- id,
2269
- { trustedWriteV3 = false, log = true, previousLockPath = undefined, previousLockSha256 = undefined, firstSeal = false, previousFromTarget = false } = {},
2270
- ) {
2271
- assertCanonicalDistributionId(id);
2272
- if (!trustedWriteV3) {
2273
- throw new Error(
2274
- 'refusing to write a schema-v3 runtime image roster without --trusted-write-v3',
2275
- );
2276
- }
2277
- // The RELEASE lock is published-only: a develop edition resolves through
2278
- // `develop-edition.mjs` and never reaches this writer.
2279
- const outPath = distributionOutputPath(
2280
- editionsRoot(),
2281
- id,
2282
- 'images.lock.json',
2283
- );
2284
- // The in-repo lane: the previous lock is the committed file at the target path (git is its authority), read before it is
2285
- // overwritten; with no file there, this is the first seal.
2286
- const previousLock = previousFromTarget
2287
- ? await readCommittedPreviousLock(id, outPath)
2288
- : await resolvePreviousSeal({ distributionId: id, previousLockPath, previousLockSha256, firstSeal });
2289
- if (previousFromTarget && previousLock === null && log) {console.log(`[dist:images] ${id}: first seal: no previous images.lock.json`);}
2290
- const { imageLock, commits, thirdPartySubjects } = await resolveImages(id, { previousLock });
2291
- assertReleaseContent(imageLock, `the images lock for "${id}"`);
2292
- writeFileSync(outPath, serializeJson(imageLock));
2293
- if (log) {
2294
- console.log(
2295
- `[dist:images] wrote ${outPath} — ${imageLock.subjects.length} actual runtime image subjects, ${imageLock.webBiomes.length} bundled web biomes, ${imageLock.platformServiceBindings.length} platform bindings.`,
2296
- );
2297
- for (const [repo, commit] of commits) {
2298
- console.log(` source ${repo}@${commit}`);
2299
- }
2300
- for (const subject of thirdPartySubjects) {
2301
- console.log(` third-party ${subject.kind}:${subject.id} ${subject.imageRef} ${attestedContentHash(subject)}`);
2302
- }
2303
- }
2304
- return { imageLock, commits };
2305
- }
2306
-
2307
- async function main() {
2308
- const id = process.argv[2];
2309
- const trustedWriteV3 = process.argv.includes('--trusted-write-v3');
2310
- if (!id) {
2311
- throw new Error(
2312
- `usage: resolve-images.mjs <distribution-id> --trusted-write-v3 (${PREVIOUS_SEAL_USAGE})`,
2313
- );
2314
- }
2315
- // `--editions-root` (and its `=` spelling, plus the value that follows
2316
- // the bare form) is consumed by `editionsRoot()` in lib.mjs, off
2317
- // `process.argv`, before this parser ever runs. It has to be accepted here or
2318
- // the flag that decides WHICH editions root is read is rejected by the one
2319
- // command that reads it.
2320
- const rest = process.argv.slice(3);
2321
- const unknownArgs = [];
2322
- let previousLockPath;
2323
- let previousLockSha256;
2324
- let firstSeal = false;
2325
- for (let i = 0; i < rest.length; i += 1) {
2326
- const argument = rest[i];
2327
- if (argument === '--trusted-write-v3') {continue;}
2328
- if (argument === '--first-seal') {
2329
- firstSeal = true;
2330
- continue;
2331
- }
2332
- if (argument === '--previous-lock' || argument === '--previous-lock-sha256') {
2333
- i += 1;
2334
- if (rest[i] === undefined) {throw new Error(`${argument} needs a value`);}
2335
- if (argument === '--previous-lock') {previousLockPath = rest[i];} else {previousLockSha256 = rest[i];}
2336
- continue;
2337
- }
2338
- unknownArgs.push(argument);
2339
- }
2340
- if (unknownArgs.length > 0) {
2341
- throw new Error(`unknown argument "${unknownArgs[0]}"`);
2342
- }
2343
- await writeResolvedImages(id, { trustedWriteV3, previousLockPath, previousLockSha256, firstSeal });
2344
- }
2345
-
2346
- if (isEntryModule(import.meta.url)) {
2347
- main().catch((error) => {
2348
- console.error(`[dist:images] ${error.message}`);
2349
- process.exit(1);
2350
- });
2351
- }