@xemahq/xema 0.25.1 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. package/README.md +70 -37
  2. package/dist/assets/appliance-base/images.lock.json +126 -126
  3. package/dist/assets/appliance-lean/images.lock.json +123 -123
  4. package/dist/assets/cloud/images.lock.json +126 -126
  5. package/dist/assets/core/images.lock.json +120 -120
  6. package/dist/assets/distribution-assets.manifest.json +15 -16
  7. package/dist/assets/minimal/images.lock.json +108 -108
  8. package/dist/assets/oss/images.lock.json +126 -126
  9. package/dist/assets/starter/images.lock.json +123 -123
  10. package/dist/cli.js +2 -2
  11. package/dist/cli.js.map +1 -1
  12. package/dist/commands/dev.js +1 -0
  13. package/dist/commands/dev.js.map +1 -1
  14. package/dist/commands/down.d.ts.map +1 -1
  15. package/dist/commands/down.js +7 -1
  16. package/dist/commands/down.js.map +1 -1
  17. package/dist/commands/edition.d.ts +3 -0
  18. package/dist/commands/edition.d.ts.map +1 -0
  19. package/dist/commands/edition.js +46 -0
  20. package/dist/commands/edition.js.map +1 -0
  21. package/dist/commands/init.js +2 -2
  22. package/dist/commands/init.js.map +1 -1
  23. package/dist/commands/serve.js +1 -0
  24. package/dist/commands/serve.js.map +1 -1
  25. package/dist/commands/up.js +1 -0
  26. package/dist/commands/up.js.map +1 -1
  27. package/dist/dev/env-defaults.d.ts.map +1 -1
  28. package/dist/dev/env-defaults.js +6 -1
  29. package/dist/dev/env-defaults.js.map +1 -1
  30. package/dist/dev/infra-preflight.d.ts +3 -2
  31. package/dist/dev/infra-preflight.d.ts.map +1 -1
  32. package/dist/dev/infra-preflight.js +18 -8
  33. package/dist/dev/infra-preflight.js.map +1 -1
  34. package/dist/edition/verify-image-evidence.bundle.mjs +54179 -0
  35. package/dist/edition/verify-image-evidence.bundle.provenance.json +2871 -0
  36. package/dist/runtime/backing-registration.d.ts.map +1 -1
  37. package/dist/runtime/backing-registration.js +7 -2
  38. package/dist/runtime/backing-registration.js.map +1 -1
  39. package/dist/runtime/config/schema.d.ts +1 -0
  40. package/dist/runtime/config/schema.d.ts.map +1 -1
  41. package/dist/runtime/custody/local-custody.d.ts +12 -0
  42. package/dist/runtime/custody/local-custody.d.ts.map +1 -0
  43. package/dist/runtime/custody/local-custody.js +22 -0
  44. package/dist/runtime/custody/local-custody.js.map +1 -0
  45. package/dist/runtime/custody/openbao-custody-bootstrap.d.ts +18 -0
  46. package/dist/runtime/custody/openbao-custody-bootstrap.d.ts.map +1 -0
  47. package/dist/runtime/custody/openbao-custody-bootstrap.js +50 -0
  48. package/dist/runtime/custody/openbao-custody-bootstrap.js.map +1 -0
  49. package/dist/runtime/custody/openbao-custody-store.d.ts +18 -0
  50. package/dist/runtime/custody/openbao-custody-store.d.ts.map +1 -0
  51. package/dist/runtime/custody/openbao-custody-store.js +55 -0
  52. package/dist/runtime/custody/openbao-custody-store.js.map +1 -0
  53. package/dist/runtime/custody/openbao-http-client.d.ts +34 -0
  54. package/dist/runtime/custody/openbao-http-client.d.ts.map +1 -0
  55. package/dist/runtime/custody/openbao-http-client.js +154 -0
  56. package/dist/runtime/custody/openbao-http-client.js.map +1 -0
  57. package/dist/runtime/custody/openbao-local-constants.d.ts +22 -0
  58. package/dist/runtime/custody/openbao-local-constants.d.ts.map +1 -0
  59. package/dist/runtime/custody/openbao-local-constants.js +48 -0
  60. package/dist/runtime/custody/openbao-local-constants.js.map +1 -0
  61. package/dist/runtime/custody/openbao-local-runner.d.ts +16 -0
  62. package/dist/runtime/custody/openbao-local-runner.d.ts.map +1 -0
  63. package/dist/runtime/custody/openbao-local-runner.js +86 -0
  64. package/dist/runtime/custody/openbao-local-runner.js.map +1 -0
  65. package/dist/runtime/edition/run-entry.d.ts +9 -0
  66. package/dist/runtime/edition/run-entry.d.ts.map +1 -0
  67. package/dist/runtime/edition/run-entry.js +36 -0
  68. package/dist/runtime/edition/run-entry.js.map +1 -0
  69. package/dist/runtime/edition/verb-map.d.ts +60 -0
  70. package/dist/runtime/edition/verb-map.d.ts.map +1 -0
  71. package/dist/runtime/edition/verb-map.js +146 -0
  72. package/dist/runtime/edition/verb-map.js.map +1 -0
  73. package/dist/runtime/env-seeder.d.ts +3 -0
  74. package/dist/runtime/env-seeder.d.ts.map +1 -1
  75. package/dist/runtime/env-seeder.js +20 -3
  76. package/dist/runtime/env-seeder.js.map +1 -1
  77. package/dist/runtime/launch.d.ts +3 -1
  78. package/dist/runtime/launch.d.ts.map +1 -1
  79. package/dist/runtime/launch.js +37 -8
  80. package/dist/runtime/launch.js.map +1 -1
  81. package/dist/runtime/paths.d.ts +1 -2
  82. package/dist/runtime/paths.d.ts.map +1 -1
  83. package/dist/runtime/paths.js +0 -10
  84. package/dist/runtime/paths.js.map +1 -1
  85. package/dist/runtime/runner/chart-local-provenance.d.ts +20 -0
  86. package/dist/runtime/runner/chart-local-provenance.d.ts.map +1 -0
  87. package/dist/runtime/runner/chart-local-provenance.js +114 -0
  88. package/dist/runtime/runner/chart-local-provenance.js.map +1 -0
  89. package/dist/runtime/runner/chart-preflight.d.ts +8 -8
  90. package/dist/runtime/runner/chart-preflight.d.ts.map +1 -1
  91. package/dist/runtime/runner/chart-preflight.js +30 -69
  92. package/dist/runtime/runner/chart-preflight.js.map +1 -1
  93. package/dist/runtime/runner/chart-pull.d.ts +14 -0
  94. package/dist/runtime/runner/chart-pull.d.ts.map +1 -0
  95. package/dist/runtime/runner/chart-pull.js +96 -0
  96. package/dist/runtime/runner/chart-pull.js.map +1 -0
  97. package/dist/runtime/runner/chart-resolver.d.ts +4 -12
  98. package/dist/runtime/runner/chart-resolver.d.ts.map +1 -1
  99. package/dist/runtime/runner/chart-resolver.js +16 -62
  100. package/dist/runtime/runner/chart-resolver.js.map +1 -1
  101. package/dist/runtime/runner/chart-set-lock.d.ts +6 -20
  102. package/dist/runtime/runner/chart-set-lock.d.ts.map +1 -1
  103. package/dist/runtime/runner/chart-set-lock.js +55 -99
  104. package/dist/runtime/runner/chart-set-lock.js.map +1 -1
  105. package/dist/runtime/runner/helm-cli.d.ts +3 -1
  106. package/dist/runtime/runner/helm-cli.d.ts.map +1 -1
  107. package/dist/runtime/runner/helm-cli.js +9 -7
  108. package/dist/runtime/runner/helm-cli.js.map +1 -1
  109. package/dist/runtime/runner/helm-runner.d.ts +4 -4
  110. package/dist/runtime/runner/helm-runner.d.ts.map +1 -1
  111. package/dist/runtime/runner/helm-runner.js +18 -16
  112. package/dist/runtime/runner/helm-runner.js.map +1 -1
  113. package/dist/runtime/sources/kinds/git.d.ts +1 -2
  114. package/dist/runtime/sources/kinds/git.d.ts.map +1 -1
  115. package/dist/runtime/sources/kinds/git.js +8 -5
  116. package/dist/runtime/sources/kinds/git.js.map +1 -1
  117. package/dist/runtime/sources/manifest-walk.d.ts +3 -1
  118. package/dist/runtime/sources/manifest-walk.d.ts.map +1 -1
  119. package/dist/runtime/sources/manifest-walk.js +8 -3
  120. package/dist/runtime/sources/manifest-walk.js.map +1 -1
  121. package/dist/runtime/sources/schema.d.ts +3 -0
  122. package/dist/runtime/sources/schema.d.ts.map +1 -1
  123. package/dist/runtime/sources/schema.js +6 -1
  124. package/dist/runtime/sources/schema.js.map +1 -1
  125. package/dist/runtime/supervisor.d.ts +3 -0
  126. package/dist/runtime/supervisor.d.ts.map +1 -1
  127. package/dist/runtime/supervisor.js +2 -0
  128. package/dist/runtime/supervisor.js.map +1 -1
  129. package/edition/README.md +315 -0
  130. package/edition/assemble-producer-input.mjs +1251 -0
  131. package/edition/authorization-base.mjs +347 -0
  132. package/edition/biome-content.mjs +268 -0
  133. package/edition/build-biome-index.mjs +175 -0
  134. package/edition/build-platform-service-index.mjs +288 -0
  135. package/edition/build-runtime-image-source-index.mjs +821 -0
  136. package/edition/built-verifier.mjs +55 -0
  137. package/edition/catalogue-edition.mjs +49 -0
  138. package/edition/catalogue-fetch.mjs +64 -0
  139. package/edition/catalogue.mjs +135 -0
  140. package/edition/chart-set-source-commit.mjs +92 -0
  141. package/edition/check-committed-image-evidence.mjs +383 -0
  142. package/edition/checkout-indexed-source-repositories.mjs +105 -0
  143. package/edition/comment-blanker.mjs +75 -0
  144. package/edition/connector-source-authority.mjs +60 -0
  145. package/edition/customer-definition.mjs +183 -0
  146. package/edition/customer-flags.mjs +7 -0
  147. package/edition/develop-customer-edition.mjs +99 -0
  148. package/edition/develop-edition-resolve.mjs +65 -0
  149. package/edition/develop-edition.mjs +274 -0
  150. package/edition/develop-freeze.mjs +173 -0
  151. package/edition/develop-image-coordinate.mjs +54 -0
  152. package/edition/develop-seal.mjs +77 -0
  153. package/edition/develop-snapshot-inputs.mjs +104 -0
  154. package/edition/dist-check.mjs +986 -0
  155. package/edition/distribution-evidence-set.mjs +987 -0
  156. package/edition/distribution-paths.mjs +191 -0
  157. package/edition/edition-artifact.mjs +270 -0
  158. package/edition/edition-biome-manifests.mjs +85 -0
  159. package/edition/edition-channel.mjs +6 -0
  160. package/edition/edition-chart-set.mjs +93 -0
  161. package/edition/edition-config.mjs +207 -0
  162. package/edition/edition-constants.mjs +57 -0
  163. package/edition/edition-contract.json +23 -0
  164. package/edition/edition-list.mjs +35 -0
  165. package/edition/edition-locks.mjs +69 -0
  166. package/edition/edition-protocol.mjs +228 -0
  167. package/edition/edition-workspace.mjs +176 -0
  168. package/edition/evidence-checkout.mjs +418 -0
  169. package/edition/evidence-grammars.mjs +16 -0
  170. package/edition/evidence-trust-pin.mjs +304 -0
  171. package/edition/evidence-trust-policy.mjs +256 -0
  172. package/edition/external-source-walk.mjs +463 -0
  173. package/edition/first-party-source-ids.mjs +59 -0
  174. package/edition/fleet-coordinate-finality.mjs +350 -0
  175. package/edition/git-source-auth.mjs +52 -0
  176. package/edition/image-evidence-replay-state.mjs +242 -0
  177. package/edition/image-evidence.mjs +1225 -0
  178. package/edition/kernel-contract-authority.mjs +443 -0
  179. package/edition/lib.mjs +3018 -0
  180. package/edition/no-follow-file.mjs +94 -0
  181. package/edition/oci-evidence-verbs.mjs +392 -0
  182. package/edition/oci-image-source-evidence.mjs +239 -0
  183. package/edition/oras.mjs +648 -0
  184. package/edition/pin-source.mjs +227 -0
  185. package/edition/plan-image-evidence-refresh.mjs +408 -0
  186. package/edition/platform-lock-readability.mjs +892 -0
  187. package/edition/platform-service-class.mjs +18 -0
  188. package/edition/platform-service-source-authority.mjs +105 -0
  189. package/edition/positive-closure.mjs +162 -0
  190. package/edition/produce-distribution-completeness-evidence.mjs +311 -0
  191. package/edition/produce-image-delivery-entitlement-evidence.mjs +294 -0
  192. package/edition/produce-image-source-evidence.mjs +765 -0
  193. package/edition/producer-evidence-bundle.mjs +157 -0
  194. package/edition/registry-credentials.mjs +193 -0
  195. package/edition/release-catalogue.mjs +39 -0
  196. package/edition/release-channel-guard.mjs +99 -0
  197. package/edition/release-edition-artifact.mjs +158 -0
  198. package/edition/remedy-command.mjs +89 -0
  199. package/edition/repin-evidence-trust.mjs +101 -0
  200. package/edition/required-resource-provider.mjs +58 -0
  201. package/edition/resolve-chart-set.mjs +415 -0
  202. package/edition/resolve-customer-edition.mjs +288 -0
  203. package/edition/resolve-distribution.mjs +2318 -0
  204. package/edition/resolve-edition.mjs +186 -0
  205. package/edition/resolve-images.mjs +1606 -0
  206. package/edition/seal-image-digests.mjs +855 -0
  207. package/edition/sealed-evidence-accept.mjs +82 -0
  208. package/edition/source-authority.mjs +725 -0
  209. package/edition/source-closure.mjs +116 -0
  210. package/edition/strict-json.mjs +121 -0
  211. package/edition/string-order.mjs +5 -0
  212. package/edition/trust-policy-arming.mjs +120 -0
  213. package/edition/trusted-executable.mjs +166 -0
  214. package/edition/verifier-binding.mjs +45 -0
  215. package/edition/verifier-files.mjs +8 -0
  216. package/edition/verifier-kernel-schemas.mjs +22 -0
  217. package/edition/verifier-provenance.mjs +64 -0
  218. package/edition/verify-image-evidence.mjs +430 -0
  219. package/edition/verify-image-platforms.mjs +495 -0
  220. package/edition/why.mjs +56 -0
  221. package/edition/workspace-sections.mjs +195 -0
  222. package/examples/06-biome-sources.yaml +1 -0
  223. package/package.json +18 -7
  224. package/dist/commands/dist.d.ts +0 -16
  225. package/dist/commands/dist.d.ts.map +0 -1
  226. package/dist/commands/dist.js +0 -182
  227. package/dist/commands/dist.js.map +0 -1
  228. package/dist/runtime/distribution/available-biome.d.ts +0 -10
  229. package/dist/runtime/distribution/available-biome.d.ts.map +0 -1
  230. package/dist/runtime/distribution/available-biome.js +0 -154
  231. package/dist/runtime/distribution/available-biome.js.map +0 -1
  232. package/dist/runtime/distribution/compose.d.ts +0 -23
  233. package/dist/runtime/distribution/compose.d.ts.map +0 -1
  234. package/dist/runtime/distribution/compose.js +0 -48
  235. package/dist/runtime/distribution/compose.js.map +0 -1
  236. package/dist/runtime/runner/chart-trust.d.ts +0 -57
  237. package/dist/runtime/runner/chart-trust.d.ts.map +0 -1
  238. package/dist/runtime/runner/chart-trust.js +0 -270
  239. package/dist/runtime/runner/chart-trust.js.map +0 -1
@@ -0,0 +1,821 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { execFileSync } from 'node:child_process';
4
+ import { existsSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
5
+ import { isAbsolute, join } from 'node:path';
6
+
7
+ import { connectorCrossRepoBuildInputs } from './connector-source-authority.mjs';
8
+ import { editionWorkspaceConfig, workspaceSection } from './edition-config.mjs';
9
+ import { biomeIndexPath, dataRoot, runtimeImageSourceIndexPath, WorkspaceRoot, configureEditionWorkspaceFromProcess, isEntryModule, sourcePath } from './edition-workspace.mjs';
10
+ import {
11
+ buildTrustedKernelContractAuthority,
12
+ describeKernelContractAuthorityDrift,
13
+ validateKernelContractAuthorityFromGit,
14
+ } from './kernel-contract-authority.mjs';
15
+ import {
16
+ canonicalPublicHttpsRepositoryUrl,
17
+ compareStrings,
18
+ loadKernelDistributionSchemas,
19
+ readGitJsonAtCommit,
20
+ serializeJson,
21
+ sha256Hex,
22
+ } from './lib.mjs';
23
+ import {
24
+ platformServiceRuntimeBindings,
25
+ } from './platform-service-source-authority.mjs';
26
+ import {
27
+ createExactCommitSourceAuthority,
28
+ createSourceAuthority,
29
+ parseSourceModeArgs,
30
+ } from './source-authority.mjs';
31
+ import { readStrictJsonFile } from './strict-json.mjs';
32
+
33
+ if (isEntryModule(import.meta.url)) {
34
+ configureEditionWorkspaceFromProcess([WorkspaceRoot.Data, WorkspaceRoot.Sources, WorkspaceRoot.Kernel, WorkspaceRoot.SourceHash]);
35
+ }
36
+
37
+ const runtimeImageSources = () =>
38
+ workspaceSection('runtimeImageSources', 'building or validating the runtime image source index');
39
+
40
+ const sourceDeclarations = () => runtimeImageSources().supplementalSources;
41
+
42
+ /**
43
+ * Content a producing repository copies OUT OF ANOTHER repository into its build
44
+ * context before the image build.
45
+ *
46
+ * This is the ONLY image build input that is not derivable from the consuming
47
+ * repository at its own commit — nothing inside `xema-base@<sha>` names the
48
+ * bytes of `xema-cultivars/biomes/platform/mailops`. Every other input (the
49
+ * resolved Dockerfile, its `COPY` sources, the `workspace:` graph) is read
50
+ * straight out of the consuming commit, so this list is the whole hand-declared
51
+ * surface and stays small by construction.
52
+ *
53
+ * The list is the data root's (`runtimeImageSources.crossRepoBuildInputs`), plus
54
+ * one input per production connector derived from `connectorSources`; the
55
+ * history of why each entry is there lives with the data. Each entry pins the vendored bytes to the SOURCE repository's own
56
+ * `repositoryPins` commit — the revision this distribution has resolved, and
57
+ * therefore the revision the producing build must clone.
58
+ *
59
+ * CLOSED (was a KNOWN GAP): the producing workflows used to clone with
60
+ * `git clone --depth 1` and no ref — the source repository's default-branch HEAD
61
+ * at build time — so a built image was not a function of any commit pair and its
62
+ * published tag could describe bytes it did not contain. They now vendor through
63
+ * `tooling/release/vendor-cross-repo-inputs.mjs`, driven by a per-repository
64
+ * record the aggregator generates FROM THIS TABLE joined to `repositoryPins`
65
+ * (`tooling/release/generate-cross-repo-build-inputs.mjs`). The same record
66
+ * supplies `compute-image-tag.mjs`'s `--vendored` pins, so the bytes in the image
67
+ * and the bytes in the hash come from one place. The gate
68
+ * `cross-repo-build-inputs` fails on a stale record, and on a workflow that
69
+ * hand-rolls a clone or a copy instead of reading it.
70
+ *
71
+ * Consequence worth knowing: a change to a source repository's vendored content
72
+ * does NOT reach a consumer's image until this index is regenerated and the
73
+ * emitted record re-committed. That is the point — the vendored revision is
74
+ * declared, not whatever HEAD happened to be.
75
+ */
76
+ export const crossRepoBuildInputs = () => [
77
+ ...runtimeImageSources().crossRepoBuildInputs,
78
+ ...connectorCrossRepoBuildInputs(),
79
+ ];
80
+
81
+ /**
82
+ * Context directories a build step creates, which exist in no commit. Declared
83
+ * (`runtimeImageSources.buildStepContextDirectories`) so a Dockerfile COPY of
84
+ * one is a REVIEWED absence rather than a silent miss.
85
+ */
86
+ const buildStepContextDirectoriesDeclared = () => runtimeImageSources().buildStepContextDirectories;
87
+
88
+ /**
89
+ * The logical-platform-service → actual-runtime-subject bindings that cannot be
90
+ * inferred. DERIVED from `platform-service-source-authority.mjs`, which is the
91
+ * one place the partition — own repository-root image vs. carried by another
92
+ * subject — is declared. It used to be a second copy here, and the two could
93
+ * only agree by hand.
94
+ */
95
+
96
+ function readJson(path) {
97
+ return readStrictJsonFile(path);
98
+ }
99
+
100
+ function git(repo, args) {
101
+ return execFileSync('git', ['-C', repo, ...args], {
102
+ encoding: 'utf8',
103
+ stdio: ['ignore', 'pipe', 'pipe'],
104
+ }).trim();
105
+ }
106
+
107
+ function declaredSourceRepositories() {
108
+ return [
109
+ ...new Set([
110
+ ...readJson(biomeIndexPath()).map(({ repo }) => repo),
111
+ ...readJson(
112
+ join(dataRoot(), 'platform-service-index.json'),
113
+ ).services.map(({ repo }) => repo),
114
+ ...sourceDeclarations().map(({ repo }) => repo),
115
+ ...crossRepoBuildInputs().flatMap(({ consumerRepo, sourceRepo }) => [
116
+ consumerRepo,
117
+ sourceRepo,
118
+ ]),
119
+ ]),
120
+ ];
121
+ }
122
+
123
+ /**
124
+ * One repository, BOTH provenance axes, in ONE record.
125
+ *
126
+ * `sourceCommit` is the IMAGE axis and its meaning is unchanged: every image
127
+ * subject from this repository must carry exactly this commit, and
128
+ * `expectedProducerSource` throws when one does not. It is therefore
129
+ * constrained to commits with published image evidence.
130
+ *
131
+ * `vendoredSourceCommit` is the SOURCE axis — where vendored bytes come from.
132
+ * It is the published tip, because a vendored path needs its content to exist
133
+ * at a published immutable commit and does NOT need an image to have been
134
+ * built there. See `publishedSourceTip` in `source-authority.mjs` for the
135
+ * measured production failure that fusing the two produced.
136
+ *
137
+ * They are emitted together, from one resolution, deliberately: the release has
138
+ * ONE authority that answers both questions about a component, never two
139
+ * competing ones that can disagree about which commit a component is at.
140
+ */
141
+ function repositoryIdentity(repo, sourceAuthority) {
142
+ const repoDirectory = sourcePath(repo);
143
+ const sourceCommit = sourceAuthority.commitForRepository(repo);
144
+ const vendoredSourceCommit =
145
+ sourceAuthority.vendoredSourceCommitForRepository(repo);
146
+ const repositoryUrl = canonicalPublicHttpsRepositoryUrl(
147
+ git(repoDirectory, ['remote', 'get-url', 'origin']),
148
+ `${repo} origin`,
149
+ );
150
+ return { repo, repositoryUrl, sourceCommit, vendoredSourceCommit };
151
+ }
152
+
153
+ /**
154
+ * Refuse to WRITE an index whose declared vendored source paths do not exist at
155
+ * the commit whose bytes the build will actually fetch.
156
+ *
157
+ * This assertion landed on `main` in `0dfbba7` and was removed by `a416f1e`
158
+ * ("Revert the develop merge on main — the CONSUMER landed before its
159
+ * PRODUCER") as COLLATERAL of reverting an unrelated cross-repo ordering
160
+ * failure — 93 lines of this file among 62 reverted files. It is re-landed here
161
+ * on its own merits, and on the SOURCE axis.
162
+ *
163
+ * Nothing reads the declared cross-repo build inputs at declaration time, so a path that
164
+ * has moved fails SILENTLY where it is written and LOUDLY somewhere else
165
+ * entirely: the consuming image build dies at "Fetch pinned cross-repo build
166
+ * inputs", in another repository, for a declaration nobody was editing. The
167
+ * defect belongs where it is authored, and an index that cannot be written is
168
+ * strictly better than one that can be written and cannot be built from.
169
+ */
170
+ function assertVendoredSourcesResolve(crossRepoBuildInputs, resolveIdentity) {
171
+ const unresolved = [];
172
+ for (const input of crossRepoBuildInputs) {
173
+ // The SOURCE axis, not the image pin. A vendored path is a fact about the
174
+ // commit whose BYTES are vendored, and `vendor-cross-repo-inputs.mjs`
175
+ // fetches exactly that commit — so asking the image-evidence pin whether a
176
+ // source path exists answers about a commit the build never reads.
177
+ const { vendoredSourceCommit } = resolveIdentity(input.sourceRepo);
178
+ const found = git(sourcePath(input.sourceRepo), [
179
+ 'ls-tree',
180
+ '--name-only',
181
+ vendoredSourceCommit,
182
+ '--',
183
+ input.sourcePath,
184
+ ]);
185
+ if (found.length === 0) {
186
+ unresolved.push(
187
+ `${input.sourceRepo}@${vendoredSourceCommit}:${input.sourcePath} does not ` +
188
+ `exist (vendored into ${input.consumerRepo}` +
189
+ `${input.consumerService ? ` service "${input.consumerService}"` : ''} ` +
190
+ `at ${input.destinationPath})`,
191
+ );
192
+ }
193
+ }
194
+ if (unresolved.length > 0) {
195
+ throw new Error(
196
+ `the declared crossRepoBuildInputs name ${unresolved.length} source path(s) that do ` +
197
+ `not exist at the vendored source commit this index pins. A vendored path ` +
198
+ `is a fact about that PINNED commit, not about anybody's working tree or ` +
199
+ `develop branch — fix the declaration, or pin a commit that carries it:` +
200
+ `\n ${unresolved.join('\n ')}`,
201
+ );
202
+ }
203
+ }
204
+
205
+ /**
206
+ * The kernel-contract authority for a pin regeneration.
207
+ *
208
+ * ── WHY THIS IS NOT `buildTrustedKernelContractAuthority` ──
209
+ *
210
+ * This function regenerates `repositoryPins` — a PROJECTION of immutable
211
+ * remote facts. Every one of those pins is read from
212
+ * `refs/remotes/origin/main` of its repository, which is exactly right: a
213
+ * production composition must resolve from durable, published state.
214
+ *
215
+ * The kernel-contract authority was the ONE exception, and it was the wrong
216
+ * kind of exception. `buildTrustedKernelContractAuthority` takes its commit
217
+ * from the LOCAL HEAD of `repos/xema-kernel-contracts` and requires that
218
+ * checkout to be EXACTLY CLEAN — so regenerating thirteen pins that have
219
+ * nothing to do with the kernel contract failed outright whenever somebody
220
+ * had an uncommitted file in that one repository. Measured 2026-09-14: ten of
221
+ * twenty-eight repositories in a shared checkout held another agent's
222
+ * in-flight work, and the pins could not be regenerated at all.
223
+ *
224
+ * That is a development-state fact deciding a release-composition question,
225
+ * and it is the boundary this change moves. Whether a colleague's working
226
+ * tree is dirty must never determine whether we can resolve what production
227
+ * ships.
228
+ *
229
+ * ── WHAT IS PRESERVED, AND WHY IT IS NOT WEAKER ──
230
+ *
231
+ * The authority is read from the COMMITTED index and re-verified from git AT
232
+ * ITS OWN PINNED COMMIT: `validateKernelContractAuthorityFromGit` recomputes
233
+ * the content digest from `ls-tree` + `cat-file` blobs at `sourceCommit` and
234
+ * refuses anything that does not match byte for byte. No worktree file is
235
+ * read on that path — it never was — so the provenance guarantee is identical
236
+ * and the clean-checkout assertion was buying nothing for it.
237
+ *
238
+ * `requireHeadMatch: false` because MOVING the authority is a different
239
+ * operation with its own tool: `xema edition index runtime-image-source
240
+ * --repin-contract-authority`, which
241
+ * legitimately demands a deliberate clean checkout because it is CHOOSING a
242
+ * new commit. Re-deriving it here made pin regeneration silently re-pin the
243
+ * contract as a side effect — two operations behind one command, and the one
244
+ * nobody asked for is the one that took the lock.
245
+ *
246
+ * BOOTSTRAP is the single case where local HEAD is the honest source: there is
247
+ * no committed index to preserve, so there is no prior decision to respect.
248
+ */
249
+ async function resolveContractAuthority(schemas, kernelRoot) {
250
+ const repositoryDirectory = kernelRoot ?? undefined;
251
+ if (!existsSync(runtimeImageSourceIndexPath())) {
252
+ return buildTrustedKernelContractAuthority({ schemas });
253
+ }
254
+ const committed = readJson(runtimeImageSourceIndexPath());
255
+ if (!committed?.contractAuthority) {
256
+ return buildTrustedKernelContractAuthority({ schemas });
257
+ }
258
+ return validateKernelContractAuthorityFromGit({
259
+ authority: committed.contractAuthority,
260
+ ...(repositoryDirectory === undefined ? {} : { repositoryDirectory }),
261
+ schemas,
262
+ requireHeadMatch: false,
263
+ });
264
+ }
265
+
266
+ /**
267
+ * Re-resolve ONE repository pin and PRESERVE every other one byte for byte.
268
+ *
269
+ * ── WHY A WHOLE-INDEX REBUILD IS THE WRONG TOOL FOR A ONE-REPOSITORY FIX ──
270
+ *
271
+ * `--trusted-write` re-resolves all thirteen pins against each repository's
272
+ * `refs/remotes/origin/main`. That is exactly right when the question is "what
273
+ * does the fleet look like now", and exactly wrong when the question is "ship
274
+ * the fix that is already built in ONE repository": every other pin moves to
275
+ * whatever that repository has published since, and every subject whose
276
+ * identity depends on a moved pin is recomputed. Measured 2026-09-18 on this
277
+ * index, a full rebuild moves 5 of 13 pins and recomputes 54 of 62 subjects,
278
+ * 53 of them collateral — so a one-line fix arrives as a fleet-wide re-pin
279
+ * nobody asked for, and every one of those 53 subjects is a coordinate the
280
+ * registry must already be serving or delivery fails.
281
+ *
282
+ * The scope is a CHOICE somebody states, and it narrows: `--repo <repository>`
283
+ * says "this repository has moved; nothing else has". Every pin outside it is
284
+ * taken verbatim from the committed index, so it cannot drift by accident, and
285
+ * every subject derived from an unmoved pin is recomputed at the SAME commit
286
+ * and therefore lands byte-identical. Only the subjects whose identity actually
287
+ * depends on the named repository change.
288
+ *
289
+ * It is deliberately NOT a separate code path: the scope seeds the memo map
290
+ * `getRepositoryIdentity` already uses, so the index is produced by the one
291
+ * builder, passes the one validator, and is byte-comparable with a full
292
+ * rebuild's output for the parts that did not move. A second writer for
293
+ * "narrow" indexes would be a second answer to what an index IS.
294
+ *
295
+ * A repository this index does not source is a REFUSAL, never a silent no-op —
296
+ * a typo that quietly rewrote nothing would read in review as "the refresh ran".
297
+ */
298
+ export function preserveOtherRepositoryPins(
299
+ repoScope,
300
+ committedIndex,
301
+ declaredRepositories,
302
+ ) {
303
+ const declared = new Set(declaredRepositories);
304
+ if (!declared.has(repoScope)) {
305
+ throw new Error(
306
+ `--repo "${repoScope}" is not a source repository of this index. ` +
307
+ `Known repositories: ${[...declared].sort(compareStrings).join(', ')}`,
308
+ );
309
+ }
310
+ const pins = Array.isArray(committedIndex?.repositoryPins)
311
+ ? committedIndex.repositoryPins
312
+ : [];
313
+ if (pins.length === 0) {
314
+ throw new Error(
315
+ 'the committed runtime image source index declares no repositoryPins to preserve',
316
+ );
317
+ }
318
+ if (!pins.some((pin) => pin.repo === repoScope)) {
319
+ throw new Error(
320
+ `--repo "${repoScope}" has no committed pin to refresh. A repository ` +
321
+ `joining this index is a full --trusted-write, not a scoped refresh.`,
322
+ );
323
+ }
324
+ return pins
325
+ .filter((pin) => pin.repo !== repoScope)
326
+ .map((pin) => ({
327
+ repo: pin.repo,
328
+ repositoryUrl: pin.repositoryUrl,
329
+ sourceCommit: pin.sourceCommit,
330
+ vendoredSourceCommit: pin.vendoredSourceCommit,
331
+ }));
332
+ }
333
+
334
+ function committedRepositoryPinsToPreserve(repoScope) {
335
+ if (!existsSync(runtimeImageSourceIndexPath())) {
336
+ throw new Error(
337
+ `--repo requires ${runtimeImageSourceIndexPath()} to exist: there is no ` +
338
+ `committed index whose other pins could be preserved. Bootstrap with ` +
339
+ `--trusted-write and no --repo.`,
340
+ );
341
+ }
342
+ return preserveOtherRepositoryPins(
343
+ repoScope,
344
+ readJson(runtimeImageSourceIndexPath()),
345
+ declaredSourceRepositories(),
346
+ );
347
+ }
348
+
349
+ /**
350
+ * `biomeIndex` and `platformServiceIndex` are the indexes this one is built
351
+ * over, INJECTED: the release CLI passes the committed ones read from disk
352
+ * (byte-identical to reading them here), a develop snapshot passes the ones it
353
+ * built at its own pins.
354
+ */
355
+ export async function buildTrustedIndex(
356
+ schemas,
357
+ sourceAuthority,
358
+ kernelRoot,
359
+ repoScope,
360
+ { biomeIndex, platformServiceIndex },
361
+ ) {
362
+ const contractAuthority = await resolveContractAuthority(schemas, kernelRoot);
363
+ const repositoryIdentities = new Map();
364
+ if (repoScope !== undefined) {
365
+ for (const pin of committedRepositoryPinsToPreserve(repoScope)) {
366
+ repositoryIdentities.set(pin.repo, pin);
367
+ }
368
+ }
369
+ const getRepositoryIdentity = (repo) => {
370
+ let identity = repositoryIdentities.get(repo);
371
+ if (!identity) {
372
+ identity = repositoryIdentity(repo, sourceAuthority);
373
+ repositoryIdentities.set(repo, identity);
374
+ }
375
+ return identity;
376
+ };
377
+ const supplementalSubjects = sourceDeclarations().map((declaration) => {
378
+ const repoDirectory = sourcePath(declaration.repo);
379
+ const { sourceCommit, repositoryUrl } = getRepositoryIdentity(
380
+ declaration.repo,
381
+ );
382
+ const sourceManifestJson = readGitJsonAtCommit(
383
+ repoDirectory,
384
+ declaration.sourceManifestPath,
385
+ sourceCommit,
386
+ );
387
+ return {
388
+ kind: declaration.kind,
389
+ id: declaration.id,
390
+ imageRef: `${editionWorkspaceConfig().imageRegistry}/${declaration.id}`,
391
+ repositoryUrl,
392
+ repo: declaration.repo,
393
+ path:
394
+ declaration.path === '.'
395
+ ? declaration.repo
396
+ : `${declaration.repo}/${declaration.path}`,
397
+ sourceManifestPath: `${declaration.repo}/${declaration.sourceManifestPath}`,
398
+ sourceCommit,
399
+ sourceManifestHash: sha256Hex(serializeJson(sourceManifestJson)),
400
+ };
401
+ }).sort(compareSubject);
402
+ const sourceRepos = new Set([
403
+ ...biomeIndex.map((biome) => biome.repo),
404
+ ...platformServiceIndex.services.map((service) => service.repo),
405
+ ...supplementalSubjects.map((subject) => subject.repo),
406
+ ]);
407
+ const repositoryPins = [...sourceRepos]
408
+ .sort(compareStrings)
409
+ .map(getRepositoryIdentity);
410
+ const crossRepoBuildInputs = [...crossRepoBuildInputs()].sort(
411
+ (left, right) =>
412
+ compareStrings(
413
+ [left.consumerRepo, left.consumerService, left.destinationPath].join(
414
+ '\u0000',
415
+ ),
416
+ [right.consumerRepo, right.consumerService, right.destinationPath].join(
417
+ '\u0000',
418
+ ),
419
+ ),
420
+ );
421
+ assertVendoredSourcesResolve(crossRepoBuildInputs, getRepositoryIdentity);
422
+ const buildStepContextDirectories = [...buildStepContextDirectoriesDeclared()].sort(
423
+ (left, right) =>
424
+ compareStrings(
425
+ `${left.repo}\u0000${left.path}`,
426
+ `${right.repo}\u0000${right.path}`,
427
+ ),
428
+ );
429
+ for (const directory of buildStepContextDirectories) {
430
+ if (!sourceRepos.has(directory.repo)) {
431
+ throw new Error(
432
+ `build-step context directory "${directory.path}" names repository ` +
433
+ `"${directory.repo}", which is not an image source repository`,
434
+ );
435
+ }
436
+ }
437
+ for (const input of crossRepoBuildInputs) {
438
+ for (const repo of [input.consumerRepo, input.sourceRepo]) {
439
+ if (!sourceRepos.has(repo)) {
440
+ throw new Error(
441
+ `cross-repo build input for "${input.consumerService}" names repository ` +
442
+ `"${repo}", which is not an image source repository`,
443
+ );
444
+ }
445
+ }
446
+ }
447
+ return {
448
+ schemaVersion: 1,
449
+ contractAuthority,
450
+ supplementalSubjects,
451
+ crossRepoBuildInputs,
452
+ buildStepContextDirectories,
453
+ repositoryPins,
454
+ platformServiceBindings: [...platformServiceRuntimeBindings()].sort(
455
+ (left, right) =>
456
+ compareStrings(left.platformService, right.platformService),
457
+ ),
458
+ };
459
+ }
460
+
461
+ function compareSubject(left, right) {
462
+ return (
463
+ compareStrings(left.kind, right.kind) || compareStrings(left.id, right.id)
464
+ );
465
+ }
466
+
467
+ export async function validateRuntimeImageSourceIndex(
468
+ index,
469
+ schemas,
470
+ {
471
+ // Whether `repositoryPins` must already cover every image source repository.
472
+ // TRUE by default, and true for everyone who PRODUCES this index — that is
473
+ // this module's own contract and the assertion below is where it lives.
474
+ //
475
+ // The kernel-authority repin passes false, because it produces nothing here:
476
+ // it rewrites ONE field, `contractAuthority`, on an index somebody else
477
+ // built. Requiring coverage of it makes onboarding a new source repository
478
+ // impossible by construction — the repin refuses the stale index, and the
479
+ // rebuild that would add the missing pin refuses the un-repinned authority.
480
+ // Measured 2026-08-26 when `repos/xema-connectors` was carved out: the
481
+ // documented six-step chain deadlocked at its own first step.
482
+ assertRepositoryCoverage = true,
483
+ // WHICH kernel checkout the committed authority is re-verified against.
484
+ // Undefined keeps the default in-aggregator checkout; see `parseKernelRoot`
485
+ // for why a shared working tree must not decide this.
486
+ kernelRoot = undefined,
487
+ // The indexes this one is validated against: the committed ones by default,
488
+ // a develop snapshot's own when it injects them.
489
+ biomeIndex = readJson(biomeIndexPath()),
490
+ platformServiceIndex = readJson(
491
+ join(dataRoot(), 'platform-service-index.json'),
492
+ ),
493
+ } = {},
494
+ ) {
495
+ await validateKernelContractAuthorityFromGit({
496
+ authority: index.contractAuthority,
497
+ ...(kernelRoot === undefined ? {} : { repositoryDirectory: kernelRoot }),
498
+ schemas,
499
+ onDrift: (drift) =>
500
+ console.log(`[runtime-image-source-index] ${describeKernelContractAuthorityDrift(drift)}`),
501
+ });
502
+ // Every platform service carries an exact source identity (the kernel lock
503
+ // schema requires it), so "is it in platform-service-index.json" no longer
504
+ // separates the two kinds — it is now true of all of them. The partition is
505
+ // the declared runtime binding, read from the one authority that owns it.
506
+ const expectedBindings = platformServiceRuntimeBindings()
507
+ .map((binding) => binding.platformService)
508
+ .sort(compareStrings);
509
+ const actualBindings = index.platformServiceBindings.map(
510
+ (binding) => binding.platformService,
511
+ );
512
+ if (serializeJson(actualBindings) !== serializeJson(expectedBindings)) {
513
+ throw new Error(
514
+ 'runtime-image-source-index platform bindings do not exactly cover every declared platform service that another subject carries',
515
+ );
516
+ }
517
+ const supplemental = new Set(
518
+ index.supplementalSubjects.map(
519
+ (subject) => `${subject.kind}\0${subject.id}`,
520
+ ),
521
+ );
522
+ const expectedRepositories = [
523
+ ...new Set([
524
+ ...biomeIndex.map((biome) => biome.repo),
525
+ ...platformServiceIndex.services.map((service) => service.repo),
526
+ ...index.supplementalSubjects.map((subject) => subject.repo),
527
+ ]),
528
+ ].sort(compareStrings);
529
+ const actualRepositories = index.repositoryPins.map(
530
+ (repository) => repository.repo,
531
+ );
532
+ if (
533
+ assertRepositoryCoverage &&
534
+ serializeJson(actualRepositories) !== serializeJson(expectedRepositories)
535
+ ) {
536
+ throw new Error(
537
+ 'runtime-image-source-index repository pins do not exactly cover every image source repository',
538
+ );
539
+ }
540
+ const repositoryPins = new Map(
541
+ index.repositoryPins.map((repository) => [repository.repo, repository]),
542
+ );
543
+ for (const repository of index.repositoryPins) {
544
+ if (
545
+ canonicalPublicHttpsRepositoryUrl(
546
+ repository.repositoryUrl,
547
+ `${repository.repo} repositoryUrl`,
548
+ ) !== repository.repositoryUrl
549
+ ) {
550
+ throw new Error(`${repository.repo} repositoryUrl is not canonical`);
551
+ }
552
+ }
553
+ // Every subject a biome contributes to the image plane — BOTH the standing
554
+ // services and the runtime components. A runtime component is a full image
555
+ // subject (built, signed, mirrored) that simply is never scheduled, so a
556
+ // platform service may legitimately bind to one: `opencode-image-prepuller`
557
+ // is a standing service whose whole job is to keep the `opencode-worker`
558
+ // runtime-component image warm on every node.
559
+ const biomeImageSubjects = new Set(
560
+ biomeIndex.flatMap((biome) => [
561
+ // A worker running a sibling service's image is not a subject of its own.
562
+ ...(biome.services ?? [])
563
+ .filter((service) => !Object.hasOwn(biome.workerImages ?? {}, service))
564
+ .map((service) => `service\0${service}`),
565
+ ...(biome.runtimeComponents ?? []).map(
566
+ (component) => `runtime-component\0${component}`,
567
+ ),
568
+ ]),
569
+ );
570
+ for (const binding of index.platformServiceBindings) {
571
+ const key = `${binding.subjectKind}\0${binding.subjectId}`;
572
+ if (!supplemental.has(key) && !biomeImageSubjects.has(key)) {
573
+ throw new Error(
574
+ `platform service "${binding.platformService}" binds unknown runtime subject "${binding.subjectKind}:${binding.subjectId}"`,
575
+ );
576
+ }
577
+ }
578
+ for (const subject of index.supplementalSubjects) {
579
+ if (
580
+ canonicalPublicHttpsRepositoryUrl(
581
+ subject.repositoryUrl,
582
+ `${subject.kind}:${subject.id} repositoryUrl`,
583
+ ) !== subject.repositoryUrl
584
+ ) {
585
+ throw new Error(
586
+ `${subject.kind}:${subject.id} repositoryUrl is not canonical`,
587
+ );
588
+ }
589
+ if (
590
+ !subject.path.startsWith(`${subject.repo}/`) &&
591
+ subject.path !== subject.repo
592
+ ) {
593
+ throw new Error(
594
+ `${subject.kind}:${subject.id} source path escapes ${subject.repo}`,
595
+ );
596
+ }
597
+ if (!subject.sourceManifestPath.startsWith(`${subject.repo}/`)) {
598
+ throw new Error(
599
+ `${subject.kind}:${subject.id} source manifest escapes ${subject.repo}`,
600
+ );
601
+ }
602
+ const repository = repositoryPins.get(subject.repo);
603
+ if (
604
+ !repository ||
605
+ repository.repositoryUrl !== subject.repositoryUrl ||
606
+ repository.sourceCommit !== subject.sourceCommit
607
+ ) {
608
+ throw new Error(
609
+ `${subject.kind}:${subject.id} does not match its repository pin`,
610
+ );
611
+ }
612
+ }
613
+ return index;
614
+ }
615
+
616
+ /** The follow-ups a re-pin owes; printed on stderr, because the repin itself changes only one field. */
617
+ export const REPIN_CONTRACT_AUTHORITY_FOLLOW_UPS = Object.freeze([
618
+ 'xema edition repin-evidence-trust (with the data-root and kernel flags): the evidence trust pin binds the authority; re-pin it.',
619
+ 'Re-resolve every edition (xema edition resolve): each images.lock.json carries the contract authority, so its normalizedImageLockDigest changes.',
620
+ 'Signed producer statements made under the old authority no longer verify: a statement binds the authority it was signed under, so they must be re-signed.',
621
+ ]);
622
+
623
+ /**
624
+ * Replace ONLY `contractAuthority` of the committed runtime image source index
625
+ * with the one trusted from the kernel checkout, and write it back.
626
+ *
627
+ * The document is read as raw strict JSON and the field replaced BEFORE it is
628
+ * parsed: the kernel's schema refuses an index whose authority `sourcePaths`
629
+ * differ from the bundled constant, so a stale index cannot be parsed first.
630
+ * Every repository pin and subject is kept byte for byte. Repository coverage
631
+ * is not asserted: this repin produces no pin, and requiring coverage would
632
+ * make a stale index unrepairable.
633
+ */
634
+ export async function repinContractAuthority(schemas, kernelRoot) {
635
+ const path = runtimeImageSourceIndexPath();
636
+ if (!existsSync(path)) {
637
+ throw new Error(`${path} does not exist: there is no index whose contract authority could be re-pinned`);
638
+ }
639
+ const raw = readJson(path);
640
+ const contractAuthority = await buildTrustedKernelContractAuthority({
641
+ ...(kernelRoot === undefined ? {} : { repositoryDirectory: kernelRoot }),
642
+ schemas,
643
+ });
644
+ const candidate = schemas.parseRuntimeImageSourceIndex({ ...raw, contractAuthority });
645
+ await validateRuntimeImageSourceIndex(candidate, schemas, {
646
+ assertRepositoryCoverage: false,
647
+ kernelRoot,
648
+ });
649
+ writeFileSync(path, serializeJson(candidate));
650
+ return { path, previous: raw.contractAuthority, current: contractAuthority };
651
+ }
652
+
653
+ /**
654
+ * `--kernel-root` names WHICH checkout of the kernel repository the committed
655
+ * contract authority is re-verified against. It relaxes NOTHING: the digest is
656
+ * still recomputed from `ls-tree` + `cat-file` blobs at the authority's own
657
+ * pinned commit and must match byte for byte.
658
+ *
659
+ * It exists for the reason this file already argues one function down — a
660
+ * development-state fact must not decide a release-composition question. The
661
+ * default checkout is SHARED, so it routinely sits on a stale or unrelated
662
+ * history, and the authority pin then fails an ancestry test that says nothing
663
+ * about the pin and everything about whose branch that directory is on.
664
+ * Measured 2026-09-14: the pin was refused as "not an ancestor of kernel HEAD"
665
+ * by a checkout 39 commits behind, while being a perfectly ordinary ancestor of
666
+ * the published history.
667
+ */
668
+ function parseRepoScope(argv) {
669
+ const index = argv.indexOf('--repo');
670
+ if (index === -1) {return { repoScope: undefined, remaining: argv };}
671
+ const value = argv[index + 1];
672
+ if (!value || value.startsWith('--')) {
673
+ throw new Error('--repo requires a repository path, e.g. repos/<name>');
674
+ }
675
+ return {
676
+ repoScope: value,
677
+ remaining: [...argv.slice(0, index), ...argv.slice(index + 2)],
678
+ };
679
+ }
680
+
681
+ /**
682
+ * `--at <sha>` — pin the `--repo` repository at an EXACT commit instead of at
683
+ * whatever the live resolver judges newest-built. Only meaningful with `--repo`,
684
+ * and refused without it: "pin everything at this one commit" is not a thing.
685
+ */
686
+ function parseExactCommit(argv) {
687
+ const index = argv.indexOf('--at');
688
+ if (index === -1) {return { exactCommit: undefined, remaining: argv };}
689
+ const value = argv[index + 1];
690
+ if (!value || value.startsWith('--')) {
691
+ throw new Error('--at requires a full 40-character commit sha');
692
+ }
693
+ return {
694
+ exactCommit: value,
695
+ remaining: [...argv.slice(0, index), ...argv.slice(index + 2)],
696
+ };
697
+ }
698
+
699
+ function parseKernelRoot(argv) {
700
+ const index = argv.indexOf('--kernel-root');
701
+ if (index === -1) {return { kernelRoot: undefined, remaining: argv };}
702
+ const value = argv[index + 1];
703
+ if (!value) {throw new Error('--kernel-root requires a directory');}
704
+ if (!isAbsolute(value)) {
705
+ throw new Error('--kernel-root requires an ABSOLUTE directory');
706
+ }
707
+ return {
708
+ kernelRoot: realpathSync(value),
709
+ remaining: [...argv.slice(0, index), ...argv.slice(index + 2)],
710
+ };
711
+ }
712
+
713
+ const REPIN_FLAG = '--repin-contract-authority';
714
+
715
+ async function main() {
716
+ const { kernelRoot, remaining: afterKernelRoot } = parseKernelRoot(
717
+ process.argv.slice(2),
718
+ );
719
+ const { repoScope, remaining: afterRepoScope } = parseRepoScope(afterKernelRoot);
720
+ const { exactCommit, remaining: argv } = parseExactCommit(afterRepoScope);
721
+ const { mode, remaining } = parseSourceModeArgs(argv);
722
+ if (exactCommit !== undefined && repoScope === undefined) {
723
+ throw new Error('--at requires --repo: it pins ONE repository at an exact commit');
724
+ }
725
+ const [flag] = remaining;
726
+ if (
727
+ remaining.length !== 1 ||
728
+ !['--check', '--check-sources', '--trusted-write', REPIN_FLAG].includes(flag)
729
+ ) {
730
+ throw new Error(
731
+ 'usage: build-runtime-image-source-index.mjs (--check | --check-sources | --trusted-write | --repin-contract-authority) ' +
732
+ '[--source published|workspace] [--kernel-root <absolute-checkout>] ' +
733
+ '[--repo <repository> [--at <40-hex-commit>]]',
734
+ );
735
+ }
736
+ const schemas = await loadKernelDistributionSchemas();
737
+ if (flag === REPIN_FLAG) {
738
+ if (repoScope !== undefined || exactCommit !== undefined) {
739
+ throw new Error(`${REPIN_FLAG} replaces the contract authority only: it takes no --repo or --at`);
740
+ }
741
+ const { path, previous, current } = await repinContractAuthority(schemas, kernelRoot);
742
+ console.log(
743
+ `[runtime-image-source-index] wrote ${path} (contract authority ${previous?.sourceCommit ?? 'absent'} -> ${current.sourceCommit}; every repository pin and subject preserved)`,
744
+ );
745
+ for (const followUp of REPIN_CONTRACT_AUTHORITY_FOLLOW_UPS) {
746
+ console.error(`[runtime-image-source-index] owed after the re-pin: ${followUp}`);
747
+ }
748
+ return;
749
+ }
750
+ if (
751
+ !existsSync(runtimeImageSourceIndexPath()) &&
752
+ flag !== '--trusted-write'
753
+ ) {
754
+ throw new Error(`${runtimeImageSourceIndexPath()} does not exist`);
755
+ }
756
+ if (flag === '--check') {
757
+ const parsed = schemas.parseRuntimeImageSourceIndex(
758
+ readJson(runtimeImageSourceIndexPath()),
759
+ );
760
+ await validateRuntimeImageSourceIndex(parsed, schemas, { kernelRoot });
761
+ console.log(
762
+ `[runtime-image-source-index] offline check passed for ${parsed.supplementalSubjects.length} supplemental subjects and ${parsed.platformServiceBindings.length} bindings`,
763
+ );
764
+ return;
765
+ }
766
+ const candidate = schemas.parseRuntimeImageSourceIndex(
767
+ await buildTrustedIndex(
768
+ schemas,
769
+ exactCommit === undefined
770
+ ? createSourceAuthority({
771
+ mode,
772
+ knownRepositories: declaredSourceRepositories(),
773
+ })
774
+ : createExactCommitSourceAuthority({
775
+ delegate: createSourceAuthority({
776
+ mode,
777
+ knownRepositories: declaredSourceRepositories(),
778
+ }),
779
+ repo: repoScope,
780
+ commit: exactCommit,
781
+ }),
782
+ kernelRoot,
783
+ repoScope,
784
+ {
785
+ biomeIndex: readJson(biomeIndexPath()),
786
+ platformServiceIndex: readJson(join(dataRoot(), 'platform-service-index.json')),
787
+ },
788
+ ),
789
+ );
790
+ await validateRuntimeImageSourceIndex(candidate, schemas, { kernelRoot });
791
+ if (flag === '--check-sources') {
792
+ if (
793
+ readFileSync(runtimeImageSourceIndexPath(), 'utf8') !==
794
+ serializeJson(candidate)
795
+ ) {
796
+ throw new Error(
797
+ 'runtime-image-source-index.json does not match exact trusted sources',
798
+ );
799
+ }
800
+ console.log('[runtime-image-source-index] trusted source check passed');
801
+ return;
802
+ }
803
+ writeFileSync(runtimeImageSourceIndexPath(), serializeJson(candidate));
804
+ console.log(
805
+ `[runtime-image-source-index] wrote ${runtimeImageSourceIndexPath()}` +
806
+ (repoScope === undefined
807
+ ? ' (all repository pins re-resolved)'
808
+ : ` (${
809
+ exactCommit === undefined
810
+ ? `re-resolved ${repoScope}`
811
+ : `pinned ${repoScope} at the exact commit ${exactCommit}`
812
+ }; ${candidate.repositoryPins.length - 1} other pin(s) preserved)`),
813
+ );
814
+ }
815
+
816
+ if (isEntryModule(import.meta.url)) {
817
+ main().catch((error) => {
818
+ console.error(`[runtime-image-source-index] ${error.message}`);
819
+ process.exitCode = 1;
820
+ });
821
+ }