@xemahq/xema 0.26.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) 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 -1
  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 +54179 -0
  28. package/dist/edition/verify-image-evidence.bundle.provenance.json +2871 -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 +315 -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 +270 -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 +207 -0
  152. package/edition/edition-constants.mjs +57 -0
  153. package/edition/edition-contract.json +23 -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 +176 -0
  158. package/edition/evidence-checkout.mjs +418 -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 +256 -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-evidence-replay-state.mjs +242 -0
  167. package/edition/image-evidence.mjs +1225 -0
  168. package/edition/kernel-contract-authority.mjs +443 -0
  169. package/edition/lib.mjs +3018 -0
  170. package/edition/no-follow-file.mjs +94 -0
  171. package/edition/oci-evidence-verbs.mjs +392 -0
  172. package/edition/oci-image-source-evidence.mjs +239 -0
  173. package/edition/oras.mjs +648 -0
  174. package/edition/pin-source.mjs +227 -0
  175. package/edition/plan-image-evidence-refresh.mjs +408 -0
  176. package/edition/platform-lock-readability.mjs +892 -0
  177. package/edition/platform-service-class.mjs +18 -0
  178. package/edition/platform-service-source-authority.mjs +105 -0
  179. package/edition/positive-closure.mjs +162 -0
  180. package/edition/produce-distribution-completeness-evidence.mjs +311 -0
  181. package/edition/produce-image-delivery-entitlement-evidence.mjs +294 -0
  182. package/edition/produce-image-source-evidence.mjs +765 -0
  183. package/edition/producer-evidence-bundle.mjs +157 -0
  184. package/edition/registry-credentials.mjs +193 -0
  185. package/edition/release-catalogue.mjs +39 -0
  186. package/edition/release-channel-guard.mjs +99 -0
  187. package/edition/release-edition-artifact.mjs +158 -0
  188. package/edition/remedy-command.mjs +89 -0
  189. package/edition/repin-evidence-trust.mjs +101 -0
  190. package/edition/required-resource-provider.mjs +58 -0
  191. package/edition/resolve-chart-set.mjs +415 -0
  192. package/edition/resolve-customer-edition.mjs +288 -0
  193. package/edition/resolve-distribution.mjs +2318 -0
  194. package/edition/resolve-edition.mjs +186 -0
  195. package/edition/resolve-images.mjs +1606 -0
  196. package/edition/seal-image-digests.mjs +855 -0
  197. package/edition/sealed-evidence-accept.mjs +82 -0
  198. package/edition/source-authority.mjs +725 -0
  199. package/edition/source-closure.mjs +116 -0
  200. package/edition/strict-json.mjs +121 -0
  201. package/edition/string-order.mjs +5 -0
  202. package/edition/trust-policy-arming.mjs +120 -0
  203. package/edition/trusted-executable.mjs +166 -0
  204. package/edition/verifier-binding.mjs +45 -0
  205. package/edition/verifier-files.mjs +8 -0
  206. package/edition/verifier-kernel-schemas.mjs +22 -0
  207. package/edition/verifier-provenance.mjs +64 -0
  208. package/edition/verify-image-evidence.mjs +430 -0
  209. package/edition/verify-image-platforms.mjs +495 -0
  210. package/edition/why.mjs +56 -0
  211. package/edition/workspace-sections.mjs +195 -0
  212. package/examples/06-biome-sources.yaml +1 -0
  213. package/package.json +14 -6
  214. package/dist/commands/dist.d.ts +0 -16
  215. package/dist/commands/dist.d.ts.map +0 -1
  216. package/dist/commands/dist.js +0 -182
  217. package/dist/commands/dist.js.map +0 -1
  218. package/dist/runtime/distribution/available-biome.d.ts +0 -10
  219. package/dist/runtime/distribution/available-biome.d.ts.map +0 -1
  220. package/dist/runtime/distribution/available-biome.js +0 -156
  221. package/dist/runtime/distribution/available-biome.js.map +0 -1
  222. package/dist/runtime/distribution/compose.d.ts +0 -23
  223. package/dist/runtime/distribution/compose.d.ts.map +0 -1
  224. package/dist/runtime/distribution/compose.js +0 -48
  225. package/dist/runtime/distribution/compose.js.map +0 -1
  226. package/dist/runtime/runner/chart-trust.d.ts +0 -57
  227. package/dist/runtime/runner/chart-trust.d.ts.map +0 -1
  228. package/dist/runtime/runner/chart-trust.js +0 -270
  229. package/dist/runtime/runner/chart-trust.js.map +0 -1
@@ -0,0 +1,175 @@
1
+ #!/usr/bin/env node
2
+ // build-biome-index — walk every `xema-biome.json` in the monorepo and emit the
3
+ // global biome index (`distributions/biome-index.json`) that the distribution
4
+ // resolver reads. The index is the single source of truth for "which biomes
5
+ // exist, at what tier/target/origin/version, and what they require". It carries
6
+ // PACKAGING-relevant fields only.
7
+ //
8
+ // Usage:
9
+ // node build-biome-index.mjs [--check] [--source published|workspace] [--unpinned]
10
+ //
11
+ // THE COMMITTED PINS ARE THE AUTHORITY, and this index is a CONSUMER of them.
12
+ // Each repository's commit comes from `runtime-image-source-index.json`'s
13
+ // `repositoryPins`; nothing here asks a repository what its newest built commit
14
+ // is. `--unpinned` is the one way to reach a live resolution and it is a POLICY
15
+ // operation — see `pinAuthority` below.
16
+ //
17
+ // Fail-fast on any manifest missing id/scope/target or with an unknown tier
18
+ // (see manifestToIndexEntry). mjs tooling — clocks are fine; the index itself
19
+ // carries no timestamp so it is reproducible.
20
+
21
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
22
+ import { dirname } from 'node:path';
23
+
24
+ import { WorkspaceRoot, biomeIndexPath, configureEditionWorkspaceFromProcess, isEntryModule, platformServiceIndexPath } from './edition-workspace.mjs';
25
+ import {
26
+ buildBiomeIndex,
27
+ distributionBiomeSourceRepositories,
28
+ loadKernelDistributionSchemas,
29
+ serializeJson,
30
+ } from './lib.mjs';
31
+ import { commandForSelf } from './remedy-command.mjs';
32
+ import {
33
+ DistributionSourceMode,
34
+ committedRepositoryPins,
35
+ createPinnedSourceAuthority,
36
+ createSourceAuthority,
37
+ parseSourceModeArgs,
38
+ } from './source-authority.mjs';
39
+
40
+ if (isEntryModule(import.meta.url)) {
41
+ configureEditionWorkspaceFromProcess([WorkspaceRoot.Data, WorkspaceRoot.Sources, WorkspaceRoot.Kernel, WorkspaceRoot.SourceHash]);
42
+ }
43
+
44
+ /**
45
+ * WHY THIS INDEX READS THE PINS INSTEAD OF RESOLVING REPOSITORIES ITSELF.
46
+ *
47
+ * This index and `runtime-image-source-index.json` used to be derived from the
48
+ * same fact and DERIVE IT SEPARATELY: each called `commitForRepository`, which
49
+ * asks the world a live question whose answer moves whenever a build lands.
50
+ * `resolve-images` refuses any disagreement between them — it re-reads each
51
+ * biome manifest at the RUNTIME pin and fails with "sourceManifestHash does not
52
+ * match" — so two derivations minutes apart could produce two artifacts that
53
+ * cannot resolve together.
54
+ *
55
+ * A `--repo <repository>` scope was added to narrow the second derivation to
56
+ * one repository. It narrowed the COLLATERAL and left the RACE: the scoped
57
+ * repository was still resolved live, so a build landing between
58
+ * `build-runtime-image-source-index --repo X` and `build-biome-index --repo X`
59
+ * split the two artifacts on exactly the repository the operator was refreshing.
60
+ *
61
+ * So the scope is gone and the derivation with it. There is ONE artifact whose
62
+ * job is to decide what commit a repository is at — the runtime image source
63
+ * index — and everything downstream reads its record. A scoped refresh of this
64
+ * index is not a mode here any more: it is what you GET, because advancing one
65
+ * pin is the only thing that can move one repository's entries.
66
+ *
67
+ * `--unpinned` is the escape, and it is a POLICY operation rather than a
68
+ * correctness one. Two cases need it and no third has appeared:
69
+ *
70
+ * - BOOTSTRAP — a repository that has JOINED the source set has no pin yet,
71
+ * and `createPinnedSourceAuthority` refuses it rather than resolving it
72
+ * live. That refusal is right (a repository joining is a full rebuild, not
73
+ * a scoped refresh) and this is how the full rebuild is asked for.
74
+ * - WORKSPACE — `--source workspace` snapshots a dirty local tree, which has
75
+ * no pin by construction. It therefore IMPLIES `--unpinned`, rather than
76
+ * making the operator state both.
77
+ */
78
+ function parseUnpinned(argv) {
79
+ const index = argv.indexOf('--unpinned');
80
+ if (index === -1) {return { unpinned: false, remaining: argv };}
81
+ return {
82
+ unpinned: true,
83
+ remaining: [...argv.slice(0, index), ...argv.slice(index + 1)],
84
+ };
85
+ }
86
+
87
+ async function main() {
88
+ const { unpinned: unpinnedFlag, remaining: afterUnpinned } = parseUnpinned(
89
+ process.argv.slice(2),
90
+ );
91
+ const { mode, remaining } = parseSourceModeArgs(afterUnpinned);
92
+ const check = remaining.includes('--check');
93
+ if (remaining.some((argument) => argument !== '--check')) {
94
+ throw new Error(
95
+ 'usage: build-biome-index.mjs [--check] [--source published|workspace] ' +
96
+ '[--unpinned]',
97
+ );
98
+ }
99
+ // A workspace snapshot has no pin and never will — see `parseUnpinned`.
100
+ const unpinned = unpinnedFlag || mode === DistributionSourceMode.Workspace;
101
+ const schemas = await loadKernelDistributionSchemas();
102
+ const platformServiceIndex = schemas.parsePlatformServiceSourceIndex(
103
+ JSON.parse(readFileSync(platformServiceIndexPath(), 'utf8')),
104
+ );
105
+ const sourceRepositories = distributionBiomeSourceRepositories();
106
+ const sourceAuthority = unpinned
107
+ ? createSourceAuthority({
108
+ mode,
109
+ knownRepositories: sourceRepositories.map(({ repo }) => repo),
110
+ })
111
+ : // NO DELEGATE, deliberately. A consumer artifact has nothing to resolve,
112
+ // and passing a live authority it "will not use" makes that a claim about
113
+ // this call site rather than a property of the object. Without one, any
114
+ // repository missing from the record REFUSES by name instead of quietly
115
+ // resolving to whatever its tip happens to be today.
116
+ createPinnedSourceAuthority({ pins: committedRepositoryPins() });
117
+ const index = await buildBiomeIndex(
118
+ platformServiceIndex,
119
+ sourceRepositories,
120
+ sourceAuthority,
121
+ );
122
+ const serialized = serializeJson(index);
123
+
124
+ if (check) {
125
+ let committed;
126
+ try {
127
+ committed = readFileSync(biomeIndexPath(), 'utf8');
128
+ } catch {
129
+ console.error(
130
+ `[dist:index --check] ${biomeIndexPath()} does not exist. Run \`${commandForSelf(import.meta.url)}\`.`,
131
+ );
132
+ process.exit(1);
133
+ }
134
+ if (committed !== serialized) {
135
+ const regenerateCommand =
136
+ mode === DistributionSourceMode.Workspace
137
+ ? `${commandForSelf(import.meta.url, '--source', 'workspace')} (local-only output; do not publish it)`
138
+ : commandForSelf(import.meta.url);
139
+ // WHICH AUTHORITY DISAGREED is the first thing the reader needs, because
140
+ // the two have different remedies: a PINNED divergence means the index
141
+ // does not match the pins and regenerating fixes it, while an UNPINNED one
142
+ // means the fleet has moved and regenerating MOVES EVERY PIN. Saying which
143
+ // ran is what keeps the second from being done by reflex.
144
+ console.error(
145
+ `[dist:index --check] authority: ${
146
+ unpinned
147
+ ? 'UNPINNED — every repository was resolved LIVE, so this verdict ' +
148
+ 'is about the fleet right now and regenerating will move pins'
149
+ : `PINNED — ${
150
+ committedRepositoryPins().length
151
+ } committed pin(s), no live resolution`
152
+ }.`,
153
+ );
154
+ console.error(
155
+ `[dist:index --check] biome-index.json is out of date. Run \`${regenerateCommand}\`.`,
156
+ );
157
+ process.exit(1);
158
+ }
159
+ console.log(
160
+ `[dist:index --check] biome-index.json is up to date (${index.length} biomes).`,
161
+ );
162
+ return;
163
+ }
164
+
165
+ mkdirSync(dirname(biomeIndexPath()), { recursive: true });
166
+ writeFileSync(biomeIndexPath(), serialized);
167
+ console.log(
168
+ `[dist:index] wrote ${biomeIndexPath()} (${index.length} biomes).`,
169
+ );
170
+ }
171
+
172
+ main().catch((error) => {
173
+ console.error(`[dist:index] ${error.message}`);
174
+ process.exitCode = 1;
175
+ });
@@ -0,0 +1,288 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { execFileSync } from 'node:child_process';
4
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
5
+
6
+ import { platformServiceIndexPath , WorkspaceRoot, configureEditionWorkspaceFromProcess, isEntryModule, sourcePath } from './edition-workspace.mjs';
7
+ import {
8
+ canonicalPublicHttpsRepositoryUrl,
9
+ loadKernelDistributionSchemas,
10
+ readGitJsonAtCommit,
11
+ serializeJson,
12
+ sha256Hex,
13
+ } from './lib.mjs';
14
+ import {
15
+ comparablePlatformService,
16
+ declaredInstallationClass,
17
+ } from './platform-service-class.mjs';
18
+ import {
19
+ platformServiceSourceDeclaration,
20
+ platformServiceSourceDeclarations,
21
+ platformServiceSourceRepositories,
22
+ } from './platform-service-source-authority.mjs';
23
+ import {
24
+ DistributionSourceMode,
25
+ committedRepositoryPins,
26
+ createPinnedSourceAuthority,
27
+ createSourceAuthority,
28
+ parseSourceModeArgs,
29
+ } from './source-authority.mjs';
30
+ import { readStrictJsonFile } from './strict-json.mjs';
31
+
32
+ if (isEntryModule(import.meta.url)) {
33
+ configureEditionWorkspaceFromProcess([WorkspaceRoot.Data, WorkspaceRoot.Sources, WorkspaceRoot.Kernel, WorkspaceRoot.SourceHash]);
34
+ }
35
+
36
+ const TRUSTED_WRITE_FLAG = '--trusted-write';
37
+ const CHECK_FLAG = '--check';
38
+ const CHECK_SOURCES_FLAG = '--check-sources';
39
+ const UNPINNED_FLAG = '--unpinned';
40
+
41
+ /**
42
+ * THE LAST INDEX IN THIS PLANE THAT COULD NOT BE SCOPED, and the one that
43
+ * measurably fell behind because of it.
44
+ *
45
+ * `build-biome-index.mjs` and `build-runtime-image-source-index.mjs` could both
46
+ * be narrowed to one repository. This one could not: every run resolved all
47
+ * eight platform-service source repositories live. So advancing the two services
48
+ * sourced from `repos/xema-base` REQUIRED moving `event-hub-api`,
49
+ * `identity-api`, `llm-gateway-api` and `xema-host-web` with them — measured
50
+ * 2026-09-23, four collateral repositories that then disagreed with the runtime
51
+ * pins a scoped refresh had just held.
52
+ *
53
+ * It did not stay theoretical. Measured on `origin/main` the same day, the two
54
+ * committed indexes pinned `repos/xema-base` at commits 164 APART —
55
+ * `platform-service-index.json` at `e1987a11` (2026-09-17) against
56
+ * `runtime-image-source-index.json` at `fcde2a83` (2026-09-20) — because the
57
+ * pinnable index had been scope-refreshed and this one could only move by
58
+ * moving everything. Two committed artifacts, one repository, two commits.
59
+ *
60
+ * The fix is not a `--repo` flag here. A flag would have made this index a
61
+ * SECOND place that decides what commit a repository is at, which is the defect
62
+ * wearing a narrower costume. It reads the pins instead, like every other
63
+ * consumer, so a scoped refresh reaches it for free and there is nothing to
64
+ * keep in step.
65
+ */
66
+
67
+ function readJson(path) {
68
+ return readStrictJsonFile(path);
69
+ }
70
+
71
+ function git(repo, args) {
72
+ return execFileSync('git', ['-C', repo, ...args], {
73
+ encoding: 'utf8',
74
+ stdio: ['ignore', 'pipe', 'pipe'],
75
+ }).trim();
76
+ }
77
+
78
+ /**
79
+ * Every platform service the data root declares (`platformServiceSources`) — the
80
+ * platform floor. NOT filtered by tier: the kernel distribution-lock schema
81
+ * requires an exact source identity on EVERY `platformServices[]` entry, so an
82
+ * index that describes only one tier makes every edition unresolvable. It did:
83
+ * filtering to `infra` left `xema-kernel-server`, `docs-api`,
84
+ * `opencode-image-prepuller` and `xema-host-web` with no identity at all.
85
+ *
86
+ * The index is derived from the declarations alone: no edition is read, so a
87
+ * data root that has no edition named `core` builds and checks it the same.
88
+ */
89
+ function loadPlatformServiceDeclarations() {
90
+ return platformServiceSourceDeclarations()
91
+ .slice()
92
+ .sort((a, b) => a.name.localeCompare(b.name));
93
+ }
94
+
95
+ function trustedSourceAuthority(sourceMode, unpinned) {
96
+ return unpinned
97
+ ? createSourceAuthority({
98
+ mode: sourceMode,
99
+ knownRepositories: platformServiceSourceRepositories(),
100
+ })
101
+ : // No delegate: this artifact decides no commits, so it must not be able
102
+ // to ask for one. A platform-service repository with no committed pin
103
+ // refuses by name rather than resolving to its tip.
104
+ createPinnedSourceAuthority({ pins: committedRepositoryPins() });
105
+ }
106
+
107
+ function buildTrustedIndex(sourceAuthority) {
108
+ const declarations = loadPlatformServiceDeclarations();
109
+ const services = [];
110
+ for (const declaration of declarations) {
111
+ const binding = platformServiceSourceDeclaration(declaration.name);
112
+ const repoDirectory = sourcePath(binding.repo);
113
+ // Read through the one explicit source authority. Published mode names a
114
+ // fetchable origin/main commit; workspace mode names a deterministic local
115
+ // snapshot commit containing tracked and untracked non-ignored files.
116
+ const before = sourceAuthority.commitForRepository(binding.repo);
117
+ const sourceManifest = readGitJsonAtCommit(
118
+ repoDirectory,
119
+ binding.manifestPath,
120
+ before,
121
+ );
122
+ if (sourceManifest.name !== binding.manifestName) {
123
+ throw new Error(
124
+ `${binding.path} at ${before} declares name "${String(sourceManifest.name)}", ` +
125
+ `but platform service "${declaration.name}" is declared to be sourced from ` +
126
+ `"${binding.manifestName}"`,
127
+ );
128
+ }
129
+ if (
130
+ typeof sourceManifest.version !== 'string' ||
131
+ sourceManifest.version.length === 0
132
+ ) {
133
+ throw new Error(
134
+ `${binding.path} has no exact version at ${before}`,
135
+ );
136
+ }
137
+ const repositoryUrl = canonicalPublicHttpsRepositoryUrl(
138
+ git(repoDirectory, ['remote', 'get-url', 'origin']),
139
+ `${binding.repo} origin`,
140
+ );
141
+ services.push({
142
+ name: declaration.name,
143
+ tier: declaration.tier,
144
+ ...declaredInstallationClass(declaration),
145
+ repositoryUrl,
146
+ repo: binding.repo,
147
+ path: binding.path,
148
+ sourceCommit: before,
149
+ version: sourceManifest.version,
150
+ sourceManifestHash: sha256Hex(serializeJson(sourceManifest)),
151
+ imageKey: binding.imageKey,
152
+ });
153
+ }
154
+ return { schemaVersion: 1, services };
155
+ }
156
+
157
+ function validateOfflineIndex(index) {
158
+ const declarations = loadPlatformServiceDeclarations();
159
+ const expected = declarations.map(comparablePlatformService);
160
+ const actual = index.services.map(comparablePlatformService);
161
+ if (serializeJson(actual) !== serializeJson(expected)) {
162
+ throw new Error(
163
+ 'platform-service-index.json does not exactly match the platformServiceSources declared in edition-workspace.json',
164
+ );
165
+ }
166
+ for (const service of index.services) {
167
+ const canonicalRepositoryUrl = canonicalPublicHttpsRepositoryUrl(
168
+ service.repositoryUrl,
169
+ `platform service "${service.name}" repositoryUrl`,
170
+ );
171
+ if (canonicalRepositoryUrl !== service.repositoryUrl) {
172
+ throw new Error(
173
+ `platform service "${service.name}" repositoryUrl is not canonical`,
174
+ );
175
+ }
176
+ const expectedRepoName = service.repo.split('/').at(-1);
177
+ // Offline, and still exact: the declared source location is the authority,
178
+ // so a committed index that names a different repo, manifest or image key
179
+ // than the declaration fails here without touching Git.
180
+ const binding = platformServiceSourceDeclaration(service.name);
181
+ if (service.repo !== binding.repo || service.path !== binding.path) {
182
+ throw new Error(
183
+ `platform service "${service.name}" source ${service.repo}:${service.path} ` +
184
+ `does not match its declared source ${binding.repo}:${binding.path}`,
185
+ );
186
+ }
187
+ if (service.imageKey !== binding.imageKey) {
188
+ throw new Error(
189
+ `platform service "${service.name}" imageKey "${service.imageKey}" ` +
190
+ `does not match its declared image key "${binding.imageKey}"`,
191
+ );
192
+ }
193
+ if (!service.path.startsWith(`${service.repo}/`)) {
194
+ throw new Error(
195
+ `platform service "${service.name}" source path is outside its repo`,
196
+ );
197
+ }
198
+ if (
199
+ !service.repositoryUrl.endsWith(`/${expectedRepoName}.git`) &&
200
+ !service.repositoryUrl.endsWith(`/${expectedRepoName}`)
201
+ ) {
202
+ throw new Error(
203
+ `platform service "${service.name}" repositoryUrl does not identify ${service.repo}`,
204
+ );
205
+ }
206
+ }
207
+ return index;
208
+ }
209
+
210
+ /**
211
+ * The index as `--trusted-write` produces and validates it, over an explicit
212
+ * source authority. The develop edition builds its snapshot index here, with a
213
+ * pinned authority whose pins are the snapshot's, so the release and the
214
+ * develop lane share one builder and one validation.
215
+ */
216
+ export function buildValidatedPlatformServiceIndex(schemas, sourceAuthority) {
217
+ const parsed = schemas.parsePlatformServiceSourceIndex(
218
+ buildTrustedIndex(sourceAuthority),
219
+ );
220
+ return validateOfflineIndex(parsed);
221
+ }
222
+
223
+ async function main() {
224
+ const { mode, remaining } = parseSourceModeArgs(process.argv.slice(2));
225
+ const flags = new Set(remaining);
226
+ const unpinnedFlag = flags.delete(UNPINNED_FLAG);
227
+ const unknown = [...flags].filter(
228
+ (flag) =>
229
+ flag !== TRUSTED_WRITE_FLAG &&
230
+ flag !== CHECK_FLAG &&
231
+ flag !== CHECK_SOURCES_FLAG,
232
+ );
233
+ if (unknown.length > 0 || flags.size !== 1) {
234
+ throw new Error(
235
+ `usage: node build-platform-service-index.mjs (${CHECK_FLAG} | ${CHECK_SOURCES_FLAG} | ${TRUSTED_WRITE_FLAG}) [--source published|workspace] [${UNPINNED_FLAG}]`,
236
+ );
237
+ }
238
+ // A workspace snapshot has no committed pin by construction, so it IMPLIES
239
+ // the live authority rather than requiring the operator to state both.
240
+ const unpinned = unpinnedFlag || mode === DistributionSourceMode.Workspace;
241
+ const schemas = await loadKernelDistributionSchemas();
242
+ if (flags.has(CHECK_FLAG)) {
243
+ if (!existsSync(platformServiceIndexPath())) {
244
+ throw new Error(`${platformServiceIndexPath()} does not exist`);
245
+ }
246
+ const parsed = schemas.parsePlatformServiceSourceIndex(
247
+ readJson(platformServiceIndexPath()),
248
+ );
249
+ validateOfflineIndex(parsed);
250
+ console.log(
251
+ `[platform-service-index] offline check passed for ${parsed.services.length} services`,
252
+ );
253
+ return;
254
+ }
255
+ const parsed = buildValidatedPlatformServiceIndex(schemas, trustedSourceAuthority(mode, unpinned));
256
+ if (flags.has(CHECK_SOURCES_FLAG)) {
257
+ if (!existsSync(platformServiceIndexPath())) {
258
+ throw new Error(`${platformServiceIndexPath()} does not exist`);
259
+ }
260
+ const committed = readFileSync(platformServiceIndexPath(), 'utf8');
261
+ if (committed !== serializeJson(parsed)) {
262
+ throw new Error(
263
+ `platform-service-index.json does not match the exact trusted clean sources ` +
264
+ `(authority: ${
265
+ unpinned
266
+ ? 'UNPINNED — every repository resolved LIVE, so this names the ' +
267
+ 'fleet as it is now and regenerating will move pins'
268
+ : 'PINNED — the committed repositoryPins, no live resolution'
269
+ }).`,
270
+ );
271
+ }
272
+ console.log(
273
+ `[platform-service-index] trusted source check passed for ${parsed.services.length} services`,
274
+ );
275
+ return;
276
+ }
277
+ writeFileSync(platformServiceIndexPath(), serializeJson(parsed));
278
+ console.log(
279
+ `[platform-service-index] wrote ${platformServiceIndexPath()} from ${parsed.services.length} clean trusted sources`,
280
+ );
281
+ }
282
+
283
+ if (isEntryModule(import.meta.url)) {
284
+ main().catch((error) => {
285
+ console.error(`[platform-service-index] ${error.message}`);
286
+ process.exitCode = 1;
287
+ });
288
+ }