@xemahq/xema 0.29.1 → 0.30.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/README.md +60 -389
- package/dist/commands/edition.js +3 -3
- package/dist/commands/edition.js.map +1 -1
- package/dist/edition/verify-image-evidence.bundle.mjs +8715 -807
- package/dist/edition/verify-image-evidence.bundle.provenance.json +873 -405
- package/edition/README.md +53 -273
- package/edition/adapter-bundled-versions.mjs +107 -0
- package/edition/adapter-host-versions.mjs +41 -0
- package/edition/authorization-base.mjs +18 -9
- package/edition/build-runtime-image-source-index.mjs +77 -4
- package/edition/catalogue.mjs +15 -12
- package/edition/customer-definition.mjs +3 -20
- package/edition/develop-customer-edition.mjs +2 -2
- package/edition/develop-edition-resolve.mjs +10 -2
- package/edition/develop-edition.mjs +56 -62
- package/edition/develop-freeze.mjs +14 -3
- package/edition/dist-check.mjs +1 -0
- package/edition/distribution-evidence-set.mjs +32 -5
- package/edition/edition-channel.mjs +15 -1
- package/edition/edition-locks.mjs +16 -6
- package/edition/edition-protocol.mjs +30 -8
- package/edition/edition-workspace.mjs +8 -0
- package/edition/image-evidence.mjs +133 -25
- package/edition/kernel-package.mjs +193 -0
- package/edition/lib.mjs +73 -1
- package/edition/oci-evidence-verbs.mjs +2 -2
- package/edition/plan-image-evidence-refresh.mjs +2 -2
- package/edition/produce-distribution-completeness-evidence.mjs +5 -0
- package/edition/produce-image-delivery-entitlement-evidence.mjs +11 -1
- package/edition/release-catalogue.mjs +10 -3
- package/edition/resolve-chart-set.mjs +1 -1
- package/edition/resolve-customer-edition.mjs +29 -33
- package/edition/resolve-distribution.mjs +5 -1
- package/edition/resolve-edition.mjs +3 -2
- package/edition/seal-image-digests.mjs +16 -4
- package/edition/trust-policy-arming-verb.mjs +2 -10
- package/edition/verify-image-evidence.mjs +1 -0
- package/examples/README.md +3 -2
- package/package.json +3 -3
|
@@ -38,7 +38,6 @@ import { assertKernelEditionProtocol } from './edition-protocol.mjs';
|
|
|
38
38
|
export const AUTHORIZATION_MODULE_DIRECTORY = 'authorization';
|
|
39
39
|
export const AUTHORIZATION_ENVELOPE_FILENAME = 'envelope.json';
|
|
40
40
|
export const AUTHORIZATION_LOCK_FILENAME = 'authorization.lock.json';
|
|
41
|
-
export const AUTHORIZATION_LOCK_SCHEMA_VERSION = 1;
|
|
42
41
|
|
|
43
42
|
/**
|
|
44
43
|
* The OpenFGA CLI the sealer tests with, EXACTLY. 0.8.1 embeds
|
|
@@ -142,10 +141,12 @@ export async function loadKernelAuthorizationComposer({ kernelPackageDir }) {
|
|
|
142
141
|
let server;
|
|
143
142
|
let contribution;
|
|
144
143
|
let generation;
|
|
144
|
+
let distribution;
|
|
145
145
|
try {
|
|
146
146
|
server = await import(`file://${serverDist}`);
|
|
147
147
|
contribution = await import(`file://${join(kernelPackageDir, 'dist', 'authorization-module', 'index.js')}`);
|
|
148
148
|
generation = await import(`file://${join(kernelPackageDir, 'dist', 'platform-generation', 'index.js')}`);
|
|
149
|
+
distribution = await import(`file://${join(kernelPackageDir, 'dist', 'distribution', 'index.js')}`);
|
|
149
150
|
} catch (err) {
|
|
150
151
|
throw new Error(`Cannot load the pinned kernel's authorization composer from ${serverDist}: ${err.message}`);
|
|
151
152
|
}
|
|
@@ -153,6 +154,7 @@ export async function loadKernelAuthorizationComposer({ kernelPackageDir }) {
|
|
|
153
154
|
[server, 'composeAuthorizationBase'],
|
|
154
155
|
[server, 'authorizationModuleInputFromContribution'],
|
|
155
156
|
[contribution, 'stampAuthorizationModuleContribution'],
|
|
157
|
+
[distribution, 'parseAuthorizationLockReadView'],
|
|
156
158
|
]) {
|
|
157
159
|
if (typeof mod[name] !== 'function') {
|
|
158
160
|
throw new Error(`the pinned kernel's authorization-module subpaths do not export ${name}.`);
|
|
@@ -169,6 +171,8 @@ export async function loadKernelAuthorizationComposer({ kernelPackageDir }) {
|
|
|
169
171
|
composeAuthorizationBase: server.composeAuthorizationBase,
|
|
170
172
|
authorizationModuleInputFromContribution: server.authorizationModuleInputFromContribution,
|
|
171
173
|
stampAuthorizationModuleContribution: contribution.stampAuthorizationModuleContribution,
|
|
174
|
+
parseAuthorizationLockReadView: distribution.parseAuthorizationLockReadView,
|
|
175
|
+
authorizationLockSchemaVersion: distribution.AUTHORIZATION_LOCK_SCHEMA_VERSION,
|
|
172
176
|
currentPlatformGeneration: parsed.data,
|
|
173
177
|
kernelPackageDir,
|
|
174
178
|
};
|
|
@@ -186,10 +190,11 @@ export function adoptedFromLock(lock) {
|
|
|
186
190
|
}
|
|
187
191
|
|
|
188
192
|
/** Compose an edition's base; a refusal names the edition and every violation. */
|
|
189
|
-
export function composeEditionAuthorizationBase({ distributionId, inputs, kernel, adopted }) {
|
|
193
|
+
export function composeEditionAuthorizationBase({ distributionId, inputs, kernel, adopted, biomeOrigins }) {
|
|
190
194
|
try {
|
|
191
195
|
return kernel.composeAuthorizationBase({
|
|
192
196
|
modules: inputs,
|
|
197
|
+
biomeOrigins,
|
|
193
198
|
currentPlatformGeneration: kernel.currentPlatformGeneration,
|
|
194
199
|
...(adopted ? { adopted } : {}),
|
|
195
200
|
});
|
|
@@ -201,10 +206,10 @@ export function composeEditionAuthorizationBase({ distributionId, inputs, kernel
|
|
|
201
206
|
}
|
|
202
207
|
}
|
|
203
208
|
|
|
204
|
-
/** The lock body for a composed base. Deterministic: no timestamp. */
|
|
209
|
+
/** The lock body for a composed base. Deterministic: no timestamp. It is read through the kernel's `parseAuthorizationLockReadView` before it is returned. */
|
|
205
210
|
export function authorizationLockOf(distributionId, kernel, composed) {
|
|
206
|
-
|
|
207
|
-
schemaVersion:
|
|
211
|
+
const lock = {
|
|
212
|
+
schemaVersion: kernel.authorizationLockSchemaVersion,
|
|
208
213
|
distributionId,
|
|
209
214
|
platformGeneration: kernel.currentPlatformGeneration,
|
|
210
215
|
modelHash: composed.modelHash,
|
|
@@ -219,6 +224,8 @@ export function authorizationLockOf(distributionId, kernel, composed) {
|
|
|
219
224
|
parentImplementers: [...composed.parentImplementers],
|
|
220
225
|
model: composed.model,
|
|
221
226
|
};
|
|
227
|
+
kernel.parseAuthorizationLockReadView(lock);
|
|
228
|
+
return lock;
|
|
222
229
|
}
|
|
223
230
|
|
|
224
231
|
/** Resolve and verify the pinned `fga` CLI. */
|
|
@@ -272,8 +279,8 @@ export function fgaComposedModelTestRunner(bin) {
|
|
|
272
279
|
}
|
|
273
280
|
|
|
274
281
|
/** Compose, test, describe. `runTests` is the `fga model test` runner. */
|
|
275
|
-
export function sealEditionAuthorizationBase({ distributionId, inputs, kernel, adopted, runTests }) {
|
|
276
|
-
const composed = composeEditionAuthorizationBase({ distributionId, inputs, kernel, adopted });
|
|
282
|
+
export function sealEditionAuthorizationBase({ distributionId, inputs, kernel, adopted, biomeOrigins, runTests }) {
|
|
283
|
+
const composed = composeEditionAuthorizationBase({ distributionId, inputs, kernel, adopted, biomeOrigins });
|
|
277
284
|
runTests(composed);
|
|
278
285
|
return authorizationLockOf(distributionId, kernel, composed);
|
|
279
286
|
}
|
|
@@ -293,7 +300,7 @@ export const AuthorizationLockCheck = Object.freeze({
|
|
|
293
300
|
* exactly as the sealer would refuse it) and compares the whole lock. The
|
|
294
301
|
* tests are not re-run: an equal `modelHash` is the model the sealer tested.
|
|
295
302
|
*/
|
|
296
|
-
export function checkAuthorizationLockCurrent({ distributionId, committed, inputs, kernel, serialize }) {
|
|
303
|
+
export function checkAuthorizationLockCurrent({ distributionId, committed, inputs, kernel, biomeOrigins, serialize }) {
|
|
297
304
|
if (kernel === null) {
|
|
298
305
|
return committed === null ? AuthorizationLockCheck.GREEN : AuthorizationLockCheck.NOT_SEALABLE;
|
|
299
306
|
}
|
|
@@ -302,6 +309,7 @@ export function checkAuthorizationLockCurrent({ distributionId, committed, input
|
|
|
302
309
|
distributionId,
|
|
303
310
|
inputs,
|
|
304
311
|
kernel,
|
|
312
|
+
biomeOrigins,
|
|
305
313
|
adopted: adoptedFromLock(committed),
|
|
306
314
|
});
|
|
307
315
|
return serialize(authorizationLockOf(distributionId, kernel, composed)) === serialize(committed)
|
|
@@ -316,7 +324,7 @@ export function checkAuthorizationLockCurrent({ distributionId, committed, input
|
|
|
316
324
|
* entries plus the edition's source-walked ones). Returns an
|
|
317
325
|
* `AuthorizationLockCheck` member; throws on a refusal.
|
|
318
326
|
*/
|
|
319
|
-
export function checkEditionAuthorizationLock({ distributionId, editionDir, lockBiomes, index, kernel, serialize }) {
|
|
327
|
+
export function checkEditionAuthorizationLock({ distributionId, editionDir, lockBiomes, biomeOrigins, index, kernel, serialize }) {
|
|
320
328
|
if (!Array.isArray(index)) {
|
|
321
329
|
throw new Error(`${distributionId}: the authorization check needs the index the lock was resolved from.`);
|
|
322
330
|
}
|
|
@@ -325,6 +333,7 @@ export function checkEditionAuthorizationLock({ distributionId, editionDir, lock
|
|
|
325
333
|
committed: readAuthorizationLock(editionDir),
|
|
326
334
|
inputs: editionAuthorizationModuleInputs(lockBiomes, new Map(index.map((e) => [e.id, e])), kernel),
|
|
327
335
|
kernel,
|
|
336
|
+
biomeOrigins,
|
|
328
337
|
serialize,
|
|
329
338
|
});
|
|
330
339
|
}
|
|
@@ -360,6 +360,72 @@ function committedRepositoryPinsToPreserve(repoScope) {
|
|
|
360
360
|
);
|
|
361
361
|
}
|
|
362
362
|
|
|
363
|
+
/**
|
|
364
|
+
* `producerWorkflowPins` are operator-stated TRUST DATA: which commit of a
|
|
365
|
+
* producer repository's workflow the verifier believes. Nothing is derived (not
|
|
366
|
+
* from `repositoryPins`, not from a policy row); the committed pins are kept
|
|
367
|
+
* exactly, and `--producer-workflow-pin <owner/repo>=<40-hex>` sets or updates
|
|
368
|
+
* one. A bootstrap index with none committed writes `[]`, which the kernel allows.
|
|
369
|
+
*/
|
|
370
|
+
export function mergeProducerWorkflowPins(committedPins, statedPins) {
|
|
371
|
+
const merged = new Map(
|
|
372
|
+
(Array.isArray(committedPins) ? committedPins : []).map((pin) => [
|
|
373
|
+
pin.repository,
|
|
374
|
+
{ repository: pin.repository, workflowCommit: pin.workflowCommit },
|
|
375
|
+
]),
|
|
376
|
+
);
|
|
377
|
+
for (const pin of statedPins) {
|
|
378
|
+
merged.set(pin.repository, { repository: pin.repository, workflowCommit: pin.workflowCommit });
|
|
379
|
+
}
|
|
380
|
+
return [...merged.values()].sort((left, right) => compareStrings(left.repository, right.repository));
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const PRODUCER_WORKFLOW_PIN_FLAG = '--producer-workflow-pin';
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Every `--producer-workflow-pin <owner/repo>=<40-hex>` of the argv. A malformed
|
|
387
|
+
* value, or a repository stated twice, is refused here; the repository and
|
|
388
|
+
* commit grammar is the kernel's, applied by the index parse.
|
|
389
|
+
*/
|
|
390
|
+
export function parseProducerWorkflowPinFlags(argv) {
|
|
391
|
+
const pins = [];
|
|
392
|
+
const remaining = [];
|
|
393
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
394
|
+
if (argv[index] !== PRODUCER_WORKFLOW_PIN_FLAG) {
|
|
395
|
+
remaining.push(argv[index]);
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
const value = argv[index + 1];
|
|
399
|
+
const separator = value?.indexOf('=') ?? -1;
|
|
400
|
+
if (separator < 1 || value.startsWith('--')) {
|
|
401
|
+
throw new Error(`${PRODUCER_WORKFLOW_PIN_FLAG} requires <owner/repo>=<40-hex-commit>`);
|
|
402
|
+
}
|
|
403
|
+
const repository = value.slice(0, separator);
|
|
404
|
+
const workflowCommit = value.slice(separator + 1);
|
|
405
|
+
if (pins.some((pin) => pin.repository === repository)) {
|
|
406
|
+
throw new Error(`${PRODUCER_WORKFLOW_PIN_FLAG} names ${repository} more than once`);
|
|
407
|
+
}
|
|
408
|
+
if (!COMMIT_PATTERN.test(workflowCommit)) {
|
|
409
|
+
throw new Error(`${PRODUCER_WORKFLOW_PIN_FLAG} ${repository}: the commit must be a full 40-hex sha`);
|
|
410
|
+
}
|
|
411
|
+
pins.push({ repository, workflowCommit });
|
|
412
|
+
index += 1;
|
|
413
|
+
}
|
|
414
|
+
return { producerWorkflowPins: pins, remaining };
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* A `--producer-workflow-pin` is trust data for the index `--trusted-write`
|
|
419
|
+
* writes, and only there. Every other mode writes nothing, so a pin stated to
|
|
420
|
+
* one would be silently ignored; `--check-sources` included: it grades the
|
|
421
|
+
* committed index against the sources, and a pin is not a source.
|
|
422
|
+
*/
|
|
423
|
+
export function assertProducerWorkflowPinsWritten(producerWorkflowPins, flag, usage) {
|
|
424
|
+
if (producerWorkflowPins.length > 0 && flag !== '--trusted-write') {
|
|
425
|
+
throw new Error(`${PRODUCER_WORKFLOW_PIN_FLAG} states trust data for a written index: ${flag} writes none\n${usage}`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
363
429
|
/**
|
|
364
430
|
* `biomeIndex` and `platformServiceIndex` are the indexes this one is built
|
|
365
431
|
* over, INJECTED: the release CLI passes the committed ones read from disk
|
|
@@ -371,7 +437,7 @@ export async function buildTrustedIndex(
|
|
|
371
437
|
sourceAuthority,
|
|
372
438
|
kernelRoot,
|
|
373
439
|
repoScope,
|
|
374
|
-
{ biomeIndex, platformServiceIndex, kernelCommit },
|
|
440
|
+
{ biomeIndex, platformServiceIndex, kernelCommit, producerWorkflowPins = [] },
|
|
375
441
|
) {
|
|
376
442
|
const contractAuthority = await resolveContractAuthority(schemas, kernelRoot, kernelCommit);
|
|
377
443
|
const repositoryIdentities = new Map();
|
|
@@ -459,12 +525,16 @@ export async function buildTrustedIndex(
|
|
|
459
525
|
}
|
|
460
526
|
}
|
|
461
527
|
return {
|
|
462
|
-
schemaVersion:
|
|
528
|
+
schemaVersion: 2,
|
|
463
529
|
contractAuthority,
|
|
464
530
|
supplementalSubjects,
|
|
465
531
|
crossRepoBuildInputs,
|
|
466
532
|
buildStepContextDirectories,
|
|
467
533
|
repositoryPins,
|
|
534
|
+
producerWorkflowPins: mergeProducerWorkflowPins(
|
|
535
|
+
existsSync(runtimeImageSourceIndexPath()) ? readJson(runtimeImageSourceIndexPath()).producerWorkflowPins : [],
|
|
536
|
+
producerWorkflowPins,
|
|
537
|
+
),
|
|
468
538
|
platformServiceBindings: [...platformServiceRuntimeBindings()].sort(
|
|
469
539
|
(left, right) =>
|
|
470
540
|
compareStrings(left.platformService, right.platformService),
|
|
@@ -749,7 +819,7 @@ const USAGE =
|
|
|
749
819
|
'usage: build-runtime-image-source-index.mjs (--check | --check-sources | --trusted-write | ' +
|
|
750
820
|
'--repin-contract-authority --kernel-commit <40-hex-commit>) ' +
|
|
751
821
|
'[--source published|workspace] [--kernel-root <absolute-checkout>] ' +
|
|
752
|
-
'[--repo <repository> [--at <40-hex-commit>]] ' +
|
|
822
|
+
'[--repo <repository> [--at <40-hex-commit>]] [--producer-workflow-pin <owner/repo>=<40-hex-commit> ...] ' +
|
|
753
823
|
'(--trusted-write on a data root with no committed contract authority also requires --kernel-commit)';
|
|
754
824
|
|
|
755
825
|
async function main() {
|
|
@@ -757,7 +827,8 @@ async function main() {
|
|
|
757
827
|
process.argv.slice(2),
|
|
758
828
|
);
|
|
759
829
|
const { kernelCommit, remaining: afterKernelRoot } = parseKernelCommit(afterKernelRootFlag);
|
|
760
|
-
const {
|
|
830
|
+
const { producerWorkflowPins, remaining: afterWorkflowPins } = parseProducerWorkflowPinFlags(afterKernelRoot);
|
|
831
|
+
const { repoScope, remaining: afterRepoScope } = parseRepoScope(afterWorkflowPins);
|
|
761
832
|
const { exactCommit, remaining: argv } = parseExactCommit(afterRepoScope);
|
|
762
833
|
const { mode, remaining } = parseSourceModeArgs(argv);
|
|
763
834
|
if (exactCommit !== undefined && repoScope === undefined) {
|
|
@@ -773,6 +844,7 @@ async function main() {
|
|
|
773
844
|
if (kernelCommit !== undefined && ![REPIN_FLAG, '--trusted-write'].includes(flag)) {
|
|
774
845
|
throw new Error(`${KERNEL_COMMIT_FLAG} states the commit of a new contract authority: ${flag} chooses none\n${USAGE}`);
|
|
775
846
|
}
|
|
847
|
+
assertProducerWorkflowPinsWritten(producerWorkflowPins, flag, USAGE);
|
|
776
848
|
const schemas = await loadKernelDistributionSchemas();
|
|
777
849
|
if (flag === REPIN_FLAG) {
|
|
778
850
|
if (repoScope !== undefined || exactCommit !== undefined) {
|
|
@@ -828,6 +900,7 @@ async function main() {
|
|
|
828
900
|
biomeIndex: readJson(biomeIndexPath()),
|
|
829
901
|
platformServiceIndex: readJson(join(dataRoot(), 'platform-service-index.json')),
|
|
830
902
|
kernelCommit,
|
|
903
|
+
producerWorkflowPins,
|
|
831
904
|
},
|
|
832
905
|
),
|
|
833
906
|
);
|
package/edition/catalogue.mjs
CHANGED
|
@@ -18,9 +18,13 @@ import { serializeJson } from './lib.mjs';
|
|
|
18
18
|
export const CATALOGUE_FILE = EDITION_OFFERING_LAYERS.catalogue;
|
|
19
19
|
export const CATALOGUE_SCHEMA = 'xema.edition-catalogue/v1';
|
|
20
20
|
|
|
21
|
-
const TOP_KEYS = ['schema', 'offeringId', 'channel', 'biomes', 'sourceTreeIds', 'platformServices', 'images', 'chartSet', 'contractAuthority', 'ceiling'];
|
|
21
|
+
const TOP_KEYS = ['schema', 'offeringId', 'channel', 'biomes', 'sourceTreeIds', 'platformServices', 'images', 'chartSet', 'contractAuthority', 'kernel', 'ceiling'];
|
|
22
22
|
const IMAGES_KEYS = ['sourceHashAlgorithm', 'subjects', 'platformServiceBindings', 'webBiomes'];
|
|
23
23
|
const CEILING_KEYS = ['trustPolicy', 'trustCeiling'];
|
|
24
|
+
const KERNEL_KEYS = ['name', 'version', 'integrity'];
|
|
25
|
+
const KERNEL_PACKAGE = '@xemahq/contracts';
|
|
26
|
+
const EXACT_SEMVER = /^\d+\.\d+\.\d+$/;
|
|
27
|
+
const NPM_INTEGRITY = /^sha512-[A-Za-z0-9+/]{86}==$/;
|
|
24
28
|
const DIGEST = /^sha256:[a-f0-9]{64}$/;
|
|
25
29
|
const GIT_TREE_ID = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/;
|
|
26
30
|
|
|
@@ -76,6 +80,14 @@ export function parseCatalogue(value) {
|
|
|
76
80
|
}
|
|
77
81
|
if (!isObject(value.chartSet)) {throw new CatalogueError(`${CATALOGUE_FILE}: chartSet must be the offering's chart-set lock`);}
|
|
78
82
|
if (!isObject(value.contractAuthority)) {throw new CatalogueError(`${CATALOGUE_FILE}: contractAuthority must be the runtime image source index's authority`);}
|
|
83
|
+
exactKeys(value.kernel, KERNEL_KEYS, `${CATALOGUE_FILE} kernel`);
|
|
84
|
+
if (value.kernel.name !== KERNEL_PACKAGE) {throw new CatalogueError(`${CATALOGUE_FILE}: kernel.name must be ${KERNEL_PACKAGE}, not ${JSON.stringify(value.kernel.name)}`);}
|
|
85
|
+
if (typeof value.kernel.version !== 'string' || !EXACT_SEMVER.test(value.kernel.version)) {
|
|
86
|
+
throw new CatalogueError(`${CATALOGUE_FILE}: kernel.version must be an exact x.y.z version, not ${JSON.stringify(value.kernel.version)}`);
|
|
87
|
+
}
|
|
88
|
+
if (typeof value.kernel.integrity !== 'string' || !NPM_INTEGRITY.test(value.kernel.integrity)) {
|
|
89
|
+
throw new CatalogueError(`${CATALOGUE_FILE}: kernel.integrity must be an npm sha512 integrity (sha512-<base64>)`);
|
|
90
|
+
}
|
|
79
91
|
exactKeys(value.ceiling, CEILING_KEYS, `${CATALOGUE_FILE} ceiling`);
|
|
80
92
|
return value;
|
|
81
93
|
}
|
|
@@ -94,6 +106,7 @@ export function buildCatalogue({
|
|
|
94
106
|
biomeIndex,
|
|
95
107
|
platformServiceIndex,
|
|
96
108
|
contractAuthority,
|
|
109
|
+
kernel,
|
|
97
110
|
leaf,
|
|
98
111
|
sourceTreeIds,
|
|
99
112
|
}) {
|
|
@@ -122,6 +135,7 @@ export function buildCatalogue({
|
|
|
122
135
|
},
|
|
123
136
|
chartSet: chartSetLock,
|
|
124
137
|
contractAuthority,
|
|
138
|
+
kernel,
|
|
125
139
|
ceiling: { trustPolicy: leaf?.trustPolicy ?? null, trustCeiling: leaf?.trustCeiling ?? null },
|
|
126
140
|
});
|
|
127
141
|
}
|
|
@@ -133,14 +147,3 @@ export function writeCatalogue(outDir, catalogue) {
|
|
|
133
147
|
writeFileSync(path, serializeJson(catalogue));
|
|
134
148
|
return path;
|
|
135
149
|
}
|
|
136
|
-
|
|
137
|
-
const KERNEL_COMMIT = /^[0-9a-f]{40}$/;
|
|
138
|
-
|
|
139
|
-
/** The commit the catalogue's contract authority was taken at (40 hex); read without the kernel schemas, which are not checked out yet where this is used. */
|
|
140
|
-
export function contractAuthoritySourceCommit(catalogue) {
|
|
141
|
-
const commit = catalogue.contractAuthority.sourceCommit;
|
|
142
|
-
if (typeof commit !== 'string' || !KERNEL_COMMIT.test(commit)) {
|
|
143
|
-
throw new CatalogueError(`${CATALOGUE_FILE}: contractAuthority.sourceCommit is not a 40-hex commit: ${JSON.stringify(commit)}`);
|
|
144
|
-
}
|
|
145
|
-
return commit;
|
|
146
|
-
}
|
|
@@ -57,32 +57,15 @@ export function parseCustomerDefinition(schemas, raw) {
|
|
|
57
57
|
} catch (error) {
|
|
58
58
|
throw new CustomerDefinitionError(error.message, { cause: error });
|
|
59
59
|
}
|
|
60
|
-
assertNoVendorReservedDomain(schemas, definition);
|
|
61
60
|
return { definition, mirror };
|
|
62
61
|
}
|
|
63
62
|
|
|
64
|
-
/** The trust domains only the vendor places a biome in: the kernel's first-party domain. */
|
|
65
|
-
export const vendorReservedTrustDomains = (schemas) => [schemas.FIRST_PARTY_TRUST_DOMAIN];
|
|
66
|
-
|
|
67
|
-
/** A customer's `trustCeiling` assigns its own biomes and never into a vendor-reserved domain. */
|
|
68
|
-
function assertNoVendorReservedDomain(schemas, definition) {
|
|
69
|
-
const reserved = new Set(vendorReservedTrustDomains(schemas));
|
|
70
|
-
for (const assignment of definition.trustCeiling?.assignments ?? []) {
|
|
71
|
-
if (reserved.has(assignment.domain)) {
|
|
72
|
-
throw new CustomerDefinitionError(
|
|
73
|
-
`customer definition "${definition.id}": \`trustCeiling\` assigns ${assignment.biomes.map((biome) => `biome "${biome}"`).join(', ')} ` +
|
|
74
|
-
`to trust domain "${assignment.domain}", which is reserved to the vendor; a customer's biomes are assigned a domain of the customer's own`,
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
63
|
/**
|
|
81
64
|
* The trustCeiling must assign every customer-source biome (a biome the resolved lock draws
|
|
82
|
-
* from the definition's own `sources[]`) and no other: the kernel's `customerTrustCeilingIssues`.
|
|
65
|
+
* from the definition's own `sources[]`) and no other, and none in a domain the catalogue's ceiling reserves: the kernel's `customerTrustCeilingIssues`.
|
|
83
66
|
*/
|
|
84
|
-
export function assertTrustCeilingAssignsCustomerBiomes(schemas, definition, customerBiomeIds) {
|
|
85
|
-
const issues = schemas.customerTrustCeilingIssues(definition, customerBiomeIds);
|
|
67
|
+
export function assertTrustCeilingAssignsCustomerBiomes(schemas, definition, customerBiomeIds, catalogueTrustCeiling) {
|
|
68
|
+
const issues = schemas.customerTrustCeilingIssues(definition, customerBiomeIds, catalogueTrustCeiling);
|
|
86
69
|
if (issues.length > 0) {
|
|
87
70
|
throw new CustomerDefinitionError(`customer definition "${definition.id}": ${issues.join('; ')}`);
|
|
88
71
|
}
|
|
@@ -72,7 +72,7 @@ export function declaredSourceNamesOf(rawDefinition) {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
/** Resolve and seal the develop customer edition; returns the four locks and the frozen provenance pins. */
|
|
75
|
-
export async function resolveCustomerDevelopEdition({ rawDefinition, catalogue, catalogueDigest, pins, sourceImages, env = process.env, sealOptions = {},
|
|
75
|
+
export async function resolveCustomerDevelopEdition({ rawDefinition, catalogue, catalogueDigest, pins, sourceImages, env = process.env, sealOptions = {}, adoptKernel, materialize }) {
|
|
76
76
|
const frozen = customerDevelopPins({ pins, declaredSourceNames: declaredSourceNamesOf(rawDefinition), catalogueDigest });
|
|
77
77
|
const resolved = await resolveCustomerEdition({
|
|
78
78
|
rawDefinition,
|
|
@@ -81,7 +81,7 @@ export async function resolveCustomerDevelopEdition({ rawDefinition, catalogue,
|
|
|
81
81
|
sourceImages,
|
|
82
82
|
pins: frozen,
|
|
83
83
|
catalogueDigest,
|
|
84
|
-
...(
|
|
84
|
+
...(adoptKernel === undefined ? {} : { adoptKernel }),
|
|
85
85
|
...(materialize === undefined ? {} : { materialize }),
|
|
86
86
|
sealOwn: ({ distributionId, subjects }) => sealDevelopImageSubjects({ distributionId, subjects, env, ...sealOptions }),
|
|
87
87
|
});
|
|
@@ -6,12 +6,14 @@
|
|
|
6
6
|
// xema-deploy charts' registry manifest digests, in the release roster shape. Nothing here writes to the editions
|
|
7
7
|
// trees; the caller writes the artifact (edition-artifact.mjs).
|
|
8
8
|
|
|
9
|
+
import { adapterHostRow, assertAdapterBundlesMatchHost, fetchRegistryManifest } from './adapter-bundled-versions.mjs';
|
|
9
10
|
import { biomeSourceTreeIds, sealLock } from './biome-content.mjs';
|
|
10
11
|
import { buildCatalogue } from './catalogue.mjs';
|
|
11
12
|
import { sealDevelopImages } from './develop-seal.mjs';
|
|
12
13
|
import { assertFrozenCheckouts, buildSnapshotInputs } from './develop-snapshot-inputs.mjs';
|
|
13
14
|
import { EditionChannel } from './edition-constants.mjs';
|
|
14
|
-
import { deployRoot } from './edition-workspace.mjs';
|
|
15
|
+
import { deployRoot, kernelPackageDir } from './edition-workspace.mjs';
|
|
16
|
+
import { kernelPackageOfAuthority } from './kernel-package.mjs';
|
|
15
17
|
import { loadKernelDistributionSchemas } from './lib.mjs';
|
|
16
18
|
import { deployPinKey } from './pin-source.mjs';
|
|
17
19
|
import { resolveDevelopChartSet } from './resolve-chart-set.mjs';
|
|
@@ -19,11 +21,12 @@ import { composeAuthorizationBase, resolveDistributionWithIndex } from './resolv
|
|
|
19
21
|
import { externalSourceTrees, resolveImages, sourceTreeLocator } from './resolve-images.mjs';
|
|
20
22
|
import { createPinnedSourceAuthority } from './source-authority.mjs';
|
|
21
23
|
|
|
22
|
-
export async function resolveDevelopEdition({ id, pins, env = process.env, sealOptions = {}, chartDigestLookup }) {
|
|
24
|
+
export async function resolveDevelopEdition({ id, pins, env = process.env, sealOptions = {}, chartDigestLookup, fetchKernelTarball, fetchManifest = fetchRegistryManifest }) {
|
|
23
25
|
const schemas = await loadKernelDistributionSchemas();
|
|
24
26
|
assertFrozenCheckouts(pins);
|
|
25
27
|
const inputs = await buildSnapshotInputs({ id, pins, schemas });
|
|
26
28
|
const { lock, index } = await resolveDistributionWithIndex(id, { inputs });
|
|
29
|
+
await assertAdapterBundlesMatchHost({ lock, hostRow: adapterHostRow(index), fetchManifest });
|
|
27
30
|
const authorizationLock = await composeAuthorizationBase(id, lock, index);
|
|
28
31
|
const { imageLock } = await resolveImages(id, { inputs, lock });
|
|
29
32
|
const sealedImageLock = await sealDevelopImages({ imageLock, env, ...sealOptions });
|
|
@@ -59,6 +62,11 @@ export async function resolveDevelopEdition({ id, pins, env = process.env, sealO
|
|
|
59
62
|
biomeIndex: inputs.biomeIndex,
|
|
60
63
|
platformServiceIndex: inputs.platformServiceIndex,
|
|
61
64
|
contractAuthority: inputs.runtimeImageSourceIndex.contractAuthority,
|
|
65
|
+
kernel: await kernelPackageOfAuthority({
|
|
66
|
+
kernelPackageDir: kernelPackageDir(),
|
|
67
|
+
contractAuthority: inputs.runtimeImageSourceIndex.contractAuthority,
|
|
68
|
+
...(fetchKernelTarball === undefined ? {} : { fetchTarball: fetchKernelTarball }),
|
|
69
|
+
}),
|
|
62
70
|
leaf: inputs.composed.leaf,
|
|
63
71
|
});
|
|
64
72
|
return { distributionLock: sealedLock, imageLock: sealedImageLock, authorizationLock, chartSetLock, catalogue };
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// customer's own pins. --pins holds exactly one `sources/<name>` commit per source the definition
|
|
16
16
|
// declares (`freeze-sources --definition` writes it); the catalogue digest is frozen into the descriptor's
|
|
17
17
|
// provenance pins as `catalogue`. The id is the definition's. Takes --data-root, --editions-root,
|
|
18
|
-
// --sources-root, --
|
|
18
|
+
// --sources-root, --cache-dir (no --deploy-root, no --kernel: the kernel is the catalogue's, from npm).
|
|
19
19
|
// target <id> --pins <file> --registry <root>
|
|
20
20
|
// print <root>/<develop segment>/xema-edition-<id>:<snapshot hash>. The root is always the caller's
|
|
21
21
|
// registry root (e.g. registry.example.com/acme); the develop namespace segment and the repository
|
|
@@ -31,8 +31,10 @@
|
|
|
31
31
|
// check-existing <id> --pins <file> --artifact <dir> --engine-image <repository>@sha256:<digest>
|
|
32
32
|
// refuse to reuse a pulled artifact whose descriptor disagrees with this snapshot.
|
|
33
33
|
// check-existing --definition <file> --pins <file> --artifact <dir> --engine-image <repository>@sha256:<digest>
|
|
34
|
+
// [--catalogue-username <name> --catalogue-token-env <VAR>]
|
|
34
35
|
// the same for a customer artifact; its descriptor's catalogue provenance pin must also equal the
|
|
35
|
-
// pinned develop catalogue digest.
|
|
36
|
+
// pinned develop catalogue digest. It fetches that catalogue (as `resolve --definition` does) to take the
|
|
37
|
+
// kernel from it. Takes --data-root, --cache-dir.
|
|
36
38
|
// check-overrides --overrides <json>
|
|
37
39
|
// refuse a pins override that would select the tooling this run executes
|
|
38
40
|
// (the edition tooling's own repository); the workflow runs it before any other checkout.
|
|
@@ -42,9 +44,6 @@
|
|
|
42
44
|
// freeze-sources --definition <file> --out <file>
|
|
43
45
|
// a CUSTOMER edition: one `sources/<name>` pin per source the definition declares, each the head of
|
|
44
46
|
// refs/heads/develop of that source's repository, resolved once; written exclusively (wx).
|
|
45
|
-
// kernel-commit --definition <file> [--catalogue-username <name> --catalogue-token-env <VAR>]
|
|
46
|
-
// fetch the pinned develop catalogue and print its contractAuthority.sourceCommit (one 40-hex line):
|
|
47
|
-
// the commit the kernel contracts are checked out at. Takes --data-root only (no --kernel).
|
|
48
47
|
//
|
|
49
48
|
// Tokens come from the environment (EVIDENCE_REPOSITORIES_TOKEN, XEMA_GHCR_PULL_TOKEN),
|
|
50
49
|
// never argv. Nothing here writes git or publishes a release edition.
|
|
@@ -54,7 +53,7 @@ import { resolve } from 'node:path';
|
|
|
54
53
|
|
|
55
54
|
import { fetchCatalogue } from './catalogue-fetch.mjs';
|
|
56
55
|
import { readCataloguePin } from './catalogue-pin.mjs';
|
|
57
|
-
import {
|
|
56
|
+
import { writeCatalogue } from './catalogue.mjs';
|
|
58
57
|
import { CATALOGUE_TOKEN_ENV_FLAG, CATALOGUE_USERNAME_FLAG, DEFINITION_FLAG, SOURCE_IMAGE_FLAG } from './customer-flags.mjs';
|
|
59
58
|
import { customerDevelopFrame, resolveCustomerDevelopEdition } from './develop-customer-edition.mjs';
|
|
60
59
|
import { resolveDevelopEdition } from './develop-edition-resolve.mjs';
|
|
@@ -65,28 +64,36 @@ import { editionWorkspaceConfig } from './edition-config.mjs';
|
|
|
65
64
|
import { EditionChannel } from './edition-constants.mjs';
|
|
66
65
|
import { WorkspaceRoot, configureEditionWorkspaceFromProcess, isEntryModule, runtimeImageSourceIndexPath } from './edition-workspace.mjs';
|
|
67
66
|
import { descriptorEvidenceTrust } from './evidence-trust-policy.mjs';
|
|
67
|
+
import { holdSource } from './git-source-auth.mjs';
|
|
68
|
+
import { adoptCatalogueKernel } from './kernel-package.mjs';
|
|
68
69
|
import { loadKernelDistributionSchemas, serializeJson } from './lib.mjs';
|
|
69
70
|
import { snapshotPinSource } from './pin-source.mjs';
|
|
70
71
|
import { parseSourceImages } from './resolve-customer-edition.mjs';
|
|
71
72
|
import { composeEdition } from './resolve-distribution.mjs';
|
|
72
73
|
import { readStrictJsonFile } from './strict-json.mjs';
|
|
73
74
|
|
|
74
|
-
// The roots a verb takes. A customer form (it names --definition) takes fewer
|
|
75
|
-
//
|
|
76
|
-
//
|
|
75
|
+
// The roots a verb takes. A customer form (it names --definition) takes fewer, and never the kernel (the
|
|
76
|
+
// catalogue's kernel is downloaded from npm into the cache root): `target` none, `check-existing` the data
|
|
77
|
+
// root and the cache, `freeze-sources` the data root only, `resolve` all but the deploy root. `check-overrides`
|
|
78
|
+
// takes only the data root in either form: it judges its --overrides JSON against the pin roles the data
|
|
79
|
+
// root declares, so it reads nothing else and refuses every other root flag.
|
|
77
80
|
const VENDOR_ROOTS = Object.freeze([WorkspaceRoot.Data, WorkspaceRoot.Editions, WorkspaceRoot.Sources, WorkspaceRoot.Kernel, WorkspaceRoot.Deploy, WorkspaceRoot.Cache]);
|
|
78
81
|
const CUSTOMER_ROOTS = Object.freeze({
|
|
79
|
-
resolve: [WorkspaceRoot.Data, WorkspaceRoot.Editions, WorkspaceRoot.Sources, WorkspaceRoot.
|
|
82
|
+
resolve: [WorkspaceRoot.Data, WorkspaceRoot.Editions, WorkspaceRoot.Sources, WorkspaceRoot.Cache],
|
|
80
83
|
target: [],
|
|
81
|
-
'check-existing': [WorkspaceRoot.Data, WorkspaceRoot.
|
|
84
|
+
'check-existing': [WorkspaceRoot.Data, WorkspaceRoot.Cache],
|
|
82
85
|
'freeze-sources': [WorkspaceRoot.Data],
|
|
83
|
-
'kernel-commit': [WorkspaceRoot.Data],
|
|
84
86
|
});
|
|
87
|
+
const DATA_ROOT_ONLY_COMMANDS = Object.freeze(['check-overrides']);
|
|
85
88
|
|
|
86
89
|
if (isEntryModule(import.meta.url)) {
|
|
87
90
|
const [command] = process.argv.slice(2);
|
|
88
|
-
|
|
89
|
-
|
|
91
|
+
if (DATA_ROOT_ONLY_COMMANDS.includes(command)) {
|
|
92
|
+
configureEditionWorkspaceFromProcess([WorkspaceRoot.Data], { refuseOthers: true });
|
|
93
|
+
} else {
|
|
94
|
+
const customerForm = process.argv.includes(DEFINITION_FLAG);
|
|
95
|
+
configureEditionWorkspaceFromProcess(customerForm ? (CUSTOMER_ROOTS[command] ?? VENDOR_ROOTS) : VENDOR_ROOTS, { refuseOthers: customerForm && CUSTOMER_ROOTS[command] !== undefined });
|
|
96
|
+
}
|
|
90
97
|
}
|
|
91
98
|
|
|
92
99
|
// Per verb: the vendor form (`positional` id, `values`, `switches`) and, where the verb has one, the customer
|
|
@@ -128,15 +135,7 @@ const COMMANDS = Object.freeze({
|
|
|
128
135
|
values: ['--pins', '--artifact', '--engine-image'],
|
|
129
136
|
switches: [],
|
|
130
137
|
required: ['--pins', '--artifact', '--engine-image'],
|
|
131
|
-
customer: { values: [DEFINITION_FLAG], required: ['--pins', '--artifact', '--engine-image'], refused: [] },
|
|
132
|
-
},
|
|
133
|
-
'kernel-commit': {
|
|
134
|
-
positional: false,
|
|
135
|
-
values: [],
|
|
136
|
-
switches: [],
|
|
137
|
-
required: [],
|
|
138
|
-
customerOnly: true,
|
|
139
|
-
customer: { values: [DEFINITION_FLAG, ...CREDENTIAL_FLAGS], required: [], refused: [] },
|
|
138
|
+
customer: { values: [DEFINITION_FLAG, ...CREDENTIAL_FLAGS], required: ['--pins', '--artifact', '--engine-image'], refused: [] },
|
|
140
139
|
},
|
|
141
140
|
});
|
|
142
141
|
|
|
@@ -173,7 +172,7 @@ export function parseDevelopEditionArgs(argv) {
|
|
|
173
172
|
positional.push(arg);
|
|
174
173
|
}
|
|
175
174
|
}
|
|
176
|
-
const customer = spec.customer !== undefined &&
|
|
175
|
+
const customer = spec.customer !== undefined && customerValues.some((flag) => options[flag] !== undefined);
|
|
177
176
|
if (customer) {
|
|
178
177
|
if (options[DEFINITION_FLAG] === undefined) {throw new Error(`a customer ${command} requires ${DEFINITION_FLAG}`);}
|
|
179
178
|
for (const refused of spec.customer.refused) {
|
|
@@ -205,21 +204,27 @@ function writeOnce(path, value) {
|
|
|
205
204
|
}
|
|
206
205
|
|
|
207
206
|
function headResolver(env) {
|
|
208
|
-
|
|
207
|
+
const workspace = editionWorkspaceConfig();
|
|
208
|
+
return { resolveHead: (source) => lsRemoteDevelopHead(source, { workspace, env }), holdSource: (source) => holdSource(source, workspace, env) };
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
-
/**
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
ref,
|
|
211
|
+
/** The pinned develop catalogue of a customer definition: fetched by its digest, as every customer develop verb does. */
|
|
212
|
+
function fetchPinnedDevelopCatalogue(options, env) {
|
|
213
|
+
return fetchCatalogue({
|
|
214
|
+
ref: readCataloguePin({ definitionFile: options[DEFINITION_FLAG], channel: EditionChannel.Develop }),
|
|
216
215
|
channel: EditionChannel.Develop,
|
|
217
216
|
username: options[CATALOGUE_USERNAME_FLAG],
|
|
218
217
|
tokenEnv: options[CATALOGUE_TOKEN_ENV_FLAG],
|
|
219
218
|
env,
|
|
220
219
|
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** A customer develop resolve: the artifact (descriptor and four locks, no catalogue: it is not an offering) goes to --out. */
|
|
223
|
+
async function resolveCustomer(options, env) {
|
|
224
|
+
const { catalogue, digest } = await fetchPinnedDevelopCatalogue(options, env);
|
|
221
225
|
const rawDefinition = readStrictJsonFile(options[DEFINITION_FLAG]);
|
|
222
|
-
|
|
226
|
+
// The catalogue's kernel is adopted inside the resolve, before any kernel schema is loaded; the evidence trust
|
|
227
|
+
// state loads kernel schemas, so it is read after.
|
|
223
228
|
const { id, pins, ...resolved } = await resolveCustomerDevelopEdition({
|
|
224
229
|
rawDefinition,
|
|
225
230
|
catalogue,
|
|
@@ -228,6 +233,7 @@ async function resolveCustomer(options, env) {
|
|
|
228
233
|
sourceImages: parseSourceImages(options[SOURCE_IMAGE_FLAG] ?? []),
|
|
229
234
|
env,
|
|
230
235
|
});
|
|
236
|
+
const evidenceTrustPolicy = await descriptorEvidenceTrust({ channel: EditionChannel.Develop });
|
|
231
237
|
const target = developEditionTarget(id, pins, { registry: options['--registry'] });
|
|
232
238
|
writeEditionArtifact({
|
|
233
239
|
outDir: resolve(options['--out']),
|
|
@@ -245,21 +251,23 @@ async function resolveCustomer(options, env) {
|
|
|
245
251
|
console.log(target);
|
|
246
252
|
}
|
|
247
253
|
|
|
248
|
-
/**
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
254
|
+
/** A customer check-existing: the pinned catalogue is fetched to take its kernel; the digest fetched is the digest the artifact is compared with. */
|
|
255
|
+
export async function checkExistingCustomer(options, env, { adopt = adoptCatalogueKernel } = {}) {
|
|
256
|
+
const frame = customerDevelopFrame({ definitionFile: options[DEFINITION_FLAG], pinsFile: options['--pins'] });
|
|
257
|
+
const { catalogue, digest } = await fetchPinnedDevelopCatalogue(options, env);
|
|
258
|
+
if (digest !== frame.catalogueDigest) {
|
|
259
|
+
throw new Error(`the fetched catalogue is ${digest}, not the pinned ${frame.catalogueDigest}`);
|
|
260
|
+
}
|
|
261
|
+
await adopt(catalogue);
|
|
262
|
+
assertExistingArtifactMatches({
|
|
263
|
+
artifactDir: resolve(options['--artifact']),
|
|
264
|
+
distributionId: frame.id,
|
|
257
265
|
channel: EditionChannel.Develop,
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
266
|
+
evidenceTrustPolicy: await descriptorEvidenceTrust({ channel: EditionChannel.Develop }),
|
|
267
|
+
pins: frame.pins,
|
|
268
|
+
engineImage: options['--engine-image'],
|
|
269
|
+
catalogueDigest: frame.catalogueDigest,
|
|
261
270
|
});
|
|
262
|
-
console.log(contractAuthoritySourceCommit(catalogue));
|
|
263
271
|
}
|
|
264
272
|
|
|
265
273
|
async function run(argv, env) {
|
|
@@ -297,19 +305,7 @@ async function run(argv, env) {
|
|
|
297
305
|
return;
|
|
298
306
|
}
|
|
299
307
|
case 'check-existing': {
|
|
300
|
-
if (customer) {
|
|
301
|
-
const frame = customerDevelopFrame({ definitionFile: options[DEFINITION_FLAG], pinsFile: options['--pins'] });
|
|
302
|
-
assertExistingArtifactMatches({
|
|
303
|
-
artifactDir: resolve(options['--artifact']),
|
|
304
|
-
distributionId: frame.id,
|
|
305
|
-
channel: EditionChannel.Develop,
|
|
306
|
-
evidenceTrustPolicy: await descriptorEvidenceTrust({ channel: EditionChannel.Develop }),
|
|
307
|
-
pins: frame.pins,
|
|
308
|
-
engineImage: options['--engine-image'],
|
|
309
|
-
catalogueDigest: frame.catalogueDigest,
|
|
310
|
-
});
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
308
|
+
if (customer) {return checkExistingCustomer(options, env);}
|
|
313
309
|
assertExistingArtifactMatches({
|
|
314
310
|
artifactDir: resolve(options['--artifact']),
|
|
315
311
|
distributionId: id,
|
|
@@ -332,7 +328,7 @@ async function run(argv, env) {
|
|
|
332
328
|
deploySha: options['--deploy-sha'],
|
|
333
329
|
editionsRootUrl: options['--editions-root-url'],
|
|
334
330
|
overrides: parseOverrides(options['--overrides']),
|
|
335
|
-
|
|
331
|
+
...headResolver(env),
|
|
336
332
|
});
|
|
337
333
|
writeOnce(options['--out'], pins);
|
|
338
334
|
return;
|
|
@@ -343,7 +339,7 @@ async function run(argv, env) {
|
|
|
343
339
|
options['--out'],
|
|
344
340
|
freezeCustomerSources({
|
|
345
341
|
definition: readStrictJsonFile(options[DEFINITION_FLAG]),
|
|
346
|
-
|
|
342
|
+
...headResolver(env),
|
|
347
343
|
sourceRepositoryUrlPrefix: editionWorkspaceConfig().sourceRepositoryUrlPrefix,
|
|
348
344
|
}),
|
|
349
345
|
);
|
|
@@ -354,12 +350,10 @@ async function run(argv, env) {
|
|
|
354
350
|
const declaredSources = composeEdition(parseDistribution, id, snapshotPinSource(pins)).declaredSources;
|
|
355
351
|
writeOnce(
|
|
356
352
|
options['--out'],
|
|
357
|
-
freezeSources({ pins, declaredSources, overrides: parseOverrides(options['--overrides']),
|
|
353
|
+
freezeSources({ pins, declaredSources, overrides: parseOverrides(options['--overrides']), ...headResolver(env) }),
|
|
358
354
|
);
|
|
359
355
|
return;
|
|
360
356
|
}
|
|
361
|
-
case 'kernel-commit':
|
|
362
|
-
return customerKernelCommit(options, env);
|
|
363
357
|
default:
|
|
364
358
|
throw new Error(`unhandled command ${command}`);
|
|
365
359
|
}
|