@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.
- package/dist/commands/edition.d.ts.map +1 -1
- package/dist/commands/edition.js +1 -0
- package/dist/commands/edition.js.map +1 -1
- package/dist/edition/verify-image-evidence.bundle.mjs +281 -9
- package/dist/edition/verify-image-evidence.bundle.provenance.json +65 -47
- package/dist/runtime/edition/verb-map.d.ts +3 -1
- package/dist/runtime/edition/verb-map.d.ts.map +1 -1
- package/dist/runtime/edition/verb-map.js +4 -0
- package/dist/runtime/edition/verb-map.js.map +1 -1
- package/edition/README.md +5 -0
- package/edition/catalogue-evidence.mjs +225 -0
- package/edition/catalogue-fetch.mjs +40 -7
- package/edition/customer-edition-verbs.mjs +477 -0
- package/edition/customer-flags.mjs +2 -0
- package/edition/develop-coverage-readers.mjs +212 -0
- package/edition/develop-coverage-run.mjs +48 -0
- package/edition/develop-coverage-verdict.mjs +153 -0
- package/edition/develop-coverage.mjs +120 -0
- package/edition/develop-edition-resolve.mjs +18 -7
- package/edition/develop-edition.mjs +47 -220
- package/edition/develop-freeze.mjs +50 -13
- package/edition/develop-image-probe.mjs +7 -7
- package/edition/edition-artifact.mjs +31 -14
- package/edition/edition-constants.mjs +9 -0
- package/edition/edition-contract.json +2 -1
- package/edition/{develop-seal.mjs → edition-image-seal.mjs} +36 -17
- package/edition/edition-workspace.mjs +4 -3
- package/edition/evidence-trust-pin.mjs +28 -8
- package/edition/evidence-trust-policy.mjs +51 -7
- package/edition/image-evidence.mjs +2 -2
- package/edition/lib.mjs +7 -0
- package/edition/oci-image-source-evidence.mjs +3 -0
- package/edition/oras-process.mjs +3 -3
- package/edition/oras.mjs +47 -5
- package/edition/produce-edition-catalogue-evidence.mjs +252 -0
- package/edition/produce-image-source-evidence.mjs +11 -2
- package/edition/release-edition.mjs +54 -0
- package/edition/repin-evidence-trust.mjs +5 -3
- package/edition/sigstore-bundle-media-type.mjs +4 -0
- package/edition/trust-policy-arming.mjs +14 -1
- package/edition/verify-catalogue-evidence.mjs +245 -0
- package/edition/verify-image-evidence.mjs +15 -1
- package/package.json +2 -2
- 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.
|
|
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:
|
|
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
|
-
}
|