@xemahq/xema 0.26.0 → 0.28.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 (230) hide show
  1. package/README.md +70 -37
  2. package/dist/assets/distribution-assets.manifest.json +1 -2
  3. package/dist/cli.js +2 -2
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/dev.js +1 -0
  6. package/dist/commands/dev.js.map +1 -1
  7. package/dist/commands/down.d.ts.map +1 -1
  8. package/dist/commands/down.js +7 -1
  9. package/dist/commands/down.js.map +1 -1
  10. package/dist/commands/edition.d.ts +3 -0
  11. package/dist/commands/edition.d.ts.map +1 -0
  12. package/dist/commands/edition.js +46 -0
  13. package/dist/commands/edition.js.map +1 -0
  14. package/dist/commands/init.js +2 -2
  15. package/dist/commands/init.js.map +1 -1
  16. package/dist/commands/serve.js +1 -0
  17. package/dist/commands/serve.js.map +1 -1
  18. package/dist/commands/up.js +1 -0
  19. package/dist/commands/up.js.map +1 -1
  20. package/dist/dev/env-defaults.d.ts.map +1 -1
  21. package/dist/dev/env-defaults.js +5 -2
  22. package/dist/dev/env-defaults.js.map +1 -1
  23. package/dist/dev/infra-preflight.d.ts +3 -2
  24. package/dist/dev/infra-preflight.d.ts.map +1 -1
  25. package/dist/dev/infra-preflight.js +18 -8
  26. package/dist/dev/infra-preflight.js.map +1 -1
  27. package/dist/edition/verify-image-evidence.bundle.mjs +54283 -0
  28. package/dist/edition/verify-image-evidence.bundle.provenance.json +2877 -0
  29. package/dist/runtime/config/schema.d.ts +1 -0
  30. package/dist/runtime/config/schema.d.ts.map +1 -1
  31. package/dist/runtime/custody/local-custody.d.ts +12 -0
  32. package/dist/runtime/custody/local-custody.d.ts.map +1 -0
  33. package/dist/runtime/custody/local-custody.js +22 -0
  34. package/dist/runtime/custody/local-custody.js.map +1 -0
  35. package/dist/runtime/custody/openbao-custody-bootstrap.d.ts +18 -0
  36. package/dist/runtime/custody/openbao-custody-bootstrap.d.ts.map +1 -0
  37. package/dist/runtime/custody/openbao-custody-bootstrap.js +50 -0
  38. package/dist/runtime/custody/openbao-custody-bootstrap.js.map +1 -0
  39. package/dist/runtime/custody/openbao-custody-store.d.ts +18 -0
  40. package/dist/runtime/custody/openbao-custody-store.d.ts.map +1 -0
  41. package/dist/runtime/custody/openbao-custody-store.js +55 -0
  42. package/dist/runtime/custody/openbao-custody-store.js.map +1 -0
  43. package/dist/runtime/custody/openbao-http-client.d.ts +34 -0
  44. package/dist/runtime/custody/openbao-http-client.d.ts.map +1 -0
  45. package/dist/runtime/custody/openbao-http-client.js +154 -0
  46. package/dist/runtime/custody/openbao-http-client.js.map +1 -0
  47. package/dist/runtime/custody/openbao-local-constants.d.ts +22 -0
  48. package/dist/runtime/custody/openbao-local-constants.d.ts.map +1 -0
  49. package/dist/runtime/custody/openbao-local-constants.js +48 -0
  50. package/dist/runtime/custody/openbao-local-constants.js.map +1 -0
  51. package/dist/runtime/custody/openbao-local-runner.d.ts +16 -0
  52. package/dist/runtime/custody/openbao-local-runner.d.ts.map +1 -0
  53. package/dist/runtime/custody/openbao-local-runner.js +86 -0
  54. package/dist/runtime/custody/openbao-local-runner.js.map +1 -0
  55. package/dist/runtime/edition/run-entry.d.ts +9 -0
  56. package/dist/runtime/edition/run-entry.d.ts.map +1 -0
  57. package/dist/runtime/edition/run-entry.js +36 -0
  58. package/dist/runtime/edition/run-entry.js.map +1 -0
  59. package/dist/runtime/edition/verb-map.d.ts +60 -0
  60. package/dist/runtime/edition/verb-map.d.ts.map +1 -0
  61. package/dist/runtime/edition/verb-map.js +146 -0
  62. package/dist/runtime/edition/verb-map.js.map +1 -0
  63. package/dist/runtime/env-seeder.d.ts +2 -0
  64. package/dist/runtime/env-seeder.d.ts.map +1 -1
  65. package/dist/runtime/env-seeder.js +18 -3
  66. package/dist/runtime/env-seeder.js.map +1 -1
  67. package/dist/runtime/launch.d.ts +2 -1
  68. package/dist/runtime/launch.d.ts.map +1 -1
  69. package/dist/runtime/launch.js +31 -7
  70. package/dist/runtime/launch.js.map +1 -1
  71. package/dist/runtime/paths.d.ts +1 -2
  72. package/dist/runtime/paths.d.ts.map +1 -1
  73. package/dist/runtime/paths.js +0 -10
  74. package/dist/runtime/paths.js.map +1 -1
  75. package/dist/runtime/runner/chart-local-provenance.d.ts +20 -0
  76. package/dist/runtime/runner/chart-local-provenance.d.ts.map +1 -0
  77. package/dist/runtime/runner/chart-local-provenance.js +114 -0
  78. package/dist/runtime/runner/chart-local-provenance.js.map +1 -0
  79. package/dist/runtime/runner/chart-preflight.d.ts +8 -8
  80. package/dist/runtime/runner/chart-preflight.d.ts.map +1 -1
  81. package/dist/runtime/runner/chart-preflight.js +30 -69
  82. package/dist/runtime/runner/chart-preflight.js.map +1 -1
  83. package/dist/runtime/runner/chart-pull.d.ts +14 -0
  84. package/dist/runtime/runner/chart-pull.d.ts.map +1 -0
  85. package/dist/runtime/runner/chart-pull.js +96 -0
  86. package/dist/runtime/runner/chart-pull.js.map +1 -0
  87. package/dist/runtime/runner/chart-resolver.d.ts +4 -12
  88. package/dist/runtime/runner/chart-resolver.d.ts.map +1 -1
  89. package/dist/runtime/runner/chart-resolver.js +16 -62
  90. package/dist/runtime/runner/chart-resolver.js.map +1 -1
  91. package/dist/runtime/runner/chart-set-lock.d.ts +6 -20
  92. package/dist/runtime/runner/chart-set-lock.d.ts.map +1 -1
  93. package/dist/runtime/runner/chart-set-lock.js +55 -99
  94. package/dist/runtime/runner/chart-set-lock.js.map +1 -1
  95. package/dist/runtime/runner/helm-cli.d.ts +3 -1
  96. package/dist/runtime/runner/helm-cli.d.ts.map +1 -1
  97. package/dist/runtime/runner/helm-cli.js +9 -7
  98. package/dist/runtime/runner/helm-cli.js.map +1 -1
  99. package/dist/runtime/runner/helm-runner.d.ts +4 -4
  100. package/dist/runtime/runner/helm-runner.d.ts.map +1 -1
  101. package/dist/runtime/runner/helm-runner.js +18 -16
  102. package/dist/runtime/runner/helm-runner.js.map +1 -1
  103. package/dist/runtime/sources/kinds/git.d.ts +1 -2
  104. package/dist/runtime/sources/kinds/git.d.ts.map +1 -1
  105. package/dist/runtime/sources/kinds/git.js +8 -5
  106. package/dist/runtime/sources/kinds/git.js.map +1 -1
  107. package/dist/runtime/sources/manifest-walk.d.ts +3 -1
  108. package/dist/runtime/sources/manifest-walk.d.ts.map +1 -1
  109. package/dist/runtime/sources/manifest-walk.js +8 -3
  110. package/dist/runtime/sources/manifest-walk.js.map +1 -1
  111. package/dist/runtime/sources/schema.d.ts +3 -0
  112. package/dist/runtime/sources/schema.d.ts.map +1 -1
  113. package/dist/runtime/sources/schema.js +6 -1
  114. package/dist/runtime/sources/schema.js.map +1 -1
  115. package/dist/runtime/supervisor.d.ts +2 -0
  116. package/dist/runtime/supervisor.d.ts.map +1 -1
  117. package/dist/runtime/supervisor.js +1 -0
  118. package/dist/runtime/supervisor.js.map +1 -1
  119. package/edition/README.md +313 -0
  120. package/edition/assemble-producer-input.mjs +1251 -0
  121. package/edition/authorization-base.mjs +347 -0
  122. package/edition/biome-content.mjs +268 -0
  123. package/edition/build-biome-index.mjs +175 -0
  124. package/edition/build-platform-service-index.mjs +288 -0
  125. package/edition/build-runtime-image-source-index.mjs +821 -0
  126. package/edition/built-verifier.mjs +55 -0
  127. package/edition/catalogue-edition.mjs +49 -0
  128. package/edition/catalogue-fetch.mjs +64 -0
  129. package/edition/catalogue.mjs +135 -0
  130. package/edition/chart-set-source-commit.mjs +92 -0
  131. package/edition/check-committed-image-evidence.mjs +383 -0
  132. package/edition/checkout-indexed-source-repositories.mjs +105 -0
  133. package/edition/comment-blanker.mjs +75 -0
  134. package/edition/connector-source-authority.mjs +60 -0
  135. package/edition/customer-definition.mjs +183 -0
  136. package/edition/customer-flags.mjs +7 -0
  137. package/edition/develop-customer-edition.mjs +99 -0
  138. package/edition/develop-edition-resolve.mjs +65 -0
  139. package/edition/develop-edition.mjs +274 -0
  140. package/edition/develop-freeze.mjs +173 -0
  141. package/edition/develop-image-coordinate.mjs +54 -0
  142. package/edition/develop-seal.mjs +77 -0
  143. package/edition/develop-snapshot-inputs.mjs +104 -0
  144. package/edition/dist-check.mjs +986 -0
  145. package/edition/distribution-evidence-set.mjs +987 -0
  146. package/edition/distribution-paths.mjs +191 -0
  147. package/edition/edition-artifact.mjs +284 -0
  148. package/edition/edition-biome-manifests.mjs +85 -0
  149. package/edition/edition-channel.mjs +6 -0
  150. package/edition/edition-chart-set.mjs +93 -0
  151. package/edition/edition-config.mjs +205 -0
  152. package/edition/edition-constants.mjs +57 -0
  153. package/edition/edition-contract.json +30 -0
  154. package/edition/edition-list.mjs +35 -0
  155. package/edition/edition-locks.mjs +69 -0
  156. package/edition/edition-protocol.mjs +228 -0
  157. package/edition/edition-workspace.mjs +187 -0
  158. package/edition/evidence-checkout.mjs +439 -0
  159. package/edition/evidence-grammars.mjs +16 -0
  160. package/edition/evidence-trust-pin.mjs +304 -0
  161. package/edition/evidence-trust-policy.mjs +294 -0
  162. package/edition/external-source-walk.mjs +463 -0
  163. package/edition/first-party-source-ids.mjs +59 -0
  164. package/edition/fleet-coordinate-finality.mjs +350 -0
  165. package/edition/git-source-auth.mjs +52 -0
  166. package/edition/image-delivery-entitlement-descriptor.mjs +106 -0
  167. package/edition/image-evidence-replay-state.mjs +242 -0
  168. package/edition/image-evidence.mjs +1233 -0
  169. package/edition/kernel-contract-authority.mjs +443 -0
  170. package/edition/lib.mjs +3021 -0
  171. package/edition/no-follow-file.mjs +94 -0
  172. package/edition/oci-evidence-verbs.mjs +392 -0
  173. package/edition/oci-image-source-evidence.mjs +239 -0
  174. package/edition/oras.mjs +648 -0
  175. package/edition/pin-source.mjs +227 -0
  176. package/edition/plan-image-evidence-refresh.mjs +408 -0
  177. package/edition/platform-lock-readability.mjs +892 -0
  178. package/edition/platform-service-class.mjs +18 -0
  179. package/edition/platform-service-source-authority.mjs +105 -0
  180. package/edition/positive-closure.mjs +162 -0
  181. package/edition/produce-distribution-completeness-evidence.mjs +311 -0
  182. package/edition/produce-image-delivery-entitlement-evidence.mjs +294 -0
  183. package/edition/produce-image-source-evidence.mjs +765 -0
  184. package/edition/producer-evidence-bundle.mjs +157 -0
  185. package/edition/registry-credentials.mjs +193 -0
  186. package/edition/release-catalogue.mjs +39 -0
  187. package/edition/release-channel-guard.mjs +109 -0
  188. package/edition/release-edition-artifact.mjs +157 -0
  189. package/edition/remedy-command.mjs +89 -0
  190. package/edition/repin-evidence-trust.mjs +101 -0
  191. package/edition/required-resource-provider.mjs +58 -0
  192. package/edition/resolve-chart-set.mjs +415 -0
  193. package/edition/resolve-customer-edition.mjs +288 -0
  194. package/edition/resolve-distribution.mjs +2318 -0
  195. package/edition/resolve-edition.mjs +186 -0
  196. package/edition/resolve-images.mjs +1606 -0
  197. package/edition/seal-image-digests.mjs +855 -0
  198. package/edition/sealed-evidence-accept.mjs +82 -0
  199. package/edition/source-authority.mjs +725 -0
  200. package/edition/source-closure.mjs +116 -0
  201. package/edition/strict-json.mjs +121 -0
  202. package/edition/string-order.mjs +5 -0
  203. package/edition/trust-policy-arming.mjs +120 -0
  204. package/edition/trusted-executable.mjs +166 -0
  205. package/edition/verifier-binding.mjs +45 -0
  206. package/edition/verifier-files.mjs +8 -0
  207. package/edition/verifier-kernel-schemas.mjs +22 -0
  208. package/edition/verifier-provenance.mjs +64 -0
  209. package/edition/verify-image-evidence.mjs +458 -0
  210. package/edition/verify-image-platforms.mjs +495 -0
  211. package/edition/why.mjs +56 -0
  212. package/edition/workspace-sections.mjs +195 -0
  213. package/examples/06-biome-sources.yaml +1 -0
  214. package/package.json +14 -6
  215. package/dist/commands/dist.d.ts +0 -16
  216. package/dist/commands/dist.d.ts.map +0 -1
  217. package/dist/commands/dist.js +0 -182
  218. package/dist/commands/dist.js.map +0 -1
  219. package/dist/runtime/distribution/available-biome.d.ts +0 -10
  220. package/dist/runtime/distribution/available-biome.d.ts.map +0 -1
  221. package/dist/runtime/distribution/available-biome.js +0 -156
  222. package/dist/runtime/distribution/available-biome.js.map +0 -1
  223. package/dist/runtime/distribution/compose.d.ts +0 -23
  224. package/dist/runtime/distribution/compose.d.ts.map +0 -1
  225. package/dist/runtime/distribution/compose.js +0 -48
  226. package/dist/runtime/distribution/compose.js.map +0 -1
  227. package/dist/runtime/runner/chart-trust.d.ts +0 -57
  228. package/dist/runtime/runner/chart-trust.d.ts.map +0 -1
  229. package/dist/runtime/runner/chart-trust.js +0 -270
  230. package/dist/runtime/runner/chart-trust.js.map +0 -1
@@ -0,0 +1,2318 @@
1
+ #!/usr/bin/env node
2
+ // resolve-distribution — resolve a `xema-distribution.json` (one manifest, no
3
+ // `extends`) against the global biome index into a deterministic
4
+ // `distribution.lock.json`. The lock is THE artifact every downstream consumer
5
+ // reads (deploy generator, test-suite mapping, biome-host boot filter,
6
+ // appliance build). No consumer re-applies the distribution rules.
7
+ //
8
+ // Usage:
9
+ // node resolve-distribution.mjs <root flags> <distribution-id>
10
+ //
11
+ // TWO resolution models, chosen per edition by `resolution` (README, "Declaring
12
+ // an edition"):
13
+ // - `positive-closure` — roots only; the kernel's ONE closure algorithm
14
+ // (`resolveBiomeClosure`) over each biome's signed dependency contract and
15
+ // its required resources, with provenance written to the lock's
16
+ // `resolution` section. See `selectByPositiveClosure`.
17
+ // - absent — the selector model below, unchanged.
18
+ //
19
+ // WHAT AN EDITION IS, in both models: its explicit selections + the UNIVERSAL
20
+ // components + their transitive dependencies. Universal means
21
+ // `installationClass: "core"` (the kernel's `BiomeComponentInstallationClass`)
22
+ // and NOTHING ELSE — the tier never decides it, the kernel tier included. That
23
+ // every kernel-tier biome is universal is a VALIDATION of the index (a
24
+ // kernel-tier entry that is not core-class is refused, see
25
+ // `assertIndexDeclaresInstallationClasses`), not a second inclusion rule.
26
+ //
27
+ // Selector-model rules enforced (fail-fast, citing the offending id/edge):
28
+ // - universal (core-class) biomes are FORCE-INCLUDED as `required` and
29
+ // `mandatory`, and CANNOT be excluded; an index that carries no
30
+ // installation classes is REFUSED (never inferred from the tier);
31
+ // - dependency closure — every `requires` edge is resolved transitively from
32
+ // biome metadata; unknown or explicitly-excluded dependencies fail closed.
33
+ // A biome the edition NAMED (or swept by tier/origin) pulls its declared
34
+ // requirements of any class; what only a CLASS admitted (the universal
35
+ // floor included) may not reach a class the edition did not ask for;
36
+ // - trustPolicy.allow (when set) — every included biome's origin must be in
37
+ // the allow list.
38
+ // Exclusion covers TWO namespaces through the one `exclude` list — a biome id
39
+ // and a composed platform-service name (see the block that partitions them).
40
+ // A core-class platform service cannot be excluded, and a biome whose API
41
+ // declares a service edge onto an excluded platform service fails closed.
42
+ // Selector semantics (mirror DistributionSelector):
43
+ // - { tier } → all biomes of that tier, NEVER audience==='operator';
44
+ // - { origin } → all biomes of that origin, NEVER audience==='operator';
45
+ // - { installationClass } → all biomes of that installation class (how
46
+ // ESSENTIAL they are), NEVER audience==='operator'. Matches EXACTLY that
47
+ // class and does not imply the more essential ones (core is universal
48
+ // anyway).
49
+ // - { biome, version? } → that one biome by id (the ONLY way to pull an
50
+ // operator biome); if `version` is given it must be satisfied by the index
51
+ // version.
52
+ //
53
+ // The composed platform substrate is narrowed the same way: core-class and
54
+ // unclassified services, the classes the edition names, and any service a
55
+ // shipped biome's API edge requires (narrowPlatformServicesByClass + the
56
+ // closure). A substrate service that declares no class is kept, never dropped —
57
+ // absent is a third answer there exactly as it is on a biome component.
58
+ //
59
+ // Both the loaded distribution manifests AND the emitted lock are validated
60
+ // against the BUILT kernel zod schemas (parseDistribution / parseDistributionLock).
61
+
62
+
63
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
64
+ import { join } from 'node:path';
65
+
66
+ import {
67
+ adoptedFromLock,
68
+ editionAuthorizationModuleInputs,
69
+ fgaComposedModelTestRunner,
70
+ loadKernelAuthorizationComposer,
71
+ readAuthorizationLock,
72
+ resolveFgaCli,
73
+ sealEditionAuthorizationBase,
74
+ writeAuthorizationLock,
75
+ } from './authorization-base.mjs';
76
+ import { fleetChartSetLockPath, writeEditionChartSet } from './edition-chart-set.mjs';
77
+ import { workspaceSection } from './edition-config.mjs';
78
+ import { WorkspaceRoot, biomeIndexPath, configureEditionWorkspaceFromProcess, editionsRoot, isEntryModule, kernelPackageDir, platformServiceIndexPath } from './edition-workspace.mjs';
79
+ import { walkDeclaredSources } from './external-source-walk.mjs';
80
+ import {
81
+ biomeTierOrder,
82
+ assertDeclaredDatabasesUnowned,
83
+ buildCapabilityDomainOwners,
84
+ compareIndexEntries,
85
+ loadKernelBiomeContracts,
86
+ loadKernelDistributionSchemas,
87
+ resolveServiceDependencyOwners,
88
+ serializeJson,
89
+ sha256Hex,
90
+ validateImmutableBiomeIndex,
91
+ validateImmutableDistributionLock,
92
+ validateServiceDependencyOwnerParity,
93
+ } from './lib.mjs';
94
+ import { PUBLISHED_PIN_SOURCE, pinnedSource } from './pin-source.mjs';
95
+ import { closureBiomeFacts } from './positive-closure.mjs';
96
+ import { assertReleaseContent, assertReleaseEditionTree } from './release-channel-guard.mjs';
97
+ import { commandFor } from './remedy-command.mjs';
98
+ import { soleResourceProvider } from './required-resource-provider.mjs';
99
+ import { readStrictJsonFile } from './strict-json.mjs';
100
+ import { RuntimeSubjectKind } from './workspace-sections.mjs';
101
+
102
+ /**
103
+ * `{ workflowSubjects }` for one index entry, or `{}` when it has none: the
104
+ * kernel's `deriveWorkflowSubjects` over the same `workerTaskQueues` and
105
+ * `workflowClients` the lock carries.
106
+ */
107
+ export function lockedWorkflowSubjects(entry, schemas) {
108
+ const subjects = schemas.deriveWorkflowSubjects({
109
+ workerTaskQueues: entry.workerTaskQueues,
110
+ workflowClients: entry.workflowClients,
111
+ });
112
+ return subjects.length > 0 ? { workflowSubjects: subjects } : {};
113
+ }
114
+
115
+ /**
116
+ * `{ trustCeiling }` for one index entry under the edition's ceiling policy,
117
+ * or `{}` when the kernel's `deriveTrustCeiling` says it is the biome's
118
+ * default domain (the writer omits the default).
119
+ */
120
+ export function lockedTrustCeiling(entry, editionCeiling, schemas) {
121
+ const ceiling = schemas.deriveTrustCeiling(entry.id, entry.origin, editionCeiling);
122
+ return ceiling === undefined ? {} : { trustCeiling: ceiling };
123
+ }
124
+
125
+ /**
126
+ * The lock's `serviceProcessFacts` for one index entry: exactly one row per
127
+ * shipped service, no more and no fewer. The index projection writes one per
128
+ * platform-scheduled image component, which IS `services`; a disagreement is
129
+ * an index the projection did not produce, and is refused rather than emitted
130
+ * as a lock that grades some services and silently not others.
131
+ */
132
+ export function lockedServiceProcessFacts(entry) {
133
+ const services = Array.isArray(entry.services) ? entry.services : [];
134
+ const facts = entry.serviceProcessFacts ?? {};
135
+ const named = Object.keys(facts).sort();
136
+ if (serializeJson(named) !== serializeJson([...services].sort())) {
137
+ throw new Error(
138
+ `biome "${entry.id}" carries process facts for [${named.join(', ')}] but ships ` +
139
+ `services [${services.join(', ')}] — every shipped service has exactly one ` +
140
+ 'row; regenerate the biome index.',
141
+ );
142
+ }
143
+ return facts;
144
+ }
145
+
146
+ if (isEntryModule(import.meta.url)) {
147
+ configureEditionWorkspaceFromProcess([WorkspaceRoot.Data, WorkspaceRoot.Editions, WorkspaceRoot.Sources, WorkspaceRoot.Kernel, WorkspaceRoot.SourceHash]);
148
+ }
149
+
150
+ /**
151
+ * Minimal, deterministic, BOUNDED semver compatibility check used when a
152
+ * `{ biome, version }` selector pins a version. `semver` is not resolvable from
153
+ * the repo-root tooling context, so we implement the two forms our manifests
154
+ * actually use: an exact version (`1.2.3`) and a caret/tilde-free comparison.
155
+ * Supports a leading `^` (compatible-with) and `~` (approximately-equivalent),
156
+ * and a bare exact version. Anything more exotic fails fast — we do NOT
157
+ * silently accept a range we cannot reason about.
158
+ */
159
+ function versionSatisfies(actual, range) {
160
+ const parse = (v) => {
161
+ const m = /^(\d+)\.(\d+)\.(\d+)/.exec(v.trim());
162
+ if (!m) {return null;}
163
+ return [Number(m[1]), Number(m[2]), Number(m[3])];
164
+ };
165
+ const a = parse(actual);
166
+ if (!a) {
167
+ throw new Error(`cannot parse actual version "${actual}"`);
168
+ }
169
+ const trimmed = range.trim();
170
+ const op = trimmed[0] === '^' || trimmed[0] === '~' ? trimmed[0] : '=';
171
+ const bare = op === '=' ? trimmed : trimmed.slice(1);
172
+ const r = parse(bare);
173
+ if (!r) {
174
+ throw new Error(
175
+ `cannot parse version range "${range}" — only exact / ^ / ~ forms are supported by this bounded checker`,
176
+ );
177
+ }
178
+ const cmp = (x, y) => {
179
+ for (let i = 0; i < 3; i += 1) {
180
+ if (x[i] !== y[i]) {return x[i] - y[i];}
181
+ }
182
+ return 0;
183
+ };
184
+ if (op === '=') {return cmp(a, r) === 0;}
185
+ if (cmp(a, r) < 0) {return false;} // must be >= the floor
186
+ if (op === '^') {
187
+ // compatible-with: same major (or, for 0.x, same minor)
188
+ if (r[0] > 0) {return a[0] === r[0];}
189
+ if (r[1] > 0) {return a[0] === 0 && a[1] === r[1];}
190
+ return cmp(a, r) === 0;
191
+ }
192
+ // '~': same major+minor
193
+ return a[0] === r[0] && a[1] === r[1];
194
+ }
195
+
196
+ // ─────────────────────────────────────────────────────────────────────────────
197
+ // ONE MANIFEST, ONE EDITION
198
+ //
199
+ // An edition states its own selections (a positive definition). There is no
200
+ // parent, no chain and no inheritance: the manifest at
201
+ // `<editions root>/<id>/xema-distribution.json` is the whole definition. A
202
+ // manifest that still carries `extends` is refused before any other work.
203
+
204
+ /** The field the edition model no longer has; a definition carrying it is refused. */
205
+ const REFUSED_EXTENDS_FIELD = 'extends';
206
+
207
+ /** An edition definition that breaks the edition model (a refused field); a caller tells it from an I/O or schema error by class. */
208
+ export class EditionModelError extends Error {
209
+ constructor(message) {
210
+ super(message);
211
+ this.name = 'EditionModelError';
212
+ }
213
+ }
214
+
215
+ /**
216
+ * The substrate candidates come from platform-service-index.json, never from an
217
+ * edition. A definition that declares them is refused: they would be a second,
218
+ * driftable copy of the index.
219
+ */
220
+ const REFUSED_PLATFORM_SERVICES_FIELD = 'platformServices';
221
+
222
+ /**
223
+ * A selection or declared dependency the index does not hold. Against a
224
+ * catalogue it is the catalogue's ceiling: the caller names the catalogue.
225
+ */
226
+ export class UnknownSelectionError extends Error {
227
+ constructor(message) {
228
+ super(message);
229
+ this.name = 'UnknownSelectionError';
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Load, refuse `extends` on, and parse (kernel-validated) the single manifest of
235
+ * an edition from the editions root.
236
+ */
237
+ export function loadEditionManifest(parseDistribution, id) {
238
+ const path = join(editionsRoot(), id, 'xema-distribution.json');
239
+ if (!existsSync(path)) {
240
+ throw new Error(`distribution "${id}" not found: ${path} does not exist`);
241
+ }
242
+ const raw = readStrictJsonFile(path);
243
+ if (raw !== null && typeof raw === 'object' && Object.hasOwn(raw, REFUSED_EXTENDS_FIELD)) {
244
+ throw new EditionModelError(
245
+ `edition "${id}": "${REFUSED_EXTENDS_FIELD}" is not part of the edition model; ` +
246
+ "state the edition's own selections (positive definition)",
247
+ );
248
+ }
249
+ if (raw !== null && typeof raw === 'object' && Object.hasOwn(raw, REFUSED_PLATFORM_SERVICES_FIELD)) {
250
+ throw new Error(
251
+ `edition "${id}": "${REFUSED_PLATFORM_SERVICES_FIELD}" is not part of the edition definition; ` +
252
+ 'the platform substrate is drawn from platform-service-index.json by installation class ' +
253
+ "(core class, the classes the edition's include names, and services a shipped biome's API requires)",
254
+ );
255
+ }
256
+ const manifest = parseDistribution(raw);
257
+ if (manifest.id !== id) {
258
+ throw new Error(`distribution at ${path} declares id "${manifest.id}", but it was addressed as "${id}"`);
259
+ }
260
+ return manifest;
261
+ }
262
+
263
+ /**
264
+ * Compose an edition's single manifest into the flat
265
+ * { include[], exclude, trustPolicy?, trustCeiling?, requiredPlatforms, ... }
266
+ * the resolver consumes.
267
+ *
268
+ * `requiredPlatforms` is REQUIRED on every manifest (the kernel schema enforces
269
+ * it), so an edition always states its own architecture commitment. See
270
+ * `Distribution.requiredPlatforms`. Under a snapshot pin source every declared
271
+ * source's `ref` is the snapshot pin.
272
+ */
273
+ export function composeEdition(parseDistribution, id, pinSource = PUBLISHED_PIN_SOURCE) {
274
+ return composeManifest(loadEditionManifest(parseDistribution, id), pinSource);
275
+ }
276
+
277
+ /** The composition of an already-parsed manifest (a customer definition is composed without an editions-root file). */
278
+ export function composeManifest(manifest, pinSource = PUBLISHED_PIN_SOURCE) {
279
+ const roots = { product: [], ownerSelected: [], policy: [] };
280
+ for (const cls of Object.keys(roots)) {
281
+ roots[cls].push(...(manifest.roots?.[cls] ?? []));
282
+ }
283
+ // `sources[]` — external repositories this edition's biomes may additionally
284
+ // be drawn from, collected as BOTH the declarations (so the walk can
285
+ // materialise them) and their names (so `entrySourceAdmissible` can scope
286
+ // what they draw). `external-source-walk.mjs` stamps every entry it draws with
287
+ // the `source.name` that drew it, and edition scoping keys on that stamp.
288
+ const sources = manifest.sources ?? [];
289
+ const { resolution } = manifest;
290
+
291
+ return {
292
+ include: [...(manifest.include ?? [])],
293
+ exclude: new Set(manifest.exclude ?? []),
294
+ trustPolicy: manifest.trustPolicy,
295
+ trustCeiling: manifest.trustCeiling,
296
+ requiredPlatforms: manifest.requiredPlatforms,
297
+ // Visibility is a RESTRICTION; the caller (which holds the kernel enum)
298
+ // reduces the declarations.
299
+ declaredVisibilities: manifest.visibility === undefined ? [] : [manifest.visibility],
300
+ declaredSourceNames: new Set(sources.map((source) => source.name)),
301
+ declaredSources: sources.map((source) => pinnedSource(source, pinSource)),
302
+ owner: manifest.owner,
303
+ leaf: manifest,
304
+ // Present ONLY for a positive-closure edition, so a selector-model
305
+ // composition carries exactly the keys it always did.
306
+ ...(resolution === undefined ? {} : { resolution, roots }),
307
+ };
308
+ }
309
+
310
+ function readBiomeIndex() {
311
+ if (!existsSync(biomeIndexPath())) {
312
+ throw new Error(
313
+ `${biomeIndexPath()} not found — run \`${commandFor('build-biome-index.mjs')}\` first`,
314
+ );
315
+ }
316
+ return JSON.parse(readFileSync(biomeIndexPath(), 'utf8'));
317
+ }
318
+
319
+ function readPlatformServiceIndex(parsePlatformServiceSourceIndex) {
320
+ if (!existsSync(platformServiceIndexPath())) {
321
+ throw new Error(
322
+ `${platformServiceIndexPath()} not found — run the trusted platform-service index generator first`,
323
+ );
324
+ }
325
+ return parsePlatformServiceSourceIndex(
326
+ JSON.parse(readFileSync(platformServiceIndexPath(), 'utf8')),
327
+ );
328
+ }
329
+
330
+ /** Unique key for an index/lock entry (id+target+repo distinguishes cultivar/reference + web/server pairs). */
331
+ function entryKey(e) {
332
+ return JSON.stringify([e.id, e.target, e.repo]);
333
+ }
334
+
335
+ /**
336
+ * The only tree a `target: web` biome can be BUILT from: the repository of the
337
+ * host image, which the data root declares as its one `host`-kind runtime image
338
+ * source (`runtimeImageSources`). Derived, so it is stated once.
339
+ *
340
+ * A web biome ships by being bundled into the single host image: host-web's
341
+ * `generate-biome-loaders.mjs` builds `WEB_BIOME_LOADERS` by walking
342
+ * `biomes/<tier>/<id>/xema-biome.json` under its OWN repo, and the SPA imports
343
+ * a biome only through that map. A web biome whose manifest lives anywhere
344
+ * else therefore has no producer — the REMOTE_FEDERATED path that would give
345
+ * it one does not ship yet.
346
+ */
347
+ function webBiomeProducerRepo() {
348
+ const hosts = workspaceSection('runtimeImageSources', 'knowing which repository produces web biomes')
349
+ .supplementalSources.filter((source) => source.kind === RuntimeSubjectKind.Host);
350
+ if (hosts.length !== 1) {
351
+ throw new Error(
352
+ `edition-workspace.json runtimeImageSources must declare exactly one "${RuntimeSubjectKind.Host}" source ` +
353
+ `(the image web biomes are bundled into); it declares ${hosts.length}`,
354
+ );
355
+ }
356
+ return hosts[0].repo;
357
+ }
358
+
359
+ /**
360
+ * Whether this index entry can actually be DELIVERED by some build.
361
+ *
362
+ * Server biomes are always deliverable — they are images, and
363
+ * `check-image-producers` already asserts every lock subject has a producing
364
+ * workflow. That assertion never covered web biomes, and this is it.
365
+ *
366
+ * WHY THIS IS A SELECTOR RULE, exactly like `audience !== 'operator'` above.
367
+ * A `{ tier: "platform" }` selector states an intent about a DEPENDENCY TIER;
368
+ * it cannot state that the thing it swept in is buildable. Three web biomes
369
+ * authored in xema-cultivars and xema-community were swept into `cloud`,
370
+ * `oss`, `appliance-base` and `xema-internal` that way, and every org received
371
+ * them as catalog entries the SPA resolves and then drops with
372
+ * `unavailable_in_build` — measured live on xema.dev at three warnings per page
373
+ * load.
374
+ *
375
+ * That was first fixed by naming the three in `core.exclude`. This replaces
376
+ * that list, and the difference is the whole point: a list of three names is
377
+ * silently wrong the day a FOURTH web biome is authored outside host-web,
378
+ * which is the exact shape this repository calls a gate that cannot fail. The
379
+ * property — "a web biome must have a producer" — is derivable from the index,
380
+ * so it is asserted rather than enumerated.
381
+ *
382
+ * ── THIS RULE DOES NOT RETIRE ITSELF. SAY SO, BECAUSE IT LOOKS LIKE IT MIGHT ──
383
+ *
384
+ * The intended architecture is that a web biome authored in a carved repo ships
385
+ * as a Module Federation REMOTE, not by being vendored into the host image, and
386
+ * that path is BUILT: `submodules/xema-host-web/src/lib/biomes/load-remote-biome.ts`
387
+ * is a complete 488-line loader — origin allowlist, trust-verdict gate,
388
+ * `registerRemotes` then `loadRemote`. It is inert only because the backend
389
+ * publishes nothing for it: `next.config.mjs` records that
390
+ * `listRemoteFederated` returns `[]` today, so no remote is ever registered.
391
+ *
392
+ * So the honest reading is that this predicate encodes a PREMISE — bundling is
393
+ * currently the only path that delivers — and not a permanent truth about where
394
+ * web biomes belong. It cannot detect the premise changing: the index carries
395
+ * `repo`/`path`/`sourceCommit` and NO remote coordinate (see `images.lock.json`
396
+ * `webBiomes`), so there is nothing here to derive MF-readiness from, and a
397
+ * dormant branch reading a field no producer writes would be machinery shipped
398
+ * before its second consumer.
399
+ *
400
+ * The day `listRemoteFederated` returns rows, this predicate starts excluding
401
+ * biomes that CAN ship — the same silent wrongness in the opposite direction —
402
+ * and someone must widen it. That trigger is named here so a grep for
403
+ * `listRemoteFederated` finds it. Do not restate this rule anywhere else; one
404
+ * premise, one place to correct.
405
+ */
406
+ export function isDeliverable(entry, webProducerRepos) {
407
+ return entry.target !== 'web' || (webProducerRepos ?? [webBiomeProducerRepo()]).includes(entry.repo);
408
+ }
409
+
410
+ /**
411
+ * The ONE installation class that is universal: in every edition, `required`,
412
+ * `mandatory`, never excludable. Read from the kernel enum, never restated.
413
+ */
414
+ export function universalInstallationClass() {
415
+ return loadKernelBiomeContracts().BiomeComponentInstallationClass.Core;
416
+ }
417
+
418
+ /** Is this index entry (or platform service) universal? Its class decides; nothing else. */
419
+ export function isUniversal(entry) {
420
+ return entry.installationClass === universalInstallationClass();
421
+ }
422
+
423
+ /**
424
+ * The resolver's precondition on its index: installation classes are DECLARED,
425
+ * and the kernel tier is universal.
426
+ *
427
+ * AN INDEX THAT CARRIES NO CLASSES CANNOT ANSWER "WHAT IS IN EVERY EDITION",
428
+ * and it is refused rather than read some other way. Reading the tier instead
429
+ * would be the coupling this resolver no longer has (essentialness is the
430
+ * kernel `installationClass`, a separate axis from the tier ladder), and
431
+ * resolving it as "nothing is universal" would lock an edition with no floor
432
+ * that reads as deliberate. The index is generated from a pinned source ref, so
433
+ * an index built before the manifests declared classes produces exactly that.
434
+ *
435
+ * A kernel-tier entry that is not core-class is a manifest defect, refused by
436
+ * name: the kernel tier is the floor everything else loads on, so its biomes
437
+ * declare `installationClass: "core"` (xema-base
438
+ * `check-installation-class-declared` holds the same rule at the source). And a
439
+ * core-class OPERATOR biome is refused because the two answers contradict: an
440
+ * operator biome ships only where an edition names it, a universal one ships
441
+ * everywhere.
442
+ */
443
+ export function assertIndexDeclaresInstallationClasses(id, index) {
444
+ if (!index.some((entry) => entry.installationClass !== undefined)) {
445
+ throw new Error(
446
+ `distribution "${id}": NO entry in the biome index carries an installationClass, so ` +
447
+ `the index cannot say which components are universal (installationClass core) ` +
448
+ `and every edition would resolve without its floor. The tier is never read in its ` +
449
+ `place. Regenerate the index from a ref where the biome manifests declare ` +
450
+ `xema.components[].installationClass (\`${commandFor('build-biome-index.mjs')}\`).`,
451
+ );
452
+ }
453
+ const universal = universalInstallationClass();
454
+ for (const entry of [...index].sort(compareIndexEntries)) {
455
+ if (entry.tier === 'kernel' && entry.installationClass !== universal) {
456
+ throw new Error(
457
+ `distribution "${id}": kernel-tier biome "${entry.id}" (${entry.repo}) declares ` +
458
+ `installationClass ${entry.installationClass === undefined ? '(none)' : `"${entry.installationClass}"`}; ` +
459
+ `every kernel-tier biome must declare "${universal}" — fix the manifest and regenerate the index.`,
460
+ );
461
+ }
462
+ if (entry.installationClass === universal && entry.audience === 'operator') {
463
+ throw new Error(
464
+ `distribution "${id}": operator biome "${entry.id}" (${entry.repo}) declares installationClass ` +
465
+ `"${universal}" — an operator biome ships only where an edition names it, so it cannot be universal.`,
466
+ );
467
+ }
468
+ }
469
+ }
470
+
471
+ /** Every installation class an edition's `include[]` explicitly asks for. */
472
+ export function selectedInstallationClasses(include) {
473
+ const classes = new Set();
474
+ for (const selector of include) {
475
+ if ('installationClass' in selector) {classes.add(selector.installationClass);}
476
+ }
477
+ return classes;
478
+ }
479
+
480
+ /**
481
+ * The classes an edition's substrate and closure may reach WITHOUT a named
482
+ * root: the universal class plus every class the edition names.
483
+ */
484
+ export function admittedInstallationClasses(include) {
485
+ const classes = selectedInstallationClasses(include);
486
+ classes.add(universalInstallationClass());
487
+ return classes;
488
+ }
489
+
490
+ /**
491
+ * Narrow the composed substrate to the admitted classes
492
+ * (`admittedInstallationClasses`). Two rules, and the second is the one that is
493
+ * easy to get wrong:
494
+ *
495
+ * 1. a service whose declared class is admitted is KEPT;
496
+ * 2. a service that declares NO class is KEPT, never dropped. Absent is a
497
+ * third answer on the substrate exactly as it is on a component: dropping
498
+ * an unclassified service would substitute a class ("not asked for") for
499
+ * an absent one, which is precisely what the kernel's docblock forbids.
500
+ *
501
+ * Anything else is not shipped HERE — but the closure still adds a service a
502
+ * shipped biome's API edge requires (see `requirePlatformOwner`), so naming a
503
+ * biome brings the substrate it needs.
504
+ */
505
+ export function narrowPlatformServicesByClass(platformServices, classes) {
506
+ const kept = new Map();
507
+ for (const [name, service] of platformServices) {
508
+ if (
509
+ service.installationClass === undefined ||
510
+ classes.has(service.installationClass)
511
+ ) {
512
+ kept.set(name, service);
513
+ }
514
+ }
515
+ return kept;
516
+ }
517
+
518
+ /**
519
+ * Is this index entry drawn from a source the SELECTED EDITION declared?
520
+ *
521
+ * This is the one question that keeps one customer's private biome out of
522
+ * another customer's release, and it is asked at THREE seams below because a
523
+ * foreign entry has three ways in: a sweep selector, an explicit name, and a
524
+ * transitive `requires` pull.
525
+ *
526
+ * ── ABSENT IS THE IDENTITY, AND THAT IS WHY THIS CHANGES NOTHING TODAY ──
527
+ *
528
+ * An entry carries `source` iff some edition's `sources[]` walk drew it in.
529
+ * All 117 entries in the committed index are drawn from the platform's own
530
+ * repositories and carry no `source`, so every one of them stays admissible to
531
+ * every edition and all nine committed locks resolve byte-identically. That
532
+ * is the whole point of keying on the STAMP rather than on a list of
533
+ * first-party repository names: a list would be a change-tax that an eighth
534
+ * first-party repository forces somebody to edit, and it would be silently
535
+ * wrong until they did.
536
+ *
537
+ * ── WHY THIS LANDS BEFORE THE WALK IT FENCES ──
538
+ *
539
+ * This predicate landed BEFORE the walk it fences, while `composeChain` still
540
+ * refused a non-empty `sources[]` and nothing could stamp an entry. That was
541
+ * deliberate: on the day the refusal was deleted, a `{ tier: "platform" }`
542
+ * sweep would otherwise have pulled every fetched entry into `oss` — measured,
543
+ * before this predicate existed, at 5 of 9 editions swept in a synthetic
544
+ * private biome, `oss` and `cloud` among them. A fence added afterwards is a
545
+ * fence added in response to a leak.
546
+ *
547
+ * THE WALK NOW EXISTS (`external-source-walk.mjs`), so entries CAN be stamped
548
+ * and this predicate has live subjects. An empty declared set still means no
549
+ * edition admits a stamped entry, which stays the correct fail-closed reading:
550
+ * an edition that declares no sources has no business receiving a biome drawn
551
+ * from one.
552
+ */
553
+ /**
554
+ * Merge the entries an edition's source walk drew into the index THIS
555
+ * RESOLUTION will select from — and never into `biome-index.json`.
556
+ *
557
+ * The committed index is the PLATFORM's, shared by every edition. An external
558
+ * entry written there is visible to all of them, and `{ tier: "platform" }`
559
+ * matches on tier and audience and never on origin, so the merge has to be
560
+ * per-resolution or the scoping fence has nothing left to scope.
561
+ */
562
+ export function mergeExternalSourceEntries(
563
+ index,
564
+ externalEntries,
565
+ platformServiceIndex,
566
+ ) {
567
+ if (externalEntries.length === 0) {return index;}
568
+
569
+ // RE-ASSERTED HERE, not merely trusted from the walk. `walkDeclaredSources`
570
+ // already checks this, and the two checks are separated by a caller — this is
571
+ // the merge, the one point at which an entry from anywhere joins the shared
572
+ // index, and an unstamped entry arriving here is admissible to EVERY edition.
573
+ // A duplicated assertion is the right trade when what it catches is a
574
+ // customer's private biome shipping inside `oss`.
575
+ const unstamped = externalEntries.filter((entry) => entry.source === undefined);
576
+ if (unstamped.length > 0) {
577
+ throw new Error(
578
+ `refusing to merge ${unstamped.length} UNSTAMPED external entr(ies) ` +
579
+ `(${unstamped.map((entry) => entry.id).join(', ')}) into the biome index: an entry ` +
580
+ 'with no source stamp is platform-default and admissible to every edition.',
581
+ );
582
+ }
583
+
584
+ // Resolve each NEW entry's service-dependency owners against the MERGED
585
+ // ownership picture — a customer biome may legitimately require a platform
586
+ // service, and only the merged set knows who owns one.
587
+ //
588
+ // The committed entries are passed as THROWAWAY CLONES carrying an empty
589
+ // `serviceRequirements`, because `resolveServiceDependencyOwners` MUTATES IN
590
+ // PLACE: it assigns `serviceDependencies` and deletes `serviceRequirements`
591
+ // on every entry it walks. The committed index ships the RESOLVED projection
592
+ // and no requirements (measured: 117 of 117 carry `serviceDependencies`, 0
593
+ // carry `serviceRequirements`), so walking the originals would overwrite all
594
+ // 117 projections with `[]`. The clones absorb that write; the real external
595
+ // entries are passed by reference and are the only ones resolved for real.
596
+ const probe = [
597
+ ...index.map((entry) => ({ ...entry, serviceRequirements: [] })),
598
+ ...externalEntries,
599
+ ];
600
+ resolveServiceDependencyOwners(probe, platformServiceIndex);
601
+
602
+ const merged = [...index, ...externalEntries].sort(compareIndexEntries);
603
+
604
+ // A DECLARED database key must name a database nothing else already answers
605
+ // to. This is the bound on the entitlement `org-database-pool-api` hands out,
606
+ // and it is the one check that genuinely must see the MERGED set: a customer
607
+ // manifest declaring `llm-registry` would otherwise be handed another
608
+ // principal's schema. `buildBiomeIndex` asserts it for the first-party half;
609
+ // this is where the external half joins.
610
+ assertDeclaredDatabasesUnowned(merged);
611
+ return merged;
612
+ }
613
+
614
+ /**
615
+ * The pinned source record for a stamped entry, or a refusal.
616
+ *
617
+ * A stamped entry whose handle resolves to no record means the walk and the
618
+ * merge disagree about what was fetched. Emitting the entry without its source
619
+ * would produce a lock whose member cannot be traced to a revision, and
620
+ * emitting `source: undefined` would make it read as first-party — so this
621
+ * fails rather than choosing either.
622
+ */
623
+ function requireSourceRecord(entry, sourceRecords) {
624
+ const record = sourceRecords.get(entry.source);
625
+ if (record === undefined) {
626
+ throw new Error(
627
+ `biome "${entry.id}" is stamped with source "${entry.source}", but no pinned source ` +
628
+ 'record was produced for that name. A stamped entry with no record cannot be traced ' +
629
+ 'to a revision, and emitting it unstamped would make it read as first-party.',
630
+ );
631
+ }
632
+ return record;
633
+ }
634
+
635
+ export function entrySourceAdmissible(entry, declaredSourceNames) {
636
+ if (entry.source === undefined) {return true;}
637
+ return declaredSourceNames.has(entry.source);
638
+ }
639
+
640
+ function applySelectors(index, include, declaredSourceNames = new Set(), webProducerRepos = undefined) {
641
+ const selected = new Map(); // key → entry
642
+ // Keys a selector explicitly marked `mandatory: true` — the DISTRIBUTION's
643
+ // force-on decision. A biome is mandatory iff ANY matching selector flags it
644
+ // (a universal, core-class biome is mandatory regardless — see the lock). `{ origin }` selectors
645
+ // cannot carry `mandatory` (provenance is not a force-on axis).
646
+ const mandatoryKeys = new Set();
647
+ // Keys a NAME or a tier/origin sweep admitted — an explicit opt-in to the
648
+ // biome AND to its declared requirements of any class. A key only a class
649
+ // selector admitted is not in here (see the widening guard in
650
+ // `selectBySelectors`).
651
+ const explicitKeys = new Set();
652
+ // key → { policy, form } — the selector-provided install policy. Most
653
+ // specific matching selector wins: a `{ biome }` policy beats a SWEEP policy
654
+ // (`{ tier }` or `{ installationClass }`); two conflicting policies at the
655
+ // SAME specificity fail fast, INCLUDING one tier sweep against one class
656
+ // sweep — those are two axes stamping one biome, and arbitrating between
657
+ // them would be a precedence rule nobody wrote down.
658
+ // (`{ origin }` selectors cannot carry `installPolicy` — schema-enforced.)
659
+ const policyByKey = new Map();
660
+ const recordPolicy = (key, entryId, policy, form) => {
661
+ const existing = policyByKey.get(key);
662
+ if (existing === undefined) {
663
+ policyByKey.set(key, { policy, form });
664
+ return;
665
+ }
666
+ if (existing.form === form) {
667
+ if (existing.policy !== policy) {
668
+ throw new Error(
669
+ `conflicting installPolicy for biome "${entryId}": two ${form}-form selectors ` +
670
+ `stamp "${existing.policy}" and "${policy}" — resolve the manifest ambiguity`,
671
+ );
672
+ }
673
+ return;
674
+ }
675
+ // biome-form (more specific) overrides tier-form.
676
+ if (form === 'biome') {
677
+ policyByKey.set(key, { policy, form });
678
+ }
679
+ };
680
+ for (const sel of include) {
681
+ let matches;
682
+ if ('tier' in sel) {
683
+ matches = index.filter(
684
+ (e) =>
685
+ e.tier === sel.tier &&
686
+ e.audience !== 'operator' &&
687
+ isDeliverable(e, webProducerRepos) &&
688
+ entrySourceAdmissible(e, declaredSourceNames),
689
+ );
690
+ } else if ('origin' in sel) {
691
+ matches = index.filter(
692
+ (e) =>
693
+ e.origin === sel.origin &&
694
+ e.audience !== 'operator' &&
695
+ isDeliverable(e, webProducerRepos) &&
696
+ entrySourceAdmissible(e, declaredSourceNames),
697
+ );
698
+ } else if ('installationClass' in sel) {
699
+ // The index is known to carry classes: `assertIndexDeclaresInstallationClasses`
700
+ // refused it before selection otherwise.
701
+ matches = index.filter(
702
+ (e) =>
703
+ e.installationClass === sel.installationClass &&
704
+ e.audience !== 'operator' &&
705
+ isDeliverable(e, webProducerRepos) &&
706
+ entrySourceAdmissible(e, declaredSourceNames),
707
+ );
708
+ if (matches.length === 0) {
709
+ throw new Error(
710
+ `include { installationClass: "${sel.installationClass}" } matches no biome in ` +
711
+ `the index. An edition that asks for a class nothing declares is asking for ` +
712
+ `something that cannot be built; name the biomes explicitly or drop the selector.`,
713
+ );
714
+ }
715
+ } else if ('biome' in sel) {
716
+ const idMatches = index.filter((e) => e.id === sel.biome);
717
+ if (idMatches.length === 0) {
718
+ throw new UnknownSelectionError(
719
+ `include { biome: "${sel.biome}" } matches no biome in the index`,
720
+ );
721
+ }
722
+ // A SWEEP silently skips an inadmissible entry; an explicit NAME must
723
+ // refuse. Same asymmetry the deliverability rule one branch down already
724
+ // draws, and for the same reason: a sweep states an intent about a tier
725
+ // and cannot know what it swept, while a name is a stated intent about
726
+ // one biome. Dropping a named biome would resolve an edition that is
727
+ // missing exactly the thing its author asked for, and the lock would read
728
+ // as deliberate.
729
+ const inadmissible = idMatches.filter(
730
+ (e) => !entrySourceAdmissible(e, declaredSourceNames),
731
+ );
732
+ if (inadmissible.length > 0 && inadmissible.length === idMatches.length) {
733
+ const names = [...new Set(inadmissible.map((e) => e.source))].sort();
734
+ throw new Error(
735
+ `include { biome: "${sel.biome}" } names a biome drawn from source(s) ` +
736
+ `${names.map((name) => `"${name}"`).join(', ')}, which this edition does not ` +
737
+ `declare in sources[]. A biome is reachable only from a source its own ` +
738
+ `edition named: another edition declaring that source does not make it ` +
739
+ `visible here. Declare the source on this edition, or drop the include.`,
740
+ );
741
+ }
742
+ // An explicit name is a stated intent, so an undeliverable one FAILS
743
+ // rather than being dropped the way a selector sweep is. This is the one
744
+ // place the operator-biome rule and this rule differ, and deliberately:
745
+ // naming an operator biome is how you force it in, while naming a web
746
+ // biome with no producer asks for something no build can make.
747
+ if (idMatches.every((e) => !isDeliverable(e, webProducerRepos))) {
748
+ throw new Error(
749
+ `include { biome: "${sel.biome}" } names a target:web biome authored in ` +
750
+ `"${idMatches[0].repo}". Only web biomes under ` +
751
+ `"${(webProducerRepos ?? [webBiomeProducerRepo()]).join(', ')}" are bundled into the host image, so nothing ` +
752
+ `can build this one and every org would receive it as a catalog entry that ` +
753
+ `fails at boot with unavailable_in_build. Move it there (a thin re-export ` +
754
+ `wrapper is enough — see biomes/platform/org-databases-web), or drop the ` +
755
+ `include.`,
756
+ );
757
+ }
758
+ matches = sel.version
759
+ ? idMatches.filter((e) => {
760
+ try {
761
+ return versionSatisfies(e.version, sel.version);
762
+ } catch (err) {
763
+ throw new Error(
764
+ `version check for biome "${sel.biome}" failed: ${err.message}`,
765
+ );
766
+ }
767
+ })
768
+ : idMatches;
769
+ if (sel.version && matches.length === 0) {
770
+ throw new Error(
771
+ `include { biome: "${sel.biome}", version: "${sel.version}" } matched ids but no index version satisfied the range ` +
772
+ `(available: ${idMatches.map((m) => m.version).join(', ')})`,
773
+ );
774
+ }
775
+ } else {
776
+ throw new Error(`unrecognized selector shape: ${JSON.stringify(sel)}`);
777
+ }
778
+ for (const e of matches) {
779
+ const key = entryKey(e);
780
+ selected.set(key, e);
781
+ if (!('installationClass' in sel)) {explicitKeys.add(key);}
782
+ if (sel.mandatory === true) {mandatoryKeys.add(key);}
783
+ if (sel.installPolicy !== undefined) {
784
+ recordPolicy(
785
+ key,
786
+ e.id,
787
+ sel.installPolicy,
788
+ 'biome' in sel ? 'biome' : 'sweep',
789
+ );
790
+ }
791
+ }
792
+ }
793
+ return { selected, mandatoryKeys, policyByKey, explicitKeys };
794
+ }
795
+
796
+ /**
797
+ * POSITIVE-CLOSURE SELECTION — an edition that declared `resolution:
798
+ * "positive-closure"` is its roots plus what they REQUIRE, computed by the
799
+ * kernel's one closure algorithm (`resolveBiomeClosure`). This function only
800
+ * projects the plane's facts onto it and the answer back:
801
+ *
802
+ * * biome roots are validated as the `{ biome }` selector always validated a
803
+ * name — it exists, its source is admissible, it is deliverable, a pinned
804
+ * version is satisfied — and carry the same `mandatory` / `installPolicy`
805
+ * distribution decisions;
806
+ * * every universal (core-class) biome is a PLATFORM_INVARIANT root, the
807
+ * same rule the selector model applies; the kernel's own
808
+ * `platformInvariantRoots` (kernel-tier biomes and platform services) is
809
+ * a subset of it for biomes, since a kernel-tier biome that is not
810
+ * core-class is refused before selection;
811
+ * * a policy root is a PLATFORM_INVARIANT root carrying its written reason;
812
+ * * the candidates are every deliverable, source-admissible index entry and
813
+ * every platform service the platform-service index knows.
814
+ *
815
+ * There is no exclude and no substrate list: a platform service ships iff it is
816
+ * a root, a kernel invariant, or something in the edition requires it.
817
+ *
818
+ * AND THE MANIFEST'S OWN DECLARATIONS MUST AGREE. A biome's `requires` and its
819
+ * `serviceDependencies` are what the deploy plane and the runtime still read
820
+ * from the lock. Under positive closure they are no longer an INCLUSION driver
821
+ * — the contract is — but a closure that ships a biome whose declared hard
822
+ * requirement it left out would resolve a lock that cannot boot. So each is
823
+ * checked against the closure and a disagreement is REFUSED, naming the edge:
824
+ * either the contract is missing a required edge, or the declaration is stale.
825
+ */
826
+ function selectByPositiveClosure({
827
+ id,
828
+ index,
829
+ schemas,
830
+ roots,
831
+ declaredSourceNames,
832
+ platformSourcesByName,
833
+ }) {
834
+ const {
835
+ ClosureEntityKind,
836
+ ClosureProvenanceKind,
837
+ BiomeClosureError,
838
+ DistributionResolutionMode,
839
+ } = schemas;
840
+ const biomeRoots = [];
841
+ const platformServiceRoots = [];
842
+ const operationRoots = [];
843
+ for (const [cls, kind] of [
844
+ ['product', ClosureProvenanceKind.ProductRoot],
845
+ ['ownerSelected', ClosureProvenanceKind.OwnerSelectedRoot],
846
+ ['policy', ClosureProvenanceKind.PlatformInvariant],
847
+ ]) {
848
+ for (const root of roots[cls] ?? []) {
849
+ if ('operationId' in root) {
850
+ // One operation of a service, stated with a reason. The kernel closure
851
+ // resolves the owning biome and REFUSES an operationId the producer's
852
+ // contract does not expose.
853
+ operationRoots.push({
854
+ provenance: {
855
+ kind: ClosureProvenanceKind.OperationRoot,
856
+ reason: root.reason,
857
+ operation: { serviceName: root.service, operationId: root.operationId },
858
+ },
859
+ });
860
+ continue;
861
+ }
862
+ const provenance =
863
+ kind === ClosureProvenanceKind.PlatformInvariant
864
+ ? { kind, reason: root.reason }
865
+ : { kind };
866
+ if ('platformService' in root) {
867
+ if (!platformSourcesByName.has(root.platformService)) {
868
+ throw new Error(
869
+ `distribution "${id}": ${cls} root names platform service ` +
870
+ `"${root.platformService}", which platform-service-index.json does not describe`,
871
+ );
872
+ }
873
+ platformServiceRoots.push({
874
+ entity: {
875
+ kind: ClosureEntityKind.PlatformService,
876
+ id: root.platformService,
877
+ },
878
+ provenance,
879
+ });
880
+ } else {
881
+ biomeRoots.push({ root, provenance });
882
+ }
883
+ }
884
+ }
885
+
886
+ // Biome roots are named selections; reuse the selector's validation of a
887
+ // name, and its `mandatory` / `installPolicy` bookkeeping, verbatim.
888
+ const { selected: rootSelection, mandatoryKeys, policyByKey } = applySelectors(
889
+ index,
890
+ biomeRoots.map(({ root }) => {
891
+ const { reason: _reason, ...selector } = root;
892
+ return selector;
893
+ }),
894
+ declaredSourceNames,
895
+ );
896
+ const rootIds = new Set([...rootSelection.values()].map((entry) => entry.id));
897
+
898
+ const candidateEntries = index.filter(
899
+ (entry) =>
900
+ isDeliverable(entry) && entrySourceAdmissible(entry, declaredSourceNames),
901
+ );
902
+ for (const entry of rootSelection.values()) {
903
+ if (!candidateEntries.includes(entry)) {candidateEntries.push(entry);}
904
+ }
905
+ const candidates = {
906
+ biomes: candidateEntries.map(closureBiomeFacts),
907
+ platformServices: [...platformSourcesByName.values()].map(
908
+ ({ name, tier, installationClass }) => ({
909
+ name,
910
+ tier,
911
+ ...(installationClass === undefined ? {} : { installationClass }),
912
+ }),
913
+ ),
914
+ };
915
+
916
+ // The universal (core-class) biomes are roots of every edition, exactly as
917
+ // in the selector model: explicit roots + universal + closure.
918
+ const universalRoots = candidateEntries
919
+ .filter((entry) => isUniversal(entry) && !rootIds.has(entry.id))
920
+ .sort(compareIndexEntries)
921
+ .map((entry) => ({
922
+ entity: { kind: ClosureEntityKind.Biome, id: entry.id },
923
+ provenance: {
924
+ kind: ClosureProvenanceKind.PlatformInvariant,
925
+ reason: `installationClass "${universalInstallationClass()}": universal, in every edition`,
926
+ },
927
+ }));
928
+
929
+ let result;
930
+ try {
931
+ result = schemas.resolveBiomeClosure({
932
+ roots: [
933
+ ...schemas.platformInvariantRoots(candidates),
934
+ ...universalRoots,
935
+ ...biomeRoots
936
+ .filter(({ root }) => rootIds.has(root.biome))
937
+ .map(({ root, provenance }) => ({
938
+ entity: { kind: ClosureEntityKind.Biome, id: root.biome },
939
+ provenance,
940
+ })),
941
+ ...platformServiceRoots,
942
+ ...operationRoots,
943
+ ],
944
+ candidates,
945
+ });
946
+ } catch (error) {
947
+ if (error instanceof BiomeClosureError) {
948
+ throw new Error(`distribution "${id}": ${error.message}`, {
949
+ cause: error,
950
+ });
951
+ }
952
+ throw error;
953
+ }
954
+
955
+ const entriesById = new Map(candidateEntries.map((entry) => [entry.id, entry]));
956
+ const selected = new Map();
957
+ const shippedPlatformServices = new Map();
958
+ for (const { entity } of result.entries) {
959
+ if (entity.kind === ClosureEntityKind.Biome) {
960
+ const entry = entriesById.get(entity.id);
961
+ selected.set(entryKey(entry), entry);
962
+ } else {
963
+ const source = platformSourcesByName.get(entity.id);
964
+ shippedPlatformServices.set(entity.id, {
965
+ name: entity.id,
966
+ tier: source.tier,
967
+ });
968
+ }
969
+ }
970
+
971
+ // The manifest's declarations must agree with the closure — see above.
972
+ const shippedIds = new Set([...selected.values()].map((entry) => entry.id));
973
+ for (const entry of [...selected.values()].sort(compareIndexEntries)) {
974
+ for (const required of entry.requires ?? []) {
975
+ if (!shippedIds.has(required)) {
976
+ throw new Error(
977
+ `distribution "${id}": biome "${entry.id}" declares that it requires biome ` +
978
+ `"${required}", which positive closure did not include — its dependency ` +
979
+ 'contract carries no required edge to it. Either the contract is missing ' +
980
+ 'the edge, or the declaration is stale and belongs deleted from the manifest.',
981
+ );
982
+ }
983
+ }
984
+ for (const edge of entry.serviceDependencies ?? []) {
985
+ const satisfied =
986
+ edge.owner.kind === 'platform'
987
+ ? shippedPlatformServices.has(edge.requiredService)
988
+ : shippedIds.has(edge.owner.biomeId);
989
+ if (!satisfied) {
990
+ throw new Error(
991
+ `distribution "${id}": biome "${entry.id}" API "${edge.consumerService}" declares ` +
992
+ `that it requires service "${edge.requiredService}" (owned by ` +
993
+ `${edge.owner.kind === 'platform' ? 'platform service' : `biome "${edge.owner.biomeId}"`}), ` +
994
+ 'which positive closure did not include — its dependency contract carries no ' +
995
+ 'required edge to it. Either the contract is missing the edge, or the ' +
996
+ 'declaration is stale.',
997
+ );
998
+ }
999
+ }
1000
+ }
1001
+
1002
+ return {
1003
+ selected,
1004
+ mandatoryKeys,
1005
+ policyByKey,
1006
+ shippedPlatformServices,
1007
+ resolutionSection: {
1008
+ mode: DistributionResolutionMode.PositiveClosure,
1009
+ entries: result.entries,
1010
+ },
1011
+ };
1012
+ }
1013
+
1014
+ /**
1015
+ * Bind every selected biome's declared resource requirements to providers.
1016
+ *
1017
+ * For each requirement the providers are the SHIPPED components declaring
1018
+ * `providesResources` for its kind, across the edition's resolved set:
1019
+ *
1020
+ * - exactly one → a binding row naming it;
1021
+ * - none, `required` → REFUSE: the consumer cannot function in this edition;
1022
+ * - none, `optional` → NO row. Optional never pulls a provider in; the
1023
+ * consumer handles the absence, and nothing is admitted;
1024
+ * - more than one → REFUSE: no provider-selection policy exists yet, and
1025
+ * picking one would be a guess made on the consumer's behalf.
1026
+ *
1027
+ * The selector-model closure has already pulled in the sole provider of every
1028
+ * `required` requirement (required-resource-provider.mjs), so a provider is
1029
+ * always among `selectedEntries` here and the `none, required` branch below is
1030
+ * a backstop the closure makes unreachable.
1031
+ *
1032
+ * Returns a function from an index entry to the spread the lock's biome
1033
+ * literal takes: `{ serviceResources }` when that biome has a binding, `{}`
1034
+ * otherwise.
1035
+ */
1036
+ export function serviceResourceBinder(distributionId, selectedEntries, schemas) {
1037
+ const providersByKind = new Map();
1038
+ for (const entry of selectedEntries) {
1039
+ for (const provision of entry.providesResources ?? []) {
1040
+ if (!(Array.isArray(entry.services) && entry.services.includes(provision.service))) {
1041
+ throw new Error(
1042
+ `distribution "${distributionId}": biome "${entry.id}" declares that ` +
1043
+ `"${provision.service}" provides ${provision.kind}, but it does not ` +
1044
+ `ship that service`,
1045
+ );
1046
+ }
1047
+ const providers = providersByKind.get(provision.kind) ?? [];
1048
+ providers.push({ biomeId: entry.id, serviceName: provision.service });
1049
+ providersByKind.set(provision.kind, providers);
1050
+ }
1051
+ }
1052
+ return (entry) => {
1053
+ const rows = [];
1054
+ for (const requirement of entry.serviceResources ?? []) {
1055
+ const providers = providersByKind.get(requirement.kind) ?? [];
1056
+ const subject =
1057
+ `biome "${entry.id}" service "${requirement.consumerService}" requires ` +
1058
+ `${requirement.kind} "${requirement.logicalName}" (${requirement.scope}-scoped, ` +
1059
+ `${requirement.inclusion})`;
1060
+ if (providers.length > 1) {
1061
+ throw new Error(
1062
+ `distribution "${distributionId}": ${subject}, and ${providers.length} ` +
1063
+ `shipped services provide that kind: ` +
1064
+ `${providers.map((p) => `${p.biomeId}/${p.serviceName}`).join(', ')}. ` +
1065
+ `No provider-selection policy exists yet, so the resolver refuses ` +
1066
+ `rather than choosing one; ship exactly one provider of the kind.`,
1067
+ );
1068
+ }
1069
+ if (providers.length === 0) {
1070
+ if (requirement.inclusion === 'required') {
1071
+ throw new Error(
1072
+ `distribution "${distributionId}": ${subject}, and no shipped service ` +
1073
+ `provides ${requirement.kind}. Select the biome that provides it, ` +
1074
+ `or exclude the consumer.`,
1075
+ );
1076
+ }
1077
+ // Optional and absent: no binding, nothing admitted. The consumer
1078
+ // handles the provider's absence.
1079
+ continue;
1080
+ }
1081
+ rows.push({
1082
+ consumerService: requirement.consumerService,
1083
+ kind: requirement.kind,
1084
+ logicalName: requirement.logicalName,
1085
+ scope: requirement.scope,
1086
+ inclusion: requirement.inclusion,
1087
+ provider: providers[0],
1088
+ });
1089
+ }
1090
+ if (rows.length === 0) {return {};}
1091
+ return {
1092
+ serviceResources: rows.sort(schemas.compareLockedServiceResources),
1093
+ };
1094
+ };
1095
+ }
1096
+
1097
+ /**
1098
+ * Bind every Nexus service a shipped worker REQUIRES to the ONE shipped
1099
+ * component that PROVIDES it, and return the lock's `nexusBindings` rows.
1100
+ *
1101
+ * The RULE is the kernel's `resolveNexusBindings` — same service, same major
1102
+ * version, EVERY required operation; absent, ambiguous and missing-operation
1103
+ * refusals; optional-and-absent binds nothing — and the runtime resolver
1104
+ * (`@xemahq/distribution-resolver`) calls the same function, so the two can
1105
+ * never bind one edition two ways. Nothing of the algorithm is restated here.
1106
+ *
1107
+ * What stays here is what only this repository knows: the index projection
1108
+ * (`nexusProvides` / `nexusRequires`, see `componentNexusDeclarations`) and
1109
+ * the check that a provider row names a component its biome SHIPS as a
1110
+ * scheduled image — a binding to anything else would register an endpoint to
1111
+ * a worker that is not deployed.
1112
+ *
1113
+ * Every refusal the kernel returns is reported at once, one line each. Like
1114
+ * `serviceResourceBinder`, this never INCLUDES a provider the edition did not
1115
+ * already select: the kernel only ever sees the selected entries. Returns `[]`
1116
+ * when no shipped worker requires anything, so the producer omits the field
1117
+ * and every such lock stays byte-identical.
1118
+ */
1119
+ export function nexusBinder(distributionId, selectedEntries, schemas) {
1120
+ for (const entry of selectedEntries) {
1121
+ for (const provision of entry.nexusProvides ?? []) {
1122
+ if (!(Array.isArray(entry.services) && entry.services.includes(provision.component))) {
1123
+ throw new Error(
1124
+ `distribution "${distributionId}": biome "${entry.id}" declares that ` +
1125
+ `component "${provision.component}" provides nexus service ` +
1126
+ `${provision.service}@${provision.version}, but it does not ship that ` +
1127
+ `component as a scheduled image`,
1128
+ );
1129
+ }
1130
+ }
1131
+ }
1132
+ const result = schemas.resolveNexusBindings(
1133
+ selectedEntries.map((entry) => ({
1134
+ biomeId: entry.id,
1135
+ provides: entry.nexusProvides ?? [],
1136
+ requires: entry.nexusRequires ?? [],
1137
+ })),
1138
+ );
1139
+ if (!result.ok) {
1140
+ throw new Error(
1141
+ `distribution "${distributionId}": ${result.refusals.length} Nexus ` +
1142
+ `requirement(s) cannot be bound:\n` +
1143
+ result.refusals
1144
+ .map((refusal) => ` - ${schemas.describeNexusBindingRefusal(refusal)}`)
1145
+ .join('\n'),
1146
+ );
1147
+ }
1148
+ return result.bindings;
1149
+ }
1150
+
1151
+ export function resolveDistributionFromInputs(
1152
+ id,
1153
+ index,
1154
+ schemas,
1155
+ composed,
1156
+ resolvedAt = new Date().toISOString(),
1157
+ platformServiceIndex = { schemaVersion: 1, services: [] },
1158
+ ) {
1159
+ const {
1160
+ parseDistribution,
1161
+ BiomeInstallPolicy,
1162
+ BiomeLaunchMode,
1163
+ DistributionVisibility,
1164
+ DistributionResolutionMode,
1165
+ } = schemas;
1166
+ const {
1167
+ include,
1168
+ exclude,
1169
+ trustPolicy,
1170
+ trustCeiling,
1171
+ declaredVisibilities,
1172
+ owner,
1173
+ leaf,
1174
+ // Absent on a selector-model composition — including every `composed` a
1175
+ // caller built before positive closure existed.
1176
+ resolution: composedResolution,
1177
+ roots: composedRoots,
1178
+ } = composed ?? composeEdition(parseDistribution, id);
1179
+ // A `composed` handed in by a caller (the contract suite drives the resolver
1180
+ // this way) may predate this field; an absent set is the empty set, which is
1181
+ // the fail-closed reading.
1182
+ const declaredSourceNames = composed?.declaredSourceNames ?? new Set();
1183
+ // What is universal is read from the index's installation classes, in BOTH
1184
+ // resolution models — so an index that cannot answer is refused first.
1185
+ assertIndexDeclaresInstallationClasses(id, index);
1186
+ // name → pinned `LockedBiomeSource`, produced by the walk. Absent for every
1187
+ // edition that declares no sources, which is all nine committed ones.
1188
+ const sourceRecords = composed?.sourceRecords ?? new Map();
1189
+
1190
+ // Every selector/dependency/exclude uses a biome id. Until the canonical
1191
+ // schema grows a compound reference, two index entries with the same id are
1192
+ // ambiguous even when target/repo differ. Refuse the index rather than
1193
+ // silently selecting one or treating one selected identity as satisfying
1194
+ // another.
1195
+ const entriesById = new Map();
1196
+ for (const entry of index) {
1197
+ const entries = entriesById.get(entry.id) ?? [];
1198
+ entries.push(entry);
1199
+ entriesById.set(entry.id, entries);
1200
+ }
1201
+ for (const [biomeId, entries] of entriesById) {
1202
+ if (entries.length > 1) {
1203
+ throw new Error(
1204
+ `distribution "${id}": biome id "${biomeId}" resolves to multiple compound identities: ` +
1205
+ entries
1206
+ .map((entry) => `${entry.target}@${entry.repo}/${entry.path}`)
1207
+ .sort()
1208
+ .join(', ') +
1209
+ '. The id-only distribution schema cannot choose safely.',
1210
+ );
1211
+ }
1212
+ }
1213
+ const platformSourcesByName = new Map();
1214
+ for (const source of platformServiceIndex.services) {
1215
+ if (platformSourcesByName.has(source.name)) {
1216
+ throw new Error(
1217
+ `distribution "${id}": platform service "${source.name}" has ambiguous source identities in platform-service-index.json`,
1218
+ );
1219
+ }
1220
+ platformSourcesByName.set(source.name, source);
1221
+ }
1222
+ // The substrate CANDIDATES: every service in the index, with the facts the
1223
+ // selection reads. Selection (class narrowing, edge pulls, `exclude`) runs over
1224
+ // this; the edition contributes none.
1225
+ const platformServices = new Map(
1226
+ [...platformSourcesByName.values()].map(({ name, tier, installationClass }) => [
1227
+ name,
1228
+ { name, tier, installationClass },
1229
+ ]),
1230
+ );
1231
+ // SELECTION — the one step the two resolution models do differently. Each
1232
+ // returns the same four things; everything after (trust policy, resource
1233
+ // bindings, the lock literal) is shared, so a positive-closure lock and a
1234
+ // selector-model lock are built by the same code from the same kind of set.
1235
+ const {
1236
+ selected,
1237
+ mandatoryKeys,
1238
+ policyByKey,
1239
+ shippedPlatformServices,
1240
+ resolutionSection,
1241
+ } =
1242
+ composedResolution === DistributionResolutionMode.PositiveClosure
1243
+ ? selectByPositiveClosure({
1244
+ id,
1245
+ index,
1246
+ schemas,
1247
+ roots: composedRoots,
1248
+ declaredSourceNames,
1249
+ platformSourcesByName,
1250
+ })
1251
+ : selectBySelectors();
1252
+
1253
+ /** The legacy selector model: include selectors, exclude, `requires` closure. */
1254
+ function selectBySelectors() {
1255
+ // `exclude` names what this distribution REMOVES after composition. TWO
1256
+ // namespaces are removable, and they share the one list rather than each
1257
+ // getting a field of its own:
1258
+ //
1259
+ // - a biome id, resolved against the biome index;
1260
+ // - a composed platform-service name, resolved against `platformServices`.
1261
+ //
1262
+ // Sharing the list is not a shortcut, it is the only mechanism available
1263
+ // here: `DistributionSchema` is a plain zod object, so it STRIPS unknown keys
1264
+ // silently — a second manifest field (`excludePlatformServices`) would be
1265
+ // dropped by `parseDistribution` before this resolver ever saw it, and the
1266
+ // edition would ship the service it declared it did not want, with no error
1267
+ // anywhere. Adding the field for real means changing the published kernel
1268
+ // contract. `exclude` is already parsed and already means exactly "remove this".
1269
+ //
1270
+ // The two namespaces are disjoint today (no biome id equals a platform
1271
+ // service name) and a name resolving in BOTH is refused rather than
1272
+ // arbitrated, so the shared list can never silently pick one. A name in
1273
+ // NEITHER fails closed, exactly as it did when only biomes were removable.
1274
+ //
1275
+ // Why a platform service must be excludable at all: billing. A fresh
1276
+ // instance — a personal user, an SME, a corporate — may have no billing
1277
+ // plane. `llm-gateway-api` answers 501 BILLING_NOT_CONFIGURED for a metered
1278
+ // request when none is wired, and `llm-registry-api` asks the service
1279
+ // registry whether this deployment declared one. Neither can help while the
1280
+ // packaging plane ships `billing-api` into every edition with no opt-out.
1281
+ const excludedBiomes = new Set();
1282
+ const excludedPlatformServices = new Set();
1283
+ for (const excludedId of exclude) {
1284
+ const isBiome = entriesById.has(excludedId);
1285
+ const platformService = platformServices.get(excludedId);
1286
+ if (isBiome && platformService) {
1287
+ throw new Error(
1288
+ `distribution "${id}" excludes "${excludedId}", which is BOTH a biome id and a ` +
1289
+ `platform service name — the shared exclude namespace cannot choose safely`,
1290
+ );
1291
+ }
1292
+ if (platformService) {
1293
+ // Mirrors the universal-biome rule below: a core-class service is in
1294
+ // every installation, so it can never be opted out of.
1295
+ if (isUniversal(platformService)) {
1296
+ throw new Error(
1297
+ `distribution "${id}" tries to exclude universal (installationClass core) platform ` +
1298
+ `service "${excludedId}" — a universal service is in every edition and cannot be excluded`,
1299
+ );
1300
+ }
1301
+ excludedPlatformServices.add(excludedId);
1302
+ continue;
1303
+ }
1304
+ if (!isBiome) {
1305
+ throw new Error(
1306
+ `distribution "${id}" excludes unknown biome or platform service "${excludedId}"`,
1307
+ );
1308
+ }
1309
+ excludedBiomes.add(excludedId);
1310
+ }
1311
+
1312
+ // The platform substrate this edition actually ships. Derived BEFORE the
1313
+ // dependency closure below, so `requirePlatformOwner` sees the post-exclusion
1314
+ // set: a biome whose API declares a service edge onto an excluded platform
1315
+ // service then fails closed, citing the exact edge, instead of resolving into
1316
+ // a lock that promises a service the edition does not carry. That is what
1317
+ // makes a billing-free edition COHERENT rather than merely smaller.
1318
+ //
1319
+ // A copy, never a mutation of `platformServices`: callers may pass a
1320
+ // pre-built `composed` and reuse it across resolves.
1321
+ //
1322
+ // The class narrowing runs BEFORE the exclusions and AFTER the partition
1323
+ // above, deliberately. `exclude` is resolved against the COMPOSED map, so a
1324
+ // class-drawn edition can still name a service the narrowing already removed
1325
+ // without the exclude failing closed as "unknown"; and narrowing before the
1326
+ // deletion means the two mechanisms compose rather than fight — an edition
1327
+ // may draw a class and still exclude one member of it.
1328
+ const admittedClasses = admittedInstallationClasses(include);
1329
+ const shippedPlatformServices = narrowPlatformServicesByClass(
1330
+ new Map(platformServices),
1331
+ admittedClasses,
1332
+ );
1333
+ for (const name of excludedPlatformServices) {
1334
+ shippedPlatformServices.delete(name);
1335
+ }
1336
+
1337
+ // (1) Apply include selectors (also collecting the distribution's per-biome
1338
+ // `mandatory` force-on decision + selector-provided install policies).
1339
+ const { selected, mandatoryKeys, policyByKey, explicitKeys } = applySelectors(
1340
+ index,
1341
+ include,
1342
+ declaredSourceNames,
1343
+ composed?.webProducerRepos,
1344
+ );
1345
+
1346
+ // (2) Force-include every UNIVERSAL (core-class) biome; a universal biome
1347
+ // can NEVER be excluded. Added after exclude resolution below to
1348
+ // enforce that. A biome drawn from an undeclared source is never
1349
+ // universal here — it is not admissible to this edition at all.
1350
+ const universalEntries = index.filter(
1351
+ (e) => isUniversal(e) && entrySourceAdmissible(e, declaredSourceNames),
1352
+ );
1353
+
1354
+ // (3) Apply exclude — but a universal biome cannot be excluded.
1355
+ for (const ex of excludedBiomes) {
1356
+ if (universalEntries.some((e) => e.id === ex)) {
1357
+ throw new Error(
1358
+ `distribution "${id}" tries to exclude universal (installationClass core) biome "${ex}" — ` +
1359
+ `a universal biome is in every edition and cannot be excluded`,
1360
+ );
1361
+ }
1362
+ for (const key of [...selected.keys()]) {
1363
+ if (selected.get(key).id === ex) {selected.delete(key);}
1364
+ }
1365
+ }
1366
+
1367
+ // Force-include the universal set after exclude so exclude can never drop it.
1368
+ for (const e of universalEntries) {selected.set(entryKey(e), e);}
1369
+
1370
+ // (4) Dependency closure — selector entries are feature/profile roots, while
1371
+ // xema-biome dependency metadata is the ONLY composition graph. Resolve
1372
+ // that graph transitively into the lock; never make a distribution author
1373
+ // repeat the dependency roster by hand. An excluded or unknown dependency
1374
+ // fails closed and cites the exact edge.
1375
+ const includedIds = new Set([...selected.values()].map((e) => e.id));
1376
+ const queue = [...selected.values()].sort(compareIndexEntries);
1377
+ const resourceProviderCandidates = index.filter(
1378
+ (e) => isDeliverable(e, composed?.webProducerRepos) && entrySourceAdmissible(e, declaredSourceNames),
1379
+ );
1380
+ const requireBiomeOwner = (consumer, edge) => {
1381
+ const owner = edge.owner;
1382
+ if (owner.target !== 'server') {
1383
+ throw new Error(
1384
+ `distribution "${id}": biome "${consumer.id}" API "${edge.consumerService}" requires service ` +
1385
+ `"${edge.requiredService}" from cross-target ${owner.target} biome "${owner.biomeId}"`,
1386
+ );
1387
+ }
1388
+ const candidates = entriesById.get(owner.biomeId) ?? [];
1389
+ const matches = candidates.filter(
1390
+ (candidate) =>
1391
+ candidate.target === owner.target &&
1392
+ candidate.repo === owner.repo &&
1393
+ candidate.path === owner.path &&
1394
+ candidate.version === owner.version &&
1395
+ candidate.sourceManifestHash === owner.sourceManifestHash &&
1396
+ candidate.services.includes(edge.requiredService),
1397
+ );
1398
+ if (matches.length !== 1) {
1399
+ throw new Error(
1400
+ `distribution "${id}": biome "${consumer.id}" API "${edge.consumerService}" requires service ` +
1401
+ `"${edge.requiredService}", but its exact biome owner ${owner.biomeId} ` +
1402
+ `(${owner.target}@${owner.repo}/${owner.path}, ${owner.version}, ${owner.sourceManifestHash}) ` +
1403
+ `resolved ${matches.length} times`,
1404
+ );
1405
+ }
1406
+ return matches[0];
1407
+ };
1408
+ // Every closure edge, recorded for the widening check after the closure:
1409
+ // consumer key → [{ target key | platform service, how it was declared }].
1410
+ const biomeEdges = new Map();
1411
+ const platformEdges = new Map();
1412
+ const recordEdge = (edges, consumer, target) => {
1413
+ const key = entryKey(consumer);
1414
+ const list = edges.get(key) ?? [];
1415
+ list.push(target);
1416
+ edges.set(key, list);
1417
+ };
1418
+ const requirePlatformOwner = (consumer, edge) => {
1419
+ const source = edge.owner.source;
1420
+ if (source.name !== edge.requiredService) {
1421
+ throw new Error(
1422
+ `distribution "${id}": biome "${consumer.id}" API "${edge.consumerService}" service edge ` +
1423
+ `"${edge.requiredService}" carries mismatched platform owner "${source.name}"`,
1424
+ );
1425
+ }
1426
+ const canonical = platformSourcesByName.get(edge.requiredService);
1427
+ if (!canonical) {
1428
+ throw new UnknownSelectionError(
1429
+ `distribution "${id}": biome "${consumer.id}" API "${edge.consumerService}" requires platform service ` +
1430
+ `"${edge.requiredService}", which the platform-service index does not hold`,
1431
+ );
1432
+ }
1433
+ if (serializeJson(canonical) !== serializeJson(source)) {
1434
+ throw new Error(
1435
+ `distribution "${id}": biome "${consumer.id}" API "${edge.consumerService}" requires platform service ` +
1436
+ `"${edge.requiredService}", but its exact committed source identity is absent or mismatched`,
1437
+ );
1438
+ }
1439
+ // A required service the class narrowing left out is PULLED by the
1440
+ // edge, exactly as a required biome is — unless the edition excluded it,
1441
+ // which fails closed citing the edge.
1442
+ if (
1443
+ !shippedPlatformServices.has(edge.requiredService) &&
1444
+ !excludedPlatformServices.has(edge.requiredService)
1445
+ ) {
1446
+ shippedPlatformServices.set(
1447
+ edge.requiredService,
1448
+ platformServices.get(edge.requiredService),
1449
+ );
1450
+ }
1451
+ const declared = shippedPlatformServices.get(edge.requiredService);
1452
+ if (!declared) {
1453
+ throw new Error(
1454
+ `distribution "${id}": biome "${consumer.id}" API "${edge.consumerService}" requires platform service ` +
1455
+ `"${edge.requiredService}", but the distribution explicitly excludes it`,
1456
+ );
1457
+ }
1458
+ recordEdge(platformEdges, consumer, {
1459
+ service: declared,
1460
+ via: `API "${edge.consumerService}" requires platform service "${edge.requiredService}"`,
1461
+ });
1462
+ };
1463
+ // A web biome is the admin UI of the server biomes it `requires`: it ships
1464
+ // with them unless the edition excludes it by name. The closure edge runs
1465
+ // server -> web, so an edition that draws a server biome cannot silently
1466
+ // lose its UI. A web joins once EVERY server it requires is in the edition
1467
+ // (the last of them to be processed completes it), so a UI is never pulled
1468
+ // in ahead of the capability it administers.
1469
+ const webBiomesByRequiredServer = new Map();
1470
+ for (const candidate of resourceProviderCandidates) {
1471
+ if (
1472
+ candidate.target !== 'web' ||
1473
+ candidate.audience === 'operator' ||
1474
+ excludedBiomes.has(candidate.id)
1475
+ ) {
1476
+ continue;
1477
+ }
1478
+ for (const req of candidate.requires) {
1479
+ const webs = webBiomesByRequiredServer.get(req) ?? [];
1480
+ webs.push(candidate);
1481
+ webBiomesByRequiredServer.set(req, webs);
1482
+ }
1483
+ }
1484
+ const pullWebBiomesOf = (server) => {
1485
+ for (const web of webBiomesByRequiredServer.get(server.id) ?? []) {
1486
+ if (includedIds.has(web.id)) {continue;}
1487
+ if (!web.requires.every((req) => includedIds.has(req))) {continue;}
1488
+ recordEdge(biomeEdges, server, {
1489
+ id: web.id,
1490
+ via: `is administered by web biome "${web.id}"`,
1491
+ });
1492
+ includedIds.add(web.id);
1493
+ const key = entryKey(web);
1494
+ explicitKeys.add(key);
1495
+ selected.set(key, web);
1496
+ queue.push(web);
1497
+ }
1498
+ };
1499
+ for (let cursor = 0; cursor < queue.length; cursor += 1) {
1500
+ const entry = queue[cursor];
1501
+ pullWebBiomesOf(entry);
1502
+ for (const req of [...entry.requires].sort()) {
1503
+ if (excludedBiomes.has(req)) {
1504
+ throw new Error(
1505
+ `distribution "${id}": biome "${entry.id}" (${entry.repo}) requires "${req}", ` +
1506
+ `but the distribution explicitly excludes it`,
1507
+ );
1508
+ }
1509
+ const viaRequires = { id: req, via: `requires biome "${req}"` };
1510
+ if (includedIds.has(req)) {
1511
+ recordEdge(biomeEdges, entry, viaRequires);
1512
+ continue;
1513
+ }
1514
+ const dependencies = entriesById.get(req);
1515
+ if (!dependencies || dependencies.length === 0) {
1516
+ throw new UnknownSelectionError(
1517
+ `distribution "${id}": biome "${entry.id}" (${entry.repo}) requires unknown biome "${req}"`,
1518
+ );
1519
+ }
1520
+ if (
1521
+ dependencies.some((dependency) => dependency.audience === 'operator')
1522
+ ) {
1523
+ throw new Error(
1524
+ `distribution "${id}": biome "${entry.id}" (${entry.repo}) requires operator biome "${req}", ` +
1525
+ `which must be selected explicitly by id`,
1526
+ );
1527
+ }
1528
+ // THE THIRD DOOR, and the one a selector fence alone leaves open: an
1529
+ // admissible biome that `requires` a foreign one would drag it in
1530
+ // transitively, with no selector anywhere naming it. Refuse rather than
1531
+ // pull — a public biome that hard-requires a customer's private biome is
1532
+ // a structural defect in the dependency graph, and satisfying it here
1533
+ // would put that customer's biome into every edition the public one
1534
+ // reaches. Refuse rather than DROP, for the same reason the excluded-
1535
+ // dependency branch above refuses: a biome whose requirement is missing
1536
+ // does not boot, so a quietly smaller closure is a broken installation
1537
+ // that resolved cleanly.
1538
+ const foreignDeps = dependencies.filter(
1539
+ (dependency) => !entrySourceAdmissible(dependency, declaredSourceNames),
1540
+ );
1541
+ if (foreignDeps.length > 0 && foreignDeps.length === dependencies.length) {
1542
+ const names = [...new Set(foreignDeps.map((d) => d.source))].sort();
1543
+ throw new Error(
1544
+ `distribution "${id}": biome "${entry.id}" (${entry.repo}) requires "${req}", ` +
1545
+ `which is drawn from source(s) ${names.map((name) => `"${name}"`).join(', ')} ` +
1546
+ `that this edition does not declare in sources[]. Refusing rather than ` +
1547
+ `pulling a foreign biome in through a dependency edge no selector named.`,
1548
+ );
1549
+ }
1550
+ includedIds.add(req);
1551
+ recordEdge(biomeEdges, entry, viaRequires);
1552
+ for (const dependency of dependencies.sort(compareIndexEntries)) {
1553
+ const key = entryKey(dependency);
1554
+ if (!selected.has(key)) {
1555
+ selected.set(key, dependency);
1556
+ queue.push(dependency);
1557
+ }
1558
+ }
1559
+ }
1560
+ for (const edge of entry.serviceDependencies ?? []) {
1561
+ if (!entry.services.includes(edge.consumerService)) {
1562
+ throw new Error(
1563
+ `distribution "${id}": biome "${entry.id}" service dependency names unknown consumer API ` +
1564
+ `"${edge.consumerService}"`,
1565
+ );
1566
+ }
1567
+ if (edge.owner.kind === 'platform') {
1568
+ requirePlatformOwner(entry, edge);
1569
+ continue;
1570
+ }
1571
+ const dependency = requireBiomeOwner(entry, edge);
1572
+ if (excludedBiomes.has(dependency.id)) {
1573
+ throw new Error(
1574
+ `distribution "${id}": biome "${entry.id}" API "${edge.consumerService}" requires service ` +
1575
+ `"${edge.requiredService}" owned by explicitly excluded biome "${dependency.id}"`,
1576
+ );
1577
+ }
1578
+ if (
1579
+ dependency.audience === 'operator' &&
1580
+ !includedIds.has(dependency.id)
1581
+ ) {
1582
+ throw new Error(
1583
+ `distribution "${id}": biome "${entry.id}" API "${edge.consumerService}" requires service ` +
1584
+ `"${edge.requiredService}" owned by operator biome "${dependency.id}", which must be selected explicitly`,
1585
+ );
1586
+ }
1587
+ recordEdge(biomeEdges, entry, {
1588
+ id: dependency.id,
1589
+ via: `API "${edge.consumerService}" requires service "${edge.requiredService}" owned by biome "${dependency.id}"`,
1590
+ });
1591
+ if (!includedIds.has(dependency.id)) {
1592
+ includedIds.add(dependency.id);
1593
+ const key = entryKey(dependency);
1594
+ selected.set(key, dependency);
1595
+ queue.push(dependency);
1596
+ }
1597
+ }
1598
+ // A required resource requirement is a closure edge to the sole
1599
+ // provider of that kind (required-resource-provider.mjs). The provider
1600
+ // is a necessity of the consumer, not a class widening, so it joins as
1601
+ // an explicit root: its own requirements then follow freely.
1602
+ for (const requirement of entry.serviceResources ?? []) {
1603
+ if (requirement.inclusion !== 'required') {continue;}
1604
+ const provider = soleResourceProvider({
1605
+ distributionId: id,
1606
+ consumer: entry,
1607
+ requirement,
1608
+ candidates: resourceProviderCandidates,
1609
+ });
1610
+ if (provider.id === entry.id) {continue;}
1611
+ if (excludedBiomes.has(provider.id)) {
1612
+ throw new Error(
1613
+ `distribution "${id}": biome "${entry.id}" service "${requirement.consumerService}" requires ` +
1614
+ `${requirement.kind} "${requirement.logicalName}" provided by explicitly excluded biome "${provider.id}"`,
1615
+ );
1616
+ }
1617
+ recordEdge(biomeEdges, entry, {
1618
+ id: provider.id,
1619
+ via: `service "${requirement.consumerService}" requires ${requirement.kind} "${requirement.logicalName}" provided by biome "${provider.id}"`,
1620
+ });
1621
+ includedIds.add(provider.id);
1622
+ const key = entryKey(provider);
1623
+ explicitKeys.add(key);
1624
+ if (!selected.has(key)) {
1625
+ selected.set(key, provider);
1626
+ queue.push(provider);
1627
+ }
1628
+ }
1629
+ }
1630
+
1631
+ // (4b) THE CLOSURE MAY NOT WIDEN WHAT ONLY A CLASS ASKED FOR.
1632
+ //
1633
+ // Two ways into an edition, and they carry different intent. A biome the
1634
+ // edition NAMES, or sweeps by tier/origin, is an explicit opt-in to that
1635
+ // biome AND to everything it declares it requires, of any class: naming
1636
+ // `agent-runtime` brings the executor, the release plane and the LLM
1637
+ // gateway it cannot run without, and no edition has to name the class.
1638
+ // What only a CLASS admitted — the universal (core) floor included — is a
1639
+ // statement about that class, so its closure must stay inside the
1640
+ // admitted classes; otherwise the edition quietly grows (a universal biome
1641
+ // with an edge into a family would put that family into every
1642
+ // installation). Unclassified entries pass: absent is a third answer, and
1643
+ // the only one a web or adapter entry can give.
1644
+ //
1645
+ // Computed AFTER the closure, from the recorded edges, so the verdict does
1646
+ // not depend on the order the queue happened to visit things: an entry is
1647
+ // FREE iff it is reachable from an explicitly admitted root.
1648
+ const selectedByEntryId = new Map(
1649
+ [...selected.values()].map((e) => [e.id, e]),
1650
+ );
1651
+ const free = new Set();
1652
+ const walk = [...explicitKeys].filter((key) => selected.has(key));
1653
+ for (const key of walk) {free.add(key);}
1654
+ for (let cursor = 0; cursor < walk.length; cursor += 1) {
1655
+ for (const target of biomeEdges.get(walk[cursor]) ?? []) {
1656
+ const targetEntry = selectedByEntryId.get(target.id);
1657
+ const targetKey = entryKey(targetEntry);
1658
+ if (!free.has(targetKey)) {
1659
+ free.add(targetKey);
1660
+ walk.push(targetKey);
1661
+ }
1662
+ }
1663
+ }
1664
+ // A platform service a FREE consumer requires is in the edition by that
1665
+ // consumer's right, whoever else also requires it.
1666
+ const freePlatformServices = new Set();
1667
+ for (const key of free) {
1668
+ for (const target of platformEdges.get(key) ?? []) {
1669
+ freePlatformServices.add(target.service.name);
1670
+ }
1671
+ }
1672
+ const refuseWidening = (consumer, via, pulledClass) => {
1673
+ throw new Error(
1674
+ `distribution "${id}": biome "${consumer.id}" ${via}, which declares ` +
1675
+ `installationClass "${pulledClass}" — "${consumer.id}" is in this edition only ` +
1676
+ `through the class selector(s) [${[...admittedClasses].sort().join(', ')}], and what a ` +
1677
+ `class admitted may not widen through the dependency closure. Name the biome that ` +
1678
+ `needs it (a named biome pulls its requirements of any class), or — if the edge is ` +
1679
+ `stale or one of the two classifications is wrong — fix the declaring manifest.`,
1680
+ );
1681
+ };
1682
+ for (const consumer of [...selected.values()].sort(compareIndexEntries)) {
1683
+ const consumerKey = entryKey(consumer);
1684
+ if (free.has(consumerKey)) {continue;}
1685
+ for (const target of biomeEdges.get(consumerKey) ?? []) {
1686
+ const targetEntry = selectedByEntryId.get(target.id);
1687
+ if (free.has(entryKey(targetEntry))) {continue;}
1688
+ const pulledClass = targetEntry.installationClass;
1689
+ if (pulledClass !== undefined && !admittedClasses.has(pulledClass)) {
1690
+ refuseWidening(consumer, target.via, pulledClass);
1691
+ }
1692
+ }
1693
+ for (const target of platformEdges.get(consumerKey) ?? []) {
1694
+ if (freePlatformServices.has(target.service.name)) {continue;}
1695
+ const pulledClass = target.service.installationClass;
1696
+ if (pulledClass !== undefined && !admittedClasses.has(pulledClass)) {
1697
+ refuseWidening(consumer, target.via, pulledClass);
1698
+ }
1699
+ }
1700
+ }
1701
+
1702
+ return {
1703
+ selected,
1704
+ mandatoryKeys,
1705
+ policyByKey,
1706
+ shippedPlatformServices,
1707
+ resolutionSection: undefined,
1708
+ };
1709
+ }
1710
+
1711
+ // (5) Trust policy — every included biome's origin must be allowed.
1712
+ if (trustPolicy?.allow) {
1713
+ const allow = new Set(trustPolicy.allow);
1714
+ for (const e of selected.values()) {
1715
+ if (!allow.has(e.origin)) {
1716
+ throw new Error(
1717
+ `distribution "${id}": biome "${e.id}" (${e.repo}) has origin "${e.origin}", not in trustPolicy.allow [${[...allow].join(', ')}]`,
1718
+ );
1719
+ }
1720
+ }
1721
+ }
1722
+
1723
+ // Launch placement is a DISTRIBUTION decision applied at resolve time — like
1724
+ // `mandatory`, it is NEVER sourced from the biome manifest (WHERE/HOW a biome
1725
+ // runs varies per installation: static service, per-installation sidecar,
1726
+ // remote, on our infra or a client's — the biome only declares WHAT it is).
1727
+ // `per-installation` means the biome is a sidecar-host template instantiated
1728
+ // dynamically by biome-host-api once a biome is installed — it is NOT a
1729
+ // statically-launched service, so the static roster (dev launcher + prod
1730
+ // services.yaml) must not boot it standalone. Default (absent) = statically
1731
+ // launched. This is the minimal, edition-overridable default; a future
1732
+ // xema-distribution.json `launch` section can override it per edition.
1733
+ const PER_INSTALLATION_LAUNCH = new Set(['biome-adapter-host']);
1734
+
1735
+ // (5b) Resource bindings — each shipped requirement bound to the ONE shipped
1736
+ // provider of its kind. See `bindServiceResources`.
1737
+ const bindServiceResourcesFor = serviceResourceBinder(
1738
+ id,
1739
+ [...selected.values()],
1740
+ schemas,
1741
+ );
1742
+
1743
+ // (5c) Nexus bindings — each required (service, version) bound to the ONE
1744
+ // shipped provider. See `nexusBinder`.
1745
+ const nexusBindings = nexusBinder(id, [...selected.values()], schemas);
1746
+
1747
+ // (6) Build the lock — sort by (tier order, id, target, repo).
1748
+ const biomes = [...selected.values()]
1749
+ .map((e) => ({
1750
+ id: e.id,
1751
+ version: e.version,
1752
+ tier: e.tier,
1753
+ target: e.target,
1754
+ origin: e.origin,
1755
+ // `mandatory`: a universal (core-class) biome is always mandatory — it is
1756
+ // in every installation and cannot be opted out of; otherwise a matching
1757
+ // include selector flagged it. The tier never decides it.
1758
+ mandatory: isUniversal(e) || mandatoryKeys.has(entryKey(e)),
1759
+ // Package-model install policy: a universal (core-class) biome is ALWAYS
1760
+ // required; otherwise the most specific selector-provided policy wins
1761
+ // (collected in applySelectors); else default-selected. The tier never
1762
+ // decides it, and it is never sourced from the biome manifest.
1763
+ installPolicy:
1764
+ isUniversal(e)
1765
+ ? BiomeInstallPolicy.Required
1766
+ : (policyByKey.get(entryKey(e))?.policy ??
1767
+ BiomeInstallPolicy.DefaultSelected),
1768
+ repo: e.repo,
1769
+ path: e.path,
1770
+ services: Array.isArray(e.services) ? e.services : [],
1771
+ // `service → shared-cluster database key`, forwarded verbatim from the
1772
+ // index for the few services whose key is neither the biome id nor their
1773
+ // own name minus `-api`. org-database-pool-api's ownership fence reads
1774
+ // THIS — it is the only place the platform can learn that
1775
+ // `scm-connector-api` opens schema `scm-integration`, and reading it from
1776
+ // the resolved lock is what makes it a distribution-time fact rather than
1777
+ // something a booting service asserts about itself.
1778
+ //
1779
+ // STATED UNCONDITIONALLY, including when empty. The index omits the key
1780
+ // for the ~110 biomes that declare nothing (it keeps the index
1781
+ // byte-stable), but the LOCK is a contract every consumer parses: schema
1782
+ // v2 makes this field required precisely so a reader never has to decide
1783
+ // whether an absent key means "no declaration" or "produced by a builder
1784
+ // that predates the field".
1785
+ serviceDatabases: e.serviceDatabases ?? {},
1786
+ // `service → PostgreSQL extensions its database must carry`, forwarded
1787
+ // verbatim from the index (`protocol.databaseExtensions`, validated and
1788
+ // sorted at index build). The deployment's privileged database bootstrap
1789
+ // reads THIS to create every extension a physical database's biomes need
1790
+ // before any migration runs; the runtime only verifies.
1791
+ //
1792
+ // OMITTED when the entry carries none — the OPPOSITE of the rule one key
1793
+ // up, and deliberately so. The kernel lock schema marks this field
1794
+ // OPTIONAL so every sealed lock keeps parsing, and its reading of an
1795
+ // absent map is "this lock predates the declaration". Two things decide
1796
+ // omission over `{}`:
1797
+ // 1. BYTE-STABILITY. `serializeJson` is insertion-order
1798
+ // `JSON.stringify`, and at `origin/main` no manifest declares an
1799
+ // extension, so `{}` here would move every biome of all nine
1800
+ // committed locks — and `inputHash` with them — for a fact none of
1801
+ // their entries carries. Asserted by `dist-check`: the committed
1802
+ // locks must equal a fresh resolve byte for byte.
1803
+ // 2. THE BRIDGE. A deployment whose lock cannot yet say what its
1804
+ // databases need carries the requirement in its own inputs, and the
1805
+ // deploy plane refuses that bridge the moment ANY biome in the lock
1806
+ // carries this key. Emitting `{}` for a biome that declares nothing
1807
+ // would flip that refusal on a lock that still says nothing about
1808
+ // the one extension the deployment actually needs.
1809
+ // So per biome, absent means "declares none" exactly as it does on the
1810
+ // index entry, and a lock in which no biome declares one is byte-identical
1811
+ // to one minted before the field existed — which is the correct reading,
1812
+ // because it carries the same information.
1813
+ //
1814
+ // WITHHELD FROM THE LOCK UNTIL THE PINNED CONSUMERS CAN PARSE IT.
1815
+ //
1816
+ // The comment above is right that the KERNEL schema marks this optional
1817
+ // "so every sealed lock keeps parsing" — and that is true of
1818
+ // `@xemahq/contracts` 0.12.0, which learned the key. It is no help at all
1819
+ // to a 0.9.2 parser, which is `.strict()` and does not know it exists:
1820
+ // `Unrecognized key: "serviceDatabaseExtensions"`, thrown before Nest
1821
+ // starts. Optional-in-the-new-schema and absent-from-the-old are different
1822
+ // facts, and only the second one governs a deployed image.
1823
+ //
1824
+ // MEASURED 2026-09-17. Emitting it took presenca-dev's three
1825
+ // distribution-lock consumers — biome-host-api, capability-registry-api,
1826
+ // xema-capability-router — into CrashLoopBackOff on a sealed, signed,
1827
+ // platform-verified edition. The seal was green; the lock was unparseable
1828
+ // by the images the same lock pins.
1829
+ //
1830
+ // ── THE REASON THIS GIVES FOR THE BLOCK IS STALE. CORRECTED 2026-09-19 ──
1831
+ //
1832
+ // This comment used to continue: "xema-base `main` declares
1833
+ // `'@xemahq/contracts': ^0.9.2`, and A CARET ON A 0.x LINE IS MINOR-LOCKED
1834
+ // (`>=0.9.2 <0.10.0`), so no rebuild from `main` can ever resolve 0.12.0."
1835
+ //
1836
+ // The CONCLUSION still holds and the REASON does not, which is the worse
1837
+ // half: a premise stating that something is STRUCTURALLY IMPOSSIBLE is the
1838
+ // one a reader never re-derives, and this one stopped being true when
1839
+ // somebody raised the pin. Re-measured against the refs, not the comment:
1840
+ // xema-base origin/main `'@xemahq/contracts': ^0.15.0`
1841
+ // xema-base origin/develop `'@xemahq/contracts': ^0.16.0`
1842
+ // xema-base 2ef54a1 (the `sourceCommit` presenca-dev's images.lock.json
1843
+ // names) resolves `@xemahq/contracts@0.15.0`, sole resolution.
1844
+ // and against the published tarballs, two-sided, `npm pack` + grep:
1845
+ // 0.9.2 -> 0 files name this key; 0.12.0 / 0.15.0 / 0.17.0 -> 2 each.
1846
+ // So 0.12.0 learned it, ^0.15.0 admits it, and the pin has ALREADY moved
1847
+ // past the deletion condition this comment states below.
1848
+ //
1849
+ // WHAT ACTUALLY BLOCKS IT IS THE ROLLOUT, and a pin is not a rollout.
1850
+ // Measured 2026-09-19 inside the running containers in `presenca-dev`:
1851
+ // `cat /app/node_modules/@xemahq/contracts/package.json` -> "0.9.2",
1852
+ // one resolution each (`ls -d /app/node_modules/.pnpm/@xemahq+contracts@*`),
1853
+ // and `dist/distribution/lib/distribution-lock.js` carries `.strict()`
1854
+ // on 11 schema objects while `grep -oE 'service[A-Za-z]+'` over it yields
1855
+ // only serviceDatabases / serviceDependencies / services.
1856
+ // POSITIVE CONTROL, same tree, same probe: `grep -rl distribution` -> 64.
1857
+ // So the zero is an absence, not a broken probe.
1858
+ // And the three running digests are NOT the digests
1859
+ // `deployments/presenca-dev/images.lock.json` designates — the cluster is
1860
+ // roughly one build behind its own lock, which is why a pin raised on the
1861
+ // branch has not reached the parser.
1862
+ //
1863
+ // The block therefore moved from STRUCTURALLY IMPOSSIBLE to ONE ROLLOUT
1864
+ // AWAY, and nothing in the tree would have said so.
1865
+ //
1866
+ // This is still the fleet's producer->consumer ordering law, and
1867
+ // withholding is still the law rather than a retreat: publish the
1868
+ // producer, ADOPT IT IN THE CONSUMERS, then emit the new shape. Step one
1869
+ // is done, step two is now a deploy rather than a version bump, step
1870
+ // three is the flip below.
1871
+ //
1872
+ // DELETION CONDITION — as a PROBE you can run, not a claim to believe.
1873
+ // A version range in a manifest is a declaration; only the container
1874
+ // answers this. For each of biome-host-api, capability-registry-api and
1875
+ // xema-capability-router:
1876
+ //
1877
+ // kubectl -n presenca-dev exec deploy/<svc> -- \
1878
+ // cat /app/node_modules/@xemahq/contracts/package.json
1879
+ //
1880
+ // must report >= 0.12.0 for ALL THREE. When it does, in ONE change:
1881
+ // 1. flip this to the commented-out spread below;
1882
+ // 2. retire case (d) of `distribution-contract-tests.mjs`, which pins
1883
+ // the withholding and will correctly go red;
1884
+ // 3. re-seal the affected locks;
1885
+ // 4. delete the `postgres.extensions` BRIDGE blocks from
1886
+ // `deployments/presenca-dev/secret-inputs.yaml` and
1887
+ // `deployments/owl-xema-prod/secret-inputs.yaml` in xema-deploy —
1888
+ // the deploy plane REFUSES that bridge by name the moment any biome
1889
+ // in the lock carries this key, so 3 and 4 cannot be separated.
1890
+ //
1891
+ // Until then the field is still PARSED and still travels into the biome
1892
+ // INDEX — nothing rejects it there — so the declaration is validated and
1893
+ // only the LOCK, the one artifact a strict consumer parses, omits it.
1894
+ //
1895
+ // ...(e.serviceDatabaseExtensions === undefined
1896
+ // ? {}
1897
+ // : { serviceDatabaseExtensions: e.serviceDatabaseExtensions }),
1898
+ // Images the biome ships that are never scheduled — another workload
1899
+ // launches them one-per-allocation, prepulls them, or consumes them.
1900
+ // Carried SEPARATELY from `services` so the deploy plane cannot roster a
1901
+ // release for something that has no standing workload, while the image
1902
+ // plane still builds, addresses, signs and mirrors it.
1903
+ runtimeComponents: Array.isArray(e.runtimeComponents)
1904
+ ? e.runtimeComponents
1905
+ : [],
1906
+ // Executable modules the distribution mounts into a platform-owned host
1907
+ // (the connector adapters loaded by `biome-adapter-host`). Projected
1908
+ // verbatim from the index, which resolved each component's bundle source
1909
+ // against the biome manifest.
1910
+ hostModules: Array.isArray(e.hostModules) ? e.hostModules : [],
1911
+ // The BIOME-LEVEL bundle source, for a biome whose bundle no host module
1912
+ // above names — the field a `installPolicy: required` service-only
1913
+ // biome needs for the runtime's required-biome reconciler to fetch,
1914
+ // verify and admit it from the mounted lock. Projected verbatim from the
1915
+ // index, which validated it against the kernel's one bundle-source
1916
+ // shape (`manifestToIndexEntry`, which also says why a host-module
1917
+ // biome gains no second copy here).
1918
+ //
1919
+ // SPREAD CONDITIONALLY, for the reason `source` and `owner` are: an
1920
+ // unconditional key would move the bytes of every committed lock for a
1921
+ // fact none of their entries carries. A lock in which SOME biome carries
1922
+ // it still is the one v5 grammar once sealed.
1923
+ ...(e.bundleSource === undefined ? {} : { bundleSource: e.bundleSource }),
1924
+ serviceDependencies: Array.isArray(e.serviceDependencies)
1925
+ ? e.serviceDependencies
1926
+ : [],
1927
+ // Resolved resource BINDINGS — an AUTHORIZATION input the provider
1928
+ // enforces against the caller's verified service identity, as
1929
+ // `serviceDatabases` is for the database pool.
1930
+ //
1931
+ // SPREAD CONDITIONALLY, for the reason `bundleSource` is: no committed
1932
+ // manifest declares `protocol.resources`, so an unconditional `[]` would
1933
+ // move the bytes of every committed lock. It is an ADDITIVE, optional
1934
+ // field of the v4 grammar — no version bump — so a lock in which some
1935
+ // biome carries a binding is stamped 4, and a consumer reading it
1936
+ // through the kernel's tolerant reader schema needs no upgrade to parse
1937
+ // it. A consumer still on the strict writer schema refuses the key; that
1938
+ // one-time switch is sequenced separately, before an edition carries one.
1939
+ ...bindServiceResourcesFor(e),
1940
+ // `service → { componentKind, scalingMode, processFacts? }` — the facts
1941
+ // the deploy plane grades a composed process on (kernel
1942
+ // `LockedServiceProcessFactsSchema`), forwarded from the index, where
1943
+ // `componentProcessFacts` derived them from each platform-scheduled
1944
+ // image component. One row per name in `services`, so a service that
1945
+ // declares nothing still carries its derived kind and scaling class.
1946
+ //
1947
+ // Emitted for every biome that ships a platform-scheduled service; a
1948
+ // lock carrying it is stamped v4 by the kernel. The edition carries the
1949
+ // FACTS; which members share a process is the DEPLOYMENT's topology.
1950
+ // Every lock reader the fleet pins parses it: `repos/xema-base` resolves
1951
+ // `@xemahq/contracts` 0.31.0 and `repos/xema-operator` 0.30.0 at their
1952
+ // pins (the key exists with `componentKind` from 0.29.0), and the seal
1953
+ // refuses an edition whose own images cannot parse its lock
1954
+ // (platform-lock-readability.mjs).
1955
+ ...(e.serviceProcessFacts !== undefined
1956
+ ? { serviceProcessFacts: lockedServiceProcessFacts(e) }
1957
+ : {}),
1958
+ // The biome's worker Nexus FACTS — what it provides and requires — copied
1959
+ // from the index in the kernel's canonical form. The lock carries them
1960
+ // beside the `nexusBindings` they derive (the kernel writer requires the
1961
+ // two to agree), so a composition or proposal re-resolving from this lock
1962
+ // re-checks every requirement without the index. Omitted when empty, so
1963
+ // every edition declaring none is byte-identical. Because every lock is
1964
+ // re-resolved from the index and compared byte for byte (dist:check's lock
1965
+ // parity), a lock's facts that disagree with the index fail that check.
1966
+ ...schemas.canonicalNexusFacts({
1967
+ nexusProvides: e.nexusProvides,
1968
+ nexusRequires: e.nexusRequires,
1969
+ }),
1970
+ // The protocol worker/v2 components and the queues each polls — what the
1971
+ // deploy plane renders under a Worker Controller (W3.4 / U14). Omitted
1972
+ // when empty, so an edition shipping no v2 worker is byte-identical.
1973
+ ...(e.workerTaskQueues !== undefined && Object.keys(e.workerTaskQueues).length > 0
1974
+ ? { workerTaskQueues: e.workerTaskQueues }
1975
+ : {}),
1976
+ // The queues each http service starts workflows on, by the same service
1977
+ // name `workerTaskQueues` keys by. Omitted when empty.
1978
+ ...(e.workflowClients !== undefined && Object.keys(e.workflowClients).length > 0
1979
+ ? { workflowClients: e.workflowClients }
1980
+ : {}),
1981
+ // The workflow SUBJECTS among those components (every v2 worker and every
1982
+ // workflow-starting service) — derived by the kernel, whose lock schema
1983
+ // requires the stated list to equal the same derivation. Omitted when
1984
+ // empty, as the kernel requires.
1985
+ ...lockedWorkflowSubjects(e, schemas),
1986
+ // The biome's trust CEILING under the edition's `trustCeiling` policy,
1987
+ // by the kernel's one derivation; omitted when it is the biome's default
1988
+ // domain, so an edition that groups nothing seals the bytes it always did.
1989
+ ...lockedTrustCeiling(e, trustCeiling, schemas),
1990
+ // The workers that run a sibling service's sealed image with their own
1991
+ // entry module — what the deploy plane pins and renders as
1992
+ // `node <entry>`. STATED ON EVERY BIOME, `{}` when it has none: the
1993
+ // kernel writer requires it, and the deploy plane refuses a lock that
1994
+ // does not state it, because an omitted field read as "no workers" is
1995
+ // how a customer edition 0.0.41 shipped two declared workers and
1996
+ // deployed neither. The INDEX keeps omitting an empty map (it is
1997
+ // byte-stable and re-derived from the manifests by dist:check, and
1998
+ // `manifestToIndexEntry` projects EVERY shared-oci-image worker or
1999
+ // throws), so an entry without the key is one whose manifest declares
2000
+ // no such worker.
2001
+ workerImages: e.workerImages ?? {},
2002
+ // --- Catalog projection (consumed by biome-host's DistributionCatalogSource) ---
2003
+ // Forwarded verbatim from the index so the lock is the SELF-CONTAINED
2004
+ // single source of truth for the biome catalog: biome-host builds the
2005
+ // web/server registry DTOs from these without re-reading any manifest.
2006
+ displayName: e.displayName,
2007
+ packageName: e.packageName ?? null,
2008
+ requiresServerBiomes: Array.isArray(e.requires) ? e.requires : [],
2009
+ optionalServerBiomes: Array.isArray(e.optionalServerBiomes)
2010
+ ? e.optionalServerBiomes
2011
+ : [],
2012
+ audience: e.audience ?? 'org',
2013
+ storeListed: typeof e.storeListed === 'boolean' ? e.storeListed : true,
2014
+ sourceManifestHash: e.sourceManifestHash,
2015
+ ...(e.display ? { display: e.display } : {}),
2016
+ // Absent in the index means the manifest declared no `systemSurface`,
2017
+ // which IS `false` — the biome is store-listed rather than rail-mounted.
2018
+ // Stated here rather than omitted for the same reason as
2019
+ // `serviceDatabases`: the lock is the contract, not the index.
2020
+ systemSurface: e.systemSurface ?? false,
2021
+ // Distribution launch-placement decision (see PER_INSTALLATION_LAUNCH).
2022
+ // Always stated: `static` is a DECISION this resolver made, not an
2023
+ // absence, and a reader that had to infer it would be re-deciding
2024
+ // placement at boot from a missing key.
2025
+ launch: PER_INSTALLATION_LAUNCH.has(e.id)
2026
+ ? BiomeLaunchMode.PerInstallation
2027
+ : BiomeLaunchMode.Static,
2028
+ // The pinned external source this biome was drawn from, when one was.
2029
+ //
2030
+ // LAST IN THE LITERAL AND SPREAD CONDITIONALLY, for the reason `owner`
2031
+ // and `visibility` are appended after `inputHash` below: `serializeJson`
2032
+ // is insertion-order `JSON.stringify`, so an unconditional key would
2033
+ // change the bytes of every one of the nine committed locks for a fact
2034
+ // none of their 117 entries carries (measured: 0 of 117 carry `source`).
2035
+ //
2036
+ // `e.source` is the source NAME — that is what `entrySourceAdmissible`
2037
+ // scopes on — and the lock records the full pinned `LockedBiomeSource`,
2038
+ // so a reader of the artifact never has to resolve a handle against a
2039
+ // manifest it does not have.
2040
+ ...(e.source === undefined
2041
+ ? {}
2042
+ : { source: requireSourceRecord(e, sourceRecords) }),
2043
+ }))
2044
+ .sort(compareIndexEntries);
2045
+
2046
+ // Platform substrate services (non-biome), tier-ordered then by name.
2047
+ // The kernel already derives exactly this as `PLATFORM_SERVICE_TIER_RANK`
2048
+ // (`@xemahq/kernel-contracts/distribution`) — `'infra'` below the biome
2049
+ // ladder, `'edge'` above it. It is still spelled here because that subpath
2050
+ // loads through the ASYNC `loadKernelDistributionSchemas` and is a rank MAP,
2051
+ // not an array used with `indexOf`. The biome ladder itself is no longer a
2052
+ // local literal: `biomeTierOrder()` reads the kernel's.
2053
+ const TIER_WITH_EDGE = ['infra', ...biomeTierOrder(), 'edge'];
2054
+ const platformServiceList = [...shippedPlatformServices.values()]
2055
+ .map((service) => {
2056
+ const source = platformSourcesByName.get(service.name);
2057
+ // `installationClass` is a RESOLVE-TIME INPUT and is deliberately NOT
2058
+ // carried into the lock. It answers "should this edition contain the
2059
+ // service", which the resolver has already answered by the time this
2060
+ // list exists; no lock consumer — the deploy roster generator, the
2061
+ // bring-up plan, the appliance build — asks it. Emitting it would ship a
2062
+ // field with no reader AND would change the bytes of every committed
2063
+ // lock for a fact none of them uses.
2064
+ //
2065
+ // The strip is EXPLICIT rather than implied by the destructure order,
2066
+ // because `LockedPlatformServiceSchema` is `.strict()`: a spread that
2067
+ // carried the field would fail the lock parse rather than silently
2068
+ // widening it. That refusal is the right posture and this is the right
2069
+ // side of it.
2070
+ const { installationClass: _installationClass, ...locked } = service;
2071
+ return { ...locked, source };
2072
+ })
2073
+ .sort((a, b) => {
2074
+ const ta = TIER_WITH_EDGE.indexOf(a.tier);
2075
+ const tb = TIER_WITH_EDGE.indexOf(b.tier);
2076
+ if (ta !== tb) {return ta - tb;}
2077
+ return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
2078
+ });
2079
+
2080
+ // Capability-domain reservation table -- built from the FULL index, so it is
2081
+ // IDENTICAL in every edition regardless of which biomes this one selects.
2082
+ // Deriving it from the SELECTED set would be fail-open: a trimmed edition
2083
+ // would leave `kb`/`agent`/... unowned, and an org admin could install the
2084
+ // owning biome from the store and then bind its refs to a URL they control.
2085
+ // See `buildCapabilityDomainOwners`. Throws on a duplicate claim.
2086
+ const capabilityDomainOwners = buildCapabilityDomainOwners(index);
2087
+
2088
+ // inputHash: deterministic over the index SUBSET that fed the lock + the
2089
+ // reservation table + the leaf manifest, so drift in any input is detectable.
2090
+ const subset = serializeJson(biomes);
2091
+ const platform = serializeJson(platformServiceList);
2092
+ const owners = serializeJson(capabilityDomainOwners);
2093
+ const manifestImg = serializeJson(leaf);
2094
+ const inputHash = sha256Hex(
2095
+ `${subset}\0${platform}\0${owners}\0${manifestImg}`,
2096
+ );
2097
+
2098
+ const lock = {
2099
+ // The resolver writes the UNSEALED document: it has no schemaVersion and no
2100
+ // biomeContentHash. The seal mints each hash and the kernel's
2101
+ // `sealDistributionLock` produces the one v5 grammar; the CLI never
2102
+ // assembles a sealed lock itself.
2103
+ unsealedSchema: schemas.UNSEALED_DISTRIBUTION_LOCK_SCHEMA,
2104
+ distributionId: id,
2105
+ biomes,
2106
+ platformServices: platformServiceList,
2107
+ capabilityDomainOwners,
2108
+ resolvedAt,
2109
+ inputHash,
2110
+ };
2111
+
2112
+ // Owner and visibility are written ONLY when something declared them, and
2113
+ // they are appended AFTER `inputHash` rather than woven into the literal
2114
+ // above. Both halves matter for the nine committed locks: `serializeJson`
2115
+ // is insertion-order `JSON.stringify`, so an unconditional key would change
2116
+ // every lock's bytes, and a key inserted mid-literal would reorder them even
2117
+ // when absent-valued. Neither field is in the inputHash — the hash covers
2118
+ // the leaf manifest, which already carries both declarations.
2119
+ if (owner !== undefined) {lock.owner = owner;}
2120
+ // Tighten-only: one `private` anywhere in the edition's declarations makes the edition
2121
+ // private, whatever a descendant says. Absent everywhere stays absent, which
2122
+ // is what keeps all eight existing locks byte-identical.
2123
+ const visibility = (declaredVisibilities ?? []).includes(
2124
+ DistributionVisibility.Private,
2125
+ )
2126
+ ? DistributionVisibility.Private
2127
+ : (declaredVisibilities ?? [])[(declaredVisibilities ?? []).length - 1];
2128
+ if (visibility !== undefined) {lock.visibility = visibility;}
2129
+ // Provenance — written ONLY by a positive-closure edition, appended last for
2130
+ // the reason owner and visibility are: a selector-model lock carries exactly
2131
+ // the bytes it always did. Not in `inputHash`: it is DERIVED from the inputs
2132
+ // the hash already covers (the leaf manifest's roots and the index subset).
2133
+ if (resolutionSection !== undefined) {lock.resolution = resolutionSection;}
2134
+ // Nexus bindings — written ONLY when some shipped worker requires a Nexus
2135
+ // service, appended last for the same reason, so every lock that binds none
2136
+ // carries exactly the bytes it always did. Not in `inputHash`: derived from
2137
+ // the selected biomes, whose `sourceManifestHash` the hash already covers.
2138
+ if (nexusBindings.length > 0) {lock.nexusBindings = nexusBindings;}
2139
+
2140
+ // (7) Re-derive the ownership index through the KERNEL so the table this
2141
+ // resolver emits is validated by the exact code every consumer will read
2142
+ // it with (`capability-registry-api`, `xema-capability-router`). Catches
2143
+ // a domain whose grammar the kernel rejects, and any collision that
2144
+ // survived `buildCapabilityDomainOwners`. Run BEFORE the schema parse so
2145
+ // the message names the offending domain rather than surfacing as a
2146
+ // generic shape error, and so the failure lands at BUILD time — the
2147
+ // boot-time throw is only the backstop. No try/catch: it must propagate.
2148
+ schemas.resolveFirstPartyCapabilityDomains(lock);
2149
+
2150
+ // (8) Validate against the BUILT kernel schema — fail-fast.
2151
+ schemas.parseUnsealedDistributionLock(lock);
2152
+ return validateImmutableDistributionLock(lock);
2153
+ }
2154
+
2155
+ /**
2156
+ * Produce a lock object for a distribution id (used by both the CLI and
2157
+ * dist-check). `resolvedAt` is freshly stamped each call; callers that diff
2158
+ * against a committed lock must normalize it out.
2159
+ */
2160
+ export async function resolveDistribution(id) {
2161
+ return (await resolveDistributionWithIndex(id)).lock;
2162
+ }
2163
+
2164
+ /**
2165
+ * The lock TOGETHER WITH the index it was resolved from: the committed
2166
+ * platform index plus this edition's source-walked entries, each carrying its
2167
+ * `source` stamp.
2168
+ *
2169
+ * Anything that must look a lock biome up again — the authorization seal, and
2170
+ * dist-check's currency check of it — reads THIS index. `biome-index.json`
2171
+ * alone is the platform's and never holds a source-walked biome, so a second
2172
+ * lookup against it refuses every edition that declares `sources[]` right
2173
+ * after resolving it correctly. One walk, one merged index, handed on.
2174
+ *
2175
+ * `materialize` is the walk's one network seam, injectable for the contract
2176
+ * suite exactly as it is on `walkDeclaredSources`.
2177
+ */
2178
+ export async function resolveDistributionWithIndex(id, { materialize, inputs } = {}) {
2179
+ const schemas = await loadKernelDistributionSchemas();
2180
+ // THE RELEASE READS THESE FROM DISK; A DEVELOP SNAPSHOT INJECTS THEM
2181
+ // (`develop-snapshot-inputs.mjs`). Either way they pass through the one
2182
+ // validation below, so there is a single resolve path and two sources.
2183
+ const platformServiceIndex =
2184
+ inputs?.platformServiceIndex ?? readPlatformServiceIndex(schemas.parsePlatformServiceSourceIndex);
2185
+ const platformIndex = validateServiceDependencyOwnerParity(
2186
+ validateImmutableBiomeIndex(inputs?.biomeIndex ?? readBiomeIndex()),
2187
+ platformServiceIndex,
2188
+ );
2189
+ // The manifest is composed FIRST, because it is what declares the sources. The
2190
+ // walk then runs once per resolve and its entries are merged into a
2191
+ // per-resolution index — never into `biome-index.json`, which is the
2192
+ // platform's and is read by every edition.
2193
+ const composed = inputs?.composed ?? composeEdition(schemas.parseDistribution, id);
2194
+ const { entries, sourceRecords } = await walkDeclaredSources({
2195
+ sources: composed.declaredSources,
2196
+ ...(materialize === undefined ? {} : { materialize }),
2197
+ });
2198
+ const index = mergeExternalSourceEntries(platformIndex, entries, platformServiceIndex);
2199
+ const lock = resolveDistributionFromInputs(
2200
+ id,
2201
+ index,
2202
+ schemas,
2203
+ { ...composed, sourceRecords },
2204
+ new Date().toISOString(),
2205
+ platformServiceIndex,
2206
+ );
2207
+ return { lock, index };
2208
+ }
2209
+
2210
+ /**
2211
+ * Resolve and persist one distribution lock. The CLI and the plane refresh use
2212
+ * this exact writer so an in-process refresh cannot drift from the standalone
2213
+ * command's serialization or diagnostics.
2214
+ */
2215
+ export async function writeResolvedDistribution(id, { log = true, materialize } = {}) {
2216
+ const { lock, index } = await resolveDistributionWithIndex(id, { materialize });
2217
+ const outPath = join(editionsRoot(), id, 'distribution.lock.json');
2218
+ assertReleaseContent(lock, `the distribution lock for "${id}"`);
2219
+ assertReleaseEditionTree(join(editionsRoot(), id));
2220
+ mkdirSync(join(editionsRoot(), id), { recursive: true });
2221
+ writeFileSync(outPath, serializeJson(lock));
2222
+ await sealAuthorizationBase(id, lock, index, { log });
2223
+ // The chart generation this edition is rendered with: the fleet pin, copied
2224
+ // byte-identically (edition-chart-set.mjs). A resolve always yields a whole
2225
+ // edition; resolve-chart-set.mjs keeps every copy on the pin thereafter.
2226
+ const pinPath = fleetChartSetLockPath();
2227
+ if (!existsSync(pinPath)) {
2228
+ throw new Error(`${pinPath} is missing — run resolve-chart-set.mjs; an edition is not resolved without its chart set`);
2229
+ }
2230
+ const pinText = readFileSync(pinPath, 'utf8');
2231
+ writeEditionChartSet(join(editionsRoot(), id), pinText);
2232
+ if (log) {
2233
+ const byTier = biomeTierOrder().map(
2234
+ (t) => `${t}=${lock.biomes.filter((b) => b.tier === t).length}`,
2235
+ ).join(' ');
2236
+ console.log(
2237
+ `[dist:resolve] wrote ${outPath} — ${lock.biomes.length} biomes (${byTier}), ` +
2238
+ `${lock.platformServices.length} platform service(s).`,
2239
+ );
2240
+ }
2241
+ return lock;
2242
+ }
2243
+
2244
+ /**
2245
+ * Compose the edition's authorization base with the PINNED kernel's composer
2246
+ * (platform modules + every selected biome's module), run every composed
2247
+ * `.fga.yaml` with the pinned `fga model test`, and seal
2248
+ * `authorization.lock.json` beside the lock. The committed lock, when present,
2249
+ * is the `adopted` base the composition must stay compatible with. A pinned
2250
+ * kernel without the composer seals nothing and retires a stale lock.
2251
+ *
2252
+ * `index` is the index THIS lock was resolved from
2253
+ * (`resolveDistributionWithIndex`), required: it carries the edition's
2254
+ * source-walked biomes, which the committed platform index never does.
2255
+ */
2256
+ export async function sealAuthorizationBase(id, lock, index, { log = true } = {}) {
2257
+ const sealed = await composeAuthorizationBase(id, lock, index);
2258
+ assertReleaseContent(sealed, `the authorization lock for "${id}"`);
2259
+ const editionDir = join(editionsRoot(), id);
2260
+ const path = writeAuthorizationLock(editionDir, sealed, serializeJson);
2261
+ if (!log) {return sealed;}
2262
+ console.log(
2263
+ sealed === null
2264
+ ? `[dist:resolve] ${id}: the pinned kernel ships no authorization composer — no authorization base sealed.`
2265
+ : `[dist:resolve] wrote ${path} — generation ${sealed.platformGeneration}, ` +
2266
+ `${sealed.modules.length} biome module(s), model ${sealed.modelHash}.`,
2267
+ );
2268
+ return sealed;
2269
+ }
2270
+
2271
+ /**
2272
+ * The PURE half of {@link sealAuthorizationBase}: compose and test the
2273
+ * authorization base, return it (or `null` under a kernel without a composer),
2274
+ * write nothing. The committed lock of the editions root is the `adopted` base.
2275
+ * A develop edition calls only this; the release calls it and then writes.
2276
+ */
2277
+ export async function composeAuthorizationBase(id, lock, index) {
2278
+ if (!Array.isArray(index)) {
2279
+ throw new Error(
2280
+ `${id}: sealAuthorizationBase needs the index the lock was resolved from ` +
2281
+ '(resolveDistributionWithIndex). The committed platform index alone does not hold a source-walked biome.',
2282
+ );
2283
+ }
2284
+ const editionDir = join(editionsRoot(), id);
2285
+ const kernel = await loadKernelAuthorizationComposer({ kernelPackageDir: kernelPackageDir() });
2286
+ // Refuses an edition that carries a module under a pin with no composer.
2287
+ const inputs = editionAuthorizationModuleInputs(lock.biomes, new Map(index.map((e) => [e.id, e])), kernel);
2288
+ let sealed = null;
2289
+ if (kernel !== null) {
2290
+ sealed = sealEditionAuthorizationBase({
2291
+ distributionId: id,
2292
+ inputs,
2293
+ kernel,
2294
+ adopted: adoptedFromLock(readAuthorizationLock(editionDir)),
2295
+ runTests: fgaComposedModelTestRunner(resolveFgaCli()),
2296
+ });
2297
+ }
2298
+ return sealed;
2299
+ }
2300
+
2301
+ async function main() {
2302
+ const id = process.argv[2];
2303
+ if (!id) {
2304
+ console.error(
2305
+ 'usage: node resolve-distribution.mjs <root flags> <distribution-id>',
2306
+ );
2307
+ process.exit(1);
2308
+ }
2309
+ await writeResolvedDistribution(id);
2310
+ }
2311
+
2312
+ // Only run main() when invoked directly, not when imported by dist-check.
2313
+ if (isEntryModule(import.meta.url)) {
2314
+ main().catch((err) => {
2315
+ console.error(`[dist:resolve] ${err.message}`);
2316
+ process.exit(1);
2317
+ });
2318
+ }