@xemahq/edition-tooling 0.0.0-stage → 0.1.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/LICENSE +175 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +47 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/edition.d.ts +4 -0
- package/dist/commands/edition.d.ts.map +1 -0
- package/dist/commands/edition.js +58 -0
- package/dist/commands/edition.js.map +1 -0
- package/dist/edition/verify-image-evidence.bundle.mjs +72104 -0
- package/dist/edition/verify-image-evidence.bundle.provenance.json +4119 -0
- package/dist/lib/exit-codes.d.ts +6 -0
- package/dist/lib/exit-codes.d.ts.map +1 -0
- package/dist/lib/exit-codes.js +21 -0
- package/dist/lib/exit-codes.js.map +1 -0
- package/dist/runtime/edition/run-entry.d.ts +10 -0
- package/dist/runtime/edition/run-entry.d.ts.map +1 -0
- package/dist/runtime/edition/run-entry.js +49 -0
- package/dist/runtime/edition/run-entry.js.map +1 -0
- package/dist/runtime/edition/verb-map.d.ts +70 -0
- package/dist/runtime/edition/verb-map.d.ts.map +1 -0
- package/dist/runtime/edition/verb-map.js +166 -0
- package/dist/runtime/edition/verb-map.js.map +1 -0
- package/edition/README.md +291 -0
- package/edition/adapter-bundled-versions.mjs +128 -0
- package/edition/adapter-host-versions.mjs +76 -0
- package/edition/assemble-producer-input.mjs +1423 -0
- package/edition/assemble-third-party-input.mjs +348 -0
- package/edition/authored-source-check.mjs +141 -0
- package/edition/authority-binding.mjs +23 -0
- package/edition/authorization-base.mjs +357 -0
- package/edition/biome-content.mjs +327 -0
- package/edition/build-biome-index.mjs +180 -0
- package/edition/build-ledger-completeness.mjs +67 -0
- package/edition/build-ledger.mjs +107 -0
- package/edition/build-platform-service-index.mjs +292 -0
- package/edition/build-runtime-image-source-index.mjs +1197 -0
- package/edition/built-verifier.mjs +55 -0
- package/edition/candidate-edition.mjs +735 -0
- package/edition/canonical-evidence-json.mjs +82 -0
- package/edition/canonical-repository-url.mjs +61 -0
- package/edition/catalogue-edition.mjs +49 -0
- package/edition/catalogue-evidence.mjs +228 -0
- package/edition/catalogue-fetch.mjs +124 -0
- package/edition/catalogue-lock-field.mjs +41 -0
- package/edition/catalogue-pin.mjs +71 -0
- package/edition/catalogue.mjs +237 -0
- package/edition/chart-set-source-commit.mjs +97 -0
- package/edition/check-committed-image-evidence.mjs +412 -0
- package/edition/checkout-indexed-source-repositories.mjs +283 -0
- package/edition/collected-refusals.mjs +59 -0
- package/edition/comment-blanker.mjs +75 -0
- package/edition/connector-source-authority.mjs +60 -0
- package/edition/contract-authority.mjs +122 -0
- package/edition/cosign-certificate-constraints.mjs +50 -0
- package/edition/cross-repo-build-inputs.mjs +158 -0
- package/edition/customer-definition.mjs +72 -0
- package/edition/customer-edition-verbs.mjs +610 -0
- package/edition/customer-flags.mjs +15 -0
- package/edition/definition-checkout.mjs +96 -0
- package/edition/develop-build-record.mjs +122 -0
- package/edition/develop-catalogue-free-resolve.mjs +282 -0
- package/edition/develop-catalogue-free-vendor.mjs +118 -0
- package/edition/develop-channel-evidence.mjs +23 -0
- package/edition/develop-channel-record.mjs +49 -0
- package/edition/develop-channel-satellites.mjs +65 -0
- package/edition/develop-chart-roster.mjs +96 -0
- package/edition/develop-chart-set.mjs +147 -0
- package/edition/develop-current-resolution.mjs +29 -0
- package/edition/develop-edition-invariants.mjs +142 -0
- package/edition/develop-edition-resolve.mjs +250 -0
- package/edition/develop-edition.mjs +332 -0
- package/edition/develop-engine-image.mjs +97 -0
- package/edition/develop-freeze.mjs +304 -0
- package/edition/develop-git-bundles.mjs +296 -0
- package/edition/develop-graph.mjs +34 -0
- package/edition/develop-image-coordinate.mjs +85 -0
- package/edition/develop-image-probe.mjs +122 -0
- package/edition/develop-image-record.mjs +260 -0
- package/edition/develop-resolve-report.mjs +159 -0
- package/edition/develop-select-records.mjs +90 -0
- package/edition/develop-selection-line.mjs +29 -0
- package/edition/develop-selection-pins.mjs +36 -0
- package/edition/develop-snapshot-inputs.mjs +112 -0
- package/edition/develop-store-root.mjs +20 -0
- package/edition/dispatched-runtime-components.mjs +172 -0
- package/edition/dist-check.mjs +1174 -0
- package/edition/distribution-evidence-set.mjs +1064 -0
- package/edition/distribution-paths.mjs +191 -0
- package/edition/edition-artifact.mjs +529 -0
- package/edition/edition-biome-manifests.mjs +85 -0
- package/edition/edition-ceiling.mjs +182 -0
- package/edition/edition-channel.mjs +27 -0
- package/edition/edition-chart-set.mjs +104 -0
- package/edition/edition-config.mjs +374 -0
- package/edition/edition-constants.mjs +110 -0
- package/edition/edition-contract-parse.mjs +179 -0
- package/edition/edition-contract.json +100 -0
- package/edition/edition-image-seal.mjs +115 -0
- package/edition/edition-kind.mjs +10 -0
- package/edition/edition-list.mjs +35 -0
- package/edition/edition-locks.mjs +78 -0
- package/edition/edition-pin-verbs.mjs +183 -0
- package/edition/edition-pin.mjs +17 -0
- package/edition/edition-pinned-inputs.mjs +116 -0
- package/edition/edition-protocol.mjs +285 -0
- package/edition/edition-publisher.mjs +21 -0
- package/edition/edition-seal.mjs +257 -0
- package/edition/edition-source-pins.mjs +211 -0
- package/edition/edition-unpin.mjs +16 -0
- package/edition/edition-workspace.mjs +205 -0
- package/edition/evidence-checkout.mjs +439 -0
- package/edition/evidence-grammars.mjs +18 -0
- package/edition/evidence-input-mode.mjs +136 -0
- package/edition/evidence-trust-pin.mjs +392 -0
- package/edition/evidence-trust-policy.mjs +374 -0
- package/edition/external-source-walk.mjs +486 -0
- package/edition/first-party-source-ids.mjs +62 -0
- package/edition/fleet-coordinate-finality.mjs +363 -0
- package/edition/fulcio-certificate-extensions.mjs +278 -0
- package/edition/git-json.mjs +54 -0
- package/edition/git-source-auth.mjs +131 -0
- package/edition/image-delivery-entitlement-descriptor.mjs +106 -0
- package/edition/image-evidence-replay-state.mjs +242 -0
- package/edition/image-evidence.mjs +1328 -0
- package/edition/image-lock-service-rows.mjs +26 -0
- package/edition/image-subject-identity.mjs +19 -0
- package/edition/images-from-record.mjs +216 -0
- package/edition/indexes-at-pins.mjs +13 -0
- package/edition/infra-images-lock.mjs +105 -0
- package/edition/kernel-capability-domain.mjs +30 -0
- package/edition/kernel-contract-authority.mjs +526 -0
- package/edition/kernel-contract-source-path-set.mjs +96 -0
- package/edition/kernel-package.mjs +237 -0
- package/edition/kernel-pin-source.mjs +63 -0
- package/edition/kernel-protocol-choice.mjs +239 -0
- package/edition/kernel-signer-kind.mjs +23 -0
- package/edition/lib.mjs +3322 -0
- package/edition/locked-bundle-signer.mjs +297 -0
- package/edition/map-with-concurrency.mjs +19 -0
- package/edition/no-follow-file.mjs +94 -0
- package/edition/oci-evidence-verbs.mjs +421 -0
- package/edition/oci-image-source-evidence.mjs +377 -0
- package/edition/operation-grant-admission.mjs +285 -0
- package/edition/operation-requirements.mjs +282 -0
- package/edition/oras-process.mjs +35 -0
- package/edition/oras.mjs +664 -0
- package/edition/pin-source.mjs +242 -0
- package/edition/pinned-cosign.mjs +86 -0
- package/edition/plan-image-evidence-refresh.mjs +428 -0
- package/edition/platform-lock-readability.mjs +903 -0
- package/edition/platform-service-class.mjs +85 -0
- package/edition/platform-service-ownership.mjs +91 -0
- package/edition/platform-service-source-authority.mjs +105 -0
- package/edition/positive-closure.mjs +162 -0
- package/edition/previous-image-lock.mjs +25 -0
- package/edition/produce-distribution-completeness-evidence.mjs +317 -0
- package/edition/produce-edition-catalogue-evidence.mjs +252 -0
- package/edition/produce-image-delivery-entitlement-evidence.mjs +302 -0
- package/edition/produce-image-source-evidence.mjs +788 -0
- package/edition/producer-evidence-bundle.mjs +201 -0
- package/edition/producer-evidence-index-file.mjs +4 -0
- package/edition/record-refused.mjs +11 -0
- package/edition/registry-client-factory.mjs +21 -0
- package/edition/registry-credentials.mjs +225 -0
- package/edition/release-catalogue.mjs +48 -0
- package/edition/release-channel-guard.mjs +108 -0
- package/edition/release-edition-artifact.mjs +152 -0
- package/edition/release-edition.mjs +54 -0
- package/edition/remedy-command.mjs +89 -0
- package/edition/repin-evidence-trust.mjs +109 -0
- package/edition/required-resource-provider.mjs +58 -0
- package/edition/resolve-chart-set.mjs +376 -0
- package/edition/resolve-customer-edition.mjs +374 -0
- package/edition/resolve-distribution.mjs +2642 -0
- package/edition/resolve-edition.mjs +192 -0
- package/edition/resolve-images.mjs +2344 -0
- package/edition/resolve-infra-images.mjs +88 -0
- package/edition/seal-image-digests.mjs +954 -0
- package/edition/sealed-distribution-lock.mjs +22 -0
- package/edition/sealed-evidence-accept.mjs +82 -0
- package/edition/shallow-checkout.mjs +18 -0
- package/edition/sigstore-bundle-media-type.mjs +4 -0
- package/edition/source-authority.mjs +713 -0
- package/edition/source-closure.mjs +115 -0
- package/edition/store-publisher-trust.mjs +40 -0
- package/edition/strict-json.mjs +121 -0
- package/edition/string-order.mjs +5 -0
- package/edition/third-party-admission.mjs +118 -0
- package/edition/third-party-bundle-registry.mjs +349 -0
- package/edition/third-party-certificate-extensions.mjs +58 -0
- package/edition/third-party-image-pins.mjs +41 -0
- package/edition/third-party-pins-trusted.mjs +24 -0
- package/edition/third-party-producer-evidence.mjs +76 -0
- package/edition/third-party-source-check.mjs +28 -0
- package/edition/third-party-source-context.mjs +41 -0
- package/edition/third-party-source-evidence.mjs +326 -0
- package/edition/third-party-source-flags.mjs +148 -0
- package/edition/third-party-source-pins.mjs +270 -0
- package/edition/third-party-source-uniqueness.mjs +54 -0
- package/edition/third-party-source-walk.mjs +118 -0
- package/edition/third-party-source-writer.mjs +228 -0
- package/edition/third-party-subjects.mjs +128 -0
- package/edition/trust-policy-arming-verb.mjs +60 -0
- package/edition/trust-policy-arming.mjs +144 -0
- package/edition/trust-policy-image-ownership.mjs +85 -0
- package/edition/trusted-executable.mjs +166 -0
- package/edition/verifier-binding.mjs +45 -0
- package/edition/verifier-digest-pin.mjs +17 -0
- package/edition/verifier-files.mjs +8 -0
- package/edition/verifier-kernel-schemas.mjs +22 -0
- package/edition/verifier-provenance.mjs +64 -0
- package/edition/verify-catalogue-evidence.mjs +247 -0
- package/edition/verify-image-evidence.mjs +493 -0
- package/edition/verify-image-platforms.mjs +523 -0
- package/edition/why.mjs +56 -0
- package/edition/workspace-sections.mjs +287 -0
- package/package.json +83 -4
- package/README.md +0 -3
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
// develop-edition-resolve — resolve and seal one DEVELOP edition from a snapshot.
|
|
2
|
+
//
|
|
3
|
+
// The whole pipeline, in the order the release runs it (every check reports its refusal; see develop-resolve-report.mjs), over the snapshot's
|
|
4
|
+
// injected inputs: distribution lock -> plane invariants (reservation table,
|
|
5
|
+
// confinement; develop-edition-invariants.mjs) -> authorization base (composed, never
|
|
6
|
+
// written) -> images lock -> develop seal, plus the chart-set lock: the frozen
|
|
7
|
+
// deploy commit's develop chart set (develop-chart-set.mjs). Nothing here writes to the editions
|
|
8
|
+
// trees; the caller writes the artifact (edition-artifact.mjs).
|
|
9
|
+
//
|
|
10
|
+
// What the resolve takes on trust and what it proves is its LANE's, never a flag of the resolve:
|
|
11
|
+
//
|
|
12
|
+
// the channel lane (`developChannelLane`, `develop resolve --images-from --channel`, every deployment we run):
|
|
13
|
+
// the engine image is the caller's `--engine-image` digest; every first-party image digest is the build record's
|
|
14
|
+
// (images-from-record.mjs), satellites checked only for consistency with their producers' channel records; a
|
|
15
|
+
// git-sourced OCI bundle is taken by the digest its tag serves (develop-git-bundles.mjs
|
|
16
|
+
// `createDevelopGitBundleDigestReader`); the third-party pins are read as the data root holds them. No cosign,
|
|
17
|
+
// no content hash, no bundle-signer pre-check and no trust-pin read.
|
|
18
|
+
// the verified vendor lane of the catalogue-free customer resolve (develop-catalogue-free-vendor.mjs, the official
|
|
19
|
+
// lane): the engine found and cosign-proven, the images probed by computed content hash, the git-sourced bundles
|
|
20
|
+
// cosign-verified, the bundle signers asserted, the third-party pins held to the evidence trust pin.
|
|
21
|
+
import { adapterHostRow, assertAdapterBundlesMatchHost, fetchRegistryManifest } from './adapter-bundled-versions.mjs';
|
|
22
|
+
import { biomeSourceTreeIds, readBiomeSourceFacts, sealLock } from './biome-content.mjs';
|
|
23
|
+
import { buildCatalogue } from './catalogue.mjs';
|
|
24
|
+
import { resolveDevelopChartSet } from './develop-chart-set.mjs';
|
|
25
|
+
import { assertDevelopEditionInvariants } from './develop-edition-invariants.mjs';
|
|
26
|
+
import { createDevelopGitBundleDigestReader } from './develop-git-bundles.mjs';
|
|
27
|
+
import { createDevelopImageProbe } from './develop-image-probe.mjs';
|
|
28
|
+
import { BLOCKED, DevelopResolveCheck, RESOLVE_CHECK_DEPENDENCIES, createResolveReport } from './develop-resolve-report.mjs';
|
|
29
|
+
import { assertFrozenCheckouts, buildDevelopSnapshotInputs } from './develop-snapshot-inputs.mjs';
|
|
30
|
+
import { EditionChannel } from './edition-constants.mjs';
|
|
31
|
+
import { sealImages } from './edition-image-seal.mjs';
|
|
32
|
+
import { assertKernelEditionProtocol } from './edition-protocol.mjs';
|
|
33
|
+
import { dataRoot, kernelPackageDir } from './edition-workspace.mjs';
|
|
34
|
+
import { firstPartySources } from './first-party-source-ids.mjs';
|
|
35
|
+
import { resolveImagesFromRecord, sealImagesFromRecord } from './images-from-record.mjs';
|
|
36
|
+
import { kernelRowOfAuthority } from './kernel-package.mjs';
|
|
37
|
+
import { loadKernelDistributionSchemas } from './lib.mjs';
|
|
38
|
+
import { GitSourcedBundle, assertLockBundleSigners, isBundleSignerRefusal } from './locked-bundle-signer.mjs';
|
|
39
|
+
import { admitDistributionLock, ungrantedOptionalLinesOfRefusal } from './operation-grant-admission.mjs';
|
|
40
|
+
import { deployPinKey } from './pin-source.mjs';
|
|
41
|
+
import { admitResolvedLock, composeAuthorizationBase, prepareDistributionWithIndex } from './resolve-distribution.mjs';
|
|
42
|
+
import { externalSourceTrees, resolveImages, sourceTreeLocator } from './resolve-images.mjs';
|
|
43
|
+
import { createPinnedSourceAuthority } from './source-authority.mjs';
|
|
44
|
+
import { loadThirdPartySourcePins } from './third-party-source-pins.mjs';
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The steps the resolve runs, named so a test can stand in for the ones that
|
|
48
|
+
* need a real snapshot and observe that the later ones are never reached.
|
|
49
|
+
*/
|
|
50
|
+
export const DEVELOP_EDITION_STEPS = Object.freeze({
|
|
51
|
+
assertFrozenCheckouts,
|
|
52
|
+
buildSnapshotInputs: buildDevelopSnapshotInputs,
|
|
53
|
+
assertKernelEditionProtocol,
|
|
54
|
+
prepareDistributionWithIndex,
|
|
55
|
+
assertDevelopEditionInvariants,
|
|
56
|
+
assertAdapterBundlesMatchHost,
|
|
57
|
+
composeAuthorizationBase,
|
|
58
|
+
resolveImages,
|
|
59
|
+
sealImages,
|
|
60
|
+
sealImagesFromRecord,
|
|
61
|
+
resolveDevelopChartSet,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const stderrProgress = (line) => process.stderr.write(`${line}\n`);
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The first half of every develop resolve: the frozen snapshot's inputs and the
|
|
68
|
+
* distribution lock composed over them, NOT yet admitted. `resolveDevelopEdition`
|
|
69
|
+
* admits it as one of its checks; `resolveDevelopSnapshot` admits it at once. Both
|
|
70
|
+
* start here, so the content hash each computes for a subject is the same by construction.
|
|
71
|
+
*/
|
|
72
|
+
async function prepareDevelopSnapshot({ id, pins, steps, phase, developGitBundles }) {
|
|
73
|
+
const schemas = await loadKernelDistributionSchemas();
|
|
74
|
+
steps.assertFrozenCheckouts(pins);
|
|
75
|
+
const inputs = await steps.buildSnapshotInputs({ id, pins, schemas });
|
|
76
|
+
phase('snapshot inputs built');
|
|
77
|
+
const prepared = await steps.prepareDistributionWithIndex(id, { channel: EditionChannel.Develop, inputs: developGitBundles === undefined ? inputs : { ...inputs, developGitBundles } });
|
|
78
|
+
// The schemas the lock was composed with are the ones that admit it.
|
|
79
|
+
return { inputs, ...prepared };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The image-record snapshot: prepared as the resolve prepares it, then admitted (a refusal
|
|
84
|
+
* throws). It seals no distribution lock, so it verifies no git-sourced OCI bundle (no registry, no cosign): its lock
|
|
85
|
+
* rows of a git source carry none, which image-record does not read.
|
|
86
|
+
*/
|
|
87
|
+
export async function resolveDevelopSnapshot({ id, pins, steps = DEVELOP_EDITION_STEPS, phase = () => {} }) {
|
|
88
|
+
const snapshot = await prepareDevelopSnapshot({ id, pins, steps, phase });
|
|
89
|
+
admitResolvedLock(snapshot.schemas, snapshot.lock);
|
|
90
|
+
return snapshot;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The channel lane (see the header): what every deployment we run resolves with.
|
|
95
|
+
*
|
|
96
|
+
* @param {object} request
|
|
97
|
+
* @param {string} request.engineImage `<repository>@sha256:<64 hex>`, the caller's (already validated by the verb)
|
|
98
|
+
* @param {object} request.record the parsed image build record (`--images-from`)
|
|
99
|
+
* @param {Function} request.verifySatellites the channel check of its satellites (develop-channel-satellites.mjs)
|
|
100
|
+
*/
|
|
101
|
+
export function developChannelLane({ engineImage, record, verifySatellites }) {
|
|
102
|
+
return Object.freeze({
|
|
103
|
+
engineImage: async () => engineImage,
|
|
104
|
+
developGitBundles: createDevelopGitBundleDigestReader({ registryRoot: dataRoot() }),
|
|
105
|
+
checkBundleSigners: false,
|
|
106
|
+
thirdPartyPins: () => loadThirdPartySourcePins(dataRoot()),
|
|
107
|
+
imagesFrom: { record, verifySatellites },
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const LANE_KEYS = Object.freeze(['engineImage', 'developGitBundles', 'checkBundleSigners', 'thirdPartyPins']);
|
|
112
|
+
|
|
113
|
+
/** A lane names every choice; one it leaves out is a defect of the caller, never a default. */
|
|
114
|
+
function assertLane(lane) {
|
|
115
|
+
const missing = LANE_KEYS.filter((key) => lane?.[key] === undefined);
|
|
116
|
+
if (missing.length > 0) {throw new Error(`resolveDevelopEdition: the lane names no ${missing.join(', ')}`);}
|
|
117
|
+
if (lane.imagesFrom !== undefined && (lane.imagesFrom.record === undefined || lane.imagesFrom.verifySatellites === undefined)) {
|
|
118
|
+
throw new Error('resolveDevelopEdition takes an image build record and its satellite check together');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* @param {object} request
|
|
124
|
+
* @param {object} request.lane `developChannelLane(...)` or the catalogue-free vendor step's verified lane: the engine
|
|
125
|
+
* image, the git-bundle reader or verifier, whether bundle signers are pre-checked, the third-party pins, and either
|
|
126
|
+
* `imagesFrom` (digests from the build record, no probe and no hash) or none (the probe by computed content hash)
|
|
127
|
+
* @param {object|null} [request.previousLock] the previous edition's parsed images lock: its composed artifacts carry their generation forward (null: generation 1)
|
|
128
|
+
*/
|
|
129
|
+
export async function resolveDevelopEdition({ id, pins, lane, env = process.env, sealOptions = {}, fetchManifest = fetchRegistryManifest, progress = stderrProgress, steps = DEVELOP_EDITION_STEPS, declaredSources = firstPartySources('naming the develop repository of each first-party image'), previousLock = null }) {
|
|
130
|
+
assertLane(lane);
|
|
131
|
+
const { imagesFrom } = lane;
|
|
132
|
+
const startedAt = Date.now();
|
|
133
|
+
// One line per phase, with the time since the resolve began, so the step is never silent for long.
|
|
134
|
+
const phase = (name) => progress(`[develop-edition] ${name} (+${Date.now() - startedAt}ms)`);
|
|
135
|
+
// Every check runs unless a check it needs was refused; all refusals are reported together, and nothing is sealed or written then.
|
|
136
|
+
const report = createResolveReport();
|
|
137
|
+
const checked = (check, fn, options) => report.run(check, RESOLVE_CHECK_DEPENDENCIES[check], fn, options);
|
|
138
|
+
const chartSetLock = await checked(DevelopResolveCheck.ChartSet, async () => {
|
|
139
|
+
const chartSet = await steps.resolveDevelopChartSet({ xemaDeploySha: pins[deployPinKey()] });
|
|
140
|
+
phase('chart set resolved');
|
|
141
|
+
return chartSet;
|
|
142
|
+
});
|
|
143
|
+
const engineImage = await checked(DevelopResolveCheck.EngineImage, async () => {
|
|
144
|
+
const image = await lane.engineImage();
|
|
145
|
+
phase('engine image resolved');
|
|
146
|
+
return image;
|
|
147
|
+
});
|
|
148
|
+
await checked(DevelopResolveCheck.EditionProtocol, () => steps.assertKernelEditionProtocol(kernelPackageDir()));
|
|
149
|
+
// A git-sourced biome's OCI bundle, by the lane's reader (by digest) or verifier (develop-git-bundles.mjs).
|
|
150
|
+
const snapshot = await checked(DevelopResolveCheck.DistributionLock, () => prepareDevelopSnapshot({ id, pins, steps, phase, developGitBundles: lane.developGitBundles }), {
|
|
151
|
+
classify: (error) => (isBundleSignerRefusal(error) ? DevelopResolveCheck.BundleSigner : DevelopResolveCheck.DistributionLock),
|
|
152
|
+
});
|
|
153
|
+
const lockProduced = snapshot !== BLOCKED;
|
|
154
|
+
const { schemas, inputs, lock, index } = lockProduced ? snapshot : {};
|
|
155
|
+
if (lockProduced && lane.checkBundleSigners) {
|
|
156
|
+
await checked(DevelopResolveCheck.BundleSigner, () => assertLockBundleSigners({ lock, schemas, gitSourcedBundle: GitSourcedBundle.DevelopVerified }));
|
|
157
|
+
}
|
|
158
|
+
// The plane invariants, against the graph this candidate froze (develop-edition-invariants.mjs).
|
|
159
|
+
await checked(DevelopResolveCheck.PlaneInvariants, () => {
|
|
160
|
+
const graded = steps.assertDevelopEditionInvariants({ id, lock, biomeIndex: inputs.biomeIndex, schemas });
|
|
161
|
+
phase(`reservation table asserted (${graded.reservedDomains} domains); confinement asserted (${graded.confinedBiomes} declaration(s))`);
|
|
162
|
+
});
|
|
163
|
+
await checked(DevelopResolveCheck.OperationAdmission, () => {
|
|
164
|
+
try {
|
|
165
|
+
admitDistributionLock(schemas, lock, { report: (line) => report.notice(line) });
|
|
166
|
+
} catch (error) {
|
|
167
|
+
ungrantedOptionalLinesOfRefusal(error).forEach((line) => report.notice(line));
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
phase('operation requirements admitted');
|
|
171
|
+
});
|
|
172
|
+
await checked(DevelopResolveCheck.AdapterBundles, () => steps.assertAdapterBundlesMatchHost({ lock, hostRow: adapterHostRow(index), fetchManifest }));
|
|
173
|
+
const authorizationLock = await checked(DevelopResolveCheck.AuthorizationBase, async () => {
|
|
174
|
+
const composed = await steps.composeAuthorizationBase(id, lock, index);
|
|
175
|
+
phase('distribution lock resolved');
|
|
176
|
+
return composed;
|
|
177
|
+
});
|
|
178
|
+
// Every subject's develop image is probed the moment its hash is known, in
|
|
179
|
+
// parallel with the hashing of the rest; an absent image refuses here, naming
|
|
180
|
+
// all of them, before any later phase runs.
|
|
181
|
+
let recorded;
|
|
182
|
+
const imageLock = await checked(DevelopResolveCheck.DevelopImages, async () => {
|
|
183
|
+
if (imagesFrom !== undefined) {
|
|
184
|
+
const fromRecord = await resolveImagesFromRecord({ id, inputs, lock, record: imagesFrom.record, resolveImages: steps.resolveImages, verifySatellites: imagesFrom.verifySatellites, previousLock });
|
|
185
|
+
({ recorded } = fromRecord);
|
|
186
|
+
phase(`image subjects taken from the build record (${fromRecord.imageLock.subjects.length} subjects); satellites checked against their channel records`);
|
|
187
|
+
return fromRecord.imageLock;
|
|
188
|
+
}
|
|
189
|
+
const probe = createDevelopImageProbe({
|
|
190
|
+
distributionId: id,
|
|
191
|
+
declaredSources,
|
|
192
|
+
env,
|
|
193
|
+
...(sealOptions.distributionsRoot === undefined ? {} : { distributionsRoot: sealOptions.distributionsRoot }),
|
|
194
|
+
...(sealOptions.fetchImpl === undefined ? {} : { fetchImpl: sealOptions.fetchImpl }),
|
|
195
|
+
...(sealOptions.run === undefined ? {} : { run: sealOptions.run }),
|
|
196
|
+
progress,
|
|
197
|
+
});
|
|
198
|
+
const resolved = await steps.resolveImages(id, { inputs, lock, onSubjectHashed: probe.observe, previousLock });
|
|
199
|
+
phase(`image content hashes computed (${resolved.imageLock.subjects.length} subjects)`);
|
|
200
|
+
await probe.settle();
|
|
201
|
+
phase('every develop image is present');
|
|
202
|
+
return resolved.imageLock;
|
|
203
|
+
});
|
|
204
|
+
try {
|
|
205
|
+
report.settle();
|
|
206
|
+
} catch (error) {
|
|
207
|
+
progress(error.message);
|
|
208
|
+
throw error;
|
|
209
|
+
}
|
|
210
|
+
if (report.noticeText() !== '') {progress(report.noticeText());}
|
|
211
|
+
const sealArguments = { imageLock, declaredSources, env, progress, thirdPartyPins: lane.thirdPartyPins(), ...sealOptions };
|
|
212
|
+
const sealedImageLock = recorded === undefined
|
|
213
|
+
? await steps.sealImages({ channel: EditionChannel.Develop, ...sealArguments })
|
|
214
|
+
: await steps.sealImagesFromRecord({ ...sealArguments, recorded });
|
|
215
|
+
phase('images sealed');
|
|
216
|
+
// The unsealed lock becomes the one v5 grammar only here, once the images it
|
|
217
|
+
// owns are sealed: each biomeContentHash is recomputed from the frozen sources.
|
|
218
|
+
const authority = createPinnedSourceAuthority({ pins: inputs.runtimeImageSourceIndex.repositoryPins });
|
|
219
|
+
const biomeSources = {
|
|
220
|
+
schemas,
|
|
221
|
+
imageLock: sealedImageLock,
|
|
222
|
+
locate: sourceTreeLocator(externalSourceTrees(lock)),
|
|
223
|
+
vendoredCommit: (repo) => authority.vendoredSourceCommitForRepository(repo),
|
|
224
|
+
};
|
|
225
|
+
// ONE walk of the biome checkouts: the content hashes and the tree ids both read it.
|
|
226
|
+
const sourceFactsById = await readBiomeSourceFacts({ ...biomeSources, lock });
|
|
227
|
+
phase(`biome sources read (${sourceFactsById.size} biomes)`);
|
|
228
|
+
const sealedLock = await sealLock({ ...biomeSources, unsealed: lock, sourceFactsById, gitSourcedBundle: GitSourcedBundle.DevelopVerified });
|
|
229
|
+
phase('distribution lock sealed');
|
|
230
|
+
const sourceTreeIds = await biomeSourceTreeIds({ ...biomeSources, lock, sourceFactsById });
|
|
231
|
+
const catalogue = buildCatalogue({
|
|
232
|
+
sourceTreeIds,
|
|
233
|
+
offeringId: id,
|
|
234
|
+
channel: EditionChannel.Develop,
|
|
235
|
+
distributionLock: lock,
|
|
236
|
+
imageLock: sealedImageLock,
|
|
237
|
+
chartSetLock,
|
|
238
|
+
biomeIndex: inputs.biomeIndex,
|
|
239
|
+
platformServiceIndex: inputs.platformServiceIndex,
|
|
240
|
+
contractAuthority: inputs.runtimeImageSourceIndex.contractAuthority,
|
|
241
|
+
kernel: await kernelRowOfAuthority({
|
|
242
|
+
channel: EditionChannel.Develop,
|
|
243
|
+
kernelPackageDir: kernelPackageDir(),
|
|
244
|
+
contractAuthority: inputs.runtimeImageSourceIndex.contractAuthority,
|
|
245
|
+
}),
|
|
246
|
+
leaf: inputs.composed.leaf,
|
|
247
|
+
});
|
|
248
|
+
phase('catalogue built');
|
|
249
|
+
return { distributionLock: sealedLock, imageLock: sealedImageLock, authorizationLock, chartSetLock, catalogue, engineImage };
|
|
250
|
+
}
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// develop-edition — the develop edition's command line.
|
|
3
|
+
//
|
|
4
|
+
// The vendor verbs are the CHANNEL lane every deployment we run uses: images by digest from their producers' build
|
|
5
|
+
// records, read through a registry channel tag, and nothing else. No cosign, no signature or attestation check, no
|
|
6
|
+
// content-hash recomputation, no lock-algorithm check, no seal verification. The customer forms (`--definition`,
|
|
7
|
+
// `--definition-at`) are the official lane and keep their verification.
|
|
8
|
+
//
|
|
9
|
+
// select-records --branch develop --channel <tag> --out <file>
|
|
10
|
+
// for every develop-graph member that does not keep its current resolution, read the one manifest
|
|
11
|
+
// <root>/build-records/<name>:<tag> (a tag the producer's CI moves to its newest green record; tags are never
|
|
12
|
+
// listed, so <root> may be a pull-through proxy), parse its single build-record layer with the CLI's own
|
|
13
|
+
// @xemahq/contracts BuildRecordSchema (select-records takes no --kernel: it runs before any member pin exists) and hold it to the member (its producer repository, branch develop). The selection is
|
|
14
|
+
// { channel, pins, snapshot, images, currentResolution }, written once (wx); images[] entries carry `member`, the
|
|
15
|
+
// owning repo handle, and the record's digest and contentHash. Each member prints
|
|
16
|
+
// `<short name>: channel <tag> -> record <12-hex commit> (run <run>)`, a member that keeps its current resolution
|
|
17
|
+
// (a declared currentResolution, develop-current-resolution.mjs) `xema-docs: current resolution (leaves the edition,
|
|
18
|
+
// XEMA-2373)` with no registry read. A missing tag or a malformed record refuses the whole run naming the member and
|
|
19
|
+
// writes nothing (develop-select-records.mjs).
|
|
20
|
+
// freeze-graph --data-sha <sha>
|
|
21
|
+
// --deploy-sha <sha> [--editions-root-url <https url>]
|
|
22
|
+
// [--overrides <json> | --member-pins <selection file>] --out <file>
|
|
23
|
+
// resolve refs/heads/develop once per develop-graph repository (the data root's
|
|
24
|
+
// firstPartySources, at each declared URL) and the editions root; the three checked-out
|
|
25
|
+
// repositories are pinned at what the workflow checked out; write the pins. --member-pins takes a select-records
|
|
26
|
+
// selection (only .pins, 40-hex, is read) and pins each graph member at its selected record commit instead of its
|
|
27
|
+
// develop head: the selection must pin exactly the graph minus its currentResolution members (which must equal
|
|
28
|
+
// the graph members' declared currentResolution), a missing or extra member is refused by name, and a currentResolution
|
|
29
|
+
// member is pinned at its develop head. --member-pins together with --overrides is refused. The kernel comes from the
|
|
30
|
+
// pinned xema-base member checkout (--kernel names its contracts at the folded path, kernel-pin-source.mjs): the
|
|
31
|
+
// kernel pin is that member's pin, and --kernel-sha is refused. The pin must declare an edition protocol this CLI
|
|
32
|
+
// supports (the check choose-kernel applies to a pin).
|
|
33
|
+
// freeze-sources <id> --pins <file> [--overrides <json>] --out <file>
|
|
34
|
+
// add one pin per source the edition's chain declares (run after the editions
|
|
35
|
+
// root is checked out and passed as --editions-root); write the final pins.
|
|
36
|
+
// image-record <id> --selection <file> --pins <file> --out <file> --channel <tag>
|
|
37
|
+
// write the edition's image build record (the file `resolve --images-from` reads) from a `select-records --channel
|
|
38
|
+
// <tag>` selection (develop-image-record.mjs). The subjects are the checked-out snapshot's first-party image
|
|
39
|
+
// subjects, enumerated with the selection standing in for every hash: each takes the selection's image of its
|
|
40
|
+
// develop repository (digest AND contentHash), whose selected commit must be the snapshot's, and goes in
|
|
41
|
+
// `satellites`, checked against its producer's <tag> record as `resolve` checks it. The one declared exception: a
|
|
42
|
+
// subject of a member that keeps its current resolution (XEMA-2373) has no build record, so its content hash is
|
|
43
|
+
// computed and its develop image probed by it, and it goes in `images`. Platforms are read from the registry by
|
|
44
|
+
// digest. Any subject without an image, any commit mismatch refuses the whole run and writes nothing, once (wx).
|
|
45
|
+
// Third-party subjects are ignored. Takes the roots `resolve` takes.
|
|
46
|
+
// target <id> --pins <file> --registry <root> --engine-image <repository>@sha256:<digest> [--images-from <image build record>]
|
|
47
|
+
// print <root>/<develop segment>/xema-edition-<id>:<tag>, the tag being sha256 of the canonical
|
|
48
|
+
// { cli, engine, pins, sourceImages } (snapshotEditionTag): every input of the bytes the artifact records,
|
|
49
|
+
// so the same pins with another engine, source-image map or edition-tooling version are another artifact. With
|
|
50
|
+
// --images-from the record's digest (imageBuildRecordDigest) is one more input, named imageBuildRecord; resolve
|
|
51
|
+
// prints the same target. The root is always the caller's registry root (e.g. registry.example.com/acme); the
|
|
52
|
+
// develop namespace segment and the repository prefix come from edition-contract.json, and no root is implied.
|
|
53
|
+
// check-existing <id> --pins <file> --artifact <dir> --engine-image <repository>@sha256:<digest>
|
|
54
|
+
// refuse to reuse a pulled artifact whose descriptor disagrees with this snapshot (its evidence trust the channel
|
|
55
|
+
// lane's `not-armed`, develop-channel-evidence.mjs).
|
|
56
|
+
// resolve <id> --pins <file> --out <dir> --registry <root> --engine-image <repository>@sha256:<digest>
|
|
57
|
+
// --images-from <image build record> --channel <tag> --selection <select-records file>
|
|
58
|
+
// (--previous-lock <file> --previous-lock-sha256 <hex> | --first-seal) [--local]
|
|
59
|
+
// resolve the edition from the snapshot, write the descriptor and the four locks to <dir>, print the target. The
|
|
60
|
+
// engine is the --engine-image digest, recorded as given. Every first-party digest is the build record's
|
|
61
|
+
// (images-from-record.mjs): no probe and no hash; a subject missing, duplicated or extra in the record refuses.
|
|
62
|
+
// Each satellite must be listed, {name, digest}, by its producer's build record at the commit the snapshot pins
|
|
63
|
+
// (develop-channel-satellites.mjs): the record --selection pinned, read by its own record tag
|
|
64
|
+
// <root>/build-records/<name>:<branch>-<run>-<attempt>, so a channel that moved after selection does not affect
|
|
65
|
+
// the run. The selection must have been selected through --channel. A git-sourced OCI bundle is taken by the
|
|
66
|
+
// digest its tag serves (develop-git-bundles.mjs). The evidence verifier and its data-root pin are not loaded: the
|
|
67
|
+
// descriptor records evidenceTrustPolicy not-armed, stated on stderr (develop-channel-evidence.mjs). The previous
|
|
68
|
+
// seal is the edition's last images lock, held to the sha256 of its bytes and to the id, or --first-seal. Every
|
|
69
|
+
// refusal of the run is reported in one table (develop-resolve-report.mjs): independent checks all run, a
|
|
70
|
+
// dependent one is reported `blocked by <check>`, and when anything is refused nothing is sealed or written. The
|
|
71
|
+
// workflow run is recorded from GITHUB_REPOSITORY / GITHUB_RUN_ID; --local stamps `local` instead.
|
|
72
|
+
// resolve --definition <file> --pins <file> --out <dir> --registry <root>
|
|
73
|
+
// --engine-image <repository>@sha256:<digest> [--source-image <source>=<registry>/<prefix>]...
|
|
74
|
+
// [--catalogue-username <name> --catalogue-token-env <VAR>] [--cosign <absolute-path>] [--local]
|
|
75
|
+
// a CUSTOMER develop edition (official lane): the definition resolved against the develop catalogue pinned in
|
|
76
|
+
// catalogue.pin.json beside it (see catalogue-pin.mjs and resolve-customer-edition.mjs), frozen at the
|
|
77
|
+
// customer's own pins. --pins holds exactly one `sources/<name>` commit per source the definition
|
|
78
|
+
// declares (`freeze-sources --definition` writes it); the catalogue digest is frozen into the descriptor's
|
|
79
|
+
// provenance pins as `catalogue`. The id is the definition's. Takes --data-root, --editions-root,
|
|
80
|
+
// --sources-root, --cache-dir (no --deploy-root, no --kernel: the kernel is the catalogue's, from npm).
|
|
81
|
+
// It takes the customer edition's OWN previous seal, exactly one of --previous-lock <file> --previous-lock-sha256 <hex>
|
|
82
|
+
// (the customer edition's last images lock: sha256 of its bytes, its id the definition's) or --first-seal; it carries
|
|
83
|
+
// the generations of the customer's own composed artifacts forward (vendor rows are the catalogue's, unchanged).
|
|
84
|
+
// resolve --definition-at <owner>/<repo>,<path>,<dataRoot> --offering <owner>/<repo>,<path>,<dataRoot>
|
|
85
|
+
// --pins <file> --out <dir> --registry <root> --engine-repository <repository> --engine-signer <certificate identity>
|
|
86
|
+
// --cosign <absolute-path> --offering-previous-lock <file> --offering-previous-lock-sha256 <hex>
|
|
87
|
+
// (--previous-lock <file> --previous-lock-sha256 <hex> | --first-seal) [--source-image <source>=<registry>/<prefix>]... [--local]
|
|
88
|
+
// a CATALOGUE-FREE customer develop edition (official lane, develop-catalogue-free-resolve.mjs): the vendor
|
|
89
|
+
// offering named by --offering (its directory in the vendor data checkout) is resolved first by the vendor step
|
|
90
|
+
// (develop-catalogue-free-vendor.mjs, a child of this CLI on the vendor roots, NOT the vendor `resolve` verb) on
|
|
91
|
+
// the verified lane: the engine found and cosign-proven from --engine-repository/--engine-signer, the images probed
|
|
92
|
+
// by content hash, the git-sourced bundles cosign-verified, sealed against the OFFERING's previous seal
|
|
93
|
+
// (--offering-previous-lock, held to its sha256 and the offering's id); then the definition, read from its
|
|
94
|
+
// repository at --pins "definition-repository", is resolved against the catalogue that step wrote, on its own
|
|
95
|
+
// roots, carrying the customer's own composed generations from --previous-lock (or --first-seal). --pins is the
|
|
96
|
+
// complete frozen set (the vendor pins, the definition's sources/<name>, "definition-repository"); no head is read
|
|
97
|
+
// and nothing is pushed. The edition pins those and the definition digest, no catalogue (the catalogue's content
|
|
98
|
+
// digest is in the distribution lock only), and records the vendor step's engine. catalogue.pin.json beside the
|
|
99
|
+
// definition is refused. Takes the vendor roots.
|
|
100
|
+
// target --definition <file> --pins <file> --registry <root> --engine-image <repository>@sha256:<digest>
|
|
101
|
+
// [--source-image <source>=<registry>/<prefix>]...
|
|
102
|
+
// the same for a customer edition (id = the definition's; the tag covers the pinned catalogue digest).
|
|
103
|
+
// check-existing --definition <file> --pins <file> --artifact <dir> --engine-image <repository>@sha256:<digest>
|
|
104
|
+
// [--catalogue-username <name> --catalogue-token-env <VAR>] [--source-image <source>=<registry>/<prefix>]...
|
|
105
|
+
// the same for a customer artifact; its descriptor's catalogue provenance pin must also equal the
|
|
106
|
+
// pinned develop catalogue digest, and its own image rows must be the rows the --source-image map composes. It fetches that catalogue (as `resolve --definition` does) to take the
|
|
107
|
+
// kernel from it. Takes --data-root, --cache-dir.
|
|
108
|
+
// freeze-sources --definition <file> --out <file>
|
|
109
|
+
// a CUSTOMER edition: one `sources/<name>` pin per source the definition declares, each the head of
|
|
110
|
+
// refs/heads/develop of that source's repository, resolved once; written exclusively (wx).
|
|
111
|
+
// choose-kernel [--kernel-pin <40-hex>] --out <file>
|
|
112
|
+
// choose the develop kernel commit in the --kernel checkout (full history; its refs/remotes/origin/develop is the develop head):
|
|
113
|
+
// the newest first-parent commit whose declared edition protocol this CLI supports
|
|
114
|
+
// (kernel-protocol-choice.mjs). Writes {chosenSha, headSha, held, reason} (reason: head-supported |
|
|
115
|
+
// head-ahead | explicit-pin); when held, prints `kernel held at v<N> (<sha12>), head at v<M> (<sha12>):
|
|
116
|
+
// raise the CLI` to stdout (never persisted). A pin wins and is still checked; nothing supported is refused.
|
|
117
|
+
// Takes only --kernel (the checkout need not be built).
|
|
118
|
+
// check-overrides --overrides <json>
|
|
119
|
+
// refuse a pins override that would select the tooling this run executes
|
|
120
|
+
// (the edition tooling's own repository); the workflow runs it before any other checkout.
|
|
121
|
+
//
|
|
122
|
+
// Tokens come from the environment (EVIDENCE_REPOSITORIES_TOKEN, XEMA_GHCR_PULL_TOKEN),
|
|
123
|
+
// never argv. Nothing here writes git or publishes a release edition.
|
|
124
|
+
|
|
125
|
+
import { resolve } from 'node:path';
|
|
126
|
+
|
|
127
|
+
import { writeCatalogue } from './catalogue.mjs';
|
|
128
|
+
import { CHANNEL_FLAG, CUSTOMER_ROOTS, DEVELOP_VENDOR_ROOTS, FIRST_SEAL_FLAG, IMAGES_FROM_FLAG, PREVIOUS_LOCK_FLAG, PREVIOUS_LOCK_SHA256_FLAG, SELECTION_FLAG, SELECT_RECORDS_ROOTS, headResolver, parseEditionVerbArgs, runCustomerVerb, writeOnce } from './customer-edition-verbs.mjs';
|
|
129
|
+
import { DEFINITION_FLAG } from './customer-flags.mjs';
|
|
130
|
+
import { resolveCatalogueFree } from './develop-catalogue-free-resolve.mjs';
|
|
131
|
+
import { DEVELOP_CHANNEL_EVIDENCE_TRUST, developResolveEvidenceTrust } from './develop-channel-evidence.mjs';
|
|
132
|
+
import { createChannelSatelliteVerifier } from './develop-channel-satellites.mjs';
|
|
133
|
+
import { developChannelLane, resolveDevelopEdition } from './develop-edition-resolve.mjs';
|
|
134
|
+
import { assertNoToolingOverride, freezeGraph, freezeSources } from './develop-freeze.mjs';
|
|
135
|
+
import { developGraphMembers } from './develop-graph.mjs';
|
|
136
|
+
import { parseSelection, runImageRecord } from './develop-image-record.mjs';
|
|
137
|
+
import { runSelectRecords } from './develop-select-records.mjs';
|
|
138
|
+
import { readSelectionPins } from './develop-selection-pins.mjs';
|
|
139
|
+
import { LOCAL_WORKFLOW, assertExistingArtifactMatches, developEditionTarget, workflowFromEnv, writeEditionArtifact } from './edition-artifact.mjs';
|
|
140
|
+
import { EditionChannel } from './edition-constants.mjs';
|
|
141
|
+
import { WorkspaceRoot, configureEditionWorkspaceFromProcess, isEntryModule, kernelRepoDir, sourcePath } from './edition-workspace.mjs';
|
|
142
|
+
import { ImageBuildRecordError, imageBuildRecordDigest, readImageBuildRecord } from './images-from-record.mjs';
|
|
143
|
+
import { kernelMemberOf } from './kernel-pin-source.mjs';
|
|
144
|
+
import { assertKernelCommitSupported, chooseKernelCommit } from './kernel-protocol-choice.mjs';
|
|
145
|
+
import { loadKernelDistributionSchemas } from './lib.mjs';
|
|
146
|
+
import { kernelPinKey, snapshotPinSource } from './pin-source.mjs';
|
|
147
|
+
import { composeEdition } from './resolve-distribution.mjs';
|
|
148
|
+
import { resolvePreviousSeal } from './resolve-images.mjs';
|
|
149
|
+
import { readStrictJsonFile } from './strict-json.mjs';
|
|
150
|
+
|
|
151
|
+
// The roots a verb takes. A customer form (it names --definition) takes fewer, and never the kernel (the
|
|
152
|
+
// catalogue's kernel is downloaded from npm into the cache root): `target` none, `check-existing` the data
|
|
153
|
+
// root and the cache, `freeze-sources` the data root only, `resolve` all but the deploy root. `check-overrides`
|
|
154
|
+
// takes only the data root in either form: it judges its --overrides JSON against the pin roles the data
|
|
155
|
+
// root declares, so it reads nothing else and refuses every other root flag.
|
|
156
|
+
const DATA_ROOT_ONLY_COMMANDS = Object.freeze(['check-overrides']);
|
|
157
|
+
// `choose-kernel` reads the kernel checkout's git history only, before the kernel is built.
|
|
158
|
+
const KERNEL_ROOT_ONLY_COMMANDS = Object.freeze(['choose-kernel']);
|
|
159
|
+
|
|
160
|
+
if (isEntryModule(import.meta.url)) {
|
|
161
|
+
const [command] = process.argv.slice(2);
|
|
162
|
+
if (DATA_ROOT_ONLY_COMMANDS.includes(command)) {
|
|
163
|
+
configureEditionWorkspaceFromProcess([WorkspaceRoot.Data], { refuseOthers: true });
|
|
164
|
+
} else if (command === 'select-records') {
|
|
165
|
+
configureEditionWorkspaceFromProcess(SELECT_RECORDS_ROOTS, { refuseOthers: true });
|
|
166
|
+
} else if (KERNEL_ROOT_ONLY_COMMANDS.includes(command)) {
|
|
167
|
+
configureEditionWorkspaceFromProcess([WorkspaceRoot.Kernel], { refuseOthers: true });
|
|
168
|
+
} else {
|
|
169
|
+
const customerForm = process.argv.includes(DEFINITION_FLAG);
|
|
170
|
+
configureEditionWorkspaceFromProcess(customerForm ? (CUSTOMER_ROOTS[command] ?? DEVELOP_VENDOR_ROOTS) : DEVELOP_VENDOR_ROOTS, {
|
|
171
|
+
refuseOthers: customerForm && CUSTOMER_ROOTS[command] !== undefined,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Parse `<command> [<id>] --flag value ...` against the closed vocabulary of the develop command. */
|
|
177
|
+
export const parseDevelopEditionArgs = (argv) => parseEditionVerbArgs(EditionChannel.Develop, argv);
|
|
178
|
+
|
|
179
|
+
function parseOverrides(text) {
|
|
180
|
+
if (text === undefined || text.trim() === '') {return {};}
|
|
181
|
+
const parsed = JSON.parse(text);
|
|
182
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
183
|
+
throw new Error('--overrides must be a JSON object mapping pin key -> 40-hex commit');
|
|
184
|
+
}
|
|
185
|
+
return parsed;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** The target-tag input of `--images-from`: the record's digest, or nothing when the caller names no record. */
|
|
189
|
+
async function imageBuildRecordTagInput(options) {
|
|
190
|
+
return options[IMAGES_FROM_FLAG] === undefined ? {} : { imageBuildRecordDigest: imageBuildRecordDigest(await readImageBuildRecord(options[IMAGES_FROM_FLAG])) };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** The `--selection` the run's image record came from, held to `--channel`. */
|
|
194
|
+
function readChannelSelection(options) {
|
|
195
|
+
const path = options[SELECTION_FLAG];
|
|
196
|
+
const selection = parseSelection(readStrictJsonFile(path), developGraphMembers(), path);
|
|
197
|
+
if (selection.channel !== options[CHANNEL_FLAG]) {
|
|
198
|
+
throw new ImageBuildRecordError(`selection ${path} was selected through channel ${selection.channel}, not ${options[CHANNEL_FLAG]}`);
|
|
199
|
+
}
|
|
200
|
+
return selection;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** The vendor resolve: the channel lane (develop-edition-resolve.mjs `developChannelLane`). */
|
|
204
|
+
async function resolveVendor({ id, options, env }) {
|
|
205
|
+
const pins = readStrictJsonFile(options['--pins']);
|
|
206
|
+
const workflow = options['--local'] ? LOCAL_WORKFLOW : workflowFromEnv(env);
|
|
207
|
+
const evidenceTrustPolicy = developResolveEvidenceTrust();
|
|
208
|
+
const record = await readImageBuildRecord(options[IMAGES_FROM_FLAG]);
|
|
209
|
+
const { catalogue, engineImage, ...resolved } = await resolveDevelopEdition({
|
|
210
|
+
id,
|
|
211
|
+
pins,
|
|
212
|
+
env,
|
|
213
|
+
lane: developChannelLane({
|
|
214
|
+
engineImage: options['--engine-image'],
|
|
215
|
+
record,
|
|
216
|
+
verifySatellites: createChannelSatelliteVerifier({ channel: options[CHANNEL_FLAG], selection: readChannelSelection(options), env }),
|
|
217
|
+
}),
|
|
218
|
+
previousLock: await resolvePreviousSeal({
|
|
219
|
+
distributionId: id,
|
|
220
|
+
previousLockPath: options[PREVIOUS_LOCK_FLAG],
|
|
221
|
+
previousLockSha256: options[PREVIOUS_LOCK_SHA256_FLAG],
|
|
222
|
+
firstSeal: Boolean(options[FIRST_SEAL_FLAG]),
|
|
223
|
+
}),
|
|
224
|
+
});
|
|
225
|
+
const target = developEditionTarget(id, pins, { registry: options['--registry'], engineImage, imageBuildRecordDigest: imageBuildRecordDigest(record) });
|
|
226
|
+
const outDir = resolve(options['--out']);
|
|
227
|
+
writeEditionArtifact({
|
|
228
|
+
outDir,
|
|
229
|
+
channel: EditionChannel.Develop,
|
|
230
|
+
distributionId: id,
|
|
231
|
+
pins,
|
|
232
|
+
workflow,
|
|
233
|
+
evidenceTrustPolicy,
|
|
234
|
+
engineImage,
|
|
235
|
+
...resolved,
|
|
236
|
+
});
|
|
237
|
+
writeCatalogue(outDir, catalogue);
|
|
238
|
+
console.log(target);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async function run(argv, env) {
|
|
242
|
+
const { command, id, options, customer, catalogueFree } = parseDevelopEditionArgs(argv);
|
|
243
|
+
switch (command) {
|
|
244
|
+
case 'target': {
|
|
245
|
+
if (customer) {return runCustomerVerb({ channel: EditionChannel.Develop, command, options, env });}
|
|
246
|
+
console.log(developEditionTarget(id, readStrictJsonFile(options['--pins']), { registry: options['--registry'], engineImage: options['--engine-image'], ...(await imageBuildRecordTagInput(options)) }));
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
case 'resolve': {
|
|
250
|
+
if (customer) {return runCustomerVerb({ channel: EditionChannel.Develop, command, options, env });}
|
|
251
|
+
if (catalogueFree) {
|
|
252
|
+
await resolveCatalogueFree({ options, env });
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
await resolveVendor({ id, options, env });
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
case 'select-records':
|
|
259
|
+
await runSelectRecords({ options, env });
|
|
260
|
+
return;
|
|
261
|
+
case 'image-record':
|
|
262
|
+
await runImageRecord({ id, options, env });
|
|
263
|
+
return;
|
|
264
|
+
case 'check-existing': {
|
|
265
|
+
if (customer) {return runCustomerVerb({ channel: EditionChannel.Develop, command, options, env });}
|
|
266
|
+
assertExistingArtifactMatches({
|
|
267
|
+
artifactDir: resolve(options['--artifact']),
|
|
268
|
+
distributionId: id,
|
|
269
|
+
channel: EditionChannel.Develop,
|
|
270
|
+
evidenceTrustPolicy: DEVELOP_CHANNEL_EVIDENCE_TRUST,
|
|
271
|
+
pins: readStrictJsonFile(options['--pins']),
|
|
272
|
+
engineImage: options['--engine-image'],
|
|
273
|
+
});
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
case 'check-overrides': {
|
|
277
|
+
assertNoToolingOverride(parseOverrides(options['--overrides']));
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
case 'choose-kernel': {
|
|
281
|
+
const { choice, notice } = chooseKernelCommit({ repoDir: kernelRepoDir(), pin: options['--kernel-pin'] });
|
|
282
|
+
writeOnce(options['--out'], choice);
|
|
283
|
+
if (notice !== undefined) {console.log(notice);}
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
case 'freeze-graph': {
|
|
287
|
+
const channel = EditionChannel.Develop;
|
|
288
|
+
const kernelMember = kernelMemberOf({ members: developGraphMembers(), kernelRepoDir: kernelRepoDir(), sourcePath }).repo;
|
|
289
|
+
const pins = freezeGraph({
|
|
290
|
+
channel,
|
|
291
|
+
dataSha: options['--data-sha'],
|
|
292
|
+
kernelMember,
|
|
293
|
+
kernelSha: options['--kernel-sha'],
|
|
294
|
+
deploySha: options['--deploy-sha'],
|
|
295
|
+
editionsRootUrl: options['--editions-root-url'],
|
|
296
|
+
overrides: parseOverrides(options['--overrides']),
|
|
297
|
+
...(options['--member-pins'] === undefined ? {} : { memberPins: readSelectionPins(options['--member-pins'], '--member-pins', developGraphMembers()) }),
|
|
298
|
+
...headResolver(EditionChannel.Develop, env),
|
|
299
|
+
});
|
|
300
|
+
const kernelPin = pins[kernelPinKey()];
|
|
301
|
+
assertKernelCommitSupported({ repoDir: kernelRepoDir(), commit: kernelPin, label: `the kernel pin ${kernelPin} (the pin of ${kernelMember})` });
|
|
302
|
+
writeOnce(options['--out'], pins);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
case 'freeze-sources': {
|
|
306
|
+
if (customer) {return runCustomerVerb({ channel: EditionChannel.Develop, command, options, env });}
|
|
307
|
+
const pins = readStrictJsonFile(options['--pins']);
|
|
308
|
+
const { parseDistribution } = await loadKernelDistributionSchemas();
|
|
309
|
+
const declaredSources = composeEdition(parseDistribution, id, snapshotPinSource(pins)).declaredSources;
|
|
310
|
+
writeOnce(
|
|
311
|
+
options['--out'],
|
|
312
|
+
freezeSources({ pins, declaredSources, overrides: parseOverrides(options['--overrides']), ...headResolver(EditionChannel.Develop, env) }),
|
|
313
|
+
);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
default:
|
|
317
|
+
throw new Error(`unhandled command ${command}`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (isEntryModule(import.meta.url)) {
|
|
322
|
+
const argv = process.argv.slice(2);
|
|
323
|
+
run(argv, process.env).then(
|
|
324
|
+
(code) => {
|
|
325
|
+
if (typeof code === 'number') {process.exitCode = code;}
|
|
326
|
+
},
|
|
327
|
+
(error) => {
|
|
328
|
+
console.error(`[develop-edition] ${error.message}`);
|
|
329
|
+
process.exit(1);
|
|
330
|
+
},
|
|
331
|
+
);
|
|
332
|
+
}
|