@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,89 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════
2
+ // A REMEDY THIS REPOSITORY CAN ACTUALLY RUN.
3
+ //
4
+ // WHY THIS EXISTS
5
+ //
6
+ // This repository's `package.json` has NO `scripts` block. Every `pnpm
7
+ // dist:*` command is declared one directory up, in the AGGREGATOR's
8
+ // `package.json`, as a bare `node repos-infra/xema-distributions/<file>.mjs`.
9
+ // So a diagnostic printing `pnpm dist:images <id>` names a command its reader
10
+ // cannot run — and the reader of a dist-check failure is standing in a FLAT
11
+ // checkout of this repository, which is exactly where the pnpm spelling does
12
+ // not resolve. A remedy that cannot be run where it is printed is a dead end
13
+ // (`.claude/rules/writing-boundary-checks.md`, and closure-ledger A25).
14
+ //
15
+ // Measured 2026-09-28 at `origin/develop`: 52 `pnpm dist:*` references in
16
+ // this repository, 19 such scripts declared in the aggregator, and ZERO
17
+ // declared here.
18
+ //
19
+ // WHY NOT A SECOND `scripts` BLOCK
20
+ //
21
+ // That was the obvious fix and it is the wrong one: it would put 19 name→file
22
+ // mappings in two places, free to drift, and drift between them is invisible
23
+ // — a new script reachable from one side and not the other, with nothing able
24
+ // to say so short of a new gate. Closure-ledger A26 forbids buying a fix with
25
+ // a permanent check.
26
+ //
27
+ // Deriving the command from the script's REAL PATH has one authority — the
28
+ // file system — and cannot go stale: rename the file and the command follows,
29
+ // because it IS the file. This is the treatment `seal-image-digests.mjs`
30
+ // already chose for itself and argued for in its own words; it is extracted
31
+ // here so the other ten diagnostics get it too rather than each re-deriving
32
+ // it, and so `sealCommandFor` stops being the only site that is right.
33
+ //
34
+ // WHAT IT DOES NOT DO
35
+ //
36
+ // It does not replace the aggregator's `pnpm dist:*` spelling, which is real,
37
+ // is what CI invokes, and is correct FROM THE AGGREGATOR. Documentation may
38
+ // name either; a RUNTIME diagnostic names this one, because the process
39
+ // printing it already knows where it is standing and the reader does not.
40
+ // ═══════════════════════════════════════════════════════════════════════════
41
+
42
+ import { dirname, relative, resolve } from 'node:path';
43
+ import { fileURLToPath } from 'node:url';
44
+
45
+ /** This module's directory — the distributions root in both layouts the fleet runs. */
46
+ const ROOT = dirname(fileURLToPath(import.meta.url));
47
+
48
+ /**
49
+ * Render an absolute script path as the SHORTEST form that still resolves from
50
+ * the caller's current directory.
51
+ *
52
+ * A relative path is preferred when the script is at or below `process.cwd()`
53
+ * — that is the form an operator can paste — and the absolute path is used
54
+ * otherwise, because a `../../..` chain is longer, harder to read, and no more
55
+ * runnable. Escaping to a parent (`..`) is never shown for that reason.
56
+ */
57
+ function shortestRunnablePath(script) {
58
+ const fromCwd = relative(process.cwd(), script);
59
+ return !fromCwd.startsWith('..') && fromCwd.length < script.length
60
+ ? fromCwd
61
+ : script;
62
+ }
63
+
64
+ /**
65
+ * The command that runs `scriptFileName` — a sibling of this module — with
66
+ * `args`, spelled so the reader can run it from wherever they are standing.
67
+ *
68
+ * Takes a FILE NAME rather than a script name on purpose: a file name is
69
+ * checked by the file system the moment anybody runs the remedy, whereas a
70
+ * `dist:*` name is checked by nothing and is what went wrong here.
71
+ *
72
+ * @param {string} scriptFileName e.g. `'resolve-distribution.mjs'`
73
+ * @param {...string} args positional arguments, appended verbatim
74
+ */
75
+ export function commandFor(scriptFileName, ...args) {
76
+ const shown = shortestRunnablePath(resolve(ROOT, scriptFileName));
77
+ return ['node', shown, ...args].join(' ');
78
+ }
79
+
80
+ /**
81
+ * The command that runs the CALLING module itself.
82
+ *
83
+ * @param {string} importMetaUrl the caller's `import.meta.url`
84
+ * @param {...string} args positional arguments, appended verbatim
85
+ */
86
+ export function commandForSelf(importMetaUrl, ...args) {
87
+ const shown = shortestRunnablePath(fileURLToPath(importMetaUrl));
88
+ return ['node', shown, ...args].join(' ');
89
+ }
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env node
2
+ // `xema edition repin-evidence-trust` - write the data root's evidence trust
3
+ // pin: the digest of the declared policy, the digests of THIS CLI's built
4
+ // verifier bundle and provenance, the kernel contract authority the data
5
+ // root's runtime image source index states, and the oras binaries that may
6
+ // read a registry. The result is a data-root change to review; nothing here is
7
+ // generated into the library.
8
+ //
9
+ // [--trusted-oras-digest sha256:<64 hex>]… the oras binaries to trust. Without
10
+ // the flag the list of the existing pin is kept; a first pin requires it.
11
+ //
12
+ // Refuses when the data root declares no evidenceTrustPolicy.
13
+
14
+ import { existsSync } from 'node:fs';
15
+ import { join } from 'node:path';
16
+
17
+ import { locateBuiltVerifier, readBuiltVerifierDigests } from './built-verifier.mjs';
18
+ import { editionWorkspaceConfig } from './edition-config.mjs';
19
+ import {
20
+ WorkspaceRoot,
21
+ configureEditionWorkspaceFromProcess,
22
+ dataRoot,
23
+ isEntryModule,
24
+ } from './edition-workspace.mjs';
25
+ import {
26
+ loadDeclaredTrustPolicy,
27
+ readEvidenceTrustPin,
28
+ readStatedKernelContractAuthority,
29
+ requireOrasDigests,
30
+ writeEvidenceTrustPin,
31
+ } from './evidence-trust-pin.mjs';
32
+ import { loadKernelDistributionSchemas } from './lib.mjs';
33
+
34
+ /**
35
+ * Write the pin for the configured data root and return it.
36
+ * `locateVerifier` is a parameter so a test can supply a fixture bundle.
37
+ */
38
+ export async function repinEvidenceTrust({
39
+ locateVerifier = locateBuiltVerifier,
40
+ trustedOrasBinaryDigests = undefined,
41
+ } = {}) {
42
+ const root = dataRoot();
43
+ const declaration = editionWorkspaceConfig().evidenceTrustPolicy;
44
+ if (declaration.policy === null) {
45
+ throw new Error(
46
+ `${root} declares no evidenceTrustPolicy in edition-workspace.json; there is nothing to pin`,
47
+ );
48
+ }
49
+ const schemas = await loadKernelDistributionSchemas();
50
+ const { policyDigest } = loadDeclaredTrustPolicy({ dataRoot: root, declaration, schemas });
51
+ const verifier = readBuiltVerifierDigests(locateVerifier);
52
+ let orasDigests = trustedOrasBinaryDigests;
53
+ if (orasDigests === undefined) {
54
+ if (!existsSync(join(root, declaration.pin))) {
55
+ // Names the flag: a first pin has no list to keep.
56
+ requireOrasDigests([]);
57
+ }
58
+ orasDigests = readEvidenceTrustPin({ dataRoot: root, declaration }).trustedOrasBinaryDigests;
59
+ }
60
+ return writeEvidenceTrustPin({
61
+ dataRoot: root,
62
+ declaration,
63
+ policyDigest,
64
+ verifier,
65
+ kernelContractAuthority: readStatedKernelContractAuthority({ dataRoot: root, schemas }),
66
+ trustedOrasBinaryDigests: orasDigests,
67
+ });
68
+ }
69
+
70
+ /** `--trusted-oras-digest <digest>` and `--trusted-oras-digest=<digest>`, repeated; nothing else is an argument. */
71
+ export function parseRepinArguments(argv) {
72
+ const digests = [];
73
+ for (let index = 0; index < argv.length; index += 1) {
74
+ const argument = argv[index];
75
+ if (argument === '--trusted-oras-digest') {
76
+ index += 1;
77
+ if (argv[index] === undefined) {throw new Error('--trusted-oras-digest needs a value');}
78
+ digests.push(argv[index]);
79
+ } else if (argument.startsWith('--trusted-oras-digest=')) {
80
+ digests.push(argument.slice('--trusted-oras-digest='.length));
81
+ } else {
82
+ throw new Error(`unknown argument "${argument}"; usage: xema edition repin-evidence-trust [--trusted-oras-digest sha256:<64 hex>]…`);
83
+ }
84
+ }
85
+ return { trustedOrasBinaryDigests: digests.length === 0 ? undefined : digests };
86
+ }
87
+
88
+ if (isEntryModule(import.meta.url)) {
89
+ try {
90
+ configureEditionWorkspaceFromProcess([WorkspaceRoot.Data, WorkspaceRoot.Kernel]);
91
+ const pin = await repinEvidenceTrust(parseRepinArguments(process.argv.slice(2)));
92
+ console.log(
93
+ `[repin-evidence-trust] pinned policy ${pin.policyDigest}, verifier ${pin.verifier.bundleDigest}, ` +
94
+ `kernel contract authority ${pin.kernelContractAuthority.sourceCommit} and ` +
95
+ `${pin.trustedOrasBinaryDigests.length} oras binary digest(s)`,
96
+ );
97
+ } catch (error) {
98
+ console.error(`[repin-evidence-trust] ${error.message}`);
99
+ process.exitCode = 1;
100
+ }
101
+ }
@@ -0,0 +1,58 @@
1
+ // A required resource requirement is a closure edge.
2
+ //
3
+ // A biome service that declares `inclusion: required` for a resource kind
4
+ // (postgres.schema, redis.namespace, ...) cannot run without a provider of that
5
+ // kind, exactly as it cannot run without a biome it `requires`. So the
6
+ // resolver follows the edge to the provider and ships it; naming the consumer
7
+ // is enough, no edition lists a provider by hand.
8
+ //
9
+ // The edge resolves only when exactly one deliverable biome provides the kind.
10
+ // Zero providers or several are refused with a typed error: with none the
11
+ // consumer can never boot, and with several no provider-selection policy
12
+ // exists, so choosing one would be a guess made on the consumer's behalf.
13
+
14
+ export const RequiredResourceProviderFailure = Object.freeze({
15
+ NoProvider: 'no-provider',
16
+ AmbiguousProvider: 'ambiguous-provider',
17
+ });
18
+
19
+ export class RequiredResourceProviderError extends Error {
20
+ constructor(failure, { distributionId, consumerId, requirement, providerIds }) {
21
+ const subject =
22
+ `biome "${consumerId}" service "${requirement.consumerService}" requires ` +
23
+ `${requirement.kind} "${requirement.logicalName}" (${requirement.inclusion})`;
24
+ super(
25
+ failure === RequiredResourceProviderFailure.NoProvider
26
+ ? `distribution "${distributionId}": ${subject}, and no biome in the index provides ` +
27
+ `${requirement.kind}, so the consumer cannot run in any edition.`
28
+ : `distribution "${distributionId}": ${subject}, and ${providerIds.length} biomes provide ` +
29
+ `that kind: ${providerIds.join(', ')}. No provider-selection policy exists, so the ` +
30
+ `resolver refuses rather than choosing one.`,
31
+ );
32
+ this.name = 'RequiredResourceProviderError';
33
+ this.failure = failure;
34
+ this.distributionId = distributionId;
35
+ this.consumerId = consumerId;
36
+ this.requirement = requirement;
37
+ this.providerIds = providerIds;
38
+ }
39
+ }
40
+
41
+ /**
42
+ * The biome entry that is the sole provider of the requirement's kind among
43
+ * `candidates` (the deliverable, source-admissible index entries).
44
+ */
45
+ export function soleResourceProvider({ distributionId, consumer, requirement, candidates }) {
46
+ const providers = candidates.filter((entry) =>
47
+ (entry.providesResources ?? []).some((provision) => provision.kind === requirement.kind),
48
+ );
49
+ const providerIds = [...new Set(providers.map((entry) => entry.id))].sort();
50
+ const context = { distributionId, consumerId: consumer.id, requirement, providerIds };
51
+ if (providers.length === 0) {
52
+ throw new RequiredResourceProviderError(RequiredResourceProviderFailure.NoProvider, context);
53
+ }
54
+ if (providerIds.length > 1) {
55
+ throw new RequiredResourceProviderError(RequiredResourceProviderFailure.AmbiguousProvider, context);
56
+ }
57
+ return providers[0];
58
+ }
@@ -0,0 +1,415 @@
1
+ #!/usr/bin/env node
2
+ // resolve-chart-set — pin the Helm chart-set version an install resolves, so
3
+ // that no human has to.
4
+ //
5
+ // ─────────────────────────────────────────────────────────────────────────────
6
+ // WHY THIS EXISTS
7
+ //
8
+ // `xema-appliance provision` pulls charts from the configured chart registry
9
+ // at an explicitly pinned `XEMA_CHART_VERSION`, and REFUSES to run without
10
+ // one ("refusing to pull a floating chart version"). Nothing set it. The
11
+ // value was typed by an operator, which made every platform update a step
12
+ // somebody had to remember and get right — exactly the maintenance surface
13
+ // the images plane already removed for itself with images.lock.json.
14
+ //
15
+ // This resolves it instead, and writes the answer to a lock the appliance
16
+ // reads. The env var survives as an override, never as the only source.
17
+ //
18
+ // WHY IT IS RESOLVED AND NOT COMPUTED
19
+ //
20
+ // The version cannot be derived from this repository: it is minted by the two
21
+ // publish-charts workflows (xema-deploy and xema-infra-services) at publish
22
+ // time. So the only truthful way to pin it is to ask the registry which
23
+ // versions actually exist, and that is what this does.
24
+ //
25
+ // EACH PUBLISHER IS PINNED INDEPENDENTLY
26
+ //
27
+ // One OCI namespace, two publishers, and an install resolves charts from
28
+ // BOTH — a deployments/*/infra-plan.json names components from each in the
29
+ // same list. Each closes its own half with an index chart, written only after
30
+ // every chart of that half is pushed, signed and proved served.
31
+ //
32
+ // They used to be pinned by ONE shared version, which meant both had to mint
33
+ // the same string, which meant deriving it from the only input they could both
34
+ // reproduce — the UTC date — which capped publishing at once a day. The shared
35
+ // pin was the coupling and the day of latency was its price. Each half is now
36
+ // pinned separately: a publisher that has not run does not hold the other back.
37
+ //
38
+ // WHICH VERSION IS CURRENT, WHEN COORDINATES DO NOT SORT
39
+ //
40
+ // Index versions are content-derived (`0.0.0-h<hash>`), so semver precedence
41
+ // says nothing about which is newest — hashes have no order. The registry
42
+ // does: `helm push` stamps `org.opencontainers.image.created` into the
43
+ // manifest, so the current index is the one most recently pushed, read from
44
+ // the registry rather than guessed from the tag.
45
+ //
46
+ // Deliberately NOT a moving `latest` tag. Helm derives the OCI tag from the
47
+ // chart version and will not push a non-semver one, and a moving pointer is
48
+ // exactly what this plane's image rules refuse — a late run can reach it after
49
+ // a newer one moved it. Every coordinate here stays immutable.
50
+ //
51
+ // FAIL-CLOSED
52
+ //
53
+ // No eligible version is an ERROR, never an empty or stale pin. A lock that
54
+ // silently kept yesterday's value because today's read failed is the exact
55
+ // shape this plane has been bitten by: a claim that outlives its evidence.
56
+
57
+ import { existsSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
58
+ import { basename, join } from 'node:path';
59
+
60
+ import { EditionChannel } from './edition-channel.mjs';
61
+ import {
62
+ EDITION_CHART_SET_FILE,
63
+ EditionChartSetCheck,
64
+ assertRosteredChartSetLock,
65
+ checkEditionChartSet,
66
+ writeEditionChartSet,
67
+ } from './edition-chart-set.mjs';
68
+ import { editionWorkspaceConfig } from './edition-config.mjs';
69
+ import { WorkspaceRoot, configureEditionWorkspaceFromProcess, dataRoot, editionsRoot, isEntryModule } from './edition-workspace.mjs';
70
+ import { serializeJson } from './lib.mjs';
71
+ import { OrasRegistry } from './oras.mjs';
72
+ import { deployPinKey } from './pin-source.mjs';
73
+ import {
74
+ loadRegistryCredentialDeclarations,
75
+ resolveRegistryCredential,
76
+ } from './registry-credentials.mjs';
77
+
78
+ if (isEntryModule(import.meta.url)) {
79
+ configureEditionWorkspaceFromProcess([WorkspaceRoot.Data, WorkspaceRoot.Editions, WorkspaceRoot.Deploy, WorkspaceRoot.Kernel]);
80
+ }
81
+
82
+ export const CHART_SET_LOCK_FILE = 'chart-set.lock.json';
83
+ export const chartSetLockPath = () => join(dataRoot(), CHART_SET_LOCK_FILE);
84
+
85
+ /**
86
+ * The chart registry and its publishers.
87
+ *
88
+ * ONE PIN for every edition, and EVERY EDITION CARRIES IT. The set that
89
+ * satisfies the widest edition satisfies the narrowest, so there is one answer
90
+ * to "which chart set do editions take now" — this lock. But the chart
91
+ * generation a deployment renders is part of its edition (owner ruling
92
+ * 2026-10-04), so each edition holds a byte-identical copy as
93
+ * `<id>/chart-set.lock.json`, hashed into its coordinate and published with it.
94
+ * This script is the one writer of the pin AND every copy, and dist-check
95
+ * refuses a copy that differs — so the copies cannot disagree
96
+ * (edition-chart-set.mjs).
97
+ */
98
+ const chartRegistry = () => editionWorkspaceConfig().chartRegistry;
99
+ const chartSetPublishers = () => editionWorkspaceConfig().chartPublishers;
100
+
101
+ /** `<host>/<path>` -> the repository path the registry API addresses. */
102
+ function registryRepository(indexName) {
103
+ return `${chartRegistry().replace(/^oci:\/\/[^/]+\//, '')}/${indexName}`;
104
+ }
105
+
106
+ function registryAuthority() {
107
+ return chartRegistry().replace(/^oci:\/\//, '').split('/')[0];
108
+ }
109
+
110
+ /**
111
+ * cosign publishes a signature as a TAG (`sha256-<digest>.sig`) beside the
112
+ * artifact it signs. Those are not versions and must never be pinned.
113
+ */
114
+ export function isVersionTag(tag) {
115
+ return !tag.startsWith('sha256-');
116
+ }
117
+
118
+ /**
119
+ * The newest index version for ONE publisher, by registry creation time.
120
+ * `candidates` is `[{ tag, createdAt }]`; returns the tag or null.
121
+ *
122
+ * A tag whose manifest carries no creation stamp is REFUSED rather than sorted
123
+ * last: guessing an order for a coordinate whose age is unknown is how a stale
124
+ * set gets pinned as current.
125
+ */
126
+ export function selectNewestIndex(allCandidates) {
127
+ if (!Array.isArray(allCandidates)) {return null;}
128
+ // A develop chart version (`0.0.0-dev.<sha12>`) is a frozen xema-deploy
129
+ // snapshot, never a release index: it is not a candidate for the release pin.
130
+ const candidates = allCandidates.filter((c) => !c.tag.startsWith(DEVELOP_CHART_VERSION_PREFIX));
131
+ if (candidates.length === 0) {return null;}
132
+ const undated = candidates.filter((c) => !c.createdAt);
133
+ if (undated.length > 0) {
134
+ throw new Error(
135
+ `index version(s) ${undated.map((c) => c.tag).join(', ')} carry no ` +
136
+ 'org.opencontainers.image.created stamp, so "which is current" cannot be answered. ' +
137
+ 'Refusing to guess.',
138
+ );
139
+ }
140
+ return [...candidates].sort(
141
+ (a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt),
142
+ )[0].tag;
143
+ }
144
+
145
+ const DIGEST_PATTERN = /^sha256:[a-f0-9]{64}$/;
146
+
147
+ /** The index chart annotation listing the charts the publisher closed the set with. */
148
+ export const INDEX_ROSTER_ANNOTATION = 'xema.dev/chart-set-charts';
149
+
150
+ export function buildLock(versionsByIndex, rostersByIndex, publishers = chartSetPublishers()) {
151
+ // Every publisher's VERSION first: a pin that resolved nothing for one half is
152
+ // the more fundamental refusal, whatever its roster.
153
+ for (const publisher of publishers) {
154
+ if (!versionsByIndex[publisher.index]) {
155
+ throw new Error(`no index version was resolved for '${publisher.index}'`);
156
+ }
157
+ }
158
+ return {
159
+ // 3: every member is `{version, digest}` (its OCI manifest digest), so the
160
+ // lock itself is the trust anchor. 2 added `publishers[].version` (one each)
161
+ // in place of one shared version, which would couple the two halves.
162
+ schemaVersion: 3,
163
+ registry: chartRegistry(),
164
+ publishers: publishers.map((publisher) => {
165
+ const version = versionsByIndex[publisher.index];
166
+ if (!version) {
167
+ throw new Error(`no index version was resolved for '${publisher.index}'`);
168
+ }
169
+ // THE ROSTER, sealed: chart name → {version, digest}, so an edition that
170
+ // copies this lock selects and verifies every chart without the registry.
171
+ const roster = rostersByIndex?.[publisher.index];
172
+ if (!roster || Object.keys(roster).length === 0) {
173
+ throw new Error(`no roster was read for '${publisher.index}@${version}'`);
174
+ }
175
+ const charts = {};
176
+ for (const name of Object.keys(roster).sort()) {
177
+ const member = roster[name];
178
+ if (!DIGEST_PATTERN.test(String(member?.digest))) {
179
+ throw new Error(`${publisher.index}@${version}: ${name} has no sha256 manifest digest`);
180
+ }
181
+ charts[name] = { version: member.version, digest: member.digest };
182
+ }
183
+ return { ...publisher, version, charts };
184
+ }),
185
+ };
186
+ }
187
+
188
+ /**
189
+ * The roster an index chart carries: its `xema.dev/chart-set-charts`
190
+ * annotation, one `{"name","version"[,"digest"]}` JSON object per line, read from the
191
+ * chart's OCI config blob (helm stores Chart.yaml's metadata there). The blob is
192
+ * fetched by digest and hash-checked by the client. Returns name → {version,
193
+ * digest?}; `digest` is present only when the index carries one.
194
+ */
195
+ export function parseIndexRoster(annotation, label) {
196
+ if (typeof annotation !== 'string' || annotation.trim().length === 0) {
197
+ throw new Error(`${label} carries no xema.dev/chart-set-charts roster`);
198
+ }
199
+ const charts = {};
200
+ for (const line of annotation.split('\n').map((l) => l.trim()).filter(Boolean)) {
201
+ const member = JSON.parse(line);
202
+ if (typeof member?.name !== 'string' || typeof member?.version !== 'string') {
203
+ throw new Error(`${label}: roster line is not {name, version}: ${line}`);
204
+ }
205
+ if (Object.hasOwn(charts, member.name)) {throw new Error(`${label}: roster names ${member.name} twice`);}
206
+ if (member.digest !== undefined && !DIGEST_PATTERN.test(String(member.digest))) {
207
+ throw new Error(`${label}: ${member.name} carries a malformed digest: ${member.digest}`);
208
+ }
209
+ charts[member.name] = member.digest === undefined
210
+ ? { version: member.version }
211
+ : { version: member.version, digest: member.digest };
212
+ }
213
+ return Object.fromEntries(Object.entries(charts).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)));
214
+ }
215
+
216
+ /**
217
+ * Give every member a manifest digest: the index's own when it carries one,
218
+ * else the registry's for `<name>:<version>` (`lookup`). Both present and
219
+ * different is refused.
220
+ */
221
+ export async function sealRoster(roster, lookup, label) {
222
+ const sealed = {};
223
+ for (const [name, member] of Object.entries(roster)) {
224
+ const registryDigest = await lookup(name, member.version);
225
+ if (!DIGEST_PATTERN.test(String(registryDigest))) {
226
+ throw new Error(`${label}: registry returned no sha256 manifest digest for ${name}:${member.version}`);
227
+ }
228
+ if (member.digest !== undefined && member.digest !== registryDigest) {
229
+ throw new Error(
230
+ `${label}: ${name}:${member.version} index digest ${member.digest} != registry ${registryDigest}`,
231
+ );
232
+ }
233
+ sealed[name] = { version: member.version, digest: member.digest ?? registryDigest };
234
+ }
235
+ return sealed;
236
+ }
237
+
238
+ /** The annotations of the index chart `<indexName>:<version>`, read from its OCI config blob (helm stores Chart.yaml's metadata there). */
239
+ export async function indexAnnotations(client, indexName, version) {
240
+ const repository = registryRepository(indexName);
241
+ const manifest = JSON.parse((await client.getManifestByTag(repository, version)).bytes.toString('utf8'));
242
+ const configDigest = manifest?.config?.digest;
243
+ if (typeof configDigest !== 'string') {throw new Error(`${indexName}@${version}: manifest names no config blob`);}
244
+ const config = JSON.parse((await client.getBlob(repository, configDigest)).toString('utf8'));
245
+ return config?.annotations ?? {};
246
+ }
247
+
248
+ async function indexRoster(client, indexName, version) {
249
+ const annotations = await indexAnnotations(client, indexName, version);
250
+ return parseIndexRoster(annotations[INDEX_ROSTER_ANNOTATION], `${indexName}@${version}`);
251
+ }
252
+
253
+ /** Every edition directory (one holding a distribution.lock.json) under the editions root. */
254
+ function editionDirs() {
255
+ return readdirSync(editionsRoot(), { withFileTypes: true })
256
+ .filter((entry) => entry.isDirectory() && existsSync(join(editionsRoot(), entry.name, 'distribution.lock.json')))
257
+ .map((entry) => join(editionsRoot(), entry.name))
258
+ .sort();
259
+ }
260
+
261
+ const SHA_PATTERN = /^[0-9a-f]{40}$/;
262
+ const DEVELOP_CHART_VERSION_PREFIX = '0.0.0-dev.';
263
+ const DEVELOP_SHA_LENGTH = 12;
264
+
265
+ /** The develop chart version xema-deploy publishes for a frozen SHA: `0.0.0-dev.<sha12>`, also the OCI tag. */
266
+ export const developChartVersion = (sha) => `${DEVELOP_CHART_VERSION_PREFIX}${sha.slice(0, DEVELOP_SHA_LENGTH)}`;
267
+
268
+ /** Every chart (a directory with a Chart.yaml, library charts included) of an xema-deploy checkout, sorted. */
269
+ function deployChartNames(xemaDeployDir) {
270
+ const chartsDir = join(xemaDeployDir, 'charts');
271
+ if (!existsSync(chartsDir)) {throw new Error(`${xemaDeployDir} has no charts/ directory`);}
272
+ return readdirSync(chartsDir, { withFileTypes: true })
273
+ .filter((entry) => entry.isDirectory() && existsSync(join(chartsDir, entry.name, 'Chart.yaml')))
274
+ .map((entry) => entry.name)
275
+ .sort();
276
+ }
277
+
278
+ /** The registry client for the chart registry, with the data root's declared credential. */
279
+ export function chartRegistryClient() {
280
+ const credential = resolveRegistryCredential({ authority: registryAuthority(), declarations: loadRegistryCredentialDeclarations() });
281
+ return new OrasRegistry({ credential });
282
+ }
283
+
284
+ /** The manifest digest the chart registry serves for `<chart>:<version>`. */
285
+ const registryManifestDigest = (client) => async (name, version) =>
286
+ (await client.getManifestByTag(registryRepository(name), version)).digest;
287
+
288
+ /**
289
+ * The develop chart-set lock: the SAME rostered lock a release pins, over the
290
+ * charts xema-deploy publishes for the frozen SHA. The deploy publisher (the
291
+ * workspace publisher whose repository is the deploy pin role's) publishes every
292
+ * chart directory of its tree at `0.0.0-dev.<sha12>`; the roster records each
293
+ * chart's registry manifest digest, so a consumer pulls by digest exactly as it
294
+ * does for a release. A chart the registry does not serve is refused by name.
295
+ * `lookup(name, version)` defaults to the chart registry and returns the digest.
296
+ */
297
+ export async function resolveDevelopChartSet({ xemaDeployDir, xemaDeploySha, lookup }) {
298
+ if (!SHA_PATTERN.test(xemaDeploySha ?? '')) {
299
+ throw new Error(`the deploy checkout SHA is not a 40-hex commit: ${JSON.stringify(xemaDeploySha)}`);
300
+ }
301
+ const names = deployChartNames(xemaDeployDir);
302
+ if (names.length === 0) {throw new Error(`the deploy checkout at ${xemaDeploySha} has no chart (charts/<name>/Chart.yaml)`);}
303
+ const deployRepo = basename(deployPinKey());
304
+ const publishers = chartSetPublishers().filter((publisher) => basename(publisher.repo) === deployRepo);
305
+ if (publishers.length !== 1) {
306
+ throw new Error(
307
+ `${publishers.length} chart publishers have repository ${deployRepo} (the deploy pin role); exactly one must, ` +
308
+ 'since it is the publisher whose develop charts this lock pins',
309
+ );
310
+ }
311
+ const version = developChartVersion(xemaDeploySha);
312
+ const digestOf = lookup ?? registryManifestDigest(chartRegistryClient());
313
+ const roster = Object.fromEntries(names.map((name) => [name, { version }]));
314
+ const sealed = await sealRoster(roster, digestOf, `${publishers[0].index}@${version}`);
315
+ const lock = buildLock({ [publishers[0].index]: version }, { [publishers[0].index]: sealed }, publishers);
316
+ return assertRosteredChartSetLock(lock, EDITION_CHART_SET_FILE, EditionChannel.Develop);
317
+ }
318
+
319
+ export function readLock(path = chartSetLockPath()) {
320
+ try {
321
+ return JSON.parse(readFileSync(path, 'utf8'));
322
+ } catch {
323
+ return null;
324
+ }
325
+ }
326
+
327
+ /**
328
+ * Every index tag of one publisher, with the creation stamp the registry
329
+ * recorded for it.
330
+ */
331
+ async function indexCandidates(client, indexName) {
332
+ const repository = registryRepository(indexName);
333
+ const tags = (await client.listTags(repository)).filter(isVersionTag);
334
+ const candidates = [];
335
+ for (const tag of tags) {
336
+ const manifest = await client.getManifestByTag(repository, tag);
337
+ const document = JSON.parse(manifest.bytes.toString('utf8'));
338
+ candidates.push({
339
+ tag,
340
+ createdAt: document?.annotations?.['org.opencontainers.image.created'] ?? null,
341
+ });
342
+ }
343
+ return candidates;
344
+ }
345
+
346
+ async function resolveFromRegistry() {
347
+ const client = chartRegistryClient();
348
+
349
+ const versionsByIndex = {};
350
+ const rostersByIndex = {};
351
+ for (const { index, repo } of chartSetPublishers()) {
352
+ const candidates = await indexCandidates(client, index);
353
+ const version = selectNewestIndex(candidates);
354
+ if (!version) {
355
+ throw new Error(
356
+ `${index} has no published version, so ${repo} has never closed a chart set. ` +
357
+ 'An index is written only after its publisher pushes, signs and proves served every ' +
358
+ 'chart of its half — its absence means that half has never completed.',
359
+ );
360
+ }
361
+ versionsByIndex[index] = version;
362
+ const roster = await indexRoster(client, index, version);
363
+ // The registry is asked for every member, so an index digest is checked
364
+ // against it and a member without one still gets sealed.
365
+ rostersByIndex[index] = await sealRoster(
366
+ roster,
367
+ async (name, memberVersion) => (await client.getManifestByTag(registryRepository(name), memberVersion)).digest,
368
+ `${index}@${version}`,
369
+ );
370
+ }
371
+ return { versionsByIndex, rostersByIndex };
372
+ }
373
+
374
+ async function main(argv) {
375
+ const check = argv.includes('--check');
376
+ const { versionsByIndex, rostersByIndex } = await resolveFromRegistry();
377
+ const next = buildLock(versionsByIndex, rostersByIndex);
378
+ assertRosteredChartSetLock(next, CHART_SET_LOCK_FILE, EditionChannel.Release);
379
+ const pinText = serializeJson(next);
380
+ const current = readLock();
381
+ // The pin AND every edition's copy of it: one writer, one run, so the N copies
382
+ // of one fact cannot disagree (edition-chart-set.mjs).
383
+ const staleEditions = editionDirs().filter((dir) => checkEditionChartSet(dir, pinText) !== EditionChartSetCheck.GREEN);
384
+ const changed = serializeJson(current) !== pinText || staleEditions.length > 0;
385
+
386
+ const describe = next.publishers
387
+ .map((publisher) => `${publisher.index}=${publisher.version}`)
388
+ .join(' ');
389
+
390
+ if (check) {
391
+ if (changed) {
392
+ throw new Error(
393
+ `${CHART_SET_LOCK_FILE} is stale${staleEditions.length ? ` (and ${staleEditions.length} edition copy/copies differ from it)` : ''}. The registry's current indexes are ${describe}. ` +
394
+ "Run 'node resolve-chart-set.mjs' and commit the result.",
395
+ );
396
+ }
397
+ process.stdout.write(`chart-set: ${describe} (lock is current)\n`);
398
+ return;
399
+ }
400
+
401
+ if (!changed) {
402
+ process.stdout.write(`chart-set: ${describe} (unchanged)\n`);
403
+ return;
404
+ }
405
+ writeFileSync(chartSetLockPath(), pinText);
406
+ for (const dir of editionDirs()) {writeEditionChartSet(dir, pinText);}
407
+ process.stdout.write(`chart-set: ${describe} (pin + ${editionDirs().length} edition ${EDITION_CHART_SET_FILE})\n`);
408
+ }
409
+
410
+ if (isEntryModule(import.meta.url)) {
411
+ main(process.argv.slice(2)).catch((error) => {
412
+ process.stderr.write(`resolve-chart-set: ${error.message}\n`);
413
+ process.exit(1);
414
+ });
415
+ }