@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.
- package/dist/commands/edition.d.ts.map +1 -1
- package/dist/commands/edition.js +8 -7
- package/dist/commands/edition.js.map +1 -1
- package/dist/edition/verify-image-evidence.bundle.mjs +88 -11
- package/dist/edition/verify-image-evidence.bundle.provenance.json +48 -42
- package/dist/runtime/edition/verb-map.d.ts +2 -1
- package/dist/runtime/edition/verb-map.d.ts.map +1 -1
- package/dist/runtime/edition/verb-map.js +2 -0
- package/dist/runtime/edition/verb-map.js.map +1 -1
- package/dist/runtime/runner/chart-set-lock.d.ts +4 -1
- package/dist/runtime/runner/chart-set-lock.d.ts.map +1 -1
- package/dist/runtime/runner/chart-set-lock.js +39 -4
- package/dist/runtime/runner/chart-set-lock.js.map +1 -1
- package/edition/README.md +69 -32
- package/edition/build-biome-index.mjs +1 -1
- package/edition/build-platform-service-index.mjs +1 -1
- package/edition/build-runtime-image-source-index.mjs +64 -20
- package/edition/catalogue-fetch.mjs +36 -9
- package/edition/catalogue-pin.mjs +71 -0
- package/edition/catalogue.mjs +11 -0
- package/edition/chart-set-source-commit.mjs +17 -7
- package/edition/checkout-indexed-source-repositories.mjs +17 -17
- package/edition/customer-flags.mjs +0 -1
- package/edition/develop-customer-edition.mjs +23 -5
- package/edition/develop-edition.mjs +147 -48
- package/edition/develop-freeze.mjs +69 -25
- package/edition/develop-seal.mjs +39 -25
- package/edition/dist-check.mjs +1 -1
- package/edition/edition-artifact.mjs +65 -18
- package/edition/edition-biome-manifests.mjs +2 -3
- package/edition/edition-chart-set.mjs +59 -6
- package/edition/edition-config.mjs +28 -2
- package/edition/edition-constants.mjs +10 -4
- package/edition/edition-contract-parse.mjs +52 -0
- package/edition/edition-contract.json +10 -1
- package/edition/edition-workspace.mjs +12 -9
- package/edition/evidence-trust-policy.mjs +26 -0
- package/edition/external-source-walk.mjs +48 -61
- package/edition/git-source-auth.mjs +104 -25
- package/edition/image-evidence.mjs +20 -10
- package/edition/kernel-contract-authority.mjs +23 -1
- package/edition/lib.mjs +13 -24
- package/edition/oras.mjs +32 -10
- package/edition/pin-source.mjs +6 -1
- package/edition/registry-credentials.mjs +11 -1
- package/edition/release-edition-artifact.mjs +3 -3
- package/edition/resolve-chart-set.mjs +50 -30
- package/edition/resolve-customer-edition.mjs +63 -7
- package/edition/resolve-distribution.mjs +1 -1
- package/edition/resolve-edition.mjs +12 -11
- package/edition/resolve-images.mjs +36 -15
- package/edition/seal-image-digests.mjs +31 -7
- package/edition/source-closure.mjs +1 -2
- package/edition/trust-policy-arming-verb.mjs +68 -0
- 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
|
|
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
|
-
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
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([
|
|
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
|
-
`[--
|
|
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
|
|
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(
|
|
650
|
+
catalogueFlags.has(DEFINITION_FLAG)
|
|
628
651
|
? await fetchCatalogue({
|
|
629
|
-
ref: catalogueFlags.get(
|
|
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> --
|
|
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.
|
|
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:
|
|
95
|
+
"sourceDigest": "sha256:82760f14feea4fe0626220e0f71344375964f15d36201d1e7000ebfb6b2e4ecd"
|
|
96
96
|
}
|
|
97
97
|
}
|