@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,173 @@
1
+ // develop-freeze — freeze the develop branches into one snapshot of pins.
2
+ //
3
+ // Resolves `refs/heads/develop` ONCE per distinct repository URL, applies the
4
+ // caller's per-key overrides (an override wins and is never looked up), and
5
+ // returns the pins. After this, NOTHING reads a branch: every later step takes
6
+ // the pins. Two phases, because the declared sources are only known once the
7
+ // edition's chain can be read from the checked-out editions root:
8
+ //
9
+ // freezeGraph the graph repositories, the data root, the kernel, the deploy checkout, the editions root
10
+ // freezeSources one pin per `sources[]` entry the chain declares
11
+ //
12
+ // `resolveHead(url)` is the one network seam (default: `git ls-remote`
13
+ // authenticated through git-source-auth, token from the environment, never argv).
14
+
15
+ import { execFileSync } from 'node:child_process';
16
+
17
+ import { gitAuthEnv } from './git-source-auth.mjs';
18
+ import { canonicalPublicHttpsRepositoryUrl } from './lib.mjs';
19
+ import {
20
+ dataPinKey,
21
+ EDITIONS_ROOT_PIN_KEY,
22
+ kernelPinKey,
23
+ SnapshotPinNamespace,
24
+ deployPinKey,
25
+ snapshotPinNamespace,
26
+ sortedSnapshotPins,
27
+ sourceNameOfPinKey,
28
+ sourcePinKey,
29
+ } from './pin-source.mjs';
30
+
31
+ const DEVELOP_REF = 'refs/heads/develop';
32
+ const SHA_PATTERN = /^[0-9a-f]{40}$/;
33
+
34
+ /** `git ls-remote <url> refs/heads/develop`, authenticated as `username` with `token`; throws naming the repository when absent. */
35
+ export function lsRemoteDevelopHead(url, { username, token, runGit = execFileSync } = {}) {
36
+ const output = runGit('git', ['ls-remote', url, DEVELOP_REF], {
37
+ env: gitAuthEnv(url, username, token),
38
+ encoding: 'utf8',
39
+ stdio: ['ignore', 'pipe', 'pipe'],
40
+ }).trim();
41
+ const [sha, ref] = output.split(/\s+/);
42
+ if (!SHA_PATTERN.test(sha ?? '') || ref !== DEVELOP_REF) {
43
+ throw new Error(`${url} has no ${DEVELOP_REF}; a develop edition cannot be frozen without it`);
44
+ }
45
+ return sha;
46
+ }
47
+
48
+ /** A resolver that asks `resolveHead` at most once per distinct URL. */
49
+ function memoizedHeads(resolveHead) {
50
+ const heads = new Map();
51
+ return (url) => {
52
+ if (!heads.has(url)) {heads.set(url, resolveHead(url));}
53
+ return heads.get(url);
54
+ };
55
+ }
56
+
57
+ /**
58
+ * The pins input selects WHICH inputs a snapshot resolves, never which tooling
59
+ * code runs: the distributions plane that executes is the dispatching ref's own
60
+ * checkout, so an override for its pin key is refused outright (even one equal
61
+ * to that checkout), naming the key.
62
+ */
63
+ export function assertNoToolingOverride(overrides) {
64
+ if (overrides[dataPinKey()] !== undefined) {
65
+ throw new Error(
66
+ `pins override "${dataPinKey()}" is refused: it would select the tooling code this run executes. ` +
67
+ 'The distributions plane is always the dispatching ref\'s own checkout; pins choose inputs, never tooling.',
68
+ );
69
+ }
70
+ }
71
+
72
+ function assertOverrides(overrides, allowed) {
73
+ for (const [key, sha] of Object.entries(overrides)) {
74
+ const namespace = snapshotPinNamespace(key);
75
+ if (!allowed.has(namespace)) {continue;} // another phase's pin
76
+ if (typeof sha !== 'string' || !SHA_PATTERN.test(sha)) {
77
+ throw new Error(`override for "${key}" is not a 40-hex commit SHA: ${JSON.stringify(sha)}`);
78
+ }
79
+ }
80
+ }
81
+
82
+ const GRAPH_PHASE = new Set([
83
+ SnapshotPinNamespace.Graph,
84
+ SnapshotPinNamespace.Distributions,
85
+ SnapshotPinNamespace.Kernel,
86
+ SnapshotPinNamespace.ChartSource,
87
+ SnapshotPinNamespace.EditionsRoot,
88
+ ]);
89
+ const SOURCE_PHASE = new Set([SnapshotPinNamespace.Source]);
90
+
91
+ /** A repository the workflow checked out is pinned at what it checked out; an override may only agree. */
92
+ function assertCheckedOutPin({ key, sha, flag, what, overrides }) {
93
+ if (!SHA_PATTERN.test(sha ?? '')) {
94
+ throw new Error(`${flag} is not a 40-hex commit SHA: ${JSON.stringify(sha)}`);
95
+ }
96
+ if (overrides[key] !== undefined && overrides[key] !== sha) {
97
+ throw new Error(`override for "${key}" is ${overrides[key]}, but ${what} is checked out at ${sha}`);
98
+ }
99
+ }
100
+
101
+ /**
102
+ * @param {object} request
103
+ * @param {object} request.runtimeSourceIndex the checkout's runtime-image-source-index.json (raw)
104
+ * @param {string} request.dataSha the commit the data root checkout is at
105
+ * @param {string} request.kernelSha the commit the kernel checkout is at
106
+ * @param {string} request.deploySha the commit the deploy checkout is at
107
+ * @param {string} [request.editionsRootUrl] https URL of the editions root; absent = this repository
108
+ */
109
+ export function freezeGraph({
110
+ runtimeSourceIndex,
111
+ dataSha,
112
+ kernelSha,
113
+ deploySha,
114
+ editionsRootUrl,
115
+ overrides = {},
116
+ resolveHead,
117
+ }) {
118
+ assertNoToolingOverride(overrides);
119
+ assertOverrides(overrides, GRAPH_PHASE);
120
+ const head = memoizedHeads(resolveHead);
121
+ const graph = new Set(runtimeSourceIndex.repositoryPins.map((pin) => pin.repo));
122
+ for (const [key] of Object.entries(overrides)) {
123
+ if (snapshotPinNamespace(key) === SnapshotPinNamespace.Graph && !graph.has(key)) {
124
+ throw new Error(`override names "${key}", which is not a repository in the graph`);
125
+ }
126
+ }
127
+ const checkedOut = [
128
+ { key: dataPinKey(), sha: dataSha, flag: '--data-sha', what: 'the data root checkout' },
129
+ { key: kernelPinKey(), sha: kernelSha, flag: '--kernel-sha', what: 'the kernel checkout' },
130
+ { key: deployPinKey(), sha: deploySha, flag: '--deploy-sha', what: 'the deploy checkout' },
131
+ ];
132
+ for (const { key, sha, flag, what } of checkedOut) {assertCheckedOutPin({ key, sha, flag, what, overrides });}
133
+ if (editionsRootUrl === undefined && overrides[EDITIONS_ROOT_PIN_KEY] !== undefined) {
134
+ throw new Error(`override names "${EDITIONS_ROOT_PIN_KEY}", but the editions root is this repository`);
135
+ }
136
+ const pins = Object.fromEntries(checkedOut.map(({ key, sha }) => [key, sha]));
137
+ for (const pin of runtimeSourceIndex.repositoryPins) {
138
+ pins[pin.repo] = overrides[pin.repo] ?? head(pin.repositoryUrl);
139
+ }
140
+ if (editionsRootUrl !== undefined) {
141
+ const url = canonicalPublicHttpsRepositoryUrl(editionsRootUrl, '--editions-root-url');
142
+ pins[EDITIONS_ROOT_PIN_KEY] = overrides[EDITIONS_ROOT_PIN_KEY] ?? head(url);
143
+ }
144
+ return sortedSnapshotPins(pins);
145
+ }
146
+
147
+ /**
148
+ * @param {object} request
149
+ * @param {Record<string,string>} request.pins the graph phase's pins
150
+ * @param {object[]} request.declaredSources every `sources[]` entry in the chain (`{ name, url, ... }`)
151
+ */
152
+ export function freezeSources({ pins, declaredSources, overrides = {}, resolveHead }) {
153
+ assertOverrides(overrides, SOURCE_PHASE);
154
+ const head = memoizedHeads(resolveHead);
155
+ const urlByName = new Map();
156
+ for (const source of declaredSources) {
157
+ const known = urlByName.get(source.name);
158
+ if (known !== undefined && known !== source.url) {
159
+ throw new Error(`source "${source.name}" is declared at two URLs (${known}, ${source.url})`);
160
+ }
161
+ urlByName.set(source.name, source.url);
162
+ }
163
+ for (const key of Object.keys(overrides)) {
164
+ if (snapshotPinNamespace(key) === SnapshotPinNamespace.Source && !urlByName.has(sourceNameOfPinKey(key))) {
165
+ throw new Error(`override names "${key}", but the chain declares no such source`);
166
+ }
167
+ }
168
+ const frozen = { ...pins };
169
+ for (const [name, url] of urlByName) {
170
+ frozen[sourcePinKey(name)] = overrides[sourcePinKey(name)] ?? head(url);
171
+ }
172
+ return sortedSnapshotPins(frozen);
173
+ }
@@ -0,0 +1,54 @@
1
+ // develop-image-coordinate — the ONE mapping from a release image repository to
2
+ // the develop repository its producer pushes the same build to.
3
+ //
4
+ // <host>/<owner>/<name...> -> <host>/<owner>/dev/<name...>
5
+ //
6
+ // A producer's develop lane pushes `<devRepository>:<contentHash>` (contract C1)
7
+ // and nothing else reads or writes that namespace, so a release coordinate can
8
+ // never be satisfied by a develop build or the other way round.
9
+
10
+ import { DEVELOP_IMAGE_NAMESPACE_SEGMENT } from './edition-constants.mjs';
11
+ import { splitImageRef } from './registry-credentials.mjs';
12
+
13
+ const DEVELOP_NAMESPACE_SEGMENT = DEVELOP_IMAGE_NAMESPACE_SEGMENT;
14
+
15
+ const OWNER_SEGMENT_INDEX = 0;
16
+ const NAMESPACE_SEGMENT_INDEX = 1;
17
+ const MINIMUM_REPOSITORY_SEGMENTS = 2;
18
+
19
+ /** `developImageRef('registry.example.com/acme/identity-api') === 'registry.example.com/acme/dev/identity-api'`. */
20
+ export function developImageRef(releaseImageRef) {
21
+ const { authority, repository } = splitImageRef(releaseImageRef);
22
+ if (repository.includes('@') || repository.includes(':')) {
23
+ throw new Error(
24
+ `image reference "${releaseImageRef}" carries a tag or digest; a develop coordinate maps a bare repository`,
25
+ );
26
+ }
27
+ const segments = repository.split('/');
28
+ if (segments.length < MINIMUM_REPOSITORY_SEGMENTS) {
29
+ throw new Error(
30
+ `image reference "${releaseImageRef}" has no <owner>/<name> repository path; it cannot be mapped to a develop repository`,
31
+ );
32
+ }
33
+ if (segments[NAMESPACE_SEGMENT_INDEX] === DEVELOP_NAMESPACE_SEGMENT) {
34
+ throw new Error(`image reference "${releaseImageRef}" is already a develop repository`);
35
+ }
36
+ const [owner, ...name] = segments;
37
+ return `${authority}/${owner}/${DEVELOP_NAMESPACE_SEGMENT}/${name.join('/')}`;
38
+ }
39
+
40
+ /** Whether a repository reference lies in the develop namespace. */
41
+ export function isDevelopImageRef(imageRef) {
42
+ let repository;
43
+ try {
44
+ ({ repository } = splitImageRef(imageRef));
45
+ } catch {
46
+ return false;
47
+ }
48
+ const segments = repository.split('/');
49
+ return (
50
+ segments.length > MINIMUM_REPOSITORY_SEGMENTS &&
51
+ segments[OWNER_SEGMENT_INDEX].length > 0 &&
52
+ segments[NAMESPACE_SEGMENT_INDEX] === DEVELOP_NAMESPACE_SEGMENT
53
+ );
54
+ }
@@ -0,0 +1,77 @@
1
+ // develop-seal — seal a snapshot's images lock against the DEVELOP repositories.
2
+ //
3
+ // The release sealer verifies `<imageRef>:<contentHash>` and writes the digest
4
+ // the registry served. This does the same for a develop edition, with the
5
+ // SAME verifier (`verifyDistributionPlatforms`, required platforms enforced)
6
+ // and the SAME digest decision (`planDigestSeal`), against the repository the
7
+ // producer's develop lane pushes to (`developImageRef`, contract C1).
8
+ //
9
+ // A coordinate that is not there is "not built yet": the producer's CI is not
10
+ // green or still running. EVERY such subject is collected and named at once;
11
+ // any other failure (a 403, a malformed index, an absent required platform)
12
+ // aborts as it does in the release.
13
+
14
+ import { developImageRef } from './develop-image-coordinate.mjs';
15
+ import { editionsRoot } from './edition-workspace.mjs';
16
+ import { validateImmutableImageLock, loadKernelDistributionSchemas, } from './lib.mjs';
17
+ import { isNotPublishedYet, planDigestSeal } from './seal-image-digests.mjs';
18
+ import { verifyDistributionPlatforms } from './verify-image-platforms.mjs';
19
+
20
+ function notBuiltYetMessage(subject) {
21
+ return (
22
+ `${subject.repo}@${subject.sourceCommit} develop image ` +
23
+ `${subject.imageRef}:${subject.contentHash} not built yet`
24
+ );
25
+ }
26
+
27
+ /**
28
+ * @param {object} request
29
+ * @param {object} request.imageLock the snapshot images lock (release refs, unsealed)
30
+ * @returns the images lock with develop refs and verified digests, kernel-validated
31
+ */
32
+ export async function sealDevelopImages({
33
+ imageLock,
34
+ distributionsRoot = editionsRoot(),
35
+ env = process.env,
36
+ fetchImpl = globalThis.fetch,
37
+ run = null,
38
+ }) {
39
+ const developLock = {
40
+ ...imageLock,
41
+ subjects: imageLock.subjects.map((subject) => ({
42
+ ...subject,
43
+ imageRef: developImageRef(subject.imageRef),
44
+ })),
45
+ };
46
+ const report = await verifyDistributionPlatforms({
47
+ imageLock: developLock,
48
+ distributionsRoot,
49
+ env,
50
+ fetchImpl,
51
+ run,
52
+ collectFailures: true,
53
+ });
54
+ const aborting = report.failures.find(({ error }) => !isNotPublishedYet(error));
55
+ if (aborting) {throw aborting.error;}
56
+ if (report.failures.length > 0) {
57
+ throw new Error(
58
+ `${report.failures.length} develop image(s) of "${developLock.distributionId}" are not built yet ` +
59
+ '(the producer\'s CI is not green or still running):\n ' +
60
+ report.failures.map(({ subject }) => notBuiltYetMessage(subject)).join('\n '),
61
+ );
62
+ }
63
+ const { writes, unchanged } = planDigestSeal({ subjects: developLock.subjects, verified: report.subjects });
64
+ const digests = new Map([...writes, ...unchanged].map(({ key, digest }) => [key, digest]));
65
+ const schemas = await loadKernelDistributionSchemas();
66
+ const { parseImageLock } = schemas;
67
+ return validateImmutableImageLock(
68
+ parseImageLock({
69
+ ...developLock,
70
+ subjects: developLock.subjects.map((subject) => ({
71
+ ...subject,
72
+ imageDigest: digests.get(`${subject.kind}:${subject.id}`),
73
+ })),
74
+ }),
75
+ schemas,
76
+ );
77
+ }
@@ -0,0 +1,104 @@
1
+ // develop-snapshot-inputs — every input of a DEVELOP edition's resolve, built
2
+ // from ONE frozen snapshot of pins and nothing else.
3
+ //
4
+ // The release resolvers read their inputs from committed files: the platform
5
+ // service index, the biome index, the runtime image source index and the
6
+ // edition chain. A develop edition has no committed copy of any of them at its
7
+ // pins, so each is REBUILT here by the release builder itself, over a pinned
8
+ // source authority whose pins are the snapshot's. There is no second builder:
9
+ // the same functions that regenerate the committed indexes produce the
10
+ // snapshot's, and they are handed to the release resolvers as injected inputs
11
+ // (`resolveDistributionWithIndex(id, { inputs })`, `resolveImages(id, { inputs,
12
+ // lock })`). A snapshot whose pins equal the committed ones therefore resolves
13
+ // to the committed locks, which `develop-snapshot-inputs.test.mjs` proves.
14
+ //
15
+ // No fallback: the authority has no delegate, so a repository the snapshot does
16
+ // not pin refuses by name instead of resolving to a branch tip.
17
+
18
+ import { execFileSync } from 'node:child_process';
19
+
20
+ import { buildValidatedPlatformServiceIndex } from './build-platform-service-index.mjs';
21
+ import { buildTrustedIndex, validateRuntimeImageSourceIndex } from './build-runtime-image-source-index.mjs';
22
+ import { dataRoot, deployRoot, editionsRoot, kernelRepoDir, runtimeImageSourceIndexPath } from './edition-workspace.mjs';
23
+ import {
24
+ buildBiomeIndex,
25
+ distributionBiomeSourceRepositories,
26
+ } from './lib.mjs';
27
+ import {
28
+ dataPinKey,
29
+ EDITIONS_ROOT_PIN_KEY,
30
+ kernelPinKey,
31
+ deployPinKey,
32
+ applyPinSource,
33
+ snapshotPinSource,
34
+ validateSnapshotPins,
35
+ } from './pin-source.mjs';
36
+ import { composeEdition } from './resolve-distribution.mjs';
37
+ import { createPinnedSourceAuthority } from './source-authority.mjs';
38
+ import { readStrictJsonFile } from './strict-json.mjs';
39
+
40
+ function git(dir, args) {
41
+ return execFileSync('git', ['-C', dir, ...args], {
42
+ encoding: 'utf8',
43
+ stdio: ['ignore', 'pipe', 'pipe'],
44
+ }).trim();
45
+ }
46
+
47
+ /**
48
+ * A checkout the resolve reads must BE the frozen commit and be clean: the vendor
49
+ * tree is walked for parents and runs the builders, the editions root holds the
50
+ * edition and its adopted authorization lock.
51
+ */
52
+ export function assertFrozenCheckout({ dir, pins, key }) {
53
+ const pinned = pins[key];
54
+ if (pinned === undefined) {throw new Error(`the snapshot has no pin "${key}" for ${dir}`);}
55
+ const head = git(dir, ['rev-parse', 'HEAD']);
56
+ if (head !== pinned) {
57
+ throw new Error(`${dir} is at ${head}, but the snapshot pins "${key}" at ${pinned}`);
58
+ }
59
+ const dirty = git(dir, ['status', '--porcelain']);
60
+ if (dirty.length > 0) {
61
+ throw new Error(`${dir} is not clean, so it is not the frozen commit ${pinned} ("${key}"):\n${dirty}`);
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Every checkout a develop resolve reads: the vendor tree, the kernel the
67
+ * authorization lock is sealed with, the chart tree the chart-set lock digests,
68
+ * and the editions root when it is another repository.
69
+ */
70
+ export function assertFrozenCheckouts(pins) {
71
+ assertFrozenCheckout({ dir: dataRoot(), pins, key: dataPinKey() });
72
+ assertFrozenCheckout({ dir: kernelRepoDir(), pins, key: kernelPinKey() });
73
+ assertFrozenCheckout({ dir: deployRoot(), pins, key: deployPinKey() });
74
+ if (editionsRoot() !== dataRoot()) {
75
+ assertFrozenCheckout({ dir: editionsRoot(), pins, key: EDITIONS_ROOT_PIN_KEY });
76
+ }
77
+ }
78
+
79
+ /**
80
+ * @param {{ id: string, pins: Record<string,string>, schemas: object }} request
81
+ * @returns the injected inputs for the release resolvers, plus the pin source.
82
+ */
83
+ export async function buildSnapshotInputs({ id, pins, schemas }) {
84
+ const pinSource = snapshotPinSource(pins);
85
+ const composed = composeEdition(schemas.parseDistribution, id, pinSource);
86
+ const committedIndex = schemas.parseRuntimeImageSourceIndex(
87
+ readStrictJsonFile(runtimeImageSourceIndexPath()),
88
+ );
89
+ validateSnapshotPins(pins, {
90
+ runtimeSourceIndex: committedIndex,
91
+ declaredSourceNames: composed.declaredSourceNames,
92
+ editionsRootExternal: editionsRoot() !== dataRoot(),
93
+ });
94
+ const sourceAuthority = createPinnedSourceAuthority({
95
+ pins: applyPinSource(committedIndex, pinSource).repositoryPins,
96
+ });
97
+ const platformServiceIndex = buildValidatedPlatformServiceIndex(schemas, sourceAuthority);
98
+ const biomeIndex = await buildBiomeIndex(platformServiceIndex, distributionBiomeSourceRepositories(), sourceAuthority);
99
+ const runtimeImageSourceIndex = schemas.parseRuntimeImageSourceIndex(
100
+ await buildTrustedIndex(schemas, sourceAuthority, undefined, undefined, { biomeIndex, platformServiceIndex }),
101
+ );
102
+ await validateRuntimeImageSourceIndex(runtimeImageSourceIndex, schemas, { biomeIndex, platformServiceIndex });
103
+ return { pinSource, composed, platformServiceIndex, biomeIndex, runtimeImageSourceIndex };
104
+ }