@xemahq/xema 0.40.3 → 0.41.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 (44) hide show
  1. package/dist/commands/edition.d.ts.map +1 -1
  2. package/dist/commands/edition.js +1 -0
  3. package/dist/commands/edition.js.map +1 -1
  4. package/dist/edition/verify-image-evidence.bundle.mjs +281 -9
  5. package/dist/edition/verify-image-evidence.bundle.provenance.json +65 -47
  6. package/dist/runtime/edition/verb-map.d.ts +3 -1
  7. package/dist/runtime/edition/verb-map.d.ts.map +1 -1
  8. package/dist/runtime/edition/verb-map.js +4 -0
  9. package/dist/runtime/edition/verb-map.js.map +1 -1
  10. package/edition/README.md +5 -0
  11. package/edition/catalogue-evidence.mjs +225 -0
  12. package/edition/catalogue-fetch.mjs +40 -7
  13. package/edition/customer-edition-verbs.mjs +477 -0
  14. package/edition/customer-flags.mjs +2 -0
  15. package/edition/develop-coverage-readers.mjs +212 -0
  16. package/edition/develop-coverage-run.mjs +48 -0
  17. package/edition/develop-coverage-verdict.mjs +153 -0
  18. package/edition/develop-coverage.mjs +120 -0
  19. package/edition/develop-edition-resolve.mjs +18 -7
  20. package/edition/develop-edition.mjs +47 -220
  21. package/edition/develop-freeze.mjs +50 -13
  22. package/edition/develop-image-probe.mjs +7 -7
  23. package/edition/edition-artifact.mjs +31 -14
  24. package/edition/edition-constants.mjs +9 -0
  25. package/edition/edition-contract.json +2 -1
  26. package/edition/{develop-seal.mjs → edition-image-seal.mjs} +36 -17
  27. package/edition/edition-workspace.mjs +4 -3
  28. package/edition/evidence-trust-pin.mjs +28 -8
  29. package/edition/evidence-trust-policy.mjs +51 -7
  30. package/edition/image-evidence.mjs +2 -2
  31. package/edition/lib.mjs +7 -0
  32. package/edition/oci-image-source-evidence.mjs +3 -0
  33. package/edition/oras-process.mjs +3 -3
  34. package/edition/oras.mjs +47 -5
  35. package/edition/produce-edition-catalogue-evidence.mjs +252 -0
  36. package/edition/produce-image-source-evidence.mjs +11 -2
  37. package/edition/release-edition.mjs +54 -0
  38. package/edition/repin-evidence-trust.mjs +5 -3
  39. package/edition/sigstore-bundle-media-type.mjs +4 -0
  40. package/edition/trust-policy-arming.mjs +14 -1
  41. package/edition/verify-catalogue-evidence.mjs +245 -0
  42. package/edition/verify-image-evidence.mjs +15 -1
  43. package/package.json +2 -2
  44. package/edition/develop-customer-edition.mjs +0 -149
@@ -12,6 +12,9 @@
12
12
  // * the data root and the editions root are clean git checkouts at an exact
13
13
  // HEAD with every file read from them bound to its HEAD blob.
14
14
  //
15
+ // A second mode, `catalogue-evidence arming|verify` (verify-catalogue-evidence.mjs), judges a release catalogue's E4
16
+ // statement for a customer before the catalogue's kernel is adopted; it is selected by the first argument.
17
+ //
15
18
  // usage: verify-image-evidence <distributionId> --evidence-root <dir>
16
19
  // --descriptor <absolute-path> --state <absolute-path> --cosign <absolute-path> --git <absolute-path>
17
20
  // [--evidence-set <relative-path>] --data-root <dir> --editions-root <dir>
@@ -65,6 +68,10 @@ import {
65
68
  readRunningVerifier,
66
69
  } from './verifier-binding.mjs';
67
70
  import { loadVerifierKernelSchemas } from './verifier-kernel-schemas.mjs';
71
+ import {
72
+ CATALOGUE_EVIDENCE_MODE,
73
+ runCatalogueEvidenceEntry,
74
+ } from './verify-catalogue-evidence.mjs';
68
75
 
69
76
  const USAGE =
70
77
  'usage: verify-image-evidence <distributionId> --evidence-root <dir> --descriptor <absolute-path> --state <absolute-path> ' +
@@ -445,7 +452,14 @@ export async function verifyEvidence({ argv, env, bundlePath }) {
445
452
  }
446
453
  }
447
454
 
448
- if (isEntryModule(import.meta.url)) {
455
+ if (isEntryModule(import.meta.url) && process.argv[2] === CATALOGUE_EVIDENCE_MODE) {
456
+ // The customer's pre-adoption mode: the E4 statement of a catalogue, judged with this bundle's own embedded kernel.
457
+ await runCatalogueEvidenceEntry({
458
+ argv: process.argv.slice(2),
459
+ env: process.env,
460
+ bundlePath: process.argv[1],
461
+ });
462
+ } else if (isEntryModule(import.meta.url)) {
449
463
  await verifyEvidence({
450
464
  argv: process.argv.slice(2),
451
465
  env: process.env,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xemahq/xema",
3
- "version": "0.40.3",
3
+ "version": "0.41.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/xema-dev/xema-cli.git",
@@ -92,6 +92,6 @@
92
92
  "typecheck": "tsc --noEmit"
93
93
  },
94
94
  "xemaRelease": {
95
- "sourceDigest": "sha256:55a458c54ff157ee053b4f074068c355fb35d28f54a01b0a2662bf14b07b8323"
95
+ "sourceDigest": "sha256:322929ee2999d155d5d612f17bb786adef52f44c46cee82b9bc9b11ecc792758"
96
96
  }
97
97
  }
@@ -1,149 +0,0 @@
1
- // develop-customer-edition — a DEVELOP customer edition: a customer definition
2
- // resolved against a catalogue, frozen at the customer's OWN repository pins.
3
- //
4
- // The pins file holds one `sources/<name>` commit per source the definition
5
- // declares and nothing else (the vendor repositories are the catalogue's, not
6
- // the customer's). The digest of the catalogue artifact the edition was
7
- // resolved against is frozen into the descriptor's provenance pins as
8
- // `catalogue`; the artifact tag (developEditionTag) covers it. The vendor image
9
- // rows arrive sealed from the catalogue; only the customer's own image rows are
10
- // sealed against the develop image repositories.
11
-
12
- import { join } from 'node:path';
13
-
14
- import { sealLock } from './biome-content.mjs';
15
- import { parseCatalogueRef } from './catalogue-fetch.mjs';
16
- import { readCataloguePin } from './catalogue-pin.mjs';
17
- import { developImageRef } from './develop-image-coordinate.mjs';
18
- import { sealDevelopImageSubjects } from './develop-seal.mjs';
19
- import { assertCanonicalDistributionId } from './distribution-paths.mjs';
20
- import { CATALOGUE_PIN_KEY, EditionChannel, EditionLockFile } from './edition-constants.mjs';
21
- import { loadKernelDistributionSchemas } from './lib.mjs';
22
- import { isSourcePinKey, sortedSnapshotPins, sourcePinKey } from './pin-source.mjs';
23
- import { resolveCustomerEdition } from './resolve-customer-edition.mjs';
24
- import { externalSourceTrees, imageRefForSubject, sourceTreeLocator } from './resolve-images.mjs';
25
- import { readStrictJsonFile } from './strict-json.mjs';
26
-
27
- const SHA_PATTERN = /^[0-9a-f]{40}$/;
28
-
29
- export class CustomerDevelopPinsError extends Error {
30
- constructor(message) {
31
- super(message);
32
- this.name = 'CustomerDevelopPinsError';
33
- }
34
- }
35
-
36
- /**
37
- * The pins of a develop customer edition: `sources/<name>` commits for exactly
38
- * the definition's declared sources, plus `catalogue` = the digest of the
39
- * catalogue artifact. A `catalogue` entry in the file must equal that digest.
40
- */
41
- export function customerDevelopPins({ pins, declaredSourceNames, catalogueDigest }) {
42
- if (pins === null || typeof pins !== 'object' || Array.isArray(pins)) {
43
- throw new CustomerDevelopPinsError('--pins must be an object mapping sources/<name> -> 40-hex commit');
44
- }
45
- const own = {};
46
- for (const [key, value] of Object.entries(pins)) {
47
- if (key === CATALOGUE_PIN_KEY) {
48
- if (value !== catalogueDigest) {
49
- throw new CustomerDevelopPinsError(`--pins "${CATALOGUE_PIN_KEY}" is ${JSON.stringify(value)}, but the pinned develop catalogue is ${catalogueDigest}`);
50
- }
51
- continue;
52
- }
53
- if (!isSourcePinKey(key)) {
54
- throw new CustomerDevelopPinsError(`--pins "${key}" is not one of the customer's own repositories: a customer develop pins only sources/<name> (the vendor's pins are the catalogue's)`);
55
- }
56
- if (typeof value !== 'string' || !SHA_PATTERN.test(value)) {
57
- throw new CustomerDevelopPinsError(`--pins "${key}" is not a 40-hex commit SHA: ${JSON.stringify(value)}`);
58
- }
59
- own[key] = value;
60
- }
61
- const declared = new Set(declaredSourceNames.map(sourcePinKey));
62
- for (const key of declared) {
63
- if (!(key in own)) {throw new CustomerDevelopPinsError(`--pins is missing "${key}": the definition declares that source`);}
64
- }
65
- for (const key of Object.keys(own)) {
66
- if (!declared.has(key)) {throw new CustomerDevelopPinsError(`--pins names "${key}", but the definition declares no such source`);}
67
- }
68
- return sortedSnapshotPins({ ...own, [CATALOGUE_PIN_KEY]: catalogueDigest });
69
- }
70
-
71
- /** The source names a raw definition declares (the pins are checked before the definition is composed). */
72
- export function declaredSourceNamesOf(rawDefinition) {
73
- const sources = rawDefinition?.sources;
74
- return Array.isArray(sources) ? sources.map((source) => source?.name) : [];
75
- }
76
-
77
- /** Resolve and seal the develop customer edition; returns the four locks and the frozen provenance pins. */
78
- export async function resolveCustomerDevelopEdition({ rawDefinition, catalogue, catalogueDigest, pins, sourceImages, env = process.env, sealOptions = {}, adoptKernel, materialize }) {
79
- const frozen = customerDevelopPins({ pins, declaredSourceNames: declaredSourceNamesOf(rawDefinition), catalogueDigest });
80
- const resolved = await resolveCustomerEdition({
81
- rawDefinition,
82
- catalogue,
83
- channel: EditionChannel.Develop,
84
- sourceImages,
85
- pins: frozen,
86
- catalogueDigest,
87
- ...(adoptKernel === undefined ? {} : { adoptKernel }),
88
- ...(materialize === undefined ? {} : { materialize }),
89
- sealOwn: ({ distributionId, subjects }) => sealDevelopImageSubjects({ distributionId, subjects, env, ...sealOptions }),
90
- });
91
- // The customer edition is sealed here: every biomeContentHash is recomputed through the
92
- // same code as the vendor's, vendor biomes on the CATALOGUE's authority (biome-content.mjs).
93
- const schemas = await loadKernelDistributionSchemas();
94
- const distributionLock = await sealLock({
95
- schemas,
96
- unsealed: resolved.distributionLock,
97
- imageLock: resolved.imageLock,
98
- locate: sourceTreeLocator(externalSourceTrees(resolved.distributionLock)),
99
- vendoredCommit: (repo) => {
100
- throw new Error(`customer biome source "${repo}" is first-party to no checkout; vendor biomes are taken from the catalogue`);
101
- },
102
- catalogue,
103
- catalogueDigest,
104
- storePublisherAnchorKeys: resolved.storePublisherAnchorKeys,
105
- });
106
- return { ...resolved, distributionLock, pins: frozen };
107
- }
108
-
109
- /**
110
- * What a develop verb of a customer edition derives from its definition file and its `--pins` file with no
111
- * network: the id (the definition's), the pinned develop catalogue reference and digest, and the frozen
112
- * provenance pins (the customer's `sources/<name>` commits plus that digest).
113
- */
114
- export function customerDevelopFrame({ definitionFile, pinsFile }) {
115
- const rawDefinition = readStrictJsonFile(definitionFile);
116
- assertCanonicalDistributionId(rawDefinition?.id);
117
- const catalogueRef = readCataloguePin({ definitionFile, channel: EditionChannel.Develop });
118
- const { digest } = parseCatalogueRef(catalogueRef);
119
- const pins = customerDevelopPins({ pins: readStrictJsonFile(pinsFile), declaredSourceNames: declaredSourceNamesOf(rawDefinition), catalogueDigest: digest });
120
- return { id: rawDefinition.id, rawDefinition, catalogueRef, catalogueDigest: digest, pins };
121
- }
122
-
123
- /**
124
- * The own image rows of an existing customer develop artifact are the rows this `--source-image` map composes:
125
- * every subject owned by a biome drawn from a declared source is `<registry>/<prefix><subject>` mapped to its
126
- * develop repository. The tag covers the map, but a tag is mutable registry state, so the pulled bytes are
127
- * compared too. Throws naming the first row that differs.
128
- */
129
- export function assertOwnImageRowsMatch({ artifactDir, sourceImages }) {
130
- const lock = readStrictJsonFile(join(artifactDir, EditionLockFile.Distribution));
131
- const imageLock = readStrictJsonFile(join(artifactDir, EditionLockFile.Images));
132
- const sourceOf = new Map((lock.biomes ?? []).filter((biome) => biome.source !== undefined).map((biome) => [biome.id, biome.source.name]));
133
- for (const subject of imageLock.subjects ?? []) {
134
- const biomeId = (subject.owners ?? []).find((owner) => sourceOf.has(owner.biomeId))?.biomeId;
135
- if (biomeId === undefined) {continue;}
136
- const name = sourceOf.get(biomeId);
137
- const image = sourceImages.get(name);
138
- if (image === undefined) {
139
- throw new Error(`the existing artifact draws biome "${biomeId}" from source "${name}", which has no --source-image; refusing to reuse it`);
140
- }
141
- const expected = developImageRef(imageRefForSubject(subject.id, image.imageRegistry, image.imageNamePrefix));
142
- if (subject.imageRef !== expected) {
143
- throw new Error(
144
- `the existing artifact's own image row ${subject.kind}:${subject.id} is ${subject.imageRef}, not ${expected} ` +
145
- '(the --source-image map differs); refusing to reuse it',
146
- );
147
- }
148
- }
149
- }