@xemahq/xema 0.25.1 → 0.27.0

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 (239) hide show
  1. package/README.md +70 -37
  2. package/dist/assets/appliance-base/images.lock.json +126 -126
  3. package/dist/assets/appliance-lean/images.lock.json +123 -123
  4. package/dist/assets/cloud/images.lock.json +126 -126
  5. package/dist/assets/core/images.lock.json +120 -120
  6. package/dist/assets/distribution-assets.manifest.json +15 -16
  7. package/dist/assets/minimal/images.lock.json +108 -108
  8. package/dist/assets/oss/images.lock.json +126 -126
  9. package/dist/assets/starter/images.lock.json +123 -123
  10. package/dist/cli.js +2 -2
  11. package/dist/cli.js.map +1 -1
  12. package/dist/commands/dev.js +1 -0
  13. package/dist/commands/dev.js.map +1 -1
  14. package/dist/commands/down.d.ts.map +1 -1
  15. package/dist/commands/down.js +7 -1
  16. package/dist/commands/down.js.map +1 -1
  17. package/dist/commands/edition.d.ts +3 -0
  18. package/dist/commands/edition.d.ts.map +1 -0
  19. package/dist/commands/edition.js +46 -0
  20. package/dist/commands/edition.js.map +1 -0
  21. package/dist/commands/init.js +2 -2
  22. package/dist/commands/init.js.map +1 -1
  23. package/dist/commands/serve.js +1 -0
  24. package/dist/commands/serve.js.map +1 -1
  25. package/dist/commands/up.js +1 -0
  26. package/dist/commands/up.js.map +1 -1
  27. package/dist/dev/env-defaults.d.ts.map +1 -1
  28. package/dist/dev/env-defaults.js +6 -1
  29. package/dist/dev/env-defaults.js.map +1 -1
  30. package/dist/dev/infra-preflight.d.ts +3 -2
  31. package/dist/dev/infra-preflight.d.ts.map +1 -1
  32. package/dist/dev/infra-preflight.js +18 -8
  33. package/dist/dev/infra-preflight.js.map +1 -1
  34. package/dist/edition/verify-image-evidence.bundle.mjs +54179 -0
  35. package/dist/edition/verify-image-evidence.bundle.provenance.json +2871 -0
  36. package/dist/runtime/backing-registration.d.ts.map +1 -1
  37. package/dist/runtime/backing-registration.js +7 -2
  38. package/dist/runtime/backing-registration.js.map +1 -1
  39. package/dist/runtime/config/schema.d.ts +1 -0
  40. package/dist/runtime/config/schema.d.ts.map +1 -1
  41. package/dist/runtime/custody/local-custody.d.ts +12 -0
  42. package/dist/runtime/custody/local-custody.d.ts.map +1 -0
  43. package/dist/runtime/custody/local-custody.js +22 -0
  44. package/dist/runtime/custody/local-custody.js.map +1 -0
  45. package/dist/runtime/custody/openbao-custody-bootstrap.d.ts +18 -0
  46. package/dist/runtime/custody/openbao-custody-bootstrap.d.ts.map +1 -0
  47. package/dist/runtime/custody/openbao-custody-bootstrap.js +50 -0
  48. package/dist/runtime/custody/openbao-custody-bootstrap.js.map +1 -0
  49. package/dist/runtime/custody/openbao-custody-store.d.ts +18 -0
  50. package/dist/runtime/custody/openbao-custody-store.d.ts.map +1 -0
  51. package/dist/runtime/custody/openbao-custody-store.js +55 -0
  52. package/dist/runtime/custody/openbao-custody-store.js.map +1 -0
  53. package/dist/runtime/custody/openbao-http-client.d.ts +34 -0
  54. package/dist/runtime/custody/openbao-http-client.d.ts.map +1 -0
  55. package/dist/runtime/custody/openbao-http-client.js +154 -0
  56. package/dist/runtime/custody/openbao-http-client.js.map +1 -0
  57. package/dist/runtime/custody/openbao-local-constants.d.ts +22 -0
  58. package/dist/runtime/custody/openbao-local-constants.d.ts.map +1 -0
  59. package/dist/runtime/custody/openbao-local-constants.js +48 -0
  60. package/dist/runtime/custody/openbao-local-constants.js.map +1 -0
  61. package/dist/runtime/custody/openbao-local-runner.d.ts +16 -0
  62. package/dist/runtime/custody/openbao-local-runner.d.ts.map +1 -0
  63. package/dist/runtime/custody/openbao-local-runner.js +86 -0
  64. package/dist/runtime/custody/openbao-local-runner.js.map +1 -0
  65. package/dist/runtime/edition/run-entry.d.ts +9 -0
  66. package/dist/runtime/edition/run-entry.d.ts.map +1 -0
  67. package/dist/runtime/edition/run-entry.js +36 -0
  68. package/dist/runtime/edition/run-entry.js.map +1 -0
  69. package/dist/runtime/edition/verb-map.d.ts +60 -0
  70. package/dist/runtime/edition/verb-map.d.ts.map +1 -0
  71. package/dist/runtime/edition/verb-map.js +146 -0
  72. package/dist/runtime/edition/verb-map.js.map +1 -0
  73. package/dist/runtime/env-seeder.d.ts +3 -0
  74. package/dist/runtime/env-seeder.d.ts.map +1 -1
  75. package/dist/runtime/env-seeder.js +20 -3
  76. package/dist/runtime/env-seeder.js.map +1 -1
  77. package/dist/runtime/launch.d.ts +3 -1
  78. package/dist/runtime/launch.d.ts.map +1 -1
  79. package/dist/runtime/launch.js +37 -8
  80. package/dist/runtime/launch.js.map +1 -1
  81. package/dist/runtime/paths.d.ts +1 -2
  82. package/dist/runtime/paths.d.ts.map +1 -1
  83. package/dist/runtime/paths.js +0 -10
  84. package/dist/runtime/paths.js.map +1 -1
  85. package/dist/runtime/runner/chart-local-provenance.d.ts +20 -0
  86. package/dist/runtime/runner/chart-local-provenance.d.ts.map +1 -0
  87. package/dist/runtime/runner/chart-local-provenance.js +114 -0
  88. package/dist/runtime/runner/chart-local-provenance.js.map +1 -0
  89. package/dist/runtime/runner/chart-preflight.d.ts +8 -8
  90. package/dist/runtime/runner/chart-preflight.d.ts.map +1 -1
  91. package/dist/runtime/runner/chart-preflight.js +30 -69
  92. package/dist/runtime/runner/chart-preflight.js.map +1 -1
  93. package/dist/runtime/runner/chart-pull.d.ts +14 -0
  94. package/dist/runtime/runner/chart-pull.d.ts.map +1 -0
  95. package/dist/runtime/runner/chart-pull.js +96 -0
  96. package/dist/runtime/runner/chart-pull.js.map +1 -0
  97. package/dist/runtime/runner/chart-resolver.d.ts +4 -12
  98. package/dist/runtime/runner/chart-resolver.d.ts.map +1 -1
  99. package/dist/runtime/runner/chart-resolver.js +16 -62
  100. package/dist/runtime/runner/chart-resolver.js.map +1 -1
  101. package/dist/runtime/runner/chart-set-lock.d.ts +6 -20
  102. package/dist/runtime/runner/chart-set-lock.d.ts.map +1 -1
  103. package/dist/runtime/runner/chart-set-lock.js +55 -99
  104. package/dist/runtime/runner/chart-set-lock.js.map +1 -1
  105. package/dist/runtime/runner/helm-cli.d.ts +3 -1
  106. package/dist/runtime/runner/helm-cli.d.ts.map +1 -1
  107. package/dist/runtime/runner/helm-cli.js +9 -7
  108. package/dist/runtime/runner/helm-cli.js.map +1 -1
  109. package/dist/runtime/runner/helm-runner.d.ts +4 -4
  110. package/dist/runtime/runner/helm-runner.d.ts.map +1 -1
  111. package/dist/runtime/runner/helm-runner.js +18 -16
  112. package/dist/runtime/runner/helm-runner.js.map +1 -1
  113. package/dist/runtime/sources/kinds/git.d.ts +1 -2
  114. package/dist/runtime/sources/kinds/git.d.ts.map +1 -1
  115. package/dist/runtime/sources/kinds/git.js +8 -5
  116. package/dist/runtime/sources/kinds/git.js.map +1 -1
  117. package/dist/runtime/sources/manifest-walk.d.ts +3 -1
  118. package/dist/runtime/sources/manifest-walk.d.ts.map +1 -1
  119. package/dist/runtime/sources/manifest-walk.js +8 -3
  120. package/dist/runtime/sources/manifest-walk.js.map +1 -1
  121. package/dist/runtime/sources/schema.d.ts +3 -0
  122. package/dist/runtime/sources/schema.d.ts.map +1 -1
  123. package/dist/runtime/sources/schema.js +6 -1
  124. package/dist/runtime/sources/schema.js.map +1 -1
  125. package/dist/runtime/supervisor.d.ts +3 -0
  126. package/dist/runtime/supervisor.d.ts.map +1 -1
  127. package/dist/runtime/supervisor.js +2 -0
  128. package/dist/runtime/supervisor.js.map +1 -1
  129. package/edition/README.md +315 -0
  130. package/edition/assemble-producer-input.mjs +1251 -0
  131. package/edition/authorization-base.mjs +347 -0
  132. package/edition/biome-content.mjs +268 -0
  133. package/edition/build-biome-index.mjs +175 -0
  134. package/edition/build-platform-service-index.mjs +288 -0
  135. package/edition/build-runtime-image-source-index.mjs +821 -0
  136. package/edition/built-verifier.mjs +55 -0
  137. package/edition/catalogue-edition.mjs +49 -0
  138. package/edition/catalogue-fetch.mjs +64 -0
  139. package/edition/catalogue.mjs +135 -0
  140. package/edition/chart-set-source-commit.mjs +92 -0
  141. package/edition/check-committed-image-evidence.mjs +383 -0
  142. package/edition/checkout-indexed-source-repositories.mjs +105 -0
  143. package/edition/comment-blanker.mjs +75 -0
  144. package/edition/connector-source-authority.mjs +60 -0
  145. package/edition/customer-definition.mjs +183 -0
  146. package/edition/customer-flags.mjs +7 -0
  147. package/edition/develop-customer-edition.mjs +99 -0
  148. package/edition/develop-edition-resolve.mjs +65 -0
  149. package/edition/develop-edition.mjs +274 -0
  150. package/edition/develop-freeze.mjs +173 -0
  151. package/edition/develop-image-coordinate.mjs +54 -0
  152. package/edition/develop-seal.mjs +77 -0
  153. package/edition/develop-snapshot-inputs.mjs +104 -0
  154. package/edition/dist-check.mjs +986 -0
  155. package/edition/distribution-evidence-set.mjs +987 -0
  156. package/edition/distribution-paths.mjs +191 -0
  157. package/edition/edition-artifact.mjs +270 -0
  158. package/edition/edition-biome-manifests.mjs +85 -0
  159. package/edition/edition-channel.mjs +6 -0
  160. package/edition/edition-chart-set.mjs +93 -0
  161. package/edition/edition-config.mjs +207 -0
  162. package/edition/edition-constants.mjs +57 -0
  163. package/edition/edition-contract.json +23 -0
  164. package/edition/edition-list.mjs +35 -0
  165. package/edition/edition-locks.mjs +69 -0
  166. package/edition/edition-protocol.mjs +228 -0
  167. package/edition/edition-workspace.mjs +176 -0
  168. package/edition/evidence-checkout.mjs +418 -0
  169. package/edition/evidence-grammars.mjs +16 -0
  170. package/edition/evidence-trust-pin.mjs +304 -0
  171. package/edition/evidence-trust-policy.mjs +256 -0
  172. package/edition/external-source-walk.mjs +463 -0
  173. package/edition/first-party-source-ids.mjs +59 -0
  174. package/edition/fleet-coordinate-finality.mjs +350 -0
  175. package/edition/git-source-auth.mjs +52 -0
  176. package/edition/image-evidence-replay-state.mjs +242 -0
  177. package/edition/image-evidence.mjs +1225 -0
  178. package/edition/kernel-contract-authority.mjs +443 -0
  179. package/edition/lib.mjs +3018 -0
  180. package/edition/no-follow-file.mjs +94 -0
  181. package/edition/oci-evidence-verbs.mjs +392 -0
  182. package/edition/oci-image-source-evidence.mjs +239 -0
  183. package/edition/oras.mjs +648 -0
  184. package/edition/pin-source.mjs +227 -0
  185. package/edition/plan-image-evidence-refresh.mjs +408 -0
  186. package/edition/platform-lock-readability.mjs +892 -0
  187. package/edition/platform-service-class.mjs +18 -0
  188. package/edition/platform-service-source-authority.mjs +105 -0
  189. package/edition/positive-closure.mjs +162 -0
  190. package/edition/produce-distribution-completeness-evidence.mjs +311 -0
  191. package/edition/produce-image-delivery-entitlement-evidence.mjs +294 -0
  192. package/edition/produce-image-source-evidence.mjs +765 -0
  193. package/edition/producer-evidence-bundle.mjs +157 -0
  194. package/edition/registry-credentials.mjs +193 -0
  195. package/edition/release-catalogue.mjs +39 -0
  196. package/edition/release-channel-guard.mjs +99 -0
  197. package/edition/release-edition-artifact.mjs +158 -0
  198. package/edition/remedy-command.mjs +89 -0
  199. package/edition/repin-evidence-trust.mjs +101 -0
  200. package/edition/required-resource-provider.mjs +58 -0
  201. package/edition/resolve-chart-set.mjs +415 -0
  202. package/edition/resolve-customer-edition.mjs +288 -0
  203. package/edition/resolve-distribution.mjs +2318 -0
  204. package/edition/resolve-edition.mjs +186 -0
  205. package/edition/resolve-images.mjs +1606 -0
  206. package/edition/seal-image-digests.mjs +855 -0
  207. package/edition/sealed-evidence-accept.mjs +82 -0
  208. package/edition/source-authority.mjs +725 -0
  209. package/edition/source-closure.mjs +116 -0
  210. package/edition/strict-json.mjs +121 -0
  211. package/edition/string-order.mjs +5 -0
  212. package/edition/trust-policy-arming.mjs +120 -0
  213. package/edition/trusted-executable.mjs +166 -0
  214. package/edition/verifier-binding.mjs +45 -0
  215. package/edition/verifier-files.mjs +8 -0
  216. package/edition/verifier-kernel-schemas.mjs +22 -0
  217. package/edition/verifier-provenance.mjs +64 -0
  218. package/edition/verify-image-evidence.mjs +430 -0
  219. package/edition/verify-image-platforms.mjs +495 -0
  220. package/edition/why.mjs +56 -0
  221. package/edition/workspace-sections.mjs +195 -0
  222. package/examples/06-biome-sources.yaml +1 -0
  223. package/package.json +18 -7
  224. package/dist/commands/dist.d.ts +0 -16
  225. package/dist/commands/dist.d.ts.map +0 -1
  226. package/dist/commands/dist.js +0 -182
  227. package/dist/commands/dist.js.map +0 -1
  228. package/dist/runtime/distribution/available-biome.d.ts +0 -10
  229. package/dist/runtime/distribution/available-biome.d.ts.map +0 -1
  230. package/dist/runtime/distribution/available-biome.js +0 -154
  231. package/dist/runtime/distribution/available-biome.js.map +0 -1
  232. package/dist/runtime/distribution/compose.d.ts +0 -23
  233. package/dist/runtime/distribution/compose.d.ts.map +0 -1
  234. package/dist/runtime/distribution/compose.js +0 -48
  235. package/dist/runtime/distribution/compose.js.map +0 -1
  236. package/dist/runtime/runner/chart-trust.d.ts +0 -57
  237. package/dist/runtime/runner/chart-trust.d.ts.map +0 -1
  238. package/dist/runtime/runner/chart-trust.js +0 -270
  239. package/dist/runtime/runner/chart-trust.js.map +0 -1
@@ -0,0 +1,3018 @@
1
+ // Shared helpers for the Xema distribution packaging tooling.
2
+ //
3
+ // These are pure Node ESM utilities (mjs tooling — Date.now()/clocks are fine
4
+ // here, unlike kernel contracts). They centralize the few things the three
5
+ // distribution scripts share: locating the repo root, resolving the BUILT
6
+ // kernel contract schemas, the closed BiomeTier order, and the on-disk biome
7
+ // walk. Keeping them here avoids duplicating the manifest-reading + tier-order
8
+ // logic across build/resolve/check. Trusted index generation reads immutable
9
+ // Git objects rather than mutable working-tree files.
10
+
11
+ import { execFileSync } from 'node:child_process';
12
+ import { createHash } from 'node:crypto';
13
+ import { createRequire } from 'node:module';
14
+ import { isIP } from 'node:net';
15
+ import { join } from 'node:path';
16
+
17
+ import { readBiomeAuthorizationModuleAt } from './authorization-base.mjs';
18
+ import {
19
+ isAuthoritativeProductionBiomeSource,
20
+ } from './connector-source-authority.mjs';
21
+ import { assertKernelEditionProtocol } from './edition-protocol.mjs';
22
+ import {
23
+ dataRoot,
24
+ kernelDistPath,
25
+ kernelPackageDir,
26
+ kernelRepoDir,
27
+ sourceHashPackageDir,
28
+ sourcePath,
29
+ } from './edition-workspace.mjs';
30
+ import { SHA256_HEX_PATTERN } from './evidence-grammars.mjs';
31
+ import {
32
+ createSourceAuthority,
33
+ DistributionSourceMode,
34
+ PUBLISHED_SOURCE_REF,
35
+ publishedSourceCommit,
36
+ } from './source-authority.mjs';
37
+ import { parseStrictJson, readStrictJsonFile } from './strict-json.mjs';
38
+ import { compareStrings } from './string-order.mjs';
39
+
40
+ export {
41
+ compareStrings,
42
+ createSourceAuthority,
43
+ DistributionSourceMode,
44
+ PUBLISHED_SOURCE_REF,
45
+ publishedSourceCommit,
46
+ };
47
+
48
+
49
+ /**
50
+ * Closed BiomeTier order (low→high), READ FROM THE KERNEL.
51
+ *
52
+ * The VOCABULARY and the ORDER are both `@xemahq/contracts/biome`'s:
53
+ * `BIOME_TIER_ORDER` in `src/biome/lib/biome-tier-rank.ts`, itself derived
54
+ * from `BIOME_TIER_RANK` — a map exhaustively keyed on the `BiomeTier` enum, so
55
+ * a fifth tier is a compile error in the kernel rather than a value that reads
56
+ * back `undefined` here and sorts silently to one end.
57
+ *
58
+ * The package NAME above is `@xemahq/contracts`, not the retired
59
+ * `@xemahq/kernel-contracts`; the repository directory is still
60
+ * `repos/xema-kernel-contracts`, which is why the dist path below keeps it.
61
+ * Re-measured at that repository's `origin/main` (1e2f7d7, 2026-09-15):
62
+ * `packages/kernel/kernel-contracts/package.json` declares `@xemahq/contracts`,
63
+ * `BIOME_TIER_ORDER` is exported from `src/biome/lib/biome-tier-rank.ts`, and
64
+ * `src/biome/index.ts` re-exports it.
65
+ *
66
+ * ── This replaced a literal, and the comment above it was FALSE ──
67
+ *
68
+ * That comment said "the kernel exports no order constant — there is no
69
+ * `BIOME_TIER_ORDER` in `@xemahq/kernel-contracts` (verified 2026-08-31)", and
70
+ * justified the private copy with "so the tooling does not depend on the kernel
71
+ * package being workspace-linked at the repo root (it is not)". Both halves
72
+ * failed, and each failed differently:
73
+ *
74
+ * • The FIRST half was true when written and is not now. The kernel ships
75
+ * `BIOME_TIER_RANK`, `BIOME_TIER_ORDER`, `biomeTierRank` and
76
+ * `biomeTierWithin`, re-exported by the `biome` barrel — and that file's own
77
+ * header names `['infra', ...BIOME_TIER_ORDER, 'edge']` in
78
+ * `resolve-distribution.mjs` as the hand-rolled copy it exists to delete.
79
+ * • The SECOND half was never a reason. It is true that a BARE
80
+ * `@xemahq/kernel-contracts` specifier does not resolve here — this repo's
81
+ * `package.json` declares one devDependency and it is `esbuild` — but this
82
+ * module has never used one. It reaches the kernel by the deterministic dist
83
+ * PATH below (`loadKernelBiomeContracts`), and `readBiomeManifest`, the very
84
+ * function that validated a tier against the deleted literal, already called
85
+ * that loader four lines later for `isBiomeStoreListed`. The kernel was a
86
+ * hard precondition of this exact code path the whole time.
87
+ *
88
+ * A justification that names a real constraint which does not apply is the
89
+ * worst shape of the three, because the constraint checks out: a reader
90
+ * verifying "the bare specifier does not resolve" confirms the sentence and
91
+ * stops. See `.claude/rules/writing-boundary-checks.md`, "A comment that
92
+ * JUSTIFIES a limitation is the last place anyone re-derives it".
93
+ *
94
+ * ── Why a function and not a const ──
95
+ *
96
+ * `loadKernelBiomeContracts` throws when the kernel dist is unbuilt. Reading it
97
+ * at module scope would make merely IMPORTING this module require a built
98
+ * kernel, for every consumer including those that never rank a tier. Lazy and
99
+ * memoized keeps the precondition where it already was.
100
+ *
101
+ * A remaining duplicate, deliberately not taken here: `PLATFORM_SERVICE_TIER_RANK`
102
+ * (kernel `src/distribution`) is `['infra', ...biome ladder, 'edge']` already
103
+ * derived, which is what `resolve-distribution.mjs` still builds by hand. That
104
+ * subpath is loaded through a DIFFERENT, async loader
105
+ * (`loadKernelDistributionSchemas`) and is a rank MAP rather than an array, so
106
+ * adopting it is its own change with its own call-site shape.
107
+ */
108
+ export function biomeTierOrder() {
109
+ return loadKernelBiomeContracts().BIOME_TIER_ORDER;
110
+ }
111
+
112
+ /** Is `tier` a valid BiomeTier? (closed set — callers fail fast on false). */
113
+ export function isBiomeTier(tier) {
114
+ return biomeTierOrder().includes(tier);
115
+ }
116
+
117
+ /**
118
+ * Filename of the committed per-biome generated index, beside every
119
+ * `xema-biome.json`. Value-identical to `BIOME_GENERATED_INDEX_FILENAME` in
120
+ * `@xemahq/contracts/biome`; asserted against the BUILT kernel export by
121
+ * `loadKernelBiomeContracts`, so the two cannot drift silently.
122
+ */
123
+ export const BIOME_GENERATED_INDEX_FILENAME = 'xema-biome.index.json';
124
+
125
+ /**
126
+ * Closed workload set for the v4 `xema.ships.apis[]` manifest shape this
127
+ * tooling reads.
128
+ *
129
+ * NO kernel enum spells it: there is no `BiomeWorkloadKind` and no
130
+ * `biome-workload-kind.ts` anywhere in `@xemahq/contracts` (verified
131
+ * against that repo's `origin/main`, 2026-08-31), so this literal is the only
132
+ * declaration of the set — it mirrors nothing. The v5 successor of the same
133
+ * distinction is `BiomeComponentScheduler` (`platform | runtime`) in
134
+ * `@xemahq/contracts/biome`
135
+ * (`src/biome/lib/biome-component-vocabulary.ts`), whose own docblock names
136
+ * `ships.apis[].workload: "runtime-component"` as the v4 spelling it carries
137
+ * forward. When this tooling moves to `components[]`, map onto that rather
138
+ * than re-deriving the distinction here. An entry of `xema.ships.apis[]` that
139
+ * omits `workload` resolves to `DEFAULT_BIOME_WORKLOAD_KIND`.
140
+ *
141
+ * - `service` — a standing cluster workload the deploy plane rosters.
142
+ * - `runtime-component` — an image built/published/signed/mirrored like any
143
+ * other but NEVER independently scheduled; another workload owns its
144
+ * lifecycle. No chart, no release, no roster entry.
145
+ */
146
+ export const BIOME_WORKLOAD_KINDS = new Set(['service', 'runtime-component']);
147
+
148
+ /** The kind applied when a `ships.apis[]` entry omits `workload`. */
149
+ export const DEFAULT_BIOME_WORKLOAD_KIND = 'service';
150
+
151
+ /**
152
+ * The BUILT `@xemahq/distribution-source-hash` package (CJS dist), loaded from
153
+ * the directory the entry states with `--source-hash`.
154
+ *
155
+ * That package is the SINGLE owner of `xema-source-tree-sha256-v2` — the same
156
+ * implementation every producing repository's CI uses to derive the
157
+ * `<contentHash>` release tag. This tooling must never carry a second copy: a
158
+ * divergence would put a lock and its images at coordinates that silently
159
+ * disagree. It is NOT an npm dependency of the CLI: the evidence verifier never
160
+ * hashes a source tree, so its bundle must not reach this package; only the
161
+ * entries that resolve, build, index or seal state the root.
162
+ */
163
+ const requireFromLib = createRequire(import.meta.url);
164
+
165
+ const loadedSourceHash = new Map();
166
+
167
+ function distributionSourceHash() {
168
+ const directory = sourceHashPackageDir();
169
+ const known = loadedSourceHash.get(directory);
170
+ if (known) {return known;}
171
+ let mod;
172
+ try {
173
+ mod = createRequire(join(directory, 'package.json'))(directory);
174
+ } catch (err) {
175
+ throw new Error(
176
+ `Cannot load the built distribution-source-hash package from ${directory}. ` +
177
+ `Build it first (\`pnpm install --frozen-lockfile && pnpm run build\` in its workspace). ` +
178
+ `Underlying error: ${err.message}`,
179
+ );
180
+ }
181
+ assertSourceHashMatchesKernel(mod);
182
+ loadedSourceHash.set(directory, mod);
183
+ return mod;
184
+ }
185
+
186
+ /**
187
+ * The one source-TREE hash algorithm identifier this tooling accepts — read
188
+ * from the implementation that actually derives the hash, never a local
189
+ * literal. `loadKernelDistributionSchemas` asserts it is value-identical to the
190
+ * kernel's `ImageSourceHashAlgorithm.MetadataV2`.
191
+ *
192
+ * This hashes ONE directory. It is a COMPONENT of an image coordinate, never an
193
+ * image coordinate on its own.
194
+ */
195
+ function metadataSourceHashAlgorithm(sourceHash) {
196
+ return requireAlgorithmMember(
197
+ sourceHash.SourceTreeHashAlgorithm,
198
+ 'SourceTreeHashAlgorithm',
199
+ 'MetadataV2',
200
+ );
201
+ }
202
+
203
+ /**
204
+ * Read one member off a built enum, refusing `undefined`.
205
+ *
206
+ * A missing member on a JS enum object is an ordinary property read: it yields
207
+ * `undefined` and nothing complains at the read site. That is not hypothetical
208
+ * here — renaming `ImageInputV1` to `ImageInputV2` in the source-hash package
209
+ * turned every reader of the old name into a silent `undefined`, which reaches
210
+ * a lock's `sourceHashAlgorithm` and a producer statement before anything
211
+ * notices. Failing AT THE READ names the stale artifact instead.
212
+ */
213
+ function requireAlgorithmMember(enumObject, enumName, member) {
214
+ const value = enumObject?.[member];
215
+ if (typeof value !== 'string' || value.length === 0) {
216
+ throw new Error(
217
+ `Built @xemahq/distribution-source-hash does not declare ` +
218
+ `${enumName}.${member}. The installed package is `+
219
+ `stale or the member was renamed; raise the @xemahq/distribution-source-hash range and, if the `+
220
+ `rename was deliberate, move every reader in the same change.`,
221
+ );
222
+ }
223
+ return value;
224
+ }
225
+
226
+ /**
227
+ * The one IMAGE-BUILD-INPUT closure algorithm this tooling MINTS, and the only
228
+ * value admissible as an `ImageLock.sourceHashAlgorithm`. A subject's
229
+ * `contentHash` under this algorithm covers the whole declared build-input
230
+ * closure of its image, not just its own subtree.
231
+ *
232
+ * Read from the implementation that derives the hash, never a local literal,
233
+ * so "the algorithm this tooling accepts" and "the algorithm CI can compute"
234
+ * cannot drift apart. That implementation's `ImageBuildInputHashAlgorithm`
235
+ * declares `ImageInputV2` and `ImageInputV3`; this tooling mints only
236
+ * `ImageInputV2`. `@xemahq/contracts` 0.70.0 declares `ImageInputV3` (kernel-
237
+ * contracts 259c8f5e), but every producer's release lane (and the develop lane,
238
+ * contract C1) computes V2 keys, so a V3 lock would name coordinates no producer
239
+ * pushes. V3 adoption is increment 2 and moves the producers and this function
240
+ * together.
241
+ *
242
+ * The kernel's `ImageSourceHashAlgorithm` retains `ImageInputV1` because signed
243
+ * in-toto statements and already-published locks carry that string and are
244
+ * immutable. This function is deliberately not a set: a lock the current
245
+ * producer cannot reproduce is not deliverable, so a v1 lock must be
246
+ * REGENERATED, never silently accepted.
247
+ */
248
+ export function imageInputSourceHashAlgorithm() {
249
+ return imageInputSourceHashAlgorithmOf(distributionSourceHash());
250
+ }
251
+
252
+ function imageInputSourceHashAlgorithmOf(sourceHash) {
253
+ return requireAlgorithmMember(
254
+ sourceHash.ImageBuildInputHashAlgorithm,
255
+ 'ImageBuildInputHashAlgorithm',
256
+ 'ImageInputV2',
257
+ );
258
+ }
259
+
260
+ /**
261
+ * Hold the built source-hash package value-identical to the kernel's wire names.
262
+ * Runs once per loaded package, so no hash is ever derived from a package that
263
+ * names its algorithms differently from the kernel that parses the lock.
264
+ */
265
+ function assertSourceHashMatchesKernel(sourceHash) {
266
+ const { ImageSourceHashAlgorithm } = requireFromLib(kernelDistributionDist());
267
+ if (typeof ImageSourceHashAlgorithm !== 'object' || ImageSourceHashAlgorithm === null) {
268
+ throw new Error(
269
+ `Built kernel distribution module at ${kernelDistributionDist()} does not export ImageSourceHashAlgorithm — the dist is stale; rebuild kernel-contracts.`,
270
+ );
271
+ }
272
+ // The kernel owns the WIRE name of the source-hash algorithm; the
273
+ // source-hash package owns its IMPLEMENTATION and declares the identifier
274
+ // itself so it can ship dependency-free to every producing repo's CI. This
275
+ // is the one place both built artifacts are in hand, so it is where the two
276
+ // are held value-identical — a drift here would put a lock's `contentHash`
277
+ // and the tag CI pushes at coordinates that silently disagree.
278
+ //
279
+ // BOTH members are held identical. `MetadataV2` names the component tree
280
+ // digest; `ImageInputV2` names the full image build-input closure and is what
281
+ // a lock declares. A drift in either would put a lock's `contentHash` and the
282
+ // tag CI pushes at coordinates that silently disagree.
283
+ //
284
+ // The kernel enum is a SUPERSET: it keeps `ImageInputV1` so an already-signed
285
+ // statement stays readable, while the source-hash package declares only what
286
+ // it can still COMPUTE. So the assertion pairs each implemented member with
287
+ // the kernel member of the same name — it is not a set comparison, and adding
288
+ // a retired wire name to the kernel must not make this pass vacuously.
289
+ for (const [implemented, declared, label] of [
290
+ [
291
+ metadataSourceHashAlgorithm(sourceHash),
292
+ ImageSourceHashAlgorithm.MetadataV2,
293
+ 'MetadataV2',
294
+ ],
295
+ [
296
+ imageInputSourceHashAlgorithmOf(sourceHash),
297
+ ImageSourceHashAlgorithm.ImageInputV2,
298
+ 'ImageInputV2',
299
+ ],
300
+ ]) {
301
+ if (implemented !== declared) {
302
+ throw new Error(
303
+ `Source-hash algorithm drift (${label}): @xemahq/distribution-source-hash ` +
304
+ `implements "${String(implemented)}" but @xemahq/contracts declares ` +
305
+ `"${String(declared)}". Both must name the same algorithm; rebuild both ` +
306
+ `packages, and if they genuinely diverged, fix the producer side before ` +
307
+ `any lock is written.`,
308
+ );
309
+ }
310
+ }
311
+ }
312
+
313
+ /** Path to the BUILT kernel-contracts BIOME module (CJS dist). */
314
+ const kernelBiomeDist = () => kernelDistPath('biome', 'index.js');
315
+
316
+ let kernelBiomeContracts;
317
+
318
+ /**
319
+ * The kernel's biome contracts, loaded SYNCHRONOUSLY (`createRequire`, like the
320
+ * source-hash module above) because the index builder is synchronous.
321
+ *
322
+ * It lives here rather than in each consumer because `isBiomeStoreListed` is a
323
+ * predicate with derived refusals, and a second reading of it is not a style
324
+ * problem — it is a wrong answer. `manifestToIndexEntry` used to resolve
325
+ * `storeListed` as `xema.storeListed ?? true`, which skips BOTH refusals the
326
+ * kernel applies: an `operator`-audience biome and a `kernel`-tier biome are
327
+ * never listed, whatever the flag says. So the index advertised every kernel
328
+ * biome as store-listed while the catalog projector — which calls the kernel
329
+ * function — refused them, and the two disagreed about the same bytes. The
330
+ * kernel's own docstring says it in as many words: resolve via
331
+ * `isBiomeStoreListed`, never read the field raw.
332
+ */
333
+ export function loadKernelBiomeContracts() {
334
+ assertKernelEditionProtocol(kernelPackageDir());
335
+ if (kernelBiomeContracts) {return kernelBiomeContracts;}
336
+ let mod;
337
+ try {
338
+ mod = requireFromLib(kernelBiomeDist());
339
+ } catch (err) {
340
+ throw new Error(
341
+ `Cannot load built kernel biome contracts from ${kernelBiomeDist()}. ` +
342
+ `Build it first: run \`pnpm install --frozen-lockfile && pnpm run build\` ` +
343
+ `in the kernel checkout (${kernelRepoDir()}). That names no package deliberately — ` +
344
+ `a remedy naming one goes stale on a rename, silently, because ` +
345
+ `\`pnpm --filter\` exits 0 when nothing matches. ` +
346
+ `Underlying error: ${err.message}`,
347
+ );
348
+ }
349
+ if (
350
+ typeof mod.parseBiomeManifest !== 'function' ||
351
+ typeof mod.isBiomeStoreListed !== 'function' ||
352
+ typeof mod.BiomeTrustTier !== 'object' ||
353
+ typeof mod.BiomeOrigin !== 'object' ||
354
+ typeof mod.requiredBiomeDependencyIds !== 'function' ||
355
+ typeof mod.foldInstallationClass !== 'function' ||
356
+ typeof mod.BiomeComponentInstallationClass !== 'object' ||
357
+ // The GRAMMAR a service component's `protocol.databaseExtensions` entries
358
+ // are validated against. Read from the kernel, never restated here.
359
+ //
360
+ // A PREDICATE, not a set, and that is the whole point: a PostgreSQL
361
+ // extension name is an OPEN vocabulary owned by whoever runs the database,
362
+ // so the kernel declares `^[a-z_][a-z0-9_-]*$` (<=63) and ships
363
+ // `isPostgresExtensionName` rather than an enum. This guard asked for a
364
+ // closed `PostgresExtension` object the kernel has never exported, and its
365
+ // error told the reader to rebuild a dist that was already current --- a
366
+ // false remedy for a design disagreement. Whether a name may actually be
367
+ // INSTALLED is a deployment question (`infra.yaml#postgres.allowedExtensions`),
368
+ // deliberately not a contract one.
369
+ typeof mod.isPostgresExtensionName !== 'function' ||
370
+ typeof mod.BiomeBundleGeneratedIndexSchema?.parse !== 'function' ||
371
+ // The ONE shape a biome's `xema.bundleSource` is validated against before
372
+ // it is projected — onto a host module, or onto the biome itself. Read
373
+ // from the kernel so the index can never carry a source the lock schema
374
+ // (which reuses this exact schema) would then refuse.
375
+ typeof mod.BiomeBundleSourceSpecSchema?.safeParse !== 'function' ||
376
+ // A component's process facts (`@xemahq/contracts` >= 0.30.0): the
377
+ // scaling vocabulary and the intrinsic-facts block, projected into
378
+ // `serviceProcessFacts` by `componentProcessFacts`.
379
+ typeof mod.BiomeScalingMode !== 'object' ||
380
+ typeof mod.BiomeComponentProcessFactsSchema?.safeParse !== 'function' ||
381
+ // Worker protocol v2 (`@xemahq/contracts` >= 0.52.0): the ONE schema a
382
+ // worker's `protocol.taskQueues` / `protocol.nexus` are graded against
383
+ // before `componentNexusDeclarations` projects them into the index.
384
+ typeof mod.BiomeWorkerProtocolSchema?.safeParse !== 'function' ||
385
+ // The ONE schema a service's `protocol.workflowClientOf` entries are graded
386
+ // against (`@xemahq/contracts` >= 0.60.0), before `componentWorkflowClients`
387
+ // projects them into the index.
388
+ typeof mod.BiomeWorkflowClientOfSchema?.safeParse !== 'function' ||
389
+ // Asserted here, not read hopefully: `biomeTierOrder()` above returns this
390
+ // array straight to `.includes()` / `.indexOf()`, and an absent export
391
+ // would surface as a TypeError at some sort comparator rather than as the
392
+ // actionable "rebuild kernel-contracts" below.
393
+ !Array.isArray(mod.BIOME_TIER_ORDER) ||
394
+ mod.BIOME_TIER_ORDER.length === 0
395
+ ) {
396
+ throw new Error(
397
+ `Built kernel biome module at ${kernelBiomeDist()} does not export ` +
398
+ `parseBiomeManifest/isBiomeStoreListed/BiomeTrustTier/BiomeOrigin/` +
399
+ `requiredBiomeDependencyIds/foldInstallationClass/` +
400
+ `BiomeComponentInstallationClass/isPostgresExtensionName/` +
401
+ `BiomeBundleGeneratedIndexSchema/BiomeBundleSourceSpecSchema/BiomeScalingMode/` +
402
+ `BiomeComponentProcessFactsSchema/BiomeWorkerProtocolSchema/BiomeWorkflowClientOfSchema/BIOME_TIER_ORDER — ` +
403
+ `the dist is stale (isPostgresExtensionName needs @xemahq/contracts >= 0.12.0, ` +
404
+ `the process facts >= 0.30.0, worker protocol v2 >= 0.52.0, workflowClientOf >= 0.60.0); rebuild ` +
405
+ `kernel-contracts at a commit that carries it.`,
406
+ );
407
+ }
408
+ if (mod.BIOME_GENERATED_INDEX_FILENAME !== BIOME_GENERATED_INDEX_FILENAME) {
409
+ throw new Error(
410
+ `Kernel BIOME_GENERATED_INDEX_FILENAME is "${String(mod.BIOME_GENERATED_INDEX_FILENAME)}" ` +
411
+ `but this plane reads "${BIOME_GENERATED_INDEX_FILENAME}" — the literal drifted from the kernel.`,
412
+ );
413
+ }
414
+ kernelBiomeContracts = mod;
415
+ return kernelBiomeContracts;
416
+ }
417
+
418
+ /** Path to the BUILT kernel-contracts RESOURCE-REQUIREMENT module (CJS dist). */
419
+ const kernelResourceRequirementDist = () => kernelDistPath('resource-requirement', 'index.js');
420
+
421
+ let kernelResourceRequirementContracts;
422
+
423
+ /**
424
+ * The kernel's resource-requirement grammar, loaded SYNCHRONOUSLY like
425
+ * {@link loadKernelBiomeContracts}. A component's `protocol.resources` and
426
+ * `protocol.providesResources` are validated by the kernel's own schemas —
427
+ * never a restated grammar — so the index can never carry a declaration the
428
+ * lock schema would then refuse.
429
+ */
430
+ export function loadKernelResourceRequirementContracts() {
431
+ assertKernelEditionProtocol(kernelPackageDir());
432
+ if (kernelResourceRequirementContracts) {
433
+ return kernelResourceRequirementContracts;
434
+ }
435
+ let mod;
436
+ try {
437
+ mod = requireFromLib(kernelResourceRequirementDist());
438
+ } catch (err) {
439
+ throw new Error(
440
+ `Cannot load built kernel resource-requirement contracts from ` +
441
+ `${kernelResourceRequirementDist()}. Build kernel-contracts at a commit ` +
442
+ `that carries the resource-requirement subpath: run ` +
443
+ `\`pnpm install --frozen-lockfile && pnpm run build\` in ` +
444
+ `the kernel checkout (${kernelRepoDir()}). Underlying error: ${err.message}`,
445
+ );
446
+ }
447
+ if (
448
+ typeof mod.ResourceRequirementsSchema?.safeParse !== 'function' ||
449
+ typeof mod.ResourceProvisionsSchema?.safeParse !== 'function'
450
+ ) {
451
+ throw new Error(
452
+ `Built kernel resource-requirement module at ${kernelResourceRequirementDist()} ` +
453
+ `does not export ResourceRequirementsSchema/ResourceProvisionsSchema — ` +
454
+ `the dist is stale; rebuild kernel-contracts.`,
455
+ );
456
+ }
457
+ kernelResourceRequirementContracts = mod;
458
+ return kernelResourceRequirementContracts;
459
+ }
460
+
461
+ /**
462
+ * Validate one component's `protocol.resources` / `protocol.providesResources`
463
+ * with the KERNEL's schemas, naming the manifest and the component on failure.
464
+ * Returns `[]` for an absent field — absent and `[]` make the same claim.
465
+ */
466
+ function parseComponentResourceDeclaration(schemaName, value, where) {
467
+ if (value === undefined) {return [];}
468
+ const schema = loadKernelResourceRequirementContracts()[schemaName];
469
+ const parsed = schema.safeParse(value);
470
+ if (!parsed.success) {
471
+ throw new Error(
472
+ `${where} is not a valid resource declaration: ` +
473
+ parsed.error.issues
474
+ .map((issue) => `[${issue.path.join('.')}] ${issue.message}`)
475
+ .join('; '),
476
+ );
477
+ }
478
+ return parsed.data;
479
+ }
480
+
481
+ /**
482
+ * Project one image component's Nexus declarations (worker protocol v2) into
483
+ * the two index rows the edition resolver binds from. The protocol is graded
484
+ * by the KERNEL's `BiomeWorkerProtocolSchema` first, so every v2 rule (a
485
+ * provided service names one of the worker's own `nexus` queues, nothing is
486
+ * provided and required by one worker, no duplicates) holds before anything
487
+ * is projected. Returns empty arrays for a component that declares no `nexus`
488
+ * — absent and `[]` make the same claim — so a biome that declares none adds
489
+ * no index key and `biome-index.json` stays byte-stable.
490
+ *
491
+ * Only a `worker` may declare one: the v2 shape is the worker protocol's, and
492
+ * a `nexus` block on any other kind is refused rather than dropped, because a
493
+ * dropped requirement reaches no lock and fails nothing.
494
+ */
495
+ /** The protocol kinds a `worker` component may declare (closed). */
496
+ const WorkerProtocolKind = Object.freeze({ Worker: 'worker', None: 'none' });
497
+
498
+ /**
499
+ * The task queues a protocol `worker/v2` component polls, sorted by name (a bare
500
+ * short name, or `{ name, placement }` when the queue is placed),
501
+ * or null for anything else — a runtime-launched worker (protocol `none`) polls
502
+ * nothing, and a service carries no worker protocol. Validated by the kernel's own worker-protocol
503
+ * schema, naming the manifest. The deploy plane runs exactly these components
504
+ * under a Worker Controller (durable-execution plan W3.4 / U14), so a v2 worker
505
+ * is recognised by what it declares, never by a list of chart names.
506
+ */
507
+ function componentWorkerTaskQueues(component, protocol, where) {
508
+ if (component.kind !== 'worker') {return null;}
509
+ // A CLOSED enum: a worker protocol kind this resolver does not know would
510
+ // otherwise silently contribute no queues, and a missing queue reaches no lock.
511
+ if (!Object.values(WorkerProtocolKind).includes(protocol.kind)) {
512
+ throw new Error(
513
+ `${where} declares worker protocol kind ${JSON.stringify(protocol.kind)}; ` +
514
+ `expected ${Object.values(WorkerProtocolKind).join(' | ')}`,
515
+ );
516
+ }
517
+ if (protocol.kind === WorkerProtocolKind.None) {return null;}
518
+ const parsed = loadKernelBiomeContracts().BiomeWorkerProtocolSchema.safeParse(protocol);
519
+ if (!parsed.success) {
520
+ throw new Error(
521
+ `${where} is not a valid worker protocol: ` +
522
+ parsed.error.issues.map((issue) => `[${issue.path.join('.')}] ${issue.message}`).join('; '),
523
+ );
524
+ }
525
+ // A queue with a `placement` keeps it: the lock carries `{ name, placement }`
526
+ // for it and the bare short name for every other queue.
527
+ return parsed.data.taskQueues
528
+ .map((queue) => lockedTaskQueueRef(queue.name, queue.placement))
529
+ .sort(compareTaskQueueRefs);
530
+ }
531
+
532
+ /** The lock's `WorkerTaskQueueRef`: a bare short name, or `{ name, placement }` when the queue is placed. */
533
+ function lockedTaskQueueRef(name, placement) {
534
+ return placement === undefined ? name : { name, placement };
535
+ }
536
+
537
+ function taskQueueRefName(ref) {
538
+ return typeof ref === 'string' ? ref : ref.name;
539
+ }
540
+
541
+ function compareTaskQueueRefs(a, b) {
542
+ return compareStrings(taskQueueRefName(a), taskQueueRefName(b));
543
+ }
544
+
545
+ /**
546
+ * The `{ biome?, component, taskQueue }[]` a service's `protocol.workflowClientOf`
547
+ * declares (`biome` only when the entry names another biome's worker, passed
548
+ * through from the kernel schema's parse), sorted by biome (absent first), then
549
+ * component, then queue name (`taskQueue` is a bare short
550
+ * name, or `{ name, placement }` when the queue is placed), or `null` when it declares none (absent and `[]`
551
+ * make the same claim). Each entry is graded by the kernel's own schema; that
552
+ * the target is a shipped worker/v2 whose queue is polled is the lock's
553
+ * refinement, not restated here.
554
+ */
555
+ function componentWorkflowClients(protocol, where) {
556
+ if (protocol.workflowClientOf === undefined) {return null;}
557
+ if (!Array.isArray(protocol.workflowClientOf)) {
558
+ throw new Error(`${where} protocol.workflowClientOf must be an array`);
559
+ }
560
+ if (protocol.workflowClientOf.length === 0) {return null;}
561
+ const schema = loadKernelBiomeContracts().BiomeWorkflowClientOfSchema;
562
+ return protocol.workflowClientOf.map((entry, index) => {
563
+ const parsed = schema.safeParse(entry);
564
+ if (!parsed.success) {
565
+ throw new Error(
566
+ `${where} has an invalid protocol.workflowClientOf[${index}]: ` +
567
+ parsed.error.issues.map((issue) => `[${issue.path.join('.')}] ${issue.message}`).join('; '),
568
+ );
569
+ }
570
+ return parsed.data;
571
+ }).sort(
572
+ (a, b) =>
573
+ compareStrings(a.biome ?? '', b.biome ?? '') ||
574
+ compareStrings(a.component, b.component) ||
575
+ compareTaskQueueRefs(a.taskQueue, b.taskQueue),
576
+ );
577
+ }
578
+
579
+ /**
580
+ * `{ imageOf, entry }` for a worker declaring `artifact.kind:
581
+ * shared-oci-image` — the sibling SERVICE whose sealed image it runs (by that
582
+ * service's image name, `protocol.serviceName`) and the module `node` runs in
583
+ * it — or `null` for any other component.
584
+ *
585
+ * The kernel manifest schema already refuses an inconsistent declaration; this
586
+ * projection reads the RAW manifest, so it re-checks exactly the facts it
587
+ * writes rather than trusting them: a lock naming a sibling that is not a
588
+ * platform-scheduled oci-image service would send the deploy plane to a
589
+ * subject that does not exist.
590
+ */
591
+ function sharedImageWorker(component, components, where) {
592
+ if (component.artifact?.kind !== 'shared-oci-image') {return null;}
593
+ const siblingKey = component.artifact.component;
594
+ const sibling = components.find(
595
+ (candidate) =>
596
+ candidate !== null && typeof candidate === 'object' && candidate.key === siblingKey,
597
+ );
598
+ const imageOf = sibling?.protocol?.serviceName;
599
+ if (
600
+ sibling === undefined ||
601
+ sibling.kind !== 'service' ||
602
+ sibling.artifact?.kind !== 'oci-image' ||
603
+ sibling.scheduler !== 'platform' ||
604
+ typeof imageOf !== 'string' ||
605
+ imageOf.length === 0
606
+ ) {
607
+ throw new Error(
608
+ `${where} runs the image of component "${String(siblingKey)}", which is not a ` +
609
+ `platform-scheduled service shipping its own oci-image in this biome`,
610
+ );
611
+ }
612
+ const entry = component.entrypoint?.module;
613
+ if (component.entrypoint?.kind !== 'node-module' || typeof entry !== 'string' || entry.length === 0) {
614
+ throw new Error(
615
+ `${where} declares a shared-oci-image artifact without a node-module entrypoint ` +
616
+ `naming the module to run`,
617
+ );
618
+ }
619
+ return { imageOf, entry };
620
+ }
621
+
622
+ function componentNexusDeclarations(component, name, protocol, where) {
623
+ if (protocol.nexus === undefined) {return { provides: [], requires: [] };}
624
+ if (component.kind !== 'worker') {
625
+ throw new Error(
626
+ `${where} declares protocol.nexus on a "${String(component.kind)}" component — ` +
627
+ `only a worker (protocol worker/v2) provides or requires a Nexus service`,
628
+ );
629
+ }
630
+ const parsed = loadKernelBiomeContracts().BiomeWorkerProtocolSchema.safeParse(protocol);
631
+ if (!parsed.success) {
632
+ throw new Error(
633
+ `${where} is not a valid worker protocol: ` +
634
+ parsed.error.issues
635
+ .map((issue) => `[${issue.path.join('.')}] ${issue.message}`)
636
+ .join('; '),
637
+ );
638
+ }
639
+ const nexus = parsed.data.nexus ?? {};
640
+ return {
641
+ // `operations` travels with each row: the kernel's `resolveNexusBindings`
642
+ // binds a requirement only to a provider declaring EVERY operation it
643
+ // requires, so the operation names are binding facts, not decoration.
644
+ // Sorted by code unit, so the index is byte-stable whatever order the
645
+ // manifest lists them in.
646
+ provides: (nexus.provides ?? []).map((entry) => ({
647
+ component: name,
648
+ service: entry.service,
649
+ version: entry.version,
650
+ taskQueue: entry.taskQueue,
651
+ operations: entry.operations.map((operation) => operation.name).sort(compareStrings),
652
+ })),
653
+ requires: (nexus.requires ?? []).map((entry) => ({
654
+ component: name,
655
+ service: entry.service,
656
+ version: entry.version,
657
+ operations: [...entry.operations].sort(compareStrings),
658
+ ...(entry.optional === true ? { optional: true } : {}),
659
+ })),
660
+ };
661
+ }
662
+
663
+ const compareNexusRows = (a, b) =>
664
+ compareStrings(a.service, b.service) ||
665
+ compareStrings(a.version, b.version) ||
666
+ compareStrings(a.component, b.component);
667
+
668
+ const compareResourceRows = (a, b) =>
669
+ compareStrings(a.consumerService, b.consumerService) ||
670
+ compareStrings(a.kind, b.kind) ||
671
+ compareStrings(a.logicalName, b.logicalName);
672
+
673
+
674
+
675
+ const kernelDistributionDist = () => kernelDistPath('distribution', 'index.js');
676
+
677
+ // The kernel's process-topology module: the ONE trust-domain grammar
678
+ // (`deriveTrustCeiling`) a lock writer projects an edition's `trustCeiling`
679
+ // through. A sibling of the distribution dist, loaded beside it.
680
+ const kernelProcessTopologyDist = () => kernelDistPath('process-topology', 'index.js');
681
+
682
+ /**
683
+ * Dynamically import the BUILT kernel distribution schemas. The kernel package
684
+ * is NOT linked into the repo-root node_modules (it lives in its own sub-
685
+ * workspace), so a bare `@xemahq/contracts/distribution` specifier does
686
+ * not resolve from root tooling. We import the deterministic dist path instead
687
+ * — fail-fast with an actionable message if the dist is not built.
688
+ */
689
+ export async function loadKernelDistributionSchemas() {
690
+ assertKernelEditionProtocol(kernelPackageDir());
691
+ let mod;
692
+ try {
693
+ mod = await import(`file://${kernelDistributionDist()}`);
694
+ } catch (err) {
695
+ throw new Error(
696
+ `Cannot load built kernel distribution schemas from ${kernelDistributionDist()}. ` +
697
+ `Build it first: run \`pnpm install --frozen-lockfile && pnpm run build\` ` +
698
+ `in the kernel checkout (${kernelRepoDir()}) — naming no package, deliberately. ` +
699
+ `Underlying error: ${err.message}`,
700
+ );
701
+ }
702
+ const {
703
+ parseDistribution,
704
+ parseDistributionLock,
705
+ parseDistributionLockForReading,
706
+ parseUnsealedDistributionLock,
707
+ parseCustomerEditionDefinition,
708
+ customerTrustCeilingIssues,
709
+ sealDistributionLock,
710
+ unsealedProjection,
711
+ UNSEALED_DISTRIBUTION_LOCK_SCHEMA,
712
+ DISTRIBUTION_LOCK_SCHEMA_VERSION,
713
+ computeBiomeContentHash,
714
+ computeBiomeSourceManifestHash,
715
+ parseBiomeSourceManifestHash,
716
+ parseOciDigest,
717
+ parseGitTreeId,
718
+ parseImageLock,
719
+ parseImageProducerStatement,
720
+ parseDistributionImagesStatement,
721
+ parseImageDeliveryEntitlementStatement,
722
+ parseDistributionImageEvidenceSet,
723
+ parseDistributionImageReplayState,
724
+ parseSigstoreOfflineTrustPolicy,
725
+ parsePlatformServiceSourceIndex,
726
+ parseRuntimeImageSourceIndex,
727
+ resolveRuntimeImagePlatforms,
728
+ BiomeInstallPolicy,
729
+ BiomeLaunchMode,
730
+ ImageSourceHashAlgorithm,
731
+ RuntimeImagePlatformKey,
732
+ RuntimeImageOwnerKind,
733
+ RuntimeImageSubjectKind,
734
+ IMAGE_PRODUCER_PREDICATE_TYPE,
735
+ DISTRIBUTION_IMAGES_PREDICATE_TYPE,
736
+ IMAGE_DELIVERY_ENTITLEMENT_PREDICATE_TYPE,
737
+ EVIDENCE_CHANNELS,
738
+ EVIDENCE_CHANNEL_REFS,
739
+ resolveFirstPartyCapabilityDomains,
740
+ DistributionVisibility,
741
+ computeKernelContractAuthorityContentDigest,
742
+ KernelContractContentHashAlgorithm,
743
+ XEMA_KERNEL_CONTRACT_AUTHORITY_DIGEST_DOMAIN,
744
+ XEMA_KERNEL_CONTRACT_AUTHORITY_DIGEST_VERSION,
745
+ XEMA_KERNEL_CONTRACT_AUTHORITY_GENERATOR,
746
+ XEMA_KERNEL_CONTRACT_REPOSITORY_URL,
747
+ XEMA_KERNEL_CONTRACT_SOURCE_MODE,
748
+ XEMA_KERNEL_CONTRACT_SOURCE_PATHS,
749
+ compareLockedServiceResources,
750
+ resolveNexusBindings,
751
+ describeNexusBindingRefusal,
752
+ canonicalNexusFacts,
753
+ DistributionResolutionMode,
754
+ resolveBiomeClosure,
755
+ platformInvariantRoots,
756
+ BiomeClosureError,
757
+ ClosureEntityKind,
758
+ ClosureProvenanceKind,
759
+ describeProvenance,
760
+ describeHop,
761
+ deriveWorkflowSubjects,
762
+ } = mod;
763
+ let topology;
764
+ try {
765
+ topology = await import(`file://${kernelProcessTopologyDist()}`);
766
+ } catch (err) {
767
+ throw new Error(
768
+ `Cannot load the built kernel process-topology module from ${kernelProcessTopologyDist()}. ` +
769
+ `Rebuild the kernel checkout (${kernelRepoDir()}). Underlying error: ${err.message}`,
770
+ );
771
+ }
772
+ const { deriveTrustCeiling, defaultTrustDomain, FIRST_PARTY_TRUST_DOMAIN, ProcessTrustPolicySchema } = topology;
773
+ const { DistributionTrustPolicySchema } = mod;
774
+ if (
775
+ typeof parseDistribution !== 'function' ||
776
+ typeof parseDistributionLock !== 'function' ||
777
+ // The tolerant READER (`@xemahq/contracts` >= 0.27.0). Every PURE read of a
778
+ // lock in this repository goes through it; only the resolver, which WRITES
779
+ // the lock, uses the strict `parseDistributionLock`.
780
+ typeof parseDistributionLockForReading !== 'function' ||
781
+ // Lock v5 is the only grammar. The resolver writes the UNSEALED document,
782
+ // the seal mints each biomeContentHash (`computeBiomeContentHash`) and the
783
+ // kernel's `sealDistributionLock` attaches them; the CLI never assembles v5.
784
+ DISTRIBUTION_LOCK_SCHEMA_VERSION !== 5 ||
785
+ typeof parseUnsealedDistributionLock !== 'function' ||
786
+ typeof sealDistributionLock !== 'function' ||
787
+ typeof unsealedProjection !== 'function' ||
788
+ typeof UNSEALED_DISTRIBUTION_LOCK_SCHEMA !== 'string' ||
789
+ typeof computeBiomeContentHash !== 'function' ||
790
+ typeof computeBiomeSourceManifestHash !== 'function' ||
791
+ typeof parseBiomeSourceManifestHash !== 'function' ||
792
+ typeof parseOciDigest !== 'function' ||
793
+ typeof parseGitTreeId !== 'function' ||
794
+ typeof parseImageLock !== 'function' ||
795
+ typeof parseImageProducerStatement !== 'function' ||
796
+ typeof parseDistributionImagesStatement !== 'function' ||
797
+ typeof parseImageDeliveryEntitlementStatement !== 'function' ||
798
+ typeof parseDistributionImageEvidenceSet !== 'function' ||
799
+ typeof parseDistributionImageReplayState !== 'function' ||
800
+ typeof parseSigstoreOfflineTrustPolicy !== 'function' ||
801
+ typeof parsePlatformServiceSourceIndex !== 'function' ||
802
+ typeof parseRuntimeImageSourceIndex !== 'function' ||
803
+ typeof resolveRuntimeImagePlatforms !== 'function' ||
804
+ typeof resolveFirstPartyCapabilityDomains !== 'function' ||
805
+ typeof DistributionVisibility !== 'object' ||
806
+ typeof BiomeInstallPolicy !== 'object' ||
807
+ typeof BiomeLaunchMode !== 'object' ||
808
+ typeof ImageSourceHashAlgorithm !== 'object' ||
809
+ typeof RuntimeImagePlatformKey !== 'object' ||
810
+ typeof RuntimeImageOwnerKind !== 'object' ||
811
+ typeof RuntimeImageSubjectKind !== 'object' ||
812
+ typeof IMAGE_PRODUCER_PREDICATE_TYPE !== 'string' ||
813
+ typeof DISTRIBUTION_IMAGES_PREDICATE_TYPE !== 'string' ||
814
+ typeof IMAGE_DELIVERY_ENTITLEMENT_PREDICATE_TYPE !== 'string' ||
815
+ !Array.isArray(EVIDENCE_CHANNELS) ||
816
+ typeof EVIDENCE_CHANNEL_REFS !== 'object' ||
817
+ typeof computeKernelContractAuthorityContentDigest !== 'function' ||
818
+ typeof KernelContractContentHashAlgorithm !== 'object' ||
819
+ typeof XEMA_KERNEL_CONTRACT_AUTHORITY_DIGEST_DOMAIN !== 'string' ||
820
+ XEMA_KERNEL_CONTRACT_AUTHORITY_DIGEST_VERSION !== 1 ||
821
+ typeof XEMA_KERNEL_CONTRACT_AUTHORITY_GENERATOR !== 'string' ||
822
+ typeof XEMA_KERNEL_CONTRACT_REPOSITORY_URL !== 'string' ||
823
+ typeof XEMA_KERNEL_CONTRACT_SOURCE_MODE !== 'string' ||
824
+ !Array.isArray(XEMA_KERNEL_CONTRACT_SOURCE_PATHS) ||
825
+ !Object.isFrozen(XEMA_KERNEL_CONTRACT_SOURCE_PATHS) ||
826
+ // The ONE order a lock's `serviceResources` rows are emitted in — the
827
+ // comparator the kernel lock schema asserts, never a second spelling.
828
+ typeof compareLockedServiceResources !== 'function' ||
829
+ // THE Nexus binding rule — service, major version and every required
830
+ // operation — shared with the runtime resolver (>= 0.53.0).
831
+ typeof resolveNexusBindings !== 'function' ||
832
+ typeof describeNexusBindingRefusal !== 'function' ||
833
+ // The canonical per-biome Nexus facts a lock carries (>= 0.54.0).
834
+ typeof canonicalNexusFacts !== 'function' ||
835
+ // Positive closure (`@xemahq/contracts` >= the release that added the
836
+ // `resolution`/`roots` edition fields): the ONE closure algorithm, shared
837
+ // with runtime installation, and the vocabulary its provenance is written in.
838
+ typeof DistributionResolutionMode !== 'object' ||
839
+ typeof resolveBiomeClosure !== 'function' ||
840
+ typeof platformInvariantRoots !== 'function' ||
841
+ typeof BiomeClosureError !== 'function' ||
842
+ typeof ClosureEntityKind !== 'object' ||
843
+ typeof ClosureProvenanceKind !== 'object' ||
844
+ typeof describeProvenance !== 'function' ||
845
+ typeof describeHop !== 'function' ||
846
+ // A biome's workflow subjects and its edition trust ceiling, each written
847
+ // through the kernel's one derivation (`@xemahq/contracts` >= 0.71.0).
848
+ typeof deriveWorkflowSubjects !== 'function' ||
849
+ typeof deriveTrustCeiling !== 'function' ||
850
+ // The trust-domain vocabulary a customer edition is bounded by: the vendor's
851
+ // own domain, a biome's default domain, and the two ceiling grammars a
852
+ // catalogue carries.
853
+ typeof defaultTrustDomain !== 'function' ||
854
+ typeof FIRST_PARTY_TRUST_DOMAIN !== 'string' ||
855
+ typeof ProcessTrustPolicySchema?.safeParse !== 'function' ||
856
+ typeof DistributionTrustPolicySchema?.safeParse !== 'function'
857
+ ) {
858
+ throw new Error(
859
+ `Built kernel distribution module at ${kernelDistributionDist()} does not export ` +
860
+ `parseDistribution/parseDistributionLock/parseDistributionLockForReading/DISTRIBUTION_LOCK_SCHEMA_VERSION/parseUnsealedDistributionLock/sealDistributionLock/unsealedProjection/computeBiomeContentHash/computeBiomeSourceManifestHash/parseImageLock/image-evidence parsers/` +
861
+ `parsePlatformServiceSourceIndex/parseRuntimeImageSourceIndex/` +
862
+ `resolveRuntimeImagePlatforms/RuntimeImagePlatformKey/` +
863
+ `kernel contract-authority digest SSOT/compareLockedServiceResources/resolveNexusBindings/describeNexusBindingRefusal/canonicalNexusFacts/` +
864
+ `positive closure (resolveBiomeClosure/platformInvariantRoots/DistributionResolutionMode/provenance)/` +
865
+ `deriveWorkflowSubjects/deriveTrustCeiling (>= 0.71.0)/` +
866
+ `defaultTrustDomain/FIRST_PARTY_TRUST_DOMAIN/ProcessTrustPolicySchema/DistributionTrustPolicySchema/` +
867
+ `resolveFirstPartyCapabilityDomains/DistributionVisibility/` +
868
+ `BiomeInstallPolicy/BiomeLaunchMode/ImageSourceHashAlgorithm/runtime-image enums — ` +
869
+ `the dist is stale; rebuild kernel-contracts.`,
870
+ );
871
+ }
872
+ return {
873
+ parseDistribution,
874
+ parseDistributionLock,
875
+ parseDistributionLockForReading,
876
+ parseUnsealedDistributionLock,
877
+ parseCustomerEditionDefinition,
878
+ customerTrustCeilingIssues,
879
+ sealDistributionLock,
880
+ unsealedProjection,
881
+ UNSEALED_DISTRIBUTION_LOCK_SCHEMA,
882
+ DISTRIBUTION_LOCK_SCHEMA_VERSION,
883
+ computeBiomeContentHash,
884
+ computeBiomeSourceManifestHash,
885
+ parseBiomeSourceManifestHash,
886
+ parseOciDigest,
887
+ parseGitTreeId,
888
+ parseImageLock,
889
+ parseImageProducerStatement,
890
+ parseDistributionImagesStatement,
891
+ parseImageDeliveryEntitlementStatement,
892
+ parseDistributionImageEvidenceSet,
893
+ parseDistributionImageReplayState,
894
+ parseSigstoreOfflineTrustPolicy,
895
+ parsePlatformServiceSourceIndex,
896
+ parseRuntimeImageSourceIndex,
897
+ resolveRuntimeImagePlatforms,
898
+ BiomeInstallPolicy,
899
+ BiomeLaunchMode,
900
+ ImageSourceHashAlgorithm,
901
+ RuntimeImagePlatformKey,
902
+ RuntimeImageOwnerKind,
903
+ RuntimeImageSubjectKind,
904
+ IMAGE_PRODUCER_PREDICATE_TYPE,
905
+ DISTRIBUTION_IMAGES_PREDICATE_TYPE,
906
+ IMAGE_DELIVERY_ENTITLEMENT_PREDICATE_TYPE,
907
+ EVIDENCE_CHANNELS,
908
+ EVIDENCE_CHANNEL_REFS,
909
+ resolveFirstPartyCapabilityDomains,
910
+ DistributionVisibility,
911
+ computeKernelContractAuthorityContentDigest,
912
+ KernelContractContentHashAlgorithm,
913
+ XEMA_KERNEL_CONTRACT_AUTHORITY_DIGEST_DOMAIN,
914
+ XEMA_KERNEL_CONTRACT_AUTHORITY_DIGEST_VERSION,
915
+ XEMA_KERNEL_CONTRACT_AUTHORITY_GENERATOR,
916
+ XEMA_KERNEL_CONTRACT_REPOSITORY_URL,
917
+ XEMA_KERNEL_CONTRACT_SOURCE_MODE,
918
+ XEMA_KERNEL_CONTRACT_SOURCE_PATHS,
919
+ compareLockedServiceResources,
920
+ resolveNexusBindings,
921
+ describeNexusBindingRefusal,
922
+ canonicalNexusFacts,
923
+ DistributionResolutionMode,
924
+ resolveBiomeClosure,
925
+ platformInvariantRoots,
926
+ BiomeClosureError,
927
+ ClosureEntityKind,
928
+ ClosureProvenanceKind,
929
+ describeProvenance,
930
+ describeHop,
931
+ deriveWorkflowSubjects,
932
+ deriveTrustCeiling,
933
+ defaultTrustDomain,
934
+ FIRST_PARTY_TRUST_DOMAIN,
935
+ ProcessTrustPolicySchema,
936
+ DistributionTrustPolicySchema,
937
+ };
938
+ }
939
+
940
+ /**
941
+ * The published branch every image/platform source repository is pinned at.
942
+ *
943
+ * Deliberately NOT the local `HEAD`. A pin is a coordinate a CONSUMER has to be
944
+ * able to fetch: pinning a local HEAD records commits that exist only on this
945
+ * machine — an unpushed commit, or a branch someone is mid-way through — and a
946
+ * lock built on one is unresolvable anywhere else. It also made every generator
947
+ * hostage to the aggregator's checkout state, which is never quiescent.
948
+ *
949
+ * Every digest in release mode reads immutable Git objects at this exact
950
+ * commit, so the working tree is not an accidental input. Local development
951
+ * can opt into `workspace` authority explicitly; that mode also produces exact
952
+ * Git objects, but its synthetic commits are local-only and never selected by
953
+ * default.
954
+ */
955
+ // Aligns with the CLI's manifest discovery rules:
956
+ // `fixtures`/`templates` hold test-fixture and scaffold `xema-biome.json` files
957
+ // that are NOT distributable biomes and must never enter the packaging index
958
+ // (e.g. create-biome templates, any package's `test/fixtures/*-biome`).
959
+ const SKIP_DIR_NAMES = new Set([
960
+ 'node_modules',
961
+ '.git',
962
+ 'dist',
963
+ 'fixtures',
964
+ 'templates',
965
+ ]);
966
+
967
+ /**
968
+ * Whether a discovered manifest path is an OpenAPI/test fixture rather than a
969
+ * real biome. Fixtures live under `scripts/openapi-fixture/` and re-use real
970
+ * biome ids (e.g. `runtime`), which would otherwise collide in the index.
971
+ */
972
+ function isFixtureManifest(repoRelativePath) {
973
+ return repoRelativePath.split('/').includes('openapi-fixture');
974
+ }
975
+
976
+ export function distributionBiomeSourceRepositories() {
977
+ const publishersPath = join(
978
+ dataRoot(),
979
+ 'store-publisher-identities.json',
980
+ );
981
+ const publishers = readStrictJsonFile(publishersPath);
982
+ if (
983
+ !Array.isArray(publishers.publishers) ||
984
+ publishers.publishers.length === 0
985
+ ) {
986
+ throw new Error(`${publishersPath} declares no Store publishers`);
987
+ }
988
+ const repositoryNames = publishers.publishers.flatMap((publisher) => {
989
+ if (!Array.isArray(publisher.sourceRepositories)) {
990
+ throw new Error(
991
+ `${publishersPath} publisher ${String(publisher.anchorKey)} has no sourceRepositories`,
992
+ );
993
+ }
994
+ return publisher.sourceRepositories;
995
+ });
996
+ if (new Set(repositoryNames).size !== repositoryNames.length) {
997
+ throw new Error(
998
+ `${publishersPath} declares a source repository under more than one publisher`,
999
+ );
1000
+ }
1001
+ return repositoryNames
1002
+ .map((repo) => ({ repo, directory: sourcePath(repo) }))
1003
+ .sort((a, b) => compareStrings(a.repo, b.repo));
1004
+ }
1005
+
1006
+ /**
1007
+ * Ceiling for one `git ls-tree -r` listing of a whole repository.
1008
+ *
1009
+ * Node's default is 1 MiB and this call outgrew it in production: `xema-base`
1010
+ * measured 1,056,095 bytes across 12,214 paths on 2026-08-20 — 7 KiB past the
1011
+ * default — so the biome-index step began failing with a bare
1012
+ * `spawnSync git ENOBUFS`, which names neither the repository nor the reason.
1013
+ * The listing grows with every file added to any carved repo, so the ceiling
1014
+ * has to sit far above today's number rather than just above it.
1015
+ *
1016
+ * A ceiling is safe to raise here because `execFileSync` THROWS on overflow
1017
+ * rather than truncating: exceeding it is a loud failure, never a short read
1018
+ * that would silently drop biomes from the index.
1019
+ */
1020
+ const GIT_LS_TREE_MAX_BUFFER = 256 * 1024 * 1024;
1021
+
1022
+ /**
1023
+ * Every authoritative biome manifest in one repository AT ONE COMMIT, paired
1024
+ * with the `xema-biome.index.json` committed beside it.
1025
+ *
1026
+ * Extracted so the IMAGE-axis pass and the SOURCE-axis pass read manifests the
1027
+ * SAME way. Two copies of this walk would be two places for the skip rules, the
1028
+ * authoritative-source filter and the missing-index error to drift apart — and
1029
+ * the whole point of the split is that the two axes differ ONLY in which commit
1030
+ * they read.
1031
+ *
1032
+ * The manifest and its generated index are read from the SAME commit, which is
1033
+ * load-bearing and predates this split: the index is the authority for which
1034
+ * biomes this one requires, so reading them apart would let it describe a
1035
+ * different revision of the biome than the one being locked.
1036
+ */
1037
+ function listBiomeManifestsAt(directory, repo, commit) {
1038
+ const found = [];
1039
+ const rawPaths = execFileSync(
1040
+ 'git',
1041
+ ['-C', directory, 'ls-tree', '-r', '-z', '--name-only', commit],
1042
+ { encoding: 'buffer', maxBuffer: GIT_LS_TREE_MAX_BUFFER },
1043
+ );
1044
+ for (const repoPath of decodeNullDelimitedGitRecords(
1045
+ rawPaths,
1046
+ `Git manifest paths in ${repo}@${commit}`,
1047
+ )) {
1048
+ if (
1049
+ !repoPath.endsWith('/xema-biome.json') ||
1050
+ repoPath.split('/').some((segment) => SKIP_DIR_NAMES.has(segment)) ||
1051
+ isFixtureManifest(repoPath)
1052
+ ) {
1053
+ continue;
1054
+ }
1055
+ const manifestDirectory = repoPath.slice(0, -'/xema-biome.json'.length);
1056
+ const rel = `${repo}/${repoPath}`;
1057
+ const raw = readGitJsonAtCommit(directory, repoPath, commit);
1058
+ const path = `${repo}/${manifestDirectory}`;
1059
+ if (
1060
+ !isAuthoritativeProductionBiomeSource({
1061
+ id: raw?.xema?.id,
1062
+ repo,
1063
+ path,
1064
+ })
1065
+ ) {
1066
+ continue;
1067
+ }
1068
+ // The generated index is COMMITTED beside the manifest and is the single
1069
+ // authority for which biomes this one requires. It is read from the SAME
1070
+ // pinned commit as the manifest, so the index cannot describe a different
1071
+ // revision of the biome than the one being locked.
1072
+ const indexRepoPath = `${manifestDirectory}/${BIOME_GENERATED_INDEX_FILENAME}`;
1073
+ let generatedIndex;
1074
+ try {
1075
+ generatedIndex = readGitJsonAtCommit(directory, indexRepoPath, commit);
1076
+ } catch (error) {
1077
+ throw new Error(
1078
+ `${repo}/${indexRepoPath} is missing at ${commit}: ${error.message}. ` +
1079
+ `Every biome carries a committed ${BIOME_GENERATED_INDEX_FILENAME} — it replaced the ` +
1080
+ `manifest field xema.requiresServerBiomes as the authority for required biomes. ` +
1081
+ `Run \`pnpm derive:biome-index:write\` in the aggregator and commit the result.`,
1082
+ );
1083
+ }
1084
+ // The biome's authorization module, read at the SAME pinned commit as the
1085
+ // manifest, so the sealer composes exactly the bytes the bundle signs.
1086
+ const authorizationModule = readBiomeAuthorizationModuleAt(
1087
+ manifestDirectory,
1088
+ (repoRelativePath) => readGitTextAtCommitOrNull(directory, repoRelativePath, commit),
1089
+ rel,
1090
+ );
1091
+ found.push({
1092
+ raw,
1093
+ generatedIndex,
1094
+ rel,
1095
+ repo,
1096
+ path,
1097
+ ...(authorizationModule === null ? {} : { authorizationModule }),
1098
+ });
1099
+ }
1100
+
1101
+ return found;
1102
+ }
1103
+
1104
+ /** A file's text at a commit, or `null` when the commit does not carry it. */
1105
+ export function readGitTextAtCommitOrNull(repoDir, repoRelativePath, commit) {
1106
+ try {
1107
+ execFileSync('git', ['-C', repoDir, 'cat-file', '-e', `${commit}:${repoRelativePath}`], {
1108
+ stdio: 'ignore',
1109
+ });
1110
+ } catch {
1111
+ return null;
1112
+ }
1113
+ return execFileSync('git', ['-C', repoDir, 'show', `${commit}:${repoRelativePath}`], {
1114
+ encoding: 'utf8',
1115
+ maxBuffer: GIT_LS_TREE_MAX_BUFFER,
1116
+ });
1117
+ }
1118
+
1119
+ /**
1120
+ * Does this biome ship a runtime image subject?
1121
+ *
1122
+ * THE axis discriminator, declared once. A biome that ships a service or a
1123
+ * runtime component earns image evidence, so every fact about it is pinned on
1124
+ * the IMAGE axis and verified there (`assembleProducerInput` recomputes its
1125
+ * `sourceManifestHash` at the image commit). A biome that ships neither can
1126
+ * never earn image evidence, so pinning it on the image axis makes it
1127
+ * permanently undeliverable — its content belongs on the SOURCE axis.
1128
+ *
1129
+ * Takes the `{ services, runtimeComponents }` shape that BOTH an index entry
1130
+ * (`manifestToIndexEntry`) and a LOCKED biome carry, so the two consumers that
1131
+ * must agree about this cannot drift: a second copy of the predicate is a
1132
+ * second answer to the question the whole two-axis split turns on.
1133
+ */
1134
+ export function biomeShipsRuntimeImage(entry) {
1135
+ return (
1136
+ (entry.services?.length ?? 0) > 0 ||
1137
+ (entry.runtimeComponents?.length ?? 0) > 0
1138
+ );
1139
+ }
1140
+
1141
+ async function committedBiomeManifests(sourceRepositories, sourceAuthority) {
1142
+
1143
+
1144
+ const repositories = sourceRepositories.map((repository) => ({
1145
+ ...repository,
1146
+ commit: sourceAuthority.commitForRepository(repository.repo),
1147
+ // The SOURCE axis, for the second pass below.
1148
+ vendoredCommit: sourceAuthority.vendoredSourceCommitForRepository(repository.repo),
1149
+ }));
1150
+ const manifests = [];
1151
+ for (const { repo, directory, commit } of repositories) {
1152
+ manifests.push(...listBiomeManifestsAt(directory, repo, commit));
1153
+ }
1154
+
1155
+ // ── SECOND PASS: CONTENT-ONLY BIOMES ARE READ ON THE SOURCE AXIS ──
1156
+ //
1157
+ // The pass above reads every manifest at the IMAGE commit, and for a biome
1158
+ // that SHIPS A SERVICE that is exactly right: `resolve-images` pairs this
1159
+ // biome's `sourceManifestHash` with that repository's image commit, and
1160
+ // `assembleProducerInput` verifies the hash by recomputing it AT that commit.
1161
+ // Move those and every image-producing service fails verification.
1162
+ //
1163
+ // A CONTENT-ONLY biome has no image subject, so nothing verifies it — and
1164
+ // that same property is why it was undeliverable: it can never earn image
1165
+ // evidence, so the image-evidence commit could never carry its changes. Its
1166
+ // content belongs on the SOURCE axis, which is the commit whose bytes are
1167
+ // actually vendored into whichever image carries it.
1168
+ //
1169
+ // Measured 2026-09-14: `memory-maintenance` (services: [], runtimeComponents:
1170
+ // []) had migrated `modelClass` -> `modelLane`; the fix sat on cultivars' main
1171
+ // while the image pin was 130 commits behind, and biome-host crashlooped in
1172
+ // production on the retired key for 21 hours.
1173
+ //
1174
+ // Producing-ness is decided by PROJECTING the manifest through
1175
+ // `manifestToIndexEntry` rather than re-deriving it here — one authority for
1176
+ // "does this biome ship an image", not two that can disagree.
1177
+ //
1178
+ // This pass also lets a NEW content-only biome appear with no image build at
1179
+ // all, which is the other half of the same defect.
1180
+ let replaced = 0;
1181
+ let added = 0;
1182
+ for (const { repo, directory, commit, vendoredCommit } of repositories) {
1183
+ if (!vendoredCommit || vendoredCommit === commit) {continue;}
1184
+ for (const candidate of listBiomeManifestsAt(directory, repo, vendoredCommit)) {
1185
+ let entry;
1186
+ try {
1187
+ entry = await manifestToIndexEntry(candidate);
1188
+ } catch {
1189
+ // A manifest that will not project is not this pass's problem to
1190
+ // report: the image-axis pass above raises on it with its own message.
1191
+ continue;
1192
+ }
1193
+ if (biomeShipsRuntimeImage(entry)) {continue;}
1194
+ const at = manifests.findIndex((existing) => existing.rel === candidate.rel);
1195
+ if (at === -1) {
1196
+ manifests.push(candidate);
1197
+ added += 1;
1198
+ } else {
1199
+ manifests[at] = candidate;
1200
+ replaced += 1;
1201
+ }
1202
+ }
1203
+ }
1204
+ // A CORPUS LINE, because this pass is invisible in its output when the two
1205
+ // axes happen to agree — which is the normal case and was the case on the run
1206
+ // that introduced it: zero content-only manifests differed, so a no-op and a
1207
+ // working pass produce byte-identical indexes. Printing what it SELECTED is
1208
+ // the only thing that distinguishes them, and a future regression to
1209
+ // `replaced=0` is then visible rather than silent.
1210
+ console.log(
1211
+ `[dist:index] source axis: ${replaced} content-only biome(s) re-read at the ` +
1212
+ `vendored commit, ${added} added that exist only there ` +
1213
+ `(${repositories.filter((r) => r.vendoredCommit && r.vendoredCommit !== r.commit).length} ` +
1214
+ `repositor(y/ies) where the axes differ).`,
1215
+ );
1216
+ return manifests.sort((a, b) => compareStrings(a.rel, b.rel));
1217
+ }
1218
+
1219
+ /**
1220
+ * Validate a service component's `protocol.databaseExtensions` the way the
1221
+ * kernel manifest schema does — an array of names matching the kernel's
1222
+ * extension-name GRAMMAR, with no repeats — and return it SORTED and
1223
+ * de-duplicated for the index. The predicate is read from the built kernel
1224
+ * dist so this plane cannot admit a name the kernel refuses, or refuse one it
1225
+ * admits.
1226
+ *
1227
+ * AN OPEN VOCABULARY, VALIDATED BY GRAMMAR — never a closed set. A PostgreSQL
1228
+ * extension name is owned by whoever runs the database, so a closed kernel enum
1229
+ * would produce members nothing can satisfy and would have to grow once per
1230
+ * customer: the same defect that deleted `DataResidency.Eu`. The kernel says so
1231
+ * itself — `PostgresExtensionNameSchema` is `z.string().regex(...)` and its
1232
+ * docstring reads "An OPEN vocabulary validated by GRAMMAR".
1233
+ *
1234
+ * This function previously read `Object.values(PostgresExtension)` and required
1235
+ * every entry to be a member. That export has never existed on any kernel
1236
+ * branch, so the biome-index build could not run at all while any manifest
1237
+ * declared the field, and its error blamed a stale dist. The consequence was
1238
+ * silent and total: the field reached ZERO of 62 subjects in the sealed lock.
1239
+ *
1240
+ * WHAT THIS DOES NOT DECIDE. Grammar admission is not installation permission.
1241
+ * Whether a deployment may install a given extension is
1242
+ * `infra.yaml#postgres.allowedExtensions`, enforced by the privileged bootstrap
1243
+ * — so a cloud tenant cannot install ad-hoc extensions even though the contract
1244
+ * can express any name. Contract breadth and runtime authority are separate
1245
+ * axes and this plane only carries the first.
1246
+ *
1247
+ * Throws naming the manifest and the component (`label`), because the index
1248
+ * build is where a manifest that would strand a database at boot should fail:
1249
+ * the runtime bootstrap only VERIFIES `pg_extension`, so a name nothing can
1250
+ * install is a pod that never comes up, discovered at the first deploy.
1251
+ */
1252
+ export function parseDatabaseExtensions(value, label) {
1253
+ const { isPostgresExtensionName, POSTGRES_EXTENSION_NAME_PATTERN, POSTGRES_EXTENSION_NAME_MAX_LENGTH } =
1254
+ loadKernelBiomeContracts();
1255
+ const grammar =
1256
+ `${String(POSTGRES_EXTENSION_NAME_PATTERN)}, at most ` +
1257
+ `${POSTGRES_EXTENSION_NAME_MAX_LENGTH} characters`;
1258
+ if (!Array.isArray(value)) {
1259
+ throw new Error(
1260
+ `${label} has protocol.databaseExtensions ${JSON.stringify(value)} — ` +
1261
+ `expected an array of PostgreSQL extension names (${grammar})`,
1262
+ );
1263
+ }
1264
+ const seen = new Set();
1265
+ for (const extension of value) {
1266
+ if (typeof extension !== 'string' || !isPostgresExtensionName(extension)) {
1267
+ throw new Error(
1268
+ `${label} has protocol.databaseExtensions entry ` +
1269
+ `${JSON.stringify(extension)} — not a PostgreSQL extension NAME the ` +
1270
+ `kernel grammar admits (${grammar}). The vocabulary is open, so this ` +
1271
+ `is a malformed name rather than an unlisted one; whether this ` +
1272
+ `deployment may install it is infra.yaml#postgres.allowedExtensions`,
1273
+ );
1274
+ }
1275
+ if (seen.has(extension)) {
1276
+ throw new Error(
1277
+ `${label} repeats "${extension}" in protocol.databaseExtensions — ` +
1278
+ `an extension is declared once`,
1279
+ );
1280
+ }
1281
+ seen.add(extension);
1282
+ }
1283
+ return [...seen].sort(compareStrings);
1284
+ }
1285
+
1286
+ /**
1287
+ * Read + minimally validate a raw `xema-biome.json` and project it into one
1288
+ * index entry. Fail-fast (throws) on missing id/scope/target or an unknown
1289
+ * tier. The full biome-manifest zod schema is NOT applied here — the index only
1290
+ * needs the packaging-relevant fields and must tolerate the wide variety of
1291
+ * (already-valid) manifests in the tree.
1292
+ */
1293
+ /**
1294
+ * The process facts of ONE platform-scheduled image component, in the exact
1295
+ * shape the kernel's `LockedServiceProcessFactsSchema` carries:
1296
+ * `{ componentKind, scalingMode, processFacts? }`.
1297
+ *
1298
+ * The same three inputs the kernel's `deriveProcessMemberFacts` reads off a
1299
+ * component (`kind`, `requirements.scaling.mode`, `processFacts`) — the rest
1300
+ * of a member's facts (origin, version, build source) already travel in the
1301
+ * lock and are joined by the deploy plane. Nothing is defaulted: a missing or
1302
+ * unknown scaling class REFUSES, naming the manifest, because a guessed class
1303
+ * bounds a process's replicas and the kernel requires the field on every
1304
+ * component. `processFacts` is validated by the kernel's own schema so the
1305
+ * index can never carry a block the lock schema would then refuse.
1306
+ */
1307
+ function componentProcessFacts(component, where) {
1308
+ const kernel = loadKernelBiomeContracts();
1309
+ const scalingMode = component.requirements?.scaling?.mode;
1310
+ if (!Object.values(kernel.BiomeScalingMode).includes(scalingMode)) {
1311
+ throw new Error(
1312
+ `${where} declares requirements.scaling.mode "${String(scalingMode)}" — ` +
1313
+ `expected one of ${Object.values(kernel.BiomeScalingMode).join(', ')}. ` +
1314
+ 'The scaling class is what bounds a process\'s replicas, so it is read, never defaulted.',
1315
+ );
1316
+ }
1317
+ const facts = { componentKind: component.kind, scalingMode };
1318
+ if (component.processFacts !== undefined) {
1319
+ const parsed = kernel.BiomeComponentProcessFactsSchema.safeParse(
1320
+ component.processFacts,
1321
+ );
1322
+ if (!parsed.success) {
1323
+ throw new Error(
1324
+ `${where} declares an invalid processFacts block: ` +
1325
+ parsed.error.issues
1326
+ .map((issue) => `[${issue.path.join('.')}] ${issue.message}`)
1327
+ .join('; '),
1328
+ );
1329
+ }
1330
+ facts.processFacts = parsed.data;
1331
+ }
1332
+ return facts;
1333
+ }
1334
+
1335
+ export async function manifestToIndexEntry({ raw, generatedIndex, rel, repo, path, authorizationModule }) {
1336
+ const xema = raw?.xema;
1337
+ if (!xema || typeof xema !== 'object') {
1338
+ throw new Error(`${rel}: missing top-level "xema" object`);
1339
+ }
1340
+ const id = xema.id;
1341
+ const tier = xema.scope;
1342
+ const target = xema.target;
1343
+ if (typeof id !== 'string' || id.length === 0) {
1344
+ throw new Error(`${rel}: missing or empty xema.id`);
1345
+ }
1346
+ if (typeof tier !== 'string' || tier.length === 0) {
1347
+ throw new Error(`${rel}: missing or empty xema.scope (the biome tier)`);
1348
+ }
1349
+ if (!isBiomeTier(tier)) {
1350
+ throw new Error(
1351
+ `${rel}: xema.scope "${tier}" is not a valid BiomeTier (one of ${biomeTierOrder().join(', ')})`,
1352
+ );
1353
+ }
1354
+ if (typeof target !== 'string' || target.length === 0) {
1355
+ throw new Error(`${rel}: missing or empty xema.target`);
1356
+ }
1357
+
1358
+ const version = raw.version;
1359
+ if (typeof version !== 'string' || version.length === 0) {
1360
+ throw new Error(`${rel}: missing or empty top-level "version"`);
1361
+ }
1362
+
1363
+ const origin = xema.trustTier ?? 'third-party';
1364
+ // `mandatory` is NOT a manifest field — it is a distribution decision applied
1365
+ // at resolve time from the `mandatory` selector flag (kernel-tier is always
1366
+ // mandatory). The index carries only manifest-intrinsic facts.
1367
+ const audience = xema.audience ?? 'org';
1368
+ // Resolved by the KERNEL predicate, never read raw — it applies two derived
1369
+ // refusals (operator audience, kernel tier) that a raw read silently skips.
1370
+ const storeListed = loadKernelBiomeContracts().isBiomeStoreListed(raw);
1371
+
1372
+ // Deployable service names this biome ships (manifest `ships.apis[].name`).
1373
+ // This is the biome→service mapping the deploy-roster generator needs; it is
1374
+ // baked into the index (and forwarded into the lock) because the generator
1375
+ // runs in a standalone checkout WITHOUT the biome sources. Pure skill/agent
1376
+ // biomes and web biomes have no API and resolve to [].
1377
+ const shipsApis = xema.ships?.apis;
1378
+ const legacyApis = Array.isArray(shipsApis) ? shipsApis : [];
1379
+ // component-contract-v5 shape: every image the biome ships is a
1380
+ // `xema.components[]` entry whose `kind` is "service" or "worker" and whose
1381
+ // `artifact.kind` is "oci-image". The remaining kinds (content, web, adapter,
1382
+ // and any host-module service) are loaded INTO another process and ship no
1383
+ // image of their own. This is DUAL SUPPORT for the staged ships.apis →
1384
+ // components migration, not a fallback.
1385
+ //
1386
+ // WHICH PLANE SCHEDULES the image is read from the component's `scheduler`
1387
+ // — the ONE owner of that fact, declared by the biome author. It is not
1388
+ // derivable from anything else here: the four `kind: "worker"` components in
1389
+ // xema-base are byte-identical in `entrypoint`, `protocol`, `executionModes`,
1390
+ // `requirements.scaling`, `requirements.io` and `requirements.state`, yet
1391
+ // three are standing Deployments and one must never be scheduled at all.
1392
+ //
1393
+ // Reading only `kind === "service"` (which this did until the contract regained
1394
+ // the discriminator) silently reclassified five image subjects: it dropped the
1395
+ // three platform-scheduled workers out of the roster entirely, dropped
1396
+ // `opencode-worker` out of the image plane altogether, and PROMOTED
1397
+ // `biome-adapter-host` to a rostered service that would have been given a
1398
+ // Deployment it must never have.
1399
+ const components = Array.isArray(xema.components) ? xema.components : [];
1400
+ const hostModuleComponents = components.filter(
1401
+ (component) =>
1402
+ component !== null &&
1403
+ typeof component === 'object' &&
1404
+ component.artifact?.kind === 'host-module',
1405
+ );
1406
+ const hostModules = hostModuleComponents
1407
+ .map((component) => {
1408
+ if (target !== 'server') {
1409
+ throw new Error(
1410
+ `${rel}: web biome "${id}" cannot ship host module "${String(component.key)}"`,
1411
+ );
1412
+ }
1413
+ if (!xema.bundleSource || typeof xema.bundleSource !== 'object') {
1414
+ throw new Error(
1415
+ `${rel}: host module "${String(component.key)}" requires xema.bundleSource; ` +
1416
+ 'a distribution host cannot fetch an executable from catalog prose or a source-tree path',
1417
+ );
1418
+ }
1419
+ if (
1420
+ component.entrypoint?.kind !== 'module-export' ||
1421
+ typeof component.entrypoint.module !== 'string' ||
1422
+ component.entrypoint.module.length === 0 ||
1423
+ typeof component.entrypoint.exportName !== 'string' ||
1424
+ component.entrypoint.exportName.length === 0
1425
+ ) {
1426
+ throw new Error(
1427
+ `${rel}: host module "${String(component.key)}" must declare a non-empty module-export entrypoint`,
1428
+ );
1429
+ }
1430
+ if (
1431
+ typeof component.artifact.path !== 'string' ||
1432
+ component.artifact.path.length === 0 ||
1433
+ typeof component.hostAbi?.name !== 'string' ||
1434
+ component.hostAbi.name.length === 0 ||
1435
+ typeof component.hostAbi?.versionRange !== 'string' ||
1436
+ component.hostAbi.versionRange.length === 0
1437
+ ) {
1438
+ throw new Error(
1439
+ `${rel}: host module "${String(component.key)}" must declare artifact.path and hostAbi`,
1440
+ );
1441
+ }
1442
+ return {
1443
+ componentKey: component.key,
1444
+ artifactPath: component.artifact.path,
1445
+ modulePath: component.entrypoint.module,
1446
+ exportName: component.entrypoint.exportName,
1447
+ hostAbi: {
1448
+ name: component.hostAbi.name,
1449
+ versionRange: component.hostAbi.versionRange,
1450
+ },
1451
+ bundleSource: xema.bundleSource,
1452
+ };
1453
+ })
1454
+ .sort((left, right) => compareStrings(left.componentKey, right.componentKey));
1455
+ // The BIOME-LEVEL bundle source — `xema.bundleSource`, WHERE the runtime
1456
+ // fetches this biome's bundle — for a biome whose bundle nothing above
1457
+ // already names.
1458
+ //
1459
+ // Until this existed the manifest's declaration reached the lock in exactly
1460
+ // one place: `hostModules[].bundleSource`, once per host module, because the
1461
+ // only runtime consumer that fetched a distribution-selected bundle was the
1462
+ // shared adapter host. A biome shipping `components[].kind = service` and no
1463
+ // host module — the `pai-core` / `document-translation` shape in the
1464
+ // customer edition — declared an `oci` source in its
1465
+ // manifest and reached biome-host with NONE, so the required-biome
1466
+ // reconciler reported it UNRESOLVABLE. Nothing at the runtime may guess the
1467
+ // coordinate instead: `xema biome publish` pushes to the manifest's declared
1468
+ // `ociRef` while `bundle-coordinate.mjs` derives `<registry>/<id>:<hash>`,
1469
+ // and a third rule matching one of them today fetches a DIFFERENT signed
1470
+ // bundle by the same publisher the day either moves, and fails nothing.
1471
+ //
1472
+ // STATED ONCE PER BIOME, and that is the byte-stability property: a biome
1473
+ // that ships host modules keeps its source on each host module, unchanged,
1474
+ // and gains NO biome-level key. Measured at `origin/develop`: the seventeen
1475
+ // manifests that declare `xema.bundleSource` are all connector biomes with a
1476
+ // host module, so every committed index entry and all nine committed vendor
1477
+ // locks are byte-identical under this projection. Emitting it for those too
1478
+ // would be the same value stated twice and would re-stamp five vendor locks
1479
+ // to lock schemaVersion 4 — a fleet rollout, not a projection. A reader
1480
+ // that wants one answer for any biome reads the biome-level field first and
1481
+ // falls back to the host-module projection (biome-host's
1482
+ // `resolveLockedBiomeBundleSource`).
1483
+ //
1484
+ // Validated by the KERNEL's own `BiomeBundleSourceSpecSchema` — the one
1485
+ // shape the lock reuses — so a malformed declaration fails HERE, naming the
1486
+ // manifest, rather than at `parseDistributionLock` forty fields in.
1487
+ let bundleSource;
1488
+ if (xema.bundleSource !== undefined && hostModules.length === 0) {
1489
+ const parsed = loadKernelBiomeContracts().BiomeBundleSourceSpecSchema.safeParse(
1490
+ xema.bundleSource,
1491
+ );
1492
+ if (!parsed.success) {
1493
+ throw new Error(
1494
+ `${rel}: xema.bundleSource is not a valid biome bundle source: ` +
1495
+ parsed.error.issues
1496
+ .map((issue) => `[${issue.path.join('.')}] ${issue.message}`)
1497
+ .join('; '),
1498
+ );
1499
+ }
1500
+ bundleSource = parsed.data;
1501
+ }
1502
+ const imageComponents = components.filter(
1503
+ (component) =>
1504
+ component !== null &&
1505
+ typeof component === 'object' &&
1506
+ (component.kind === 'service' || component.kind === 'worker') &&
1507
+ (component.artifact?.kind === 'oci-image' ||
1508
+ // A worker running its sibling service's image with its own entry
1509
+ // module is a standing workload like any other image component — it is
1510
+ // rostered, has process facts, task queues and peers — it just builds
1511
+ // no image of its own. `workerImages` below records whose it runs.
1512
+ (component.kind === 'worker' &&
1513
+ component.artifact?.kind === 'shared-oci-image')),
1514
+ );
1515
+ // A worker is a standing workload that runs an image: its own oci-image or a
1516
+ // sibling service's (shared-oci-image, validated by `sharedImageWorker`).
1517
+ // A worker with neither would be rostered by nothing and ship no image.
1518
+ const imagelessWorkers = components.filter(
1519
+ (component) =>
1520
+ component !== null &&
1521
+ typeof component === 'object' &&
1522
+ component.kind === 'worker' &&
1523
+ !imageComponents.includes(component),
1524
+ );
1525
+ if (imagelessWorkers.length > 0) {
1526
+ throw new Error(
1527
+ `${rel}: worker component(s) ${imagelessWorkers.map((component) => JSON.stringify(component.name ?? component.key ?? '?')).join(', ')} ` +
1528
+ 'resolve no worker image: a worker declares artifact.kind "oci-image" or "shared-oci-image"',
1529
+ );
1530
+ }
1531
+ // A biome must declare its images in EXACTLY ONE shape. Declaring both a
1532
+ // legacy `ships.apis[]` entry and a v5 image component is ambiguous during
1533
+ // the staged migration — fail fast instead of guessing.
1534
+ if (legacyApis.length > 0 && imageComponents.length > 0) {
1535
+ throw new Error(
1536
+ `${rel}: declares deployable images in BOTH xema.ships.apis[] (legacy) and ` +
1537
+ `xema.components[] (component-contract-v5) — a biome must use exactly one shape`,
1538
+ );
1539
+ }
1540
+ const serviceRequirements = [];
1541
+ const services = [];
1542
+ // `service → database key` for the few services whose key is not derivable.
1543
+ const serviceDatabases = {};
1544
+ // `service → PostgreSQL extensions its database must carry`, for the few
1545
+ // services that declare any. Travels beside `serviceDatabases` into the lock,
1546
+ // where the deployment's privileged database bootstrap reads it.
1547
+ const serviceDatabaseExtensions = {};
1548
+ // Images the biome ships that are never scheduled (`workload:
1549
+ // "runtime-component"`). They are built, content-addressed, signed and
1550
+ // mirrored exactly like a service image, but the deploy plane must never
1551
+ // roster a release for them — so they are carried in a SEPARATE array and
1552
+ // contribute NO service-dependency edge and NO service owner.
1553
+ const runtimeComponents = [];
1554
+ // Infrastructure resources each rostered component NEEDS
1555
+ // (`protocol.resources`) and the resource kinds it PROVIDES
1556
+ // (`protocol.providesResources`). Declared INTENT only: the resolver binds
1557
+ // each requirement to the one provider of its kind in the edition, and the
1558
+ // lock carries that binding as an authorization input the provider enforces.
1559
+ // Validated by the kernel's own schemas, at index build, naming the manifest.
1560
+ const serviceResources = [];
1561
+ const providesResources = [];
1562
+ // `service → what sharing a process needs to know about it`, for EVERY
1563
+ // platform-scheduled image component — see `componentProcessFacts`.
1564
+ const serviceProcessFacts = {};
1565
+ // Nexus services this biome's workers PROVIDE and REQUIRE (worker protocol
1566
+ // v2). Declared intent only, exactly like resources: the edition resolver
1567
+ // binds each requirement to the ONE shipped provider of the same
1568
+ // (service, version) and seals the answer in the lock's `nexusBindings`.
1569
+ const nexusProvides = [];
1570
+ const nexusRequires = [];
1571
+ // `component → its task queues`, for every protocol worker/v2 component —
1572
+ // see `componentWorkerTaskQueues`.
1573
+ const workerTaskQueues = {};
1574
+ // `service → the worker queues it starts workflows on` — see
1575
+ // `componentWorkflowClients`.
1576
+ const workflowClients = {};
1577
+ // `worker → { imageOf, entry }` for every worker that runs a sibling
1578
+ // service's image — see `sharedImageWorker`.
1579
+ const workerImages = {};
1580
+ if (imageComponents.length > 0) {
1581
+ for (const component of imageComponents) {
1582
+ const protocol =
1583
+ component.protocol !== null && typeof component.protocol === 'object'
1584
+ ? component.protocol
1585
+ : {};
1586
+ // A `service` registers under `protocol.serviceName`; a `worker` never
1587
+ // registers with the service registry, so its image name is the component
1588
+ // key. Same rule the producing repo's build plane applies — the image name
1589
+ // is the SERVICE build arg either way.
1590
+ const name =
1591
+ component.kind === 'service' ? protocol.serviceName : component.key;
1592
+ if (typeof name !== 'string' || name.length === 0) {
1593
+ throw new Error(
1594
+ `${rel}: xema.components[] kind "${component.kind}" component ` +
1595
+ `"${String(component.key)}" has no image name — a service declares ` +
1596
+ `protocol.serviceName, a worker a non-empty key`,
1597
+ );
1598
+ }
1599
+ if (target !== 'server') {
1600
+ throw new Error(
1601
+ `${rel}: web biome "${id}" cannot own deployable image "${name}"`,
1602
+ );
1603
+ }
1604
+ if (services.includes(name) || runtimeComponents.includes(name)) {
1605
+ throw new Error(
1606
+ `${rel}: xema.components[] declares image "${name}" more than once`,
1607
+ );
1608
+ }
1609
+ const where = `${rel}: xema.components[] "${name}"`;
1610
+ const requirements = parseComponentResourceDeclaration(
1611
+ 'ResourceRequirementsSchema',
1612
+ protocol.resources,
1613
+ `${where} protocol.resources`,
1614
+ );
1615
+ const provisions = parseComponentResourceDeclaration(
1616
+ 'ResourceProvisionsSchema',
1617
+ protocol.providesResources,
1618
+ `${where} protocol.providesResources`,
1619
+ );
1620
+ const nexus = componentNexusDeclarations(component, name, protocol, where);
1621
+ if (component.scheduler === 'runtime') {
1622
+ // A Nexus binding names the provider COMPONENT the deploy plane points
1623
+ // an endpoint at, which must be a rostered image; a component launched
1624
+ // per allocation is not one. Refused rather than dropped.
1625
+ if (nexus.provides.length > 0 || nexus.requires.length > 0) {
1626
+ throw new Error(
1627
+ `${where} declares scheduler "runtime" and protocol.nexus — a Nexus ` +
1628
+ `binding names a rostered worker, and a component that is never ` +
1629
+ `scheduled is not one`,
1630
+ );
1631
+ }
1632
+ // A resource binding is admitted against the caller's VERIFIED SERVICE
1633
+ // identity, and a runtime component — launched per allocation — is not
1634
+ // a rostered service that identity could name. Refused rather than
1635
+ // dropped: a skipped declaration would reach no lock and fail nothing.
1636
+ if (requirements.length > 0 || provisions.length > 0) {
1637
+ throw new Error(
1638
+ `${where} declares scheduler "runtime" and protocol.resources / ` +
1639
+ `protocol.providesResources — a resource binding names a rostered ` +
1640
+ `service, and a component that is never scheduled is not one`,
1641
+ );
1642
+ }
1643
+ // Never scheduled, so `requiresServices` — an edge between two ROSTERED
1644
+ // services — is meaningless on it, exactly as it was for the legacy
1645
+ // `workload: "runtime-component"` shape.
1646
+ if (
1647
+ Array.isArray(protocol.requiresServices) &&
1648
+ protocol.requiresServices.length > 0
1649
+ ) {
1650
+ throw new Error(
1651
+ `${rel}: xema.components[] "${name}" declares scheduler "runtime" and ` +
1652
+ `protocol.requiresServices — a component that is never scheduled ` +
1653
+ `cannot carry a service-dependency edge`,
1654
+ );
1655
+ }
1656
+ runtimeComponents.push(name);
1657
+ continue;
1658
+ }
1659
+ if (component.scheduler !== 'platform') {
1660
+ throw new Error(
1661
+ `${rel}: xema.components[] "${name}" declares scheduler ` +
1662
+ `"${String(component.scheduler)}" — every image component must declare ` +
1663
+ `"platform" (the deploy plane rosters a standing workload) or "runtime" ` +
1664
+ `(the runtime plane launches it on demand)`,
1665
+ );
1666
+ }
1667
+ const requiredServices = protocol.requiresServices ?? [];
1668
+ if (
1669
+ !Array.isArray(requiredServices) ||
1670
+ requiredServices.some(
1671
+ (service) => typeof service !== 'string' || service.length === 0,
1672
+ )
1673
+ ) {
1674
+ throw new Error(
1675
+ `${rel}: xema.components[] "${name}" protocol.requiresServices must be an ` +
1676
+ `array of non-empty service names`,
1677
+ );
1678
+ }
1679
+ // The shared-biome-cluster database key this service opens, declared ONLY
1680
+ // when it is neither the biome id nor the service name minus `-api`.
1681
+ // org-database-pool-api's ownership fence can derive those two and nothing
1682
+ // else, so a service whose schema name outlived a rename
1683
+ // (`scm-connector-api` → schema `scm-integration`) is unreachable unless
1684
+ // the component says so. The v5 spelling is `protocol.databaseKey`; the
1685
+ // legacy one below is `ships.apis[].database`.
1686
+ if (protocol.databaseKey !== undefined) {
1687
+ if (
1688
+ typeof protocol.databaseKey !== 'string' ||
1689
+ !/^[a-z][a-z0-9-]*$/.test(protocol.databaseKey)
1690
+ ) {
1691
+ throw new Error(
1692
+ `${rel}: xema.components[] "${name}" has protocol.databaseKey ` +
1693
+ `"${String(protocol.databaseKey)}" — expected lowercase kebab-case ` +
1694
+ `(it becomes a Postgres schema name)`,
1695
+ );
1696
+ }
1697
+ serviceDatabases[name] = protocol.databaseKey;
1698
+ }
1699
+ // The PostgreSQL extensions this service's database must carry BEFORE its
1700
+ // first migration runs (`protocol.databaseExtensions`, the sibling of
1701
+ // `databaseKey`). A database-level prerequisite the deployment's
1702
+ // privileged bootstrap satisfies, never something the service creates —
1703
+ // so it has to travel from the manifest to the lock, which is the one
1704
+ // artifact the deploy plane reads without the biome sources. Validated
1705
+ // here exactly as the kernel manifest schema does (closed set, no
1706
+ // repeats), because this projection reads the RAW manifest and the full
1707
+ // kernel schema is deliberately not applied on this path. ABSENT and
1708
+ // `[]` make the same claim — no extension — and project to no key.
1709
+ //
1710
+ // Deliberately NO legacy `ships.apis[]` spelling: that shape predates the
1711
+ // field, and every service that has an extension requirement is on the
1712
+ // component contract.
1713
+ if (protocol.databaseExtensions !== undefined) {
1714
+ const extensions = parseDatabaseExtensions(
1715
+ protocol.databaseExtensions,
1716
+ `${rel}: xema.components[] "${name}"`,
1717
+ );
1718
+ if (extensions.length > 0) {
1719
+ serviceDatabaseExtensions[name] = extensions;
1720
+ }
1721
+ }
1722
+ for (const requirement of requirements) {
1723
+ serviceResources.push({
1724
+ consumerService: name,
1725
+ kind: requirement.kind,
1726
+ logicalName: requirement.logicalName,
1727
+ scope: requirement.scope,
1728
+ inclusion: requirement.inclusion,
1729
+ });
1730
+ }
1731
+ for (const provision of provisions) {
1732
+ providesResources.push({ service: name, kind: provision.kind });
1733
+ }
1734
+ nexusProvides.push(...nexus.provides);
1735
+ nexusRequires.push(...nexus.requires);
1736
+ services.push(name);
1737
+ serviceProcessFacts[name] = componentProcessFacts(component, where);
1738
+ const queues = componentWorkerTaskQueues(component, protocol, where);
1739
+ if (queues !== null) {workerTaskQueues[name] = queues;}
1740
+ const clients = componentWorkflowClients(protocol, where);
1741
+ if (clients !== null) {workflowClients[name] = clients;}
1742
+ const shared = sharedImageWorker(component, components, where);
1743
+ if (shared !== null) {workerImages[name] = shared;}
1744
+ serviceRequirements.push({
1745
+ consumerService: name,
1746
+ requiredServices: [...new Set(requiredServices)].sort(compareStrings),
1747
+ });
1748
+ }
1749
+ } else {
1750
+ for (const api of legacyApis) {
1751
+ if (!api || typeof api.name !== 'string' || api.name.length === 0) {
1752
+ throw new Error(
1753
+ `${rel}: xema.ships.apis[] entry is missing a non-empty "name"`,
1754
+ );
1755
+ }
1756
+ if (target !== 'server') {
1757
+ throw new Error(
1758
+ `${rel}: web biome "${id}" cannot own deployable API service "${api.name}"`,
1759
+ );
1760
+ }
1761
+ const workload = api.workload ?? DEFAULT_BIOME_WORKLOAD_KIND;
1762
+ if (!BIOME_WORKLOAD_KINDS.has(workload)) {
1763
+ throw new Error(
1764
+ `${rel}: xema.ships.apis[] "${api.name}" has unknown workload ` +
1765
+ `"${String(workload)}" — expected one of ${[...BIOME_WORKLOAD_KINDS].join(', ')}`,
1766
+ );
1767
+ }
1768
+ if (workload === 'runtime-component') {
1769
+ // A runtime component has no standing workload, so `requiresServices`
1770
+ // (an edge between two ROSTERED services) is meaningless on it.
1771
+ if (
1772
+ Array.isArray(api.requiresServices) &&
1773
+ api.requiresServices.length > 0
1774
+ ) {
1775
+ throw new Error(
1776
+ `${rel}: xema.ships.apis[] "${api.name}" declares workload ` +
1777
+ `"runtime-component" and requiresServices — a component that is never ` +
1778
+ `scheduled cannot carry a service-dependency edge`,
1779
+ );
1780
+ }
1781
+ runtimeComponents.push(api.name);
1782
+ continue;
1783
+ }
1784
+ const requiredServices = api.requiresServices ?? [];
1785
+ if (
1786
+ !Array.isArray(requiredServices) ||
1787
+ requiredServices.some(
1788
+ (service) => typeof service !== 'string' || service.length === 0,
1789
+ )
1790
+ ) {
1791
+ throw new Error(
1792
+ `${rel}: xema.ships.apis[] "${api.name}" requiresServices must be an array of non-empty service names`,
1793
+ );
1794
+ }
1795
+ services.push(api.name);
1796
+ // The shared-biome-cluster database key this service opens, declared ONLY
1797
+ // when it is neither the biome id nor the service name minus `-api`.
1798
+ // org-database-pool-api's ownership fence can derive those two and
1799
+ // nothing else, so a service whose schema name outlived a rename
1800
+ // (`scm-connector-api` → schema `scm-integration`) is unreachable unless
1801
+ // the biome says so here. Carried into the lock, which is where the fence
1802
+ // reads it — a distribution-time fact, never a request-time claim.
1803
+ if (api.database !== undefined) {
1804
+ if (typeof api.database !== 'string' || !/^[a-z][a-z0-9-]*$/.test(api.database)) {
1805
+ throw new Error(
1806
+ `${rel}: xema.ships.apis[] "${api.name}" has database ` +
1807
+ `"${String(api.database)}" — expected lowercase kebab-case (it becomes a Postgres schema name)`,
1808
+ );
1809
+ }
1810
+ serviceDatabases[api.name] = api.database;
1811
+ }
1812
+ serviceRequirements.push({
1813
+ consumerService: api.name,
1814
+ requiredServices: [...new Set(requiredServices)].sort(compareStrings),
1815
+ });
1816
+ }
1817
+ }
1818
+ services.sort(compareStrings);
1819
+ runtimeComponents.sort(compareStrings);
1820
+ serviceRequirements.sort((a, b) =>
1821
+ compareStrings(a.consumerService, b.consumerService),
1822
+ );
1823
+ serviceResources.sort(compareResourceRows);
1824
+ nexusProvides.sort(compareNexusRows);
1825
+ nexusRequires.sort(compareNexusRows);
1826
+ providesResources.sort(
1827
+ (a, b) => compareStrings(a.service, b.service) || compareStrings(a.kind, b.kind),
1828
+ );
1829
+ // The provider derives a resource's PHYSICAL identity from the owning biome
1830
+ // plus (kind, scope, logicalName). Two consumers of this biome naming the
1831
+ // same tuple would be handed ONE physical resource while each believes it
1832
+ // owns it — so the declaration is refused here, where the biome is the whole
1833
+ // subject, rather than discovered at the provider.
1834
+ const physicalResources = new Map();
1835
+ for (const row of serviceResources) {
1836
+ const identity = `${row.kind}\0${row.scope}\0${row.logicalName}`;
1837
+ const holder = physicalResources.get(identity);
1838
+ if (holder !== undefined && holder !== row.consumerService) {
1839
+ throw new Error(
1840
+ `${rel}: services "${holder}" and "${row.consumerService}" both declare ` +
1841
+ `${row.scope}-scoped ${row.kind} "${row.logicalName}" — biome "${id}" ` +
1842
+ `derives ONE physical resource from that, so two consumers would share ` +
1843
+ `it while each believes it owns it. Give each its own logicalName.`,
1844
+ );
1845
+ }
1846
+ physicalResources.set(identity, row.consumerService);
1847
+ }
1848
+ // The two arrays partition the biome's images — a name in both would be
1849
+ // simultaneously rostered and unschedulable.
1850
+ for (const name of runtimeComponents) {
1851
+ if (services.includes(name)) {
1852
+ throw new Error(
1853
+ `${rel}: xema.ships.apis[] "${name}" is declared both as a service and ` +
1854
+ `as a runtime component — an image is either scheduled or it is not`,
1855
+ );
1856
+ }
1857
+ }
1858
+
1859
+ // Biomes this one REQUIRES, read from the committed generated index rather
1860
+ // than restated in the manifest.
1861
+ //
1862
+ // This used to union three manifest fields: `xema.dependencies` (server
1863
+ // biomes), `xema.requires` (an object form NO manifest has ever declared and
1864
+ // the strict schema now rejects — dead, and deleted with this change), and
1865
+ // `xema.requiresServerBiomes` (web biomes). That last one was never authored:
1866
+ // it was a generator-owned projection of "package dependencies ∩ generated
1867
+ // api-clients whose owner is a server biome". A web biome carries no
1868
+ // `xema.dependencies`, so it was the ONLY producer of `requires` for a web
1869
+ // biome — reading it after the field was deleted would silently produce
1870
+ // `requires: []`, which makes `requiresServerBiomesSatisfied` permanently
1871
+ // true and kills orphan detection outright.
1872
+ //
1873
+ // `requiredBiomeDependencyIds` answers the same question for BOTH targets
1874
+ // from one typed, evidence-carrying edge set: `xema.dependencies` for server
1875
+ // biomes and the derived client edges for web ones, filtered to
1876
+ // kind=biome + strength=required (service/connector/capability/host-abi edges
1877
+ // and optional edges are excluded by construction). Parsing with the kernel
1878
+ // schema first means a malformed or foreign index fails here rather than
1879
+ // resolving to an empty list.
1880
+ const kernelBiome = loadKernelBiomeContracts();
1881
+ let parsedIndex;
1882
+ try {
1883
+ parsedIndex = kernelBiome.BiomeBundleGeneratedIndexSchema.parse(generatedIndex);
1884
+ } catch (error) {
1885
+ throw new Error(
1886
+ `${rel}: the committed ${BIOME_GENERATED_INDEX_FILENAME} is not a valid generated index: ${error.message}`,
1887
+ );
1888
+ }
1889
+ if (parsedIndex.biome.biomeId !== id) {
1890
+ throw new Error(
1891
+ `${rel}: ${BIOME_GENERATED_INDEX_FILENAME} declares biomeId "${parsedIndex.biome.biomeId}" but the ` +
1892
+ `manifest declares "${id}" — the index does not describe this biome`,
1893
+ );
1894
+ }
1895
+ if (parsedIndex.biome.version !== version) {
1896
+ throw new Error(
1897
+ `${rel}: ${BIOME_GENERATED_INDEX_FILENAME} declares version "${parsedIndex.biome.version}" but the ` +
1898
+ `manifest declares "${version}" — the index is stale; run \`pnpm derive:biome-index:write\``,
1899
+ );
1900
+ }
1901
+ // Already de-duplicated and sorted by the kernel helper.
1902
+ const requiresUnique = [...kernelBiome.requiredBiomeDependencyIds(parsedIndex.biome)];
1903
+
1904
+ // Optional server biomes a web biome can integrate with but does not require
1905
+ // (never cause an ORPHANED resolution). Carried separately from `requires`.
1906
+ const optionalServerBiomes = Array.isArray(xema.optionalServerBiomes)
1907
+ ? [...new Set(xema.optionalServerBiomes)].sort()
1908
+ : [];
1909
+
1910
+ // Capability DOMAINS this biome claims to own (manifest
1911
+ // `xema.ownsCapabilityDomains`). The claim is carried verbatim into the index
1912
+ // and then into the lock, where — and ONLY where — it becomes trustworthy:
1913
+ // being present in a RESOLVED, COMMITTED, drift-checked lock makes ownership a
1914
+ // distribution-author decision rather than a biome's self-declaration. See
1915
+ // `first-party-capability-domains.ts` in @xemahq/contracts.
1916
+ //
1917
+ // The claim is EXPLICIT — it never defaults to `capabilityDomain` or `id`. An
1918
+ // implicit default would silently make every locked biome first-party in its
1919
+ // own id-domain, which would newly REFUSE org provider bindings that are
1920
+ // legitimately org-bound today (e.g. `media:transcribe@1`).
1921
+ const ownsCapabilityDomains = Array.isArray(xema.ownsCapabilityDomains)
1922
+ ? [...new Set(xema.ownsCapabilityDomains)].sort(compareStrings)
1923
+ : [];
1924
+ for (const domain of ownsCapabilityDomains) {
1925
+ if (typeof domain !== 'string' || !/^[a-z][a-z0-9-]*$/.test(domain)) {
1926
+ throw new Error(
1927
+ `${rel}: xema.ownsCapabilityDomains contains ${JSON.stringify(domain)}, ` +
1928
+ `which is not a valid capability domain (^[a-z][a-z0-9-]*$)`,
1929
+ );
1930
+ }
1931
+ }
1932
+ // A biome that declares a projection domain MUST also own it — otherwise it
1933
+ // projects refs into a domain any org could shadow with a provider binding.
1934
+ if (
1935
+ typeof xema.capabilityDomain === 'string' &&
1936
+ xema.capabilityDomain.length > 0 &&
1937
+ !ownsCapabilityDomains.includes(xema.capabilityDomain)
1938
+ ) {
1939
+ throw new Error(
1940
+ `${rel}: xema.capabilityDomain "${xema.capabilityDomain}" is not listed in ` +
1941
+ `xema.ownsCapabilityDomains [${ownsCapabilityDomains.join(', ')}] — a biome that ` +
1942
+ `projects capabilities into a domain must also claim ownership of it.`,
1943
+ );
1944
+ }
1945
+
1946
+ // --- Catalog projection (consumed by biome-host's DistributionCatalogSource) ---
1947
+ // The index/lock is the single source of truth for the biome CATALOG (which
1948
+ // biomes exist + the facts the web/server registry DTOs need), replacing the
1949
+ // filesystem-scan + vendored-manifest path. We carry the RAW manifest fields
1950
+ // the catalog derivation needs; biome-host applies the SAME `@xemahq/biome-sdk`
1951
+ // helpers (isSystemSurface / isBiomeStoreListed / defaultEnabledFor) it already
1952
+ // uses, so the derivation logic lives in exactly one place (no reimplementation
1953
+ // here, no drift).
1954
+ const displayName =
1955
+ typeof xema.displayName === 'string' && xema.displayName.length > 0
1956
+ ? xema.displayName
1957
+ : id;
1958
+ // npm/workspace package name. For WEB biomes this is the SPA's import specifier
1959
+ // (load-bearing) — required. For server biomes it is cosmetic.
1960
+ const packageName = typeof raw.name === 'string' ? raw.name : null;
1961
+ if (target === 'web' && !packageName) {
1962
+ throw new Error(
1963
+ `${rel}: web biome is missing a top-level "name" (the SPA import specifier).`,
1964
+ );
1965
+ }
1966
+ // Raw `xema.display` block (icon/category/summary/accent), passed through
1967
+ // verbatim; absent → omitted.
1968
+ const display =
1969
+ xema.display && typeof xema.display === 'object' ? xema.display : undefined;
1970
+ // Raw `xema.systemSurface` flag (renders in the org rail vs the store). Only a
1971
+ // boolean is meaningful; absence is normal and handled by the sdk helper.
1972
+ const systemSurface =
1973
+ typeof xema.systemSurface === 'boolean' ? xema.systemSurface : undefined;
1974
+ // Content-addressed source-manifest hash — lets biome-host (and diagnostics)
1975
+ // detect when a biome's authored manifest changed, distinct from distribution
1976
+ // resolution. Hash of the canonical manifest JSON.
1977
+ const { computeBiomeSourceManifestHash } = await loadKernelDistributionSchemas();
1978
+ const sourceManifestHash = await computeBiomeSourceManifestHash(raw);
1979
+
1980
+ // --- Installation class (the `{ installationClass }` selector's input) -----
1981
+ // HOW ESSENTIAL this biome is, folded from its components' declarations by
1982
+ // the KERNEL's `foldInstallationClass` — most essential wins. The fold is not
1983
+ // re-implemented here for the same reason `storeListed` calls
1984
+ // `isBiomeStoreListed`: a second implementation of a kernel rule is a second
1985
+ // answer waiting to drift, and this one decides whether a biome is on an
1986
+ // edition's FLOOR.
1987
+ //
1988
+ // The class is declared per COMPONENT and selected per BIOME because the
1989
+ // biome is the unit a distribution installs. Measured on 2026-09-05 across
1990
+ // all 34 classified manifests in repos/xema-base: NO biome mixes classes
1991
+ // across its runnable components, so today the fold is exact rather than
1992
+ // lossy. It is still a fold, and the kernel's docblock says which direction
1993
+ // it takes and why.
1994
+ //
1995
+ // OMITTED when no component declares a class — the third answer, never a
1996
+ // default. That also keeps the index byte-stable for every biome that
1997
+ // declares nothing, which at `origin/main` is currently all of them.
1998
+ const installationClass = kernelBiome.foldInstallationClass(
1999
+ components.map((component) =>
2000
+ component !== null && typeof component === 'object'
2001
+ ? component.installationClass
2002
+ : undefined,
2003
+ ),
2004
+ );
2005
+
2006
+ return {
2007
+ id,
2008
+ tier,
2009
+ target,
2010
+ origin,
2011
+ version,
2012
+ audience,
2013
+ storeListed,
2014
+ // The kernel fold of the components' own declarations; OMITTED when none
2015
+ // declares one (see above), so an unclassed biome stays byte-stable.
2016
+ ...(installationClass !== undefined ? { installationClass } : {}),
2017
+ services,
2018
+ // Omitted when empty — which is almost every biome — so the index stays
2019
+ // byte-stable for everything that needs no declaration.
2020
+ ...(Object.keys(serviceDatabases).length > 0
2021
+ ? {
2022
+ serviceDatabases: Object.fromEntries(
2023
+ Object.entries(serviceDatabases).sort(([a], [b]) =>
2024
+ compareStrings(a, b),
2025
+ ),
2026
+ ),
2027
+ }
2028
+ : {}),
2029
+ // Same omission rule as `serviceDatabases`, for the same reason: at
2030
+ // `origin/main` no manifest declares an extension, so an unconditional key
2031
+ // would move the bytes of every one of the ~117 entries for a fact none of
2032
+ // them carries.
2033
+ ...(Object.keys(serviceDatabaseExtensions).length > 0
2034
+ ? {
2035
+ serviceDatabaseExtensions: Object.fromEntries(
2036
+ Object.entries(serviceDatabaseExtensions).sort(([a], [b]) =>
2037
+ compareStrings(a, b),
2038
+ ),
2039
+ ),
2040
+ }
2041
+ : {}),
2042
+ runtimeComponents,
2043
+ hostModules,
2044
+ // Omitted when the biome declares none, or when its host modules already
2045
+ // carry it — see the projection above. At `origin/develop` that is every
2046
+ // committed entry, which is what keeps `biome-index.json` byte-stable.
2047
+ ...(bundleSource !== undefined ? { bundleSource } : {}),
2048
+ // The biome's authorization module (plan biome-declared-authorization.md
2049
+ // §2.1): the author's SOURCE manifest `{ envelope, moduleFiles, testFile }`
2050
+ // and the `signedBiome` facts the sealer STAMPS onto it with the kernel's
2051
+ // `stampAuthorizationModuleContribution` — derived here, from this pinned
2052
+ // manifest and its generated index, never from the author's envelope.
2053
+ // Omitted when the biome ships none — every committed biome today — so
2054
+ // `biome-index.json` stays byte-stable.
2055
+ ...(authorizationModule !== undefined
2056
+ ? {
2057
+ authorizationModule: {
2058
+ source: authorizationModule,
2059
+ signedBiome: {
2060
+ tier,
2061
+ declaredBiomeDependencies: requiresUnique,
2062
+ serviceNames: [
2063
+ ...new Set(
2064
+ (Array.isArray(xema.components) ? xema.components : [])
2065
+ .map((component) => component?.protocol?.serviceName)
2066
+ .filter((name) => typeof name === 'string' && name.length > 0),
2067
+ ),
2068
+ ].sort(compareStrings),
2069
+ },
2070
+ },
2071
+ }
2072
+ : {}),
2073
+ serviceRequirements,
2074
+ // Omitted when empty — which is every biome today — so the index stays
2075
+ // byte-stable for everything that declares nothing.
2076
+ ...(serviceResources.length > 0 ? { serviceResources } : {}),
2077
+ ...(providesResources.length > 0 ? { providesResources } : {}),
2078
+ // Omitted when empty — every committed biome today — so the index stays
2079
+ // byte-stable for everything that declares no Nexus service.
2080
+ ...(nexusProvides.length > 0 ? { nexusProvides } : {}),
2081
+ ...(nexusRequires.length > 0 ? { nexusRequires } : {}),
2082
+ // The v2 workers' task queues; omitted when the biome ships none (every
2083
+ // committed biome today), so the index stays byte-stable.
2084
+ ...(Object.keys(workerTaskQueues).length > 0
2085
+ ? {
2086
+ workerTaskQueues: Object.fromEntries(
2087
+ Object.entries(workerTaskQueues).sort(([a], [b]) => compareStrings(a, b)),
2088
+ ),
2089
+ }
2090
+ : {}),
2091
+ // The queues each http service starts workflows on; omitted when no
2092
+ // component declares any, so the index stays byte-stable.
2093
+ ...(Object.keys(workflowClients).length > 0
2094
+ ? {
2095
+ workflowClients: Object.fromEntries(
2096
+ Object.entries(workflowClients).sort(([a], [b]) => compareStrings(a, b)),
2097
+ ),
2098
+ }
2099
+ : {}),
2100
+ // The workers that run a sibling service's image with their own entry
2101
+ // module; omitted when the biome has none, so the index stays byte-stable.
2102
+ ...(Object.keys(workerImages).length > 0
2103
+ ? {
2104
+ workerImages: Object.fromEntries(
2105
+ Object.entries(workerImages).sort(([a], [b]) => compareStrings(a, b)),
2106
+ ),
2107
+ }
2108
+ : {}),
2109
+ // Every platform-scheduled image this biome ships, with the facts a
2110
+ // composed process is graded on. Present for EVERY service of a
2111
+ // component-contract biome — a service declaring nothing still gets its
2112
+ // derived `componentKind` + `scalingMode`, because absent facts are what
2113
+ // the kernel grades as `unknown-member`. Omitted only when the biome ships
2114
+ // no platform-scheduled image (and for the retired `ships.apis[]` shape,
2115
+ // which declares no scaling class to read).
2116
+ ...(Object.keys(serviceProcessFacts).length > 0
2117
+ ? {
2118
+ serviceProcessFacts: Object.fromEntries(
2119
+ Object.entries(serviceProcessFacts).sort(([a], [b]) =>
2120
+ compareStrings(a, b),
2121
+ ),
2122
+ ),
2123
+ }
2124
+ : {}),
2125
+ // The biome's signed dependency contract — DERIVED by the builder from the
2126
+ // SDK seams its code calls, read from the committed generated index and
2127
+ // validated there by the kernel schema. The edge set positive closure
2128
+ // closes over. Omitted when the biome has not adopted one, which is every
2129
+ // committed biome today, so `biome-index.json` stays byte-stable; a
2130
+ // positive-closure edition REFUSES a root or required biome that lacks it.
2131
+ ...(parsedIndex.biome.dependencyContract === undefined
2132
+ ? {}
2133
+ : { dependencyContract: parsedIndex.biome.dependencyContract }),
2134
+ repo,
2135
+ path,
2136
+ requires: requiresUnique,
2137
+ // --- catalog projection ---
2138
+ // Omitted entirely when the biome claims nothing, so the index stays stable
2139
+ // for the overwhelming majority of biomes that own no domain.
2140
+ ...(ownsCapabilityDomains.length > 0 ? { ownsCapabilityDomains } : {}),
2141
+ displayName,
2142
+ packageName,
2143
+ optionalServerBiomes,
2144
+ ...(display ? { display } : {}),
2145
+ ...(systemSurface !== undefined ? { systemSurface } : {}),
2146
+ sourceManifestHash,
2147
+ };
2148
+ }
2149
+
2150
+ /**
2151
+ * Build the full biome index (array of entries) by walking every manifest.
2152
+ * Sorted deterministically by (tier order, id, target, repo).
2153
+ */
2154
+ function buildServiceOwners(entries, platformServiceIndex) {
2155
+ const serviceOwners = new Map();
2156
+ const registerOwner = (serviceName, owner, sourceDescription) => {
2157
+ const existing = serviceOwners.get(serviceName);
2158
+ if (existing !== undefined) {
2159
+ throw new Error(
2160
+ `service "${serviceName}" has ambiguous owners: ${existing.sourceDescription} and ${sourceDescription}`,
2161
+ );
2162
+ }
2163
+ serviceOwners.set(serviceName, { owner, sourceDescription });
2164
+ };
2165
+ for (const entry of entries) {
2166
+ for (const serviceName of entry.services) {
2167
+ registerOwner(
2168
+ serviceName,
2169
+ {
2170
+ kind: 'biome',
2171
+ biomeId: entry.id,
2172
+ target: entry.target,
2173
+ repo: entry.repo,
2174
+ path: entry.path,
2175
+ version: entry.version,
2176
+ sourceManifestHash: entry.sourceManifestHash,
2177
+ },
2178
+ `biome "${entry.id}" (${entry.target}@${entry.repo}/${entry.path})`,
2179
+ );
2180
+ }
2181
+ }
2182
+ for (const source of platformServiceIndex.services) {
2183
+ registerOwner(
2184
+ source.name,
2185
+ { kind: 'platform', source },
2186
+ `platform service (${source.sourceCommit}@${source.repo}/${source.path})`,
2187
+ );
2188
+ }
2189
+ return serviceOwners;
2190
+ }
2191
+
2192
+ export function resolveServiceDependencyOwners(entries, platformServiceIndex) {
2193
+ const serviceOwners = buildServiceOwners(entries, platformServiceIndex);
2194
+ for (const entry of entries) {
2195
+ const serviceDependencies = [];
2196
+ for (const requirement of entry.serviceRequirements) {
2197
+ for (const requiredService of requirement.requiredServices) {
2198
+ const resolved = serviceOwners.get(requiredService);
2199
+ if (!resolved) {
2200
+ throw new Error(
2201
+ `biome "${entry.id}" API "${requirement.consumerService}" requires unknown service "${requiredService}"`,
2202
+ );
2203
+ }
2204
+ if (
2205
+ resolved.owner.kind === 'biome' &&
2206
+ resolved.owner.target !== 'server'
2207
+ ) {
2208
+ throw new Error(
2209
+ `biome "${entry.id}" API "${requirement.consumerService}" requires service "${requiredService}" ` +
2210
+ `owned by cross-target ${resolved.owner.target} biome "${resolved.owner.biomeId}"`,
2211
+ );
2212
+ }
2213
+ serviceDependencies.push({
2214
+ consumerService: requirement.consumerService,
2215
+ requiredService,
2216
+ owner: resolved.owner,
2217
+ });
2218
+ }
2219
+ }
2220
+ serviceDependencies.sort((a, b) => {
2221
+ const consumer = compareStrings(a.consumerService, b.consumerService);
2222
+ return consumer === 0
2223
+ ? compareStrings(a.requiredService, b.requiredService)
2224
+ : consumer;
2225
+ });
2226
+ entry.serviceDependencies = serviceDependencies;
2227
+ delete entry.serviceRequirements;
2228
+ }
2229
+ return entries;
2230
+ }
2231
+
2232
+ /**
2233
+ * Offline parity guard for the committed index: every recorded edge must still
2234
+ * name the one canonical owner identity carried by that same index plus the
2235
+ * committed platform-service index.
2236
+ */
2237
+ export function validateServiceDependencyOwnerParity(
2238
+ entries,
2239
+ platformServiceIndex,
2240
+ ) {
2241
+ const serviceOwners = buildServiceOwners(entries, platformServiceIndex);
2242
+ for (const entry of entries) {
2243
+ if (!Array.isArray(entry.serviceDependencies)) {
2244
+ throw new Error(
2245
+ `biome "${entry.id}" has no serviceDependencies projection`,
2246
+ );
2247
+ }
2248
+ for (const edge of entry.serviceDependencies) {
2249
+ const canonical = serviceOwners.get(edge.requiredService);
2250
+ if (!canonical) {
2251
+ throw new Error(
2252
+ `biome "${entry.id}" API "${edge.consumerService}" requires unknown service "${edge.requiredService}"`,
2253
+ );
2254
+ }
2255
+ if (
2256
+ canonical.owner.kind === 'biome' &&
2257
+ canonical.owner.target !== 'server'
2258
+ ) {
2259
+ throw new Error(
2260
+ `biome "${entry.id}" API "${edge.consumerService}" requires cross-target service "${edge.requiredService}"`,
2261
+ );
2262
+ }
2263
+ if (serializeJson(edge.owner) !== serializeJson(canonical.owner)) {
2264
+ throw new Error(
2265
+ `biome "${entry.id}" API "${edge.consumerService}" service "${edge.requiredService}" ` +
2266
+ `owner identity does not match ${canonical.sourceDescription}`,
2267
+ );
2268
+ }
2269
+ }
2270
+ }
2271
+ return entries;
2272
+ }
2273
+
2274
+ export async function buildBiomeIndex(
2275
+ platformServiceIndex,
2276
+ sourceRepositories = distributionBiomeSourceRepositories(),
2277
+ sourceAuthority,
2278
+ ) {
2279
+ const authority =
2280
+ sourceAuthority ??
2281
+ createSourceAuthority({
2282
+ knownRepositories: sourceRepositories.map(({ repo }) => repo),
2283
+ });
2284
+ const entries = [];
2285
+ for (const manifest of await committedBiomeManifests(sourceRepositories, authority)) {
2286
+ entries.push(await manifestToIndexEntry(manifest));
2287
+ }
2288
+ resolveServiceDependencyOwners(entries, platformServiceIndex);
2289
+ assertDeclaredDatabasesUnowned(entries);
2290
+ entries.sort(compareIndexEntries);
2291
+ return entries;
2292
+ }
2293
+
2294
+ /**
2295
+ * A declared `serviceDatabases` key must name a database NOTHING else in the
2296
+ * platform already answers to.
2297
+ *
2298
+ * This is the bound on the entitlement. org-database-pool-api hands out the
2299
+ * shared cluster's credential for whatever key the caller is entitled to, and
2300
+ * the two derived entitlements (biome id, service name minus `-api`) are
2301
+ * self-evidently non-transferable because they are derivations of the caller's
2302
+ * own verified identity. A DECLARED key is not — it is a free string in a
2303
+ * manifest — so the only thing that keeps it from naming `llm-registry` is
2304
+ * this check. The pool re-asserts it when it loads the lock; failing here too
2305
+ * means a manifest that would strand or hijack a database fails the build
2306
+ * instead of the fleet.
2307
+ */
2308
+ export function assertDeclaredDatabasesUnowned(entries) {
2309
+ const claimedBy = new Map();
2310
+ for (const entry of entries) {
2311
+ claimedBy.set(entry.id, `biome "${entry.id}"`);
2312
+ }
2313
+ for (const entry of entries) {
2314
+ for (const service of entry.services ?? []) {
2315
+ claimedBy.set(service.replace(/-api$/, ''), `service "${service}"`);
2316
+ }
2317
+ }
2318
+ for (const entry of entries) {
2319
+ for (const [service, key] of Object.entries(entry.serviceDatabases ?? {})) {
2320
+ if (key === service.replace(/-api$/, '') || key === entry.id) {
2321
+ // Already reachable by derivation; the declaration widens nothing.
2322
+ continue;
2323
+ }
2324
+ const claimant = claimedBy.get(key);
2325
+ if (claimant !== undefined) {
2326
+ throw new Error(
2327
+ `biome "${entry.id}" declares database "${key}" for service ` +
2328
+ `"${service}", but "${key}" is already the database of ${claimant}. ` +
2329
+ `A declared database must name one no other biome or service owns — ` +
2330
+ `otherwise the declaration hands "${service}" another principal's schema.`,
2331
+ );
2332
+ }
2333
+ claimedBy.set(key, `service "${service}"`);
2334
+ }
2335
+ }
2336
+ }
2337
+
2338
+ /**
2339
+ * Build the `capabilityDomainOwners` reservation table from the FULL biome
2340
+ * index — every biome the packaging pipeline discovered, NOT the subset an
2341
+ * edition selects.
2342
+ *
2343
+ * WHY THE FULL INDEX (this is the whole point of the table):
2344
+ *
2345
+ * Ownership is a property of the PLATFORM, not of what an edition happens to
2346
+ * preload. If the table were built from the selected set, a trimmed edition
2347
+ * (`minimal`, `appliance-lean`) that ships without `knowledge-base` would
2348
+ * leave `kb` unowned — and an org admin could then install `knowledge-base`
2349
+ * from the store and bind `kb:page.read@1` to a URL they control, silently
2350
+ * shadowing the platform data plane. Being AUTHORED by a first-party biome is
2351
+ * what makes a domain un-shadowable; whether a given edition preloads that
2352
+ * biome is a packaging choice and must never change the security answer.
2353
+ *
2354
+ * So every edition gets the IDENTICAL table. A domain is reserved even when
2355
+ * its owning biome is absent from the lock — that is precisely the case the
2356
+ * reservation exists to cover.
2357
+ *
2358
+ * Sorted by `domain` for deterministic, diffable output.
2359
+ *
2360
+ * @throws when two biomes claim the same domain. Ambiguous ownership decides
2361
+ * whether an org may bind refs under the domain, so it has no safe resolution
2362
+ * and must fail the build rather than pick a winner.
2363
+ */
2364
+ export function buildCapabilityDomainOwners(index) {
2365
+ const owners = new Map();
2366
+ for (const entry of index) {
2367
+ for (const domain of entry.ownsCapabilityDomains ?? []) {
2368
+ const incumbent = owners.get(domain);
2369
+ if (incumbent !== undefined && incumbent !== entry.id) {
2370
+ throw new Error(
2371
+ `capability domain "${domain}" is claimed by BOTH biome "${incumbent}" and biome "${entry.id}". ` +
2372
+ `A domain has exactly one owner — ownership decides whether an org may bind refs in it, ` +
2373
+ `so an ambiguous claim is refused.`,
2374
+ );
2375
+ }
2376
+ owners.set(domain, entry.id);
2377
+ }
2378
+ }
2379
+ return [...owners.entries()]
2380
+ .map(([domain, biomeId]) => ({ domain, biomeId }))
2381
+ .sort((a, b) => compareStrings(a.domain, b.domain));
2382
+ }
2383
+
2384
+ /** Deterministic ordering used for both the index and the lock. */
2385
+ export function compareIndexEntries(a, b) {
2386
+ const order = biomeTierOrder();
2387
+ const ta = order.indexOf(a.tier);
2388
+ const tb = order.indexOf(b.tier);
2389
+ if (ta !== tb) {return ta - tb;}
2390
+ if (a.id !== b.id) {return a.id < b.id ? -1 : 1;}
2391
+ if (a.target !== b.target) {return a.target < b.target ? -1 : 1;}
2392
+ if (a.repo !== b.repo) {return a.repo < b.repo ? -1 : 1;}
2393
+ return 0;
2394
+ }
2395
+
2396
+ /** Stable JSON serialization (pretty, trailing newline) for committed files. */
2397
+ export function serializeJson(value) {
2398
+ return `${JSON.stringify(value, null, 2)}\n`;
2399
+ }
2400
+
2401
+ /**
2402
+ * Shared generated-artifact currency gate. Callers supply canonical serialized
2403
+ * bytes; any authority change must make a previously committed projection fail.
2404
+ */
2405
+ export function assertGeneratedArtifactCurrent(
2406
+ label,
2407
+ committed,
2408
+ generated,
2409
+ regenerateCommand,
2410
+ ) {
2411
+ if (committed !== generated) {
2412
+ throw new Error(
2413
+ `${label} is out of date. Run \`${regenerateCommand}\`.`,
2414
+ );
2415
+ }
2416
+ }
2417
+
2418
+ /** sha256 hex of a string. */
2419
+ export function sha256Hex(input) {
2420
+ return createHash('sha256').update(input).digest('hex');
2421
+ }
2422
+
2423
+ const EXACT_SEMVER_PATTERN =
2424
+ /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-(?:(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*))?(?:\+(?:[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/;
2425
+ const IMAGE_REPOSITORY_SEGMENT_PATTERN =
2426
+ /^[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*$/;
2427
+ const MAX_REGISTRY_HOST_LENGTH = 253;
2428
+ const MAX_DNS_LABEL_LENGTH = 63;
2429
+ const MAX_REPOSITORY_PATH_LENGTH = 255;
2430
+
2431
+ function isCanonicalRegistryPort(port) {
2432
+ if (port === undefined) {return true;}
2433
+ if (!/^[0-9]+$/.test(port)) {return false;}
2434
+ const numericPort = Number(port);
2435
+ return numericPort >= 1 && numericPort <= 65535;
2436
+ }
2437
+
2438
+ function isCanonicalDnsHost(host) {
2439
+ if (
2440
+ host.length === 0 ||
2441
+ host.length > MAX_REGISTRY_HOST_LENGTH ||
2442
+ host !== host.toLowerCase() ||
2443
+ host.endsWith('.')
2444
+ ) {
2445
+ return false;
2446
+ }
2447
+ if (/^[0-9.]+$/.test(host)) {return isIP(host) === 4;}
2448
+ const labels = host.split('.');
2449
+ return labels.every(
2450
+ (label) =>
2451
+ label.length >= 1 &&
2452
+ label.length <= MAX_DNS_LABEL_LENGTH &&
2453
+ /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(label),
2454
+ );
2455
+ }
2456
+
2457
+ export function isCanonicalRegistryAuthority(authority) {
2458
+ if (authority.startsWith('[')) {
2459
+ const closingBracket = authority.indexOf(']');
2460
+ if (closingBracket < 0) {return false;}
2461
+ const address = authority.slice(1, closingBracket);
2462
+ const suffix = authority.slice(closingBracket + 1);
2463
+ if (
2464
+ address.length === 0 ||
2465
+ address !== address.toLowerCase() ||
2466
+ isIP(address) !== 6
2467
+ ) {
2468
+ return false;
2469
+ }
2470
+ if (suffix.length === 0) {return true;}
2471
+ return suffix.startsWith(':') && isCanonicalRegistryPort(suffix.slice(1));
2472
+ }
2473
+
2474
+ const firstColon = authority.indexOf(':');
2475
+ const lastColon = authority.lastIndexOf(':');
2476
+ if (firstColon !== lastColon) {return false;}
2477
+ const host = lastColon < 0 ? authority : authority.slice(0, lastColon);
2478
+ const port = lastColon < 0 ? undefined : authority.slice(lastColon + 1);
2479
+ return isCanonicalDnsHost(host) && isCanonicalRegistryPort(port);
2480
+ }
2481
+
2482
+ function isUntaggedImageRepository(imageRef) {
2483
+ if (
2484
+ typeof imageRef !== 'string' ||
2485
+ imageRef.length === 0 ||
2486
+ imageRef.trim() !== imageRef ||
2487
+ imageRef.includes('@') ||
2488
+ imageRef.includes('://') ||
2489
+ imageRef.includes('//') ||
2490
+ imageRef.endsWith('/')
2491
+ ) {
2492
+ return false;
2493
+ }
2494
+ const [authority, ...segments] = imageRef.split('/');
2495
+ const repositoryPath = segments.join('/');
2496
+ if (
2497
+ segments.length === 0 ||
2498
+ repositoryPath.length > MAX_REPOSITORY_PATH_LENGTH ||
2499
+ !isCanonicalRegistryAuthority(authority) ||
2500
+ segments.some(
2501
+ (segment) =>
2502
+ segment.includes(':') ||
2503
+ !IMAGE_REPOSITORY_SEGMENT_PATTERN.test(segment),
2504
+ )
2505
+ ) {
2506
+ return false;
2507
+ }
2508
+ return true;
2509
+ }
2510
+
2511
+ export const IMAGE_PRODUCER_EVIDENCE_PREREQUISITE =
2512
+ 'a complete authorized signed image-producer evidence set that binds the ' +
2513
+ 'exact distribution inputHash, source repository commit, sourceManifestHash, ' +
2514
+ 'source-tree contentHash, image repository, OCI manifest digest, complete ' +
2515
+ 'platform set, SBOM, provenance, distribution release, and customer entitlement';
2516
+
2517
+ /**
2518
+ * Enforce the immutable fields carried by the existing DistributionLock
2519
+ * schema. The kernel schema owns the shape; this packaging-plane guard owns
2520
+ * the stronger provenance invariant: a resolved lock may contain only exact
2521
+ * versions and full sha256 source-manifest identities.
2522
+ */
2523
+ export function validateImmutableBiomeIndex(index) {
2524
+ for (const biome of index) {
2525
+ if (!EXACT_SEMVER_PATTERN.test(biome.version)) {
2526
+ throw new Error(
2527
+ `biome "${biome.id}" has mutable or invalid ` +
2528
+ `version "${biome.version}" — resolved locks require an exact semantic version`,
2529
+ );
2530
+ }
2531
+ if (!SHA256_HEX_PATTERN.test(biome.sourceManifestHash)) {
2532
+ throw new Error(
2533
+ `biome "${biome.id}" has invalid ` +
2534
+ `sourceManifestHash "${String(biome.sourceManifestHash)}" — expected 64 lowercase sha256 hex characters`,
2535
+ );
2536
+ }
2537
+ const services = new Set();
2538
+ for (const service of biome.services ?? []) {
2539
+ if (services.has(service)) {
2540
+ throw new Error(
2541
+ `biome "${biome.id}" owns duplicate service "${service}"`,
2542
+ );
2543
+ }
2544
+ services.add(service);
2545
+ }
2546
+ // Runtime components are built/signed/mirrored like services but never
2547
+ // rostered. They must be a disjoint set from `services` — a name in both
2548
+ // would be simultaneously scheduled and unschedulable.
2549
+ const runtimeComponents = new Set();
2550
+ for (const component of biome.runtimeComponents ?? []) {
2551
+ if (runtimeComponents.has(component)) {
2552
+ throw new Error(
2553
+ `biome "${biome.id}" owns duplicate runtime component "${component}"`,
2554
+ );
2555
+ }
2556
+ if (services.has(component)) {
2557
+ throw new Error(
2558
+ `biome "${biome.id}" declares "${component}" both as a service and as a ` +
2559
+ `runtime component — an image is either scheduled or it is not`,
2560
+ );
2561
+ }
2562
+ runtimeComponents.add(component);
2563
+ }
2564
+ const edges = new Set();
2565
+ for (const edge of biome.serviceDependencies ?? []) {
2566
+ if (!services.has(edge.consumerService)) {
2567
+ throw new Error(
2568
+ `biome "${biome.id}" service dependency names unknown consumer API "${edge.consumerService}"`,
2569
+ );
2570
+ }
2571
+ const edgeKey = `${edge.consumerService}\0${edge.requiredService}`;
2572
+ if (edges.has(edgeKey)) {
2573
+ throw new Error(
2574
+ `biome "${biome.id}" has duplicate service dependency ${edge.consumerService} -> ${edge.requiredService}`,
2575
+ );
2576
+ }
2577
+ edges.add(edgeKey);
2578
+ if (edge.owner?.kind === 'biome') {
2579
+ if (edge.owner.target !== 'server') {
2580
+ throw new Error(
2581
+ `biome "${biome.id}" service dependency ${edge.consumerService} -> ${edge.requiredService} ` +
2582
+ `has cross-target ${String(edge.owner.target)} owner`,
2583
+ );
2584
+ }
2585
+ if (!EXACT_SEMVER_PATTERN.test(edge.owner.version)) {
2586
+ throw new Error(
2587
+ `biome "${biome.id}" service dependency owner "${edge.owner.biomeId}" has invalid exact version`,
2588
+ );
2589
+ }
2590
+ if (!SHA256_HEX_PATTERN.test(edge.owner.sourceManifestHash)) {
2591
+ throw new Error(
2592
+ `biome "${biome.id}" service dependency owner "${edge.owner.biomeId}" has invalid sourceManifestHash`,
2593
+ );
2594
+ }
2595
+ } else if (edge.owner?.kind === 'platform') {
2596
+ if (edge.owner.source?.name !== edge.requiredService) {
2597
+ throw new Error(
2598
+ `biome "${biome.id}" service dependency ${edge.consumerService} -> ${edge.requiredService} ` +
2599
+ `has mismatched platform owner "${String(edge.owner.source?.name)}"`,
2600
+ );
2601
+ }
2602
+ } else {
2603
+ throw new Error(
2604
+ `biome "${biome.id}" service dependency ${edge.consumerService} -> ${edge.requiredService} has no recognized owner`,
2605
+ );
2606
+ }
2607
+ }
2608
+ }
2609
+ return index;
2610
+ }
2611
+
2612
+ /**
2613
+ * The distribution lock as the resolve pipeline reads it: the UNSEALED
2614
+ * document a resolve just wrote, or the SEALED v5 lock a prior seal committed.
2615
+ * Both carry the same body; the kernel's two parsers each refuse the other
2616
+ * shape, so this dispatches on the document's own marker and never guesses.
2617
+ * A pre-v5 lock is refused by the kernel (`unsupported-schema-version`).
2618
+ */
2619
+ export function parseResolvedDistributionLock(schemas, raw) {
2620
+ return raw !== null && typeof raw === 'object' && 'unsealedSchema' in raw
2621
+ ? schemas.parseUnsealedDistributionLock(raw)
2622
+ : schemas.parseDistributionLockForReading(raw);
2623
+ }
2624
+
2625
+ /**
2626
+ * The text two distribution locks are compared by: the UNSEALED projection of
2627
+ * each, with the resolve timestamp removed. A committed lock is sealed (v5) and
2628
+ * a fresh resolve is unsealed; the kernel's `unsealedProjection` maps the first
2629
+ * onto the shape of the second (its key order is stable, so the comparison is
2630
+ * byte for byte). Anything that is not one of the two v5 shapes is refused by
2631
+ * the kernel's parsers, a pre-v5 lock with `unsupported-schema-version`.
2632
+ */
2633
+ export function comparableDistributionLock(schemas, raw) {
2634
+ const unsealed = schemas.parseUnsealedDistributionLock(
2635
+ raw !== null && typeof raw === 'object' && 'unsealedSchema' in raw
2636
+ ? raw
2637
+ : schemas.unsealedProjection(schemas.parseDistributionLock(raw)),
2638
+ );
2639
+ const { resolvedAt: _resolvedAt, ...rest } = unsealed;
2640
+ return serializeJson(rest);
2641
+ }
2642
+
2643
+ export function validateImmutableDistributionLock(lock) {
2644
+ if (!SHA256_HEX_PATTERN.test(lock.inputHash)) {
2645
+ throw new Error(
2646
+ `distribution "${lock.distributionId}" has invalid inputHash "${String(lock.inputHash)}" — ` +
2647
+ 'expected 64 lowercase sha256 hex characters',
2648
+ );
2649
+ }
2650
+ try {
2651
+ validateImmutableBiomeIndex(lock.biomes ?? []);
2652
+ } catch (error) {
2653
+ throw new Error(`distribution "${lock.distributionId}": ${error.message}`, {
2654
+ cause: error,
2655
+ });
2656
+ }
2657
+ return lock;
2658
+ }
2659
+
2660
+ /**
2661
+ * Enforce source-addressed image identities after canonical ImageLock parsing.
2662
+ * `imageRef` is deliberately an untagged repository; the immutable build tag
2663
+ * is the exact sha256 `contentHash` under the lock's explicit hash algorithm.
2664
+ */
2665
+ export function validateImmutableImageLock(lock, schemas) {
2666
+ if (!schemas) {
2667
+ throw new Error('trusted kernel distribution schemas are required');
2668
+ }
2669
+ if (
2670
+ lock.schemaVersion !== 3 ||
2671
+ lock.sourceHashAlgorithm !== schemas.ImageSourceHashAlgorithm.ImageInputV2
2672
+ ) {
2673
+ throw new Error(
2674
+ `distribution "${lock.distributionId}" has incompatible image-lock schema ${String(lock.schemaVersion)} ` +
2675
+ `and sourceHashAlgorithm "${String(lock.sourceHashAlgorithm)}"`,
2676
+ );
2677
+ }
2678
+ const identities = new Set();
2679
+ for (const subject of lock.subjects ?? []) {
2680
+ const identity = `${subject.kind}\0${subject.id}`;
2681
+ if (identities.has(identity)) {
2682
+ throw new Error(
2683
+ `distribution "${lock.distributionId}" has duplicate runtime image subject "${subject.kind}:${subject.id}"`,
2684
+ );
2685
+ }
2686
+ identities.add(identity);
2687
+ if (!isUntaggedImageRepository(subject.imageRef)) {
2688
+ throw new Error(
2689
+ `distribution "${lock.distributionId}": runtime subject "${subject.kind}:${subject.id}" has mutable or invalid ` +
2690
+ `imageRef "${subject.imageRef}" — expected an untagged image repository`,
2691
+ );
2692
+ }
2693
+ if (!SHA256_HEX_PATTERN.test(subject.contentHash)) {
2694
+ throw new Error(
2695
+ `distribution "${lock.distributionId}": runtime subject "${subject.kind}:${subject.id}" has invalid ` +
2696
+ `contentHash "${String(subject.contentHash)}" — expected 64 lowercase sha256 hex characters`,
2697
+ );
2698
+ }
2699
+ const owners = new Set();
2700
+ for (const owner of subject.owners ?? []) {
2701
+ const key = serializeJson(owner);
2702
+ if (owners.has(key)) {
2703
+ throw new Error(
2704
+ `distribution "${lock.distributionId}": runtime subject "${subject.kind}:${subject.id}" has a duplicate owner`,
2705
+ );
2706
+ }
2707
+ owners.add(key);
2708
+ }
2709
+ }
2710
+ const platformServices = new Set();
2711
+ for (const binding of lock.platformServiceBindings ?? []) {
2712
+ if (platformServices.has(binding.platformService)) {
2713
+ throw new Error(
2714
+ `distribution "${lock.distributionId}" has duplicate runtime image binding for platform service "${binding.platformService}"`,
2715
+ );
2716
+ }
2717
+ platformServices.add(binding.platformService);
2718
+ if (!identities.has(`${binding.subjectKind}\0${binding.subjectId}`)) {
2719
+ throw new Error(
2720
+ `distribution "${lock.distributionId}": platform service "${binding.platformService}" binds unknown runtime subject "${binding.subjectKind}:${binding.subjectId}"`,
2721
+ );
2722
+ }
2723
+ }
2724
+ return lock;
2725
+ }
2726
+
2727
+ export function canonicalPublicHttpsRepositoryUrl(raw, context) {
2728
+ if (typeof raw !== 'string' || raw.length === 0 || raw.trim() !== raw) {
2729
+ throw new Error(`${context} must be a non-empty canonical HTTPS URL`);
2730
+ }
2731
+ let parsed;
2732
+ try {
2733
+ parsed = new URL(raw);
2734
+ } catch {
2735
+ throw new Error(`${context} is not a valid URL`);
2736
+ }
2737
+ if (parsed.protocol !== 'https:') {
2738
+ throw new Error(`${context} must use HTTPS`);
2739
+ }
2740
+ if (parsed.username.length > 0 || parsed.password.length > 0) {
2741
+ throw new Error(
2742
+ `${context} must not contain username/password credentials`,
2743
+ );
2744
+ }
2745
+ if (
2746
+ parsed.search.length > 0 ||
2747
+ parsed.hash.length > 0 ||
2748
+ raw.includes('?') ||
2749
+ raw.includes('#')
2750
+ ) {
2751
+ throw new Error(`${context} must not contain a query or fragment`);
2752
+ }
2753
+ if (parsed.hostname.length === 0) {
2754
+ throw new Error(`${context} must identify a repository host`);
2755
+ }
2756
+ return parsed.toString();
2757
+ }
2758
+
2759
+ /**
2760
+ * Assert that an exact 40-hex commit exists in a source repository. Delegates
2761
+ * to `@xemahq/distribution-source-hash` — the tooling holds no second copy of
2762
+ * the Git-object reader the content hash is built on.
2763
+ */
2764
+ export function assertGitCommitExists(repoDir, commit) {
2765
+ return distributionSourceHash().assertGitCommitExists(repoDir, commit);
2766
+ }
2767
+
2768
+ /** Split the NUL-delimited output of a `-z` Git plumbing command. */
2769
+ function decodeNullDelimitedGitRecords(raw, context) {
2770
+ return distributionSourceHash().decodeNullDelimitedGitRecords(raw, context);
2771
+ }
2772
+
2773
+ /**
2774
+ * Hash the immutable tree of an exact Git commit — a distribution image
2775
+ * subject's `contentHash`, and therefore its published image tag.
2776
+ */
2777
+ export function hashGitSourceTree(
2778
+ repoDir,
2779
+ repoRelativeSourcePath,
2780
+ commit,
2781
+ algorithm,
2782
+ ) {
2783
+ return distributionSourceHash().hashGitSourceTree(
2784
+ repoDir,
2785
+ repoRelativeSourcePath,
2786
+ commit,
2787
+ algorithm,
2788
+ );
2789
+ }
2790
+
2791
+ /**
2792
+ * Every workspace package in a repository at an exact commit, keyed by name.
2793
+ *
2794
+ * Delegates to `@xemahq/distribution-source-hash`. The tooling holds no second
2795
+ * copy of the workspace-graph reader: it is derived by walking the commit's
2796
+ * tree for `package.json` files rather than by expanding `pnpm-workspace.yaml`
2797
+ * globs, and a hand-rolled YAML parser on the critical path of a release
2798
+ * coordinate is exactly what that package exists to avoid.
2799
+ */
2800
+ export function workspacePackagesAtCommit(repoDir, commit, tree) {
2801
+ return distributionSourceHash().workspacePackagesAtCommit(repoDir, commit, tree);
2802
+ }
2803
+
2804
+ /**
2805
+ * The path index of one commit's tree — the input `workspacePackagesAtCommit`
2806
+ * scans for `package.json` files.
2807
+ *
2808
+ * Exposed so a caller can build it ONCE per repository and reuse it across
2809
+ * every biome in that repository. Building it per biome would re-read the whole
2810
+ * tree 123 times for one index.
2811
+ */
2812
+ export function gitCommitTreeIndex(repoDir, commit) {
2813
+ return distributionSourceHash().GitCommitTreeIndex.atCommit(repoDir, commit);
2814
+ }
2815
+
2816
+ /**
2817
+ * The transitive workspace-package closure of one package directory, EXCLUDING
2818
+ * the root — the root is hashed separately as its own source tree, so
2819
+ * including it would frame one digest twice.
2820
+ *
2821
+ * Returns `[]` for a directory that is not a workspace member at all. For a
2822
+ * BIOME that is the correct answer and not a gap: a biome with no
2823
+ * `package.json` runs no build, so nothing outside its directory can change its
2824
+ * bundle's bytes. `check-biome-bundle-closure-derivable` pins the premise that
2825
+ * makes that true — a biome shipping BUILT OUTPUT always has a manifest.
2826
+ */
2827
+ export function workspaceClosure(rootDirectory, graph, commit) {
2828
+ return distributionSourceHash().workspaceClosure(rootDirectory, graph, commit);
2829
+ }
2830
+
2831
+ /**
2832
+ * Length-framed hash update — the same framing the image content hash uses.
2833
+ *
2834
+ * Framing is not decoration: without it, two different component lists can
2835
+ * concatenate to identical bytes and collide onto one coordinate. Delegated
2836
+ * rather than re-implemented for that reason.
2837
+ */
2838
+ export function updateFramedHash(hash, field, value) {
2839
+ return distributionSourceHash().updateFramedHash(hash, field, value);
2840
+ }
2841
+
2842
+ /**
2843
+ * Hash the FULL build-input closure of one service image — the subject's
2844
+ * `contentHash`, and therefore its published image tag.
2845
+ *
2846
+ * Delegates to `@xemahq/distribution-source-hash`, which is the same code every
2847
+ * producing repository's CI runs to derive the tag it pushes. The tooling holds
2848
+ * no second copy: a divergence would resolve a lock whose images sit at a
2849
+ * coordinate nothing published.
2850
+ */
2851
+ export function hashImageBuildInputs(request, algorithm) {
2852
+ return distributionSourceHash().hashImageBuildInputs(request, algorithm);
2853
+ }
2854
+
2855
+ /**
2856
+ * Read + parse a JSON file out of an exact Git commit (never the work tree).
2857
+ *
2858
+ * This is the seam every authored source file crosses on its way into a lock
2859
+ * or an index — `xema-biome.json`, a platform service's `package.json`,
2860
+ * `cross-repo-build-inputs.json`, the committed generated biome index — so it
2861
+ * parses STRICTLY: a duplicated object key is refused, naming the commit, the
2862
+ * path and the JSON pointer of the object. `JSON.parse` would keep the last
2863
+ * occurrence, which is how a `resources` declaration was dropped from a sealed
2864
+ * lock with every reader agreeing (see `strict-json.mjs`).
2865
+ *
2866
+ * The commit check, the buffer ceiling and the exact-UTF-8 decode are the
2867
+ * source-hash package's own; only the parse differs from its
2868
+ * `readGitJsonAtCommit`, which is lenient and is therefore not delegated to.
2869
+ */
2870
+ export function readGitJsonAtCommit(repoDir, repoRelativePath, commit) {
2871
+ const sourceHash = distributionSourceHash();
2872
+ sourceHash.assertGitCommitExists(repoDir, commit);
2873
+ let raw;
2874
+ try {
2875
+ raw = execFileSync(
2876
+ 'git',
2877
+ ['-C', repoDir, 'show', `${commit}:${repoRelativePath}`],
2878
+ {
2879
+ encoding: 'buffer',
2880
+ maxBuffer: sourceHash.GIT_OBJECT_MAX_BUFFER_BYTES,
2881
+ stdio: ['ignore', 'pipe', 'pipe'],
2882
+ },
2883
+ );
2884
+ } catch {
2885
+ throw new Error(
2886
+ `cannot read "${repoRelativePath}" from Git commit "${commit}"`,
2887
+ );
2888
+ }
2889
+ const context = `Git object "${commit}:${repoRelativePath}"`;
2890
+ return parseStrictJson(sourceHash.decodeExactUtf8(raw, context), context);
2891
+ }
2892
+
2893
+ /**
2894
+ * The producing repository's OWN committed record of what it vendored.
2895
+ *
2896
+ * `vendor-cross-repo-inputs.mjs` reads exactly this file, at the commit being
2897
+ * built, to emit the `--vendored <dest>=<repoDir>@<commit>:<src>` arguments it
2898
+ * hands `compute-image-tag.mjs`. It is therefore the ONLY source of the vendored
2899
+ * commit that can agree with the tag CI pushes.
2900
+ */
2901
+ export const CROSS_REPO_BUILD_INPUTS_PATH =
2902
+ 'tooling/release/cross-repo-build-inputs.json';
2903
+
2904
+ /** Identity of one vendored input — the tuple that survives a pin bump. */
2905
+ export function crossRepoInputKey(input) {
2906
+ return [input.sourceRepo, input.sourcePath, input.destinationPath].join('\u0000');
2907
+ }
2908
+
2909
+ /**
2910
+ * Resolve the vendored inputs of ONE consumer repository at ONE commit.
2911
+ *
2912
+ * WHY THE COMMIT COMES FROM THE CONSUMER, NOT FROM THE INDEX
2913
+ *
2914
+ * The index's `crossRepoBuildInputs` carry no `sourceCommit`; they name only
2915
+ * WHICH content is vendored. Both this tooling and the parity gate used to
2916
+ * supply the missing commit from `runtimeSourceIndex.repositoryPins`, and CI
2917
+ * supplies it from the file above — so the two disagreed the moment a consumer
2918
+ * repository's mirror lagged the index by one pin, which is its NORMAL resting
2919
+ * state: the mirror is generated FROM the index and committed separately, so it
2920
+ * is stale by construction between the index moving and the consumer
2921
+ * regenerating.
2922
+ *
2923
+ * That is not a cosmetic disagreement. `crossRepo.sourceCommit` is framed into
2924
+ * the component KEY of the image-input hash, so a pin that differs by one
2925
+ * commit yields a different `contentHash` FOR BYTE-IDENTICAL VENDORED CONTENT —
2926
+ * a coordinate no CI run has ever pushed or ever will. Measured 2026-09-07 on
2927
+ * `origin/main`: five of the sixty-eight coordinates in the eight editions
2928
+ * (`backlog-api`, `channel-gateway-api`, `control-plane-api`,
2929
+ * `document-render-api` from repos/xema-community, and `biome-host-api` from
2930
+ * repos/xema-base) 404'd in the registry for exactly this reason, while the tags
2931
+ * their producing CI DID push were present and served on both platforms.
2932
+ *
2933
+ * Reading the index pin also imposed a requirement that cannot be satisfied.
2934
+ * `repos/xema-base` vendors from `repos/xema-community` and vice versa, so
2935
+ * "every mirror agrees with the index" is a mutual fixed point over a 2-cycle,
2936
+ * and bumping either side invalidates the other. No such pair exists: scanning
2937
+ * eighty commits of each on `origin/main` found ZERO mutually coherent pairs.
2938
+ * Reading the consumer's own record removes the fixed point entirely — an
2939
+ * image's identity depends only on the repository that builds it.
2940
+ *
2941
+ * It stays edition-INDEPENDENT, which is the property the previous design was
2942
+ * protecting: the record is a property of the consumer's COMMIT, not of any
2943
+ * edition's resolved commit map, so every edition shipping a subject still
2944
+ * derives the same `contentHash` for it.
2945
+ */
2946
+ export function resolveCrossRepoVendoredInputs({
2947
+ indexInputs,
2948
+ consumerRepo,
2949
+ consumerRepoDir,
2950
+ consumerCommit,
2951
+ }) {
2952
+ const declared = indexInputs.filter(
2953
+ (input) => input.consumerRepo === consumerRepo,
2954
+ );
2955
+ if (declared.length === 0) {return [];}
2956
+ if (!consumerCommit) {
2957
+ throw new Error(
2958
+ `cross-repo build inputs are declared for "${consumerRepo}", but this ` +
2959
+ `distribution resolves no commit for it — the vendored content cannot ` +
2960
+ `be pinned, so the image cannot be addressed`,
2961
+ );
2962
+ }
2963
+ let record;
2964
+ try {
2965
+ record = readGitJsonAtCommit(
2966
+ consumerRepoDir,
2967
+ CROSS_REPO_BUILD_INPUTS_PATH,
2968
+ consumerCommit,
2969
+ );
2970
+ } catch (cause) {
2971
+ throw new Error(
2972
+ `"${consumerRepo}" declares cross-repo build inputs but ` +
2973
+ `${CROSS_REPO_BUILD_INPUTS_PATH} is unreadable at ${consumerCommit}. ` +
2974
+ `That file is what its CI feeds to compute-image-tag.mjs; without it ` +
2975
+ `no coordinate this tooling derives can match the tag CI pushes.`,
2976
+ { cause },
2977
+ );
2978
+ }
2979
+ const mirrored = new Map();
2980
+ for (const input of record?.inputs ?? []) {
2981
+ mirrored.set(crossRepoInputKey(input), input.sourceCommit);
2982
+ }
2983
+ // Set equality in BOTH directions. A commit that LAGS is normal and is now
2984
+ // honoured; a differing input SET is a genuine declaration disagreement, and
2985
+ // it moves the hash just as surely — so it must fail rather than resolve to a
2986
+ // coordinate built from a different closure.
2987
+ const declaredKeys = new Set(declared.map((input) => crossRepoInputKey(input)));
2988
+ const resolved = declared.map((input) => {
2989
+ const sourceCommit = mirrored.get(crossRepoInputKey(input));
2990
+ if (!sourceCommit) {
2991
+ throw new Error(
2992
+ `the runtime image source index declares a cross-repo build input for ` +
2993
+ `"${consumerRepo}" vendoring "${input.sourceRepo}:${input.sourcePath}" ` +
2994
+ `to "${input.destinationPath}", but ${CROSS_REPO_BUILD_INPUTS_PATH} at ` +
2995
+ `${consumerCommit} declares no such input — its CI does not vendor it, ` +
2996
+ `so the coordinate this tooling would derive is one nothing builds`,
2997
+ );
2998
+ }
2999
+ return {
3000
+ consumerService: input.consumerService ?? null,
3001
+ destinationPath: input.destinationPath,
3002
+ sourceRepoDir: sourcePath(input.sourceRepo),
3003
+ sourcePath: input.sourcePath,
3004
+ sourceCommit,
3005
+ };
3006
+ });
3007
+ for (const key of mirrored.keys()) {
3008
+ if (declaredKeys.has(key)) {continue;}
3009
+ const [sourceRepo, sourcePath, destinationPath] = key.split('\0');
3010
+ throw new Error(
3011
+ `${CROSS_REPO_BUILD_INPUTS_PATH} at ${consumerCommit} vendors ` +
3012
+ `"${sourceRepo}:${sourcePath}" to "${destinationPath}" for ` +
3013
+ `"${consumerRepo}", which the runtime image source index does not ` +
3014
+ `declare — its CI bakes content this tooling does not hash`,
3015
+ );
3016
+ }
3017
+ return resolved;
3018
+ }