@xemahq/xema 0.27.0 → 0.29.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 +8 -7
- package/dist/commands/edition.js.map +1 -1
- package/dist/dev/env-defaults.d.ts.map +1 -1
- package/dist/dev/env-defaults.js +0 -1
- package/dist/dev/env-defaults.js.map +1 -1
- package/dist/edition/verify-image-evidence.bundle.mjs +231 -50
- package/dist/edition/verify-image-evidence.bundle.provenance.json +55 -43
- 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 +76 -41
- 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 +79 -18
- package/edition/edition-biome-manifests.mjs +2 -3
- package/edition/edition-chart-set.mjs +59 -6
- package/edition/edition-config.mjs +45 -21
- package/edition/edition-constants.mjs +10 -4
- package/edition/edition-contract-parse.mjs +52 -0
- package/edition/edition-contract.json +21 -5
- package/edition/edition-workspace.mjs +23 -9
- package/edition/evidence-checkout.mjs +22 -1
- package/edition/evidence-trust-policy.mjs +142 -78
- package/edition/external-source-walk.mjs +48 -61
- package/edition/git-source-auth.mjs +104 -25
- package/edition/image-delivery-entitlement-descriptor.mjs +106 -0
- package/edition/image-evidence.mjs +21 -3
- package/edition/kernel-contract-authority.mjs +23 -1
- package/edition/lib.mjs +16 -24
- package/edition/oras.mjs +1 -1
- package/edition/pin-source.mjs +6 -1
- package/edition/produce-image-delivery-entitlement-evidence.mjs +12 -12
- package/edition/registry-credentials.mjs +11 -1
- package/edition/release-channel-guard.mjs +11 -1
- package/edition/release-edition-artifact.mjs +5 -6
- 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/edition/verify-image-evidence.mjs +40 -12
- package/package.json +3 -3
|
@@ -60,12 +60,18 @@ import { basename, join } from 'node:path';
|
|
|
60
60
|
import { EditionChannel } from './edition-channel.mjs';
|
|
61
61
|
import {
|
|
62
62
|
EDITION_CHART_SET_FILE,
|
|
63
|
+
CHART_SET_LOCK_SCHEMA_VERSION,
|
|
64
|
+
DEVELOP_CHART_VERSION_PREFIX,
|
|
63
65
|
EditionChartSetCheck,
|
|
66
|
+
OCI_DIGEST_PATTERN,
|
|
64
67
|
assertRosteredChartSetLock,
|
|
65
68
|
checkEditionChartSet,
|
|
69
|
+
DEVELOP_CHART_SHA_LENGTH,
|
|
70
|
+
hasDevelopChartVersionPrefix,
|
|
71
|
+
isDevelopChartVersion,
|
|
66
72
|
writeEditionChartSet,
|
|
67
73
|
} from './edition-chart-set.mjs';
|
|
68
|
-
import {
|
|
74
|
+
import { vendorChartSetConfig } from './edition-config.mjs';
|
|
69
75
|
import { WorkspaceRoot, configureEditionWorkspaceFromProcess, dataRoot, editionsRoot, isEntryModule } from './edition-workspace.mjs';
|
|
70
76
|
import { serializeJson } from './lib.mjs';
|
|
71
77
|
import { OrasRegistry } from './oras.mjs';
|
|
@@ -95,11 +101,11 @@ export const chartSetLockPath = () => join(dataRoot(), CHART_SET_LOCK_FILE);
|
|
|
95
101
|
* refuses a copy that differs — so the copies cannot disagree
|
|
96
102
|
* (edition-chart-set.mjs).
|
|
97
103
|
*/
|
|
98
|
-
const chartRegistry = () =>
|
|
99
|
-
const chartSetPublishers = () =>
|
|
104
|
+
const chartRegistry = () => vendorChartSetConfig().chartRegistry;
|
|
105
|
+
const chartSetPublishers = () => vendorChartSetConfig().chartPublishers;
|
|
100
106
|
|
|
101
107
|
/** `<host>/<path>` -> the repository path the registry API addresses. */
|
|
102
|
-
function registryRepository(indexName) {
|
|
108
|
+
export function registryRepository(indexName) {
|
|
103
109
|
return `${chartRegistry().replace(/^oci:\/\/[^/]+\//, '')}/${indexName}`;
|
|
104
110
|
}
|
|
105
111
|
|
|
@@ -125,9 +131,9 @@ export function isVersionTag(tag) {
|
|
|
125
131
|
*/
|
|
126
132
|
export function selectNewestIndex(allCandidates) {
|
|
127
133
|
if (!Array.isArray(allCandidates)) {return null;}
|
|
128
|
-
//
|
|
129
|
-
// snapshot, never a release index: it is not a candidate for the release pin.
|
|
130
|
-
const candidates = allCandidates.filter((c) => !c.tag
|
|
134
|
+
// Anything with the develop prefix (`0.0.0-dev.`) is a frozen xema-deploy
|
|
135
|
+
// snapshot or a malformed develop version, never a release index: it is not a candidate for the release pin.
|
|
136
|
+
const candidates = allCandidates.filter((c) => !hasDevelopChartVersionPrefix(c.tag));
|
|
131
137
|
if (candidates.length === 0) {return null;}
|
|
132
138
|
const undated = candidates.filter((c) => !c.createdAt);
|
|
133
139
|
if (undated.length > 0) {
|
|
@@ -142,12 +148,17 @@ export function selectNewestIndex(allCandidates) {
|
|
|
142
148
|
)[0].tag;
|
|
143
149
|
}
|
|
144
150
|
|
|
145
|
-
const DIGEST_PATTERN =
|
|
151
|
+
const DIGEST_PATTERN = OCI_DIGEST_PATTERN;
|
|
146
152
|
|
|
147
153
|
/** The index chart annotation listing the charts the publisher closed the set with. */
|
|
148
154
|
export const INDEX_ROSTER_ANNOTATION = 'xema.dev/chart-set-charts';
|
|
149
155
|
|
|
150
|
-
|
|
156
|
+
/**
|
|
157
|
+
* The lock for `publishers`: each publisher's index version, the manifest digest
|
|
158
|
+
* the registry served for that index (`indexDigestsByIndex`, null for a develop
|
|
159
|
+
* snapshot, which has no index chart), and its sealed roster.
|
|
160
|
+
*/
|
|
161
|
+
export function buildLock(versionsByIndex, rostersByIndex, indexDigestsByIndex, publishers = chartSetPublishers()) {
|
|
151
162
|
// Every publisher's VERSION first: a pin that resolved nothing for one half is
|
|
152
163
|
// the more fundamental refusal, whatever its roster.
|
|
153
164
|
for (const publisher of publishers) {
|
|
@@ -156,10 +167,10 @@ export function buildLock(versionsByIndex, rostersByIndex, publishers = chartSet
|
|
|
156
167
|
}
|
|
157
168
|
}
|
|
158
169
|
return {
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
schemaVersion:
|
|
170
|
+
// Every member is `{version, digest}` (its OCI manifest digest) and each
|
|
171
|
+
// publisher pins its index chart by `indexDigest`, so the lock itself is the
|
|
172
|
+
// trust anchor; `publishers[].version` is per publisher, not one shared version.
|
|
173
|
+
schemaVersion: CHART_SET_LOCK_SCHEMA_VERSION,
|
|
163
174
|
registry: chartRegistry(),
|
|
164
175
|
publishers: publishers.map((publisher) => {
|
|
165
176
|
const version = versionsByIndex[publisher.index];
|
|
@@ -180,7 +191,13 @@ export function buildLock(versionsByIndex, rostersByIndex, publishers = chartSet
|
|
|
180
191
|
}
|
|
181
192
|
charts[name] = { version: member.version, digest: member.digest };
|
|
182
193
|
}
|
|
183
|
-
|
|
194
|
+
// The digest the registry served for the index manifest the roster was read from.
|
|
195
|
+
const indexDigest = indexDigestsByIndex?.[publisher.index];
|
|
196
|
+
const wellFormed = isDevelopChartVersion(version) ? indexDigest === null : DIGEST_PATTERN.test(String(indexDigest));
|
|
197
|
+
if (!wellFormed) {
|
|
198
|
+
throw new Error(`no index manifest digest was read for '${publisher.index}@${version}' (got ${JSON.stringify(indexDigest ?? null)}; a release index needs sha256:<64 hex>, a develop snapshot null)`);
|
|
199
|
+
}
|
|
200
|
+
return { ...publisher, version, indexDigest, charts };
|
|
184
201
|
}),
|
|
185
202
|
};
|
|
186
203
|
}
|
|
@@ -235,19 +252,21 @@ export async function sealRoster(roster, lookup, label) {
|
|
|
235
252
|
return sealed;
|
|
236
253
|
}
|
|
237
254
|
|
|
238
|
-
/** The annotations of
|
|
239
|
-
export async function
|
|
255
|
+
/** The annotations of an index chart's served manifest `served` ({digest, bytes}), read from its OCI config blob (helm stores Chart.yaml's metadata there). */
|
|
256
|
+
export async function manifestAnnotations(client, indexName, version, served) {
|
|
240
257
|
const repository = registryRepository(indexName);
|
|
241
|
-
const manifest = JSON.parse(
|
|
258
|
+
const manifest = JSON.parse(served.bytes.toString('utf8'));
|
|
242
259
|
const configDigest = manifest?.config?.digest;
|
|
243
260
|
if (typeof configDigest !== 'string') {throw new Error(`${indexName}@${version}: manifest names no config blob`);}
|
|
244
261
|
const config = JSON.parse((await client.getBlob(repository, configDigest)).toString('utf8'));
|
|
245
262
|
return config?.annotations ?? {};
|
|
246
263
|
}
|
|
247
264
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
265
|
+
/** The roster and the served manifest digest of the index chart `<indexName>:<version>`, from ONE manifest read. */
|
|
266
|
+
async function indexRosterAndDigest(client, indexName, version) {
|
|
267
|
+
const served = await client.getManifestByTag(registryRepository(indexName), version);
|
|
268
|
+
const annotations = await manifestAnnotations(client, indexName, version, served);
|
|
269
|
+
return { roster: parseIndexRoster(annotations[INDEX_ROSTER_ANNOTATION], `${indexName}@${version}`), indexDigest: served.digest };
|
|
251
270
|
}
|
|
252
271
|
|
|
253
272
|
/** Every edition directory (one holding a distribution.lock.json) under the editions root. */
|
|
@@ -259,11 +278,9 @@ function editionDirs() {
|
|
|
259
278
|
}
|
|
260
279
|
|
|
261
280
|
const SHA_PATTERN = /^[0-9a-f]{40}$/;
|
|
262
|
-
const DEVELOP_CHART_VERSION_PREFIX = '0.0.0-dev.';
|
|
263
|
-
const DEVELOP_SHA_LENGTH = 12;
|
|
264
281
|
|
|
265
282
|
/** 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,
|
|
283
|
+
export const developChartVersion = (sha) => `${DEVELOP_CHART_VERSION_PREFIX}${sha.slice(0, DEVELOP_CHART_SHA_LENGTH)}`;
|
|
267
284
|
|
|
268
285
|
/** Every chart (a directory with a Chart.yaml, library charts included) of an xema-deploy checkout, sorted. */
|
|
269
286
|
function deployChartNames(xemaDeployDir) {
|
|
@@ -312,7 +329,8 @@ export async function resolveDevelopChartSet({ xemaDeployDir, xemaDeploySha, loo
|
|
|
312
329
|
const digestOf = lookup ?? registryManifestDigest(chartRegistryClient());
|
|
313
330
|
const roster = Object.fromEntries(names.map((name) => [name, { version }]));
|
|
314
331
|
const sealed = await sealRoster(roster, digestOf, `${publishers[0].index}@${version}`);
|
|
315
|
-
|
|
332
|
+
// A develop snapshot has no index chart: its roster is the deploy checkout's chart directories.
|
|
333
|
+
const lock = buildLock({ [publishers[0].index]: version }, { [publishers[0].index]: sealed }, { [publishers[0].index]: null }, publishers);
|
|
316
334
|
return assertRosteredChartSetLock(lock, EDITION_CHART_SET_FILE, EditionChannel.Develop);
|
|
317
335
|
}
|
|
318
336
|
|
|
@@ -343,11 +361,12 @@ async function indexCandidates(client, indexName) {
|
|
|
343
361
|
return candidates;
|
|
344
362
|
}
|
|
345
363
|
|
|
346
|
-
|
|
347
|
-
|
|
364
|
+
/** Read every publisher's newest index through `client`: its version, its roster, and the manifest digest the registry served for it. */
|
|
365
|
+
export async function resolveFromRegistry(client = chartRegistryClient()) {
|
|
348
366
|
|
|
349
367
|
const versionsByIndex = {};
|
|
350
368
|
const rostersByIndex = {};
|
|
369
|
+
const indexDigestsByIndex = {};
|
|
351
370
|
for (const { index, repo } of chartSetPublishers()) {
|
|
352
371
|
const candidates = await indexCandidates(client, index);
|
|
353
372
|
const version = selectNewestIndex(candidates);
|
|
@@ -359,7 +378,8 @@ async function resolveFromRegistry() {
|
|
|
359
378
|
);
|
|
360
379
|
}
|
|
361
380
|
versionsByIndex[index] = version;
|
|
362
|
-
const roster = await
|
|
381
|
+
const { roster, indexDigest } = await indexRosterAndDigest(client, index, version);
|
|
382
|
+
indexDigestsByIndex[index] = indexDigest;
|
|
363
383
|
// The registry is asked for every member, so an index digest is checked
|
|
364
384
|
// against it and a member without one still gets sealed.
|
|
365
385
|
rostersByIndex[index] = await sealRoster(
|
|
@@ -368,13 +388,13 @@ async function resolveFromRegistry() {
|
|
|
368
388
|
`${index}@${version}`,
|
|
369
389
|
);
|
|
370
390
|
}
|
|
371
|
-
return { versionsByIndex, rostersByIndex };
|
|
391
|
+
return { versionsByIndex, rostersByIndex, indexDigestsByIndex };
|
|
372
392
|
}
|
|
373
393
|
|
|
374
394
|
async function main(argv) {
|
|
375
395
|
const check = argv.includes('--check');
|
|
376
|
-
const { versionsByIndex, rostersByIndex } = await resolveFromRegistry();
|
|
377
|
-
const next = buildLock(versionsByIndex, rostersByIndex);
|
|
396
|
+
const { versionsByIndex, rostersByIndex, indexDigestsByIndex } = await resolveFromRegistry();
|
|
397
|
+
const next = buildLock(versionsByIndex, rostersByIndex, indexDigestsByIndex);
|
|
378
398
|
assertRosteredChartSetLock(next, CHART_SET_LOCK_FILE, EditionChannel.Release);
|
|
379
399
|
const pinText = serializeJson(next);
|
|
380
400
|
const current = readLock();
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
// resolve-customer-edition — resolve a CUSTOMER edition against a catalogue.
|
|
2
2
|
//
|
|
3
|
-
// resolve-edition.mjs <root flags> --definition <file>
|
|
3
|
+
// resolve-edition.mjs <root flags> --definition <file>
|
|
4
4
|
// --channel release|develop --out <dir> [--source-image <source>=<registry>/<prefix>]...
|
|
5
5
|
// [--catalogue-username <name> --catalogue-token-env <VAR>]
|
|
6
6
|
//
|
|
7
|
-
// The catalogue
|
|
7
|
+
// The catalogue of the channel is the one pinned in catalogue.pin.json beside the definition
|
|
8
|
+
// (catalogue-pin.mjs). The catalogue (catalogue.mjs) is the CEILING: it supplies availability, pins,
|
|
8
9
|
// image digests, chart-set rows and the contract authority, and never adds a
|
|
9
10
|
// component. The resolved set is the customer's positive selections, every
|
|
10
11
|
// installationClass core entry of the catalogue and the transitive declared
|
|
@@ -15,6 +16,7 @@
|
|
|
15
16
|
|
|
16
17
|
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
17
18
|
import { join } from 'node:path';
|
|
19
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
18
20
|
|
|
19
21
|
import { AUTHORIZATION_LOCK_FILENAME, writeAuthorizationLock } from './authorization-base.mjs';
|
|
20
22
|
import { CATALOGUE_FILE, CatalogueError } from './catalogue.mjs';
|
|
@@ -31,7 +33,7 @@ import { validateKernelContractAuthorityFromGit } from './kernel-contract-author
|
|
|
31
33
|
import { compareStrings, loadKernelDistributionSchemas, serializeJson, validateImmutableImageLock } from './lib.mjs';
|
|
32
34
|
import { pinnedSource, snapshotPinSource } from './pin-source.mjs';
|
|
33
35
|
import { composeAuthorizationBase, composeManifest, resolveDistributionWithIndex, UnknownSelectionError } from './resolve-distribution.mjs';
|
|
34
|
-
import {
|
|
36
|
+
import { resolveImageRows } from './resolve-images.mjs';
|
|
35
37
|
|
|
36
38
|
/** A selection or dependency the catalogue does not offer: the catalogue is the ceiling. */
|
|
37
39
|
export class CatalogueCeilingError extends Error {
|
|
@@ -186,7 +188,57 @@ export function projectVendorImages(catalogue, lock, enums) {
|
|
|
186
188
|
};
|
|
187
189
|
}
|
|
188
190
|
|
|
189
|
-
/** The
|
|
191
|
+
/** The first field in which two rows differ (compared by canonical deep equality, key by key), or undefined when they are equal. */
|
|
192
|
+
function firstDifferingField(expected, actual) {
|
|
193
|
+
for (const field of [...new Set([...Object.keys(expected), ...Object.keys(actual)])].sort(compareStrings)) {
|
|
194
|
+
if (!isDeepStrictEqual(expected[field], actual[field])) {return field;}
|
|
195
|
+
}
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* The vendor rows of the merged images lock ARE the catalogue's rows: every subject, platform-service binding and web
|
|
201
|
+
* biome that is not the customer's own deep-equals the catalogue's row (owners narrowed to the selection by
|
|
202
|
+
* `projectVendorImages`, imageRef re-homed by the declared mirror through `mirrorVendorSubjects`), and every projected
|
|
203
|
+
* catalogue row is present. A difference throws `CatalogueCeilingError`, naming the row and the first differing field.
|
|
204
|
+
*/
|
|
205
|
+
export function assertVendorRowsAreCatalogueRows({ catalogue, imageLock, lock, mirror, enums }) {
|
|
206
|
+
const ownBiomes = new Set(lock.biomes.filter((biome) => biome.source !== undefined).map((biome) => biome.id));
|
|
207
|
+
const projected = projectVendorImages(catalogue, lock, enums);
|
|
208
|
+
const expected = {
|
|
209
|
+
subjects: new Map(mirrorVendorSubjects(projected.subjects, mirror).map((subject) => [subjectKey(subject), subject])),
|
|
210
|
+
platformServiceBindings: new Map(projected.platformServiceBindings.map((binding) => [binding.platformService, binding])),
|
|
211
|
+
webBiomes: new Map(projected.webBiomes.map((biome) => [biome.biomeId, biome])),
|
|
212
|
+
};
|
|
213
|
+
const isOwnSubject = (subject) => subject.owners.length > 0 && subject.owners.every((owner) => owner.kind === enums.RuntimeImageOwnerKind.Biome && ownBiomes.has(owner.biomeId));
|
|
214
|
+
const compare = (label, name, want, got) => {
|
|
215
|
+
if (want === undefined) {
|
|
216
|
+
throw new CatalogueCeilingError(`the merged images lock carries ${label} ${name}, which is neither the customer's own nor the catalogue's`);
|
|
217
|
+
}
|
|
218
|
+
const field = firstDifferingField(want, got);
|
|
219
|
+
if (field !== undefined) {
|
|
220
|
+
throw new CatalogueCeilingError(`the merged images lock's ${label} ${name} differs from the catalogue's row at ${field}`);
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
const kinds = [
|
|
224
|
+
['image', 'subjects', (row) => subjectKey(row), (row) => `${row.kind}:${row.id}`, (row) => isOwnSubject(row)],
|
|
225
|
+
['platform-service binding', 'platformServiceBindings', (row) => row.platformService, (row) => row.platformService, () => false],
|
|
226
|
+
['web biome', 'webBiomes', (row) => row.biomeId, (row) => row.biomeId, (row) => ownBiomes.has(row.biomeId)],
|
|
227
|
+
];
|
|
228
|
+
for (const [label, field, keyOf, nameOf, isOwn] of kinds) {
|
|
229
|
+
const present = new Set();
|
|
230
|
+
for (const row of imageLock[field]) {
|
|
231
|
+
if (isOwn(row)) {continue;}
|
|
232
|
+
present.add(keyOf(row));
|
|
233
|
+
compare(label, nameOf(row), expected[field].get(keyOf(row)), row);
|
|
234
|
+
}
|
|
235
|
+
for (const [key, want] of expected[field]) {
|
|
236
|
+
if (!present.has(key)) {throw new CatalogueCeilingError(`the merged images lock lacks the catalogue's ${label} ${nameOf(want)}`);}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** The customer's image lock: vendor rows copied (and mirrored), own rows resolved (and sealed by `sealOwn`, which takes and returns subjects), the merged lock parsed once. */
|
|
190
242
|
export async function resolveCustomerImages({ id, catalogue, lock, composed, schemas, inputs, sealOwn }) {
|
|
191
243
|
const vendor = projectVendorImages(catalogue, lock, schemas);
|
|
192
244
|
const vendorSubjects = mirrorVendorSubjects(vendor.subjects, composed.mirror);
|
|
@@ -202,13 +254,15 @@ export async function resolveCustomerImages({ id, catalogue, lock, composed, sch
|
|
|
202
254
|
crossRepoBuildInputs: [],
|
|
203
255
|
buildStepContextDirectories: [],
|
|
204
256
|
};
|
|
205
|
-
own
|
|
206
|
-
|
|
257
|
+
// The own rows are ROWS, not a lock: the one parse is of the merged lock below.
|
|
258
|
+
own = await resolveImageRows(id, { inputs: { ...inputs, runtimeImageSourceIndex, composed }, lock: ownLock });
|
|
259
|
+
// A source that builds no image (a web biome) has no row to seal.
|
|
260
|
+
if (sealOwn !== undefined && own.subjects.length > 0) {own = { ...own, subjects: await sealOwn({ distributionId: id, subjects: own.subjects }) };}
|
|
207
261
|
}
|
|
208
262
|
const seen = new Set(vendorSubjects.map(subjectKey));
|
|
209
263
|
const clash = own.subjects.find((subject) => seen.has(subjectKey(subject)));
|
|
210
264
|
if (clash !== undefined) {throw new Error(`the customer's own image ${clash.kind}:${clash.id} collides with a catalogue image of the same identity`);}
|
|
211
|
-
|
|
265
|
+
const merged = validateImmutableImageLock(
|
|
212
266
|
schemas.parseImageLock({
|
|
213
267
|
schemaVersion: 3,
|
|
214
268
|
sourceHashAlgorithm: catalogue.images.sourceHashAlgorithm,
|
|
@@ -220,6 +274,8 @@ export async function resolveCustomerImages({ id, catalogue, lock, composed, sch
|
|
|
220
274
|
}),
|
|
221
275
|
schemas,
|
|
222
276
|
);
|
|
277
|
+
assertVendorRowsAreCatalogueRows({ catalogue, imageLock: merged, lock, mirror: composed.mirror, enums: schemas });
|
|
278
|
+
return merged;
|
|
223
279
|
}
|
|
224
280
|
|
|
225
281
|
/** The kernel at --kernel must carry the contract the catalogue's offering was resolved under. */
|
|
@@ -144,7 +144,7 @@ export function lockedServiceProcessFacts(entry) {
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
if (isEntryModule(import.meta.url)) {
|
|
147
|
-
configureEditionWorkspaceFromProcess([WorkspaceRoot.Data, WorkspaceRoot.Editions, WorkspaceRoot.Sources, WorkspaceRoot.Kernel
|
|
147
|
+
configureEditionWorkspaceFromProcess([WorkspaceRoot.Data, WorkspaceRoot.Editions, WorkspaceRoot.Sources, WorkspaceRoot.Kernel]);
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
/**
|
|
@@ -24,8 +24,8 @@ import { join } from 'node:path';
|
|
|
24
24
|
|
|
25
25
|
import { AUTHORIZATION_LOCK_FILENAME } from './authorization-base.mjs';
|
|
26
26
|
import { fetchCatalogue } from './catalogue-fetch.mjs';
|
|
27
|
+
import { readCataloguePin } from './catalogue-pin.mjs';
|
|
27
28
|
import {
|
|
28
|
-
CATALOGUE_FLAG,
|
|
29
29
|
CATALOGUE_TOKEN_ENV_FLAG,
|
|
30
30
|
CATALOGUE_USERNAME_FLAG,
|
|
31
31
|
CHANNEL_FLAG,
|
|
@@ -49,17 +49,17 @@ import { writeResolvedImages } from './resolve-images.mjs';
|
|
|
49
49
|
import { withoutSealedDigests } from './seal-image-digests.mjs';
|
|
50
50
|
import { readStrictJsonFile } from './strict-json.mjs';
|
|
51
51
|
|
|
52
|
-
// A customer resolve (`--
|
|
52
|
+
// A customer resolve (`--definition`) has no vendor editions root: `--out` IS its
|
|
53
53
|
// editions root, the directory its `<id>/` locks are written under.
|
|
54
54
|
if (isEntryModule(import.meta.url)) {
|
|
55
|
-
if (process.argv.includes(
|
|
55
|
+
if (process.argv.includes(DEFINITION_FLAG)) {
|
|
56
56
|
const at = process.argv.indexOf('--out');
|
|
57
|
-
if (at < 0 || process.argv[at + 1] === undefined) {throw new Error(`--out <dir> is required with ${
|
|
57
|
+
if (at < 0 || process.argv[at + 1] === undefined) {throw new Error(`--out <dir> is required with ${DEFINITION_FLAG}`);}
|
|
58
58
|
mkdirSync(process.argv[at + 1], { recursive: true });
|
|
59
59
|
process.argv.splice(at, 1, '--editions-root');
|
|
60
|
-
configureEditionWorkspaceFromProcess([WorkspaceRoot.Data, WorkspaceRoot.Editions, WorkspaceRoot.Sources, WorkspaceRoot.Kernel, WorkspaceRoot.Cache,
|
|
60
|
+
configureEditionWorkspaceFromProcess([WorkspaceRoot.Data, WorkspaceRoot.Editions, WorkspaceRoot.Sources, WorkspaceRoot.Kernel, WorkspaceRoot.Cache], { refuseOthers: true });
|
|
61
61
|
} else {
|
|
62
|
-
configureEditionWorkspaceFromProcess([WorkspaceRoot.Data, WorkspaceRoot.Editions, WorkspaceRoot.Sources, WorkspaceRoot.Kernel, WorkspaceRoot.Cache
|
|
62
|
+
configureEditionWorkspaceFromProcess([WorkspaceRoot.Data, WorkspaceRoot.Editions, WorkspaceRoot.Sources, WorkspaceRoot.Kernel, WorkspaceRoot.Cache]);
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
@@ -135,7 +135,7 @@ export async function checkResolvedLockSet(id) {
|
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
const CUSTOMER_VALUE_FLAGS = [DEFINITION_FLAG,
|
|
138
|
+
const CUSTOMER_VALUE_FLAGS = [DEFINITION_FLAG, CHANNEL_FLAG, CATALOGUE_USERNAME_FLAG, CATALOGUE_TOKEN_ENV_FLAG];
|
|
139
139
|
|
|
140
140
|
/** The arguments of a customer resolve (the root flags and `--out` are already consumed). */
|
|
141
141
|
export function parseCustomerResolveArgs(args) {
|
|
@@ -145,12 +145,12 @@ export function parseCustomerResolveArgs(args) {
|
|
|
145
145
|
const flag = args[i];
|
|
146
146
|
const value = args[i + 1];
|
|
147
147
|
if (![...CUSTOMER_VALUE_FLAGS, SOURCE_IMAGE_FLAG].includes(flag) || value === undefined || value.startsWith('--')) {
|
|
148
|
-
throw new Error(`unexpected argument ${JSON.stringify(flag)} in a customer resolve (${DEFINITION_FLAG}, ${
|
|
148
|
+
throw new Error(`unexpected argument ${JSON.stringify(flag)} in a customer resolve (${DEFINITION_FLAG}, ${CHANNEL_FLAG}, ${SOURCE_IMAGE_FLAG} take a value)`);
|
|
149
149
|
}
|
|
150
150
|
i += 1;
|
|
151
151
|
if (flag === SOURCE_IMAGE_FLAG) {sourceImages.push(value);} else if (values.has(flag)) {throw new Error(`${flag} given twice`);} else {values.set(flag, value);}
|
|
152
152
|
}
|
|
153
|
-
for (const required of [DEFINITION_FLAG,
|
|
153
|
+
for (const required of [DEFINITION_FLAG, CHANNEL_FLAG]) {
|
|
154
154
|
if (!values.has(required)) {throw new Error(`a customer resolve requires ${required}`);}
|
|
155
155
|
}
|
|
156
156
|
if (!Object.values(EditionChannel).includes(values.get(CHANNEL_FLAG))) {
|
|
@@ -162,7 +162,8 @@ export function parseCustomerResolveArgs(args) {
|
|
|
162
162
|
async function mainCustomer() {
|
|
163
163
|
const { values, sourceImages } = parseCustomerResolveArgs(process.argv.slice(2));
|
|
164
164
|
const { catalogue, digest } = await fetchCatalogue({
|
|
165
|
-
ref: values.get(
|
|
165
|
+
ref: readCataloguePin({ definitionFile: values.get(DEFINITION_FLAG), channel: values.get(CHANNEL_FLAG) }),
|
|
166
|
+
channel: values.get(CHANNEL_FLAG),
|
|
166
167
|
username: values.get(CATALOGUE_USERNAME_FLAG),
|
|
167
168
|
tokenEnv: values.get(CATALOGUE_TOKEN_ENV_FLAG),
|
|
168
169
|
});
|
|
@@ -172,7 +173,7 @@ async function mainCustomer() {
|
|
|
172
173
|
}
|
|
173
174
|
|
|
174
175
|
async function main() {
|
|
175
|
-
if (process.argv.includes(
|
|
176
|
+
if (process.argv.includes(DEFINITION_FLAG)) {return mainCustomer();}
|
|
176
177
|
const { id, check } = parseResolveEditionArgs(process.argv.slice(2));
|
|
177
178
|
assertCanonicalDistributionId(id);
|
|
178
179
|
await (check ? checkResolvedLockSet(id) : writeResolvedLockSet(id));
|
|
@@ -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
|
+
}
|