@xemahq/xema 0.28.0 → 0.29.1

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 (55) hide show
  1. package/dist/commands/edition.d.ts.map +1 -1
  2. package/dist/commands/edition.js +8 -7
  3. package/dist/commands/edition.js.map +1 -1
  4. package/dist/edition/verify-image-evidence.bundle.mjs +88 -11
  5. package/dist/edition/verify-image-evidence.bundle.provenance.json +48 -42
  6. package/dist/runtime/edition/verb-map.d.ts +2 -1
  7. package/dist/runtime/edition/verb-map.d.ts.map +1 -1
  8. package/dist/runtime/edition/verb-map.js +2 -0
  9. package/dist/runtime/edition/verb-map.js.map +1 -1
  10. package/dist/runtime/runner/chart-set-lock.d.ts +4 -1
  11. package/dist/runtime/runner/chart-set-lock.d.ts.map +1 -1
  12. package/dist/runtime/runner/chart-set-lock.js +39 -4
  13. package/dist/runtime/runner/chart-set-lock.js.map +1 -1
  14. package/edition/README.md +69 -32
  15. package/edition/build-biome-index.mjs +1 -1
  16. package/edition/build-platform-service-index.mjs +1 -1
  17. package/edition/build-runtime-image-source-index.mjs +64 -20
  18. package/edition/catalogue-fetch.mjs +36 -9
  19. package/edition/catalogue-pin.mjs +71 -0
  20. package/edition/catalogue.mjs +11 -0
  21. package/edition/chart-set-source-commit.mjs +17 -7
  22. package/edition/checkout-indexed-source-repositories.mjs +17 -17
  23. package/edition/customer-flags.mjs +0 -1
  24. package/edition/develop-customer-edition.mjs +23 -5
  25. package/edition/develop-edition.mjs +147 -48
  26. package/edition/develop-freeze.mjs +69 -25
  27. package/edition/develop-seal.mjs +39 -25
  28. package/edition/dist-check.mjs +1 -1
  29. package/edition/edition-artifact.mjs +65 -18
  30. package/edition/edition-biome-manifests.mjs +2 -3
  31. package/edition/edition-chart-set.mjs +59 -6
  32. package/edition/edition-config.mjs +28 -2
  33. package/edition/edition-constants.mjs +10 -4
  34. package/edition/edition-contract-parse.mjs +52 -0
  35. package/edition/edition-contract.json +10 -1
  36. package/edition/edition-workspace.mjs +12 -9
  37. package/edition/evidence-trust-policy.mjs +26 -0
  38. package/edition/external-source-walk.mjs +48 -61
  39. package/edition/git-source-auth.mjs +104 -25
  40. package/edition/image-evidence.mjs +20 -10
  41. package/edition/kernel-contract-authority.mjs +23 -1
  42. package/edition/lib.mjs +13 -24
  43. package/edition/oras.mjs +32 -10
  44. package/edition/pin-source.mjs +6 -1
  45. package/edition/registry-credentials.mjs +11 -1
  46. package/edition/release-edition-artifact.mjs +3 -3
  47. package/edition/resolve-chart-set.mjs +50 -30
  48. package/edition/resolve-customer-edition.mjs +63 -7
  49. package/edition/resolve-distribution.mjs +1 -1
  50. package/edition/resolve-edition.mjs +12 -11
  51. package/edition/resolve-images.mjs +36 -15
  52. package/edition/seal-image-digests.mjs +31 -7
  53. package/edition/source-closure.mjs +1 -2
  54. package/edition/trust-policy-arming-verb.mjs +68 -0
  55. package/package.json +3 -3
@@ -68,7 +68,7 @@ import { composeEdition } from './resolve-distribution.mjs';
68
68
  import { readStrictJsonFile } from './strict-json.mjs';
69
69
 
70
70
  if (isEntryModule(import.meta.url)) {
71
- configureEditionWorkspaceFromProcess([WorkspaceRoot.Data, WorkspaceRoot.Editions, WorkspaceRoot.Sources, WorkspaceRoot.Kernel, WorkspaceRoot.Cache, WorkspaceRoot.SourceHash]);
71
+ configureEditionWorkspaceFromProcess([WorkspaceRoot.Data, WorkspaceRoot.Editions, WorkspaceRoot.Sources, WorkspaceRoot.Kernel, WorkspaceRoot.Cache]);
72
72
  }
73
73
 
74
74
  const firstPartyImageRegistry = () => editionWorkspaceConfig().imageRegistry;
@@ -1383,7 +1383,15 @@ function snapshotImageInputs(schemas, { inputs, lock }) {
1383
1383
  };
1384
1384
  }
1385
1385
 
1386
- export async function resolveImages(id, { inputs, lock } = {}) {
1386
+ /**
1387
+ * The image ROWS of one edition: its subjects (hashed at the commits the lock
1388
+ * pins), its platform-service bindings and its bundled web biomes, after every
1389
+ * check that needs no parsed lock. Rows are not a lock: the kernel's image-lock
1390
+ * grammar is the grammar of a COMPLETE lock, and a caller that holds only part
1391
+ * of one (a customer's own rows, merged with the catalogue's) parses the lock
1392
+ * it completes. `resolveImages` is these rows parsed as the whole lock.
1393
+ */
1394
+ export async function resolveImageRows(id, { inputs, lock } = {}) {
1387
1395
  assertCanonicalDistributionId(id);
1388
1396
  if ((inputs === undefined) !== (lock === undefined)) {
1389
1397
  throw new Error('resolveImages takes the snapshot `inputs` and its distribution `lock` together, or neither');
@@ -1509,18 +1517,6 @@ export async function resolveImages(id, { inputs, lock } = {}) {
1509
1517
  ...biome,
1510
1518
  sourceCommit: commitsBefore.get(biome.repo),
1511
1519
  }));
1512
- const imageLock = validateImmutableImageLock(
1513
- schemas.parseImageLock({
1514
- schemaVersion: 3,
1515
- sourceHashAlgorithm: schemas.ImageSourceHashAlgorithm.ImageInputV2,
1516
- distributionId: id,
1517
- contractAuthority: runtimeSourceIndex.contractAuthority,
1518
- subjects,
1519
- platformServiceBindings: structure.platformServiceBindings,
1520
- webBiomes,
1521
- }),
1522
- schemas,
1523
- );
1524
1520
  // The SAME locator the hash used. The default locator resolves every repo
1525
1521
  // handle to a first-party tree under the aggregator root, so for an edition
1526
1522
  // with an external source it looked for `<sourcesRoot()>/<source name>`, found no
@@ -1533,7 +1529,32 @@ export async function resolveImages(id, { inputs, lock } = {}) {
1533
1529
  'refusing image-lock write: a source repository commit changed while hashing',
1534
1530
  );
1535
1531
  }
1536
- return { imageLock, commits: [...commitsBefore] };
1532
+ return {
1533
+ subjects,
1534
+ platformServiceBindings: structure.platformServiceBindings,
1535
+ webBiomes,
1536
+ contractAuthority: runtimeSourceIndex.contractAuthority,
1537
+ commits: [...commitsBefore],
1538
+ };
1539
+ }
1540
+
1541
+ /** One edition's COMPLETE image lock: its rows, parsed and validated as the kernel's image lock. */
1542
+ export async function resolveImages(id, { inputs, lock } = {}) {
1543
+ const { subjects, platformServiceBindings, webBiomes, contractAuthority, commits } = await resolveImageRows(id, { inputs, lock });
1544
+ const schemas = await loadKernelDistributionSchemas();
1545
+ const imageLock = validateImmutableImageLock(
1546
+ schemas.parseImageLock({
1547
+ schemaVersion: 3,
1548
+ sourceHashAlgorithm: schemas.ImageSourceHashAlgorithm.ImageInputV2,
1549
+ distributionId: id,
1550
+ contractAuthority,
1551
+ subjects,
1552
+ platformServiceBindings,
1553
+ webBiomes,
1554
+ }),
1555
+ schemas,
1556
+ );
1557
+ return { imageLock, commits };
1537
1558
  }
1538
1559
 
1539
1560
  /**
@@ -114,7 +114,9 @@ import { join } from 'node:path';
114
114
 
115
115
  import { sealLock, verifyBiomeContentHashes } from './biome-content.mjs';
116
116
  import { fetchCatalogue } from './catalogue-fetch.mjs';
117
- import { CATALOGUE_FLAG, CATALOGUE_TOKEN_ENV_FLAG, CATALOGUE_USERNAME_FLAG } from './customer-flags.mjs';
117
+ import { readCataloguePin } from './catalogue-pin.mjs';
118
+ import { CATALOGUE_TOKEN_ENV_FLAG, CATALOGUE_USERNAME_FLAG, DEFINITION_FLAG } from './customer-flags.mjs';
119
+ import { EditionChannel } from './edition-constants.mjs';
118
120
  import { WorkspaceRoot, configureEditionWorkspaceFromProcess, editionsRoot, isEntryModule, runtimeImageSourceIndexPath } from './edition-workspace.mjs';
119
121
  import {
120
122
  assertCoordinatesFinal,
@@ -134,7 +136,20 @@ import {
134
136
  } from './verify-image-platforms.mjs';
135
137
 
136
138
  if (isEntryModule(import.meta.url)) {
137
- configureEditionWorkspaceFromProcess([WorkspaceRoot.Data, WorkspaceRoot.Editions, WorkspaceRoot.Sources, WorkspaceRoot.Kernel, WorkspaceRoot.Cache, WorkspaceRoot.SourceHash]);
139
+ // The customer form (it names --definition) reads exactly these roots - the data root (the vendor's
140
+ // runtime-image-source-index.json), the editions root, the sources root (the readability check of the
141
+ // vendor repositories), the kernel and the cache (external source trees) - and refuses any other.
142
+ let namesDefinition;
143
+ try {
144
+ namesDefinition = parseSealArgs(process.argv.slice(2)).catalogueFlags.has(DEFINITION_FLAG);
145
+ } catch (error) {
146
+ process.stderr.write(`${error.message}\n`);
147
+ process.exit(1);
148
+ }
149
+ configureEditionWorkspaceFromProcess(
150
+ [WorkspaceRoot.Data, WorkspaceRoot.Editions, WorkspaceRoot.Sources, WorkspaceRoot.Kernel, WorkspaceRoot.Cache],
151
+ { refuseOthers: namesDefinition },
152
+ );
138
153
  }
139
154
 
140
155
  const SHA256_DIGEST = /^sha256:[a-f0-9]{64}$/u;
@@ -393,12 +408,19 @@ export function isNotPublishedYet(error) {
393
408
  */
394
409
  export function parseSealArgs(args) {
395
410
  const MODES = new Set(['--check', '--if-published', '--publication-verdict']);
396
- const CATALOGUE_VALUE_FLAGS = new Set([CATALOGUE_FLAG, CATALOGUE_USERNAME_FLAG, CATALOGUE_TOKEN_ENV_FLAG]);
411
+ const CATALOGUE_VALUE_FLAGS = new Set([DEFINITION_FLAG, CATALOGUE_USERNAME_FLAG, CATALOGUE_TOKEN_ENV_FLAG]);
397
412
  const positional = [];
398
413
  const catalogueFlags = new Map();
399
414
  for (let i = 0; i < args.length; i += 1) {
400
415
  const arg = args[i];
401
416
  if (MODES.has(arg)) {continue;}
417
+ const eq = arg.indexOf('=');
418
+ if (eq > 0 && CATALOGUE_VALUE_FLAGS.has(arg.slice(0, eq))) {
419
+ const value = arg.slice(eq + 1);
420
+ if (value === '' || value.startsWith('--')) {throw new Error(`${arg.slice(0, eq)} takes a value`);}
421
+ catalogueFlags.set(arg.slice(0, eq), value);
422
+ continue;
423
+ }
402
424
  if (CATALOGUE_VALUE_FLAGS.has(arg)) {
403
425
  if (args[i + 1] === undefined || args[i + 1].startsWith('--')) {throw new Error(`${arg} takes a value`);}
404
426
  catalogueFlags.set(arg, args[i + 1]);
@@ -530,7 +552,7 @@ async function main() {
530
552
  throw new Error(
531
553
  'usage: xema edition seal <distributionId> ' +
532
554
  `[--check | --if-published | --publication-verdict] ` +
533
- `[--catalogue oci://<repository>@sha256:<digest> [--catalogue-username <name> --catalogue-token-env <VAR>]]`,
555
+ `[--definition <file> [--catalogue-username <name> --catalogue-token-env <VAR>]]`,
534
556
  );
535
557
  }
536
558
  // The modes answer different questions and combining them would make the
@@ -621,12 +643,14 @@ async function main() {
621
643
  // `authTokenEnv` and the test reads which variable the refusal names, because
622
644
  // no structural check can tell a threaded argument from an omitted one.
623
645
  const root = editionsRoot();
624
- // CUSTOMER MODE: the digest-pinned catalogue the edition was resolved against.
646
+ // CUSTOMER MODE (`--definition`): the digest-pinned RELEASE catalogue of catalogue.pin.json beside the
647
+ // definition, the one the edition was resolved against.
625
648
  // Its vendor biomes' tree ids are inputs on ITS authority (biome-content.mjs).
626
649
  const fetched =
627
- catalogueFlags.has(CATALOGUE_FLAG)
650
+ catalogueFlags.has(DEFINITION_FLAG)
628
651
  ? await fetchCatalogue({
629
- ref: catalogueFlags.get(CATALOGUE_FLAG),
652
+ ref: readCataloguePin({ definitionFile: catalogueFlags.get(DEFINITION_FLAG), channel: EditionChannel.Release }),
653
+ channel: EditionChannel.Release,
630
654
  username: catalogueFlags.get(CATALOGUE_USERNAME_FLAG),
631
655
  tokenEnv: catalogueFlags.get(CATALOGUE_TOKEN_ENV_FLAG),
632
656
  })
@@ -2,7 +2,7 @@
2
2
  // `xema edition source-closure`: the source tree hash of one directory of a
3
3
  // source repository at an exact commit, with its workspace-package closure.
4
4
  //
5
- // usage: xema edition source-closure --repo <repo> --path <dir> --commit <40 hex> --sources-root <dir> --source-hash <dir> --kernel <dir>
5
+ // usage: xema edition source-closure --repo <repo> --path <dir> --commit <40 hex> --sources-root <dir> --kernel <dir>
6
6
  //
7
7
  // These are the facts a content coordinate is derived from. A consumer frames
8
8
  // them into its own coordinate; it never reads a git tree, a workspace graph
@@ -85,7 +85,6 @@ export function readSourceClosure({ repoDir, repo, directory, commit }) {
85
85
  function main() {
86
86
  const rest = configureEditionWorkspaceFromArgv(process.argv.slice(2), [
87
87
  WorkspaceRoot.Sources,
88
- WorkspaceRoot.SourceHash,
89
88
  // The hash implementation is held to the kernel it was built for.
90
89
  WorkspaceRoot.Kernel,
91
90
  ]);
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env node
2
+ // `xema edition arming`: is the data root's declared evidence trust policy ARMED
3
+ // on one channel?
4
+ //
5
+ // usage: xema edition arming --channel <release|develop> --data-root <dir> --kernel <dir>
6
+ // stdout: {"channel":"<channel>","state":"armed|unarmed|partial","unmet":["…"]}
7
+ // exit: 0 only when the state is armed; 1 for any other state (each unmet
8
+ // entry on stderr) or a policy that does not hold to its pin; 2 for a
9
+ // usage error. A data root that declares no policy is `unarmed`.
10
+ // It reads the policy the way `xema edition check` does and never writes.
11
+
12
+ import { EditionChannel } from './edition-channel.mjs';
13
+ import {
14
+ WorkspaceRoot,
15
+ configureEditionWorkspaceFromArgv,
16
+ isEntryModule,
17
+ } from './edition-workspace.mjs';
18
+ import { assessDeclaredTrustPolicyArming } from './evidence-trust-policy.mjs';
19
+ import { TrustPolicyArmingState } from './trust-policy-arming.mjs';
20
+
21
+ export const ARMING_USAGE = `usage: xema edition arming --channel <${Object.values(EditionChannel).join('|')}> --data-root <dir> --kernel <dir>`;
22
+
23
+ /** The process exit codes of the verb. */
24
+ export const ArmingExit = Object.freeze({ Armed: 0, NotArmed: 1, Usage: 2 });
25
+
26
+ /** The channel of `--channel <channel>` among `rest`; anything else is a usage error. */
27
+ export function parseArmingArgs(rest) {
28
+ if (rest.length !== 2 || rest[0] !== '--channel' || !Object.values(EditionChannel).includes(rest[1])) {
29
+ throw new Error(`--channel <${Object.values(EditionChannel).join('|')}> is required and nothing else is accepted (got ${rest.length === 0 ? 'nothing' : JSON.stringify(rest.join(' '))})`);
30
+ }
31
+ return { channel: rest[1] };
32
+ }
33
+
34
+ /**
35
+ * Judge the configured data root's policy on `channel`, writing the one JSON
36
+ * line to `stdout` and each unmet entry to `stderr`. Returns the exit code.
37
+ */
38
+ export async function runArming({ channel, locateVerifier, stdout = process.stdout, stderr = process.stderr }) {
39
+ let verdict;
40
+ try {
41
+ verdict = await assessDeclaredTrustPolicyArming({ channel, locateVerifier });
42
+ } catch (error) {
43
+ stderr.write(`arming: ${error.message}\n`);
44
+ return ArmingExit.NotArmed;
45
+ }
46
+ const { state, unmet } = verdict;
47
+ stdout.write(`${JSON.stringify({ channel, state, unmet })}\n`);
48
+ if (state === TrustPolicyArmingState.ARMED) {return ArmingExit.Armed;}
49
+ for (const entry of unmet) {stderr.write(`${entry}\n`);}
50
+ return ArmingExit.NotArmed;
51
+ }
52
+
53
+ async function main() {
54
+ let channel;
55
+ try {
56
+ const rest = configureEditionWorkspaceFromArgv(process.argv.slice(2), [WorkspaceRoot.Data, WorkspaceRoot.Kernel]);
57
+ ({ channel } = parseArmingArgs(rest));
58
+ } catch (error) {
59
+ process.stderr.write(`arming: ${error.message}\n${ARMING_USAGE}\n`);
60
+ process.exitCode = ArmingExit.Usage;
61
+ return;
62
+ }
63
+ process.exitCode = await runArming({ channel });
64
+ }
65
+
66
+ if (isEntryModule(import.meta.url)) {
67
+ await main();
68
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xemahq/xema",
3
- "version": "0.28.0",
3
+ "version": "0.29.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/xema-dev/xema-cli.git",
@@ -51,6 +51,7 @@
51
51
  "@nestjs/platform-express": "^11.1.28",
52
52
  "@xemahq/biome-sdk": "^18.1.0",
53
53
  "@xemahq/biome-supply-chain": "^0.5.0",
54
+ "@xemahq/distribution-source-hash": "^0.3.0",
54
55
  "@xemahq/dsl": "^0.23.0",
55
56
  "@xemahq/identity-client": "^0.16.0",
56
57
  "@xemahq/contracts": "^0.76.0",
@@ -68,7 +69,6 @@
68
69
  "@types/jest": "^30.0.0",
69
70
  "@types/node": "25.2.3",
70
71
  "@xemahq/biome-host-api-client": "^0.3.31",
71
- "@xemahq/distribution-source-hash": "^0.3.0",
72
72
  "@xemahq/eslint-config": "^0.9.2",
73
73
  "esbuild": "0.28.2",
74
74
  "eslint": "^9.39.4",
@@ -92,6 +92,6 @@
92
92
  "typecheck": "tsc --noEmit"
93
93
  },
94
94
  "xemaRelease": {
95
- "sourceDigest": "sha256:c33104cb26add433525e6447c931da38c0fc51a552b7f61f59b293734aeeb23c"
95
+ "sourceDigest": "sha256:82760f14feea4fe0626220e0f71344375964f15d36201d1e7000ebfb6b2e4ecd"
96
96
  }
97
97
  }