@xemahq/xema 0.58.0 → 0.59.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/edition.js +1 -1
- package/dist/commands/edition.js.map +1 -1
- package/dist/edition/verify-image-evidence.bundle.mjs +90 -1
- package/dist/edition/verify-image-evidence.bundle.provenance.json +66 -66
- package/edition/README.md +7 -1
- package/edition/assemble-producer-input.mjs +15 -6
- package/edition/biome-content.mjs +25 -16
- package/edition/catalogue-lock-field.mjs +41 -0
- package/edition/customer-edition-verbs.mjs +93 -12
- package/edition/customer-flags.mjs +7 -0
- package/edition/develop-catalogue-free-resolve.mjs +280 -0
- package/edition/develop-edition.mjs +43 -1
- package/edition/develop-follow.mjs +491 -0
- package/edition/develop-record-ancestry.mjs +21 -16
- package/edition/develop-selection-pins.mjs +11 -5
- package/edition/distribution-evidence-set.mjs +2 -0
- package/edition/edition-artifact.mjs +44 -1
- package/edition/edition-constants.mjs +22 -2
- package/edition/edition-contract-parse.mjs +42 -0
- package/edition/edition-contract.json +18 -1
- package/edition/image-evidence.mjs +71 -0
- package/edition/pin-source.mjs +3 -1
- package/edition/record-refused.mjs +3 -2
- package/edition/resolve-customer-edition.mjs +37 -8
- package/edition/resolve-edition.mjs +3 -0
- package/edition/resolve-images.mjs +86 -20
- package/edition/third-party-bundle-registry.mjs +54 -17
- package/package.json +3 -3
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
// develop-catalogue-free-resolve — the catalogue-free CUSTOMER develop resolve:
|
|
2
|
+
//
|
|
3
|
+
// xema edition develop resolve --definition-at <owner>/<repo>,<path>,<dataRoot> --offering <owner>/<repo>,<path>,<dataRoot>
|
|
4
|
+
// --pins <file> --out <dir> --registry <root> --engine-repository <repository> --engine-signer <certificate identity>
|
|
5
|
+
// --cosign <abs path> --offering-previous-lock <file> --offering-previous-lock-sha256 <hex>
|
|
6
|
+
// (--previous-lock <file> --previous-lock-sha256 <hex> | --first-seal) [--source-image <source>=<registry>/<prefix>]... [--local]
|
|
7
|
+
//
|
|
8
|
+
// A dev deployment of a customer definition resolves the vendor offering it builds on IN THE SAME CALL instead of
|
|
9
|
+
// pinning a published catalogue: no catalogue.pin.json is read (its presence beside the definition is refused, never
|
|
10
|
+
// a fallback), and no public flag substitutes a catalogue file. Two workspaces, two steps, one CLI:
|
|
11
|
+
//
|
|
12
|
+
// vendor a child `develop resolve <offering id> --pins <vendor pins> --local` of this CLI on the VENDOR roots
|
|
13
|
+
// (the roots this call was given), unchanged: it writes the vendor artifact and catalogue.json into a
|
|
14
|
+
// scratch directory. --offering names the offering's directory inside the vendor data checkout, held to
|
|
15
|
+
// it like follow's --definition-at (the checkout is the declared repository, clean at the vendor data
|
|
16
|
+
// pin, the data root and the directory where the triple says). Never defaulted.
|
|
17
|
+
// customer the definition repository fetched at --pins "definition-repository" (the walk's git source fetch, by
|
|
18
|
+
// exact commit) and its tree at that commit checked out from the object store into this call's private
|
|
19
|
+
// scratch before the vendor step runs (a temporary index, symlinks as plain files): the shared fetch cache
|
|
20
|
+
// is never read again, so a cache tree that moves or changes mid-run cannot change what is resolved. The
|
|
21
|
+
// workspace is re-stated on the scratch copy's roots (data = <copy>/<dataRoot>, editions = the parent of
|
|
22
|
+
// <copy>/<path>, a fresh sources root, the same cache), and the customer resolve run against the catalogue
|
|
23
|
+
// the vendor step wrote. The edition's pins are the catalogue-free form
|
|
24
|
+
// (edition-contract.json provenancePins.customerForms): the vendor pins, the customer's own
|
|
25
|
+
// sources/<name> commits, "definition-repository" and the definition digest, never a catalogue pin.
|
|
26
|
+
// The catalogue CONTENT digest is sha256 of the EXACT catalogue.json bytes the vendor step wrote (parsed
|
|
27
|
+
// strictly, so a duplicate key refuses), the convention of the catalogue layer digest of an offering
|
|
28
|
+
// artifact (produce-edition-catalogue-evidence's predicate catalogueDigest). It goes into the distribution
|
|
29
|
+
// lock only, as catalogueContentDigest (kernel edition protocol v37), never catalogueDigest: no catalogue
|
|
30
|
+
// artifact exists, so the lock never names one (catalogue-lock-field.mjs).
|
|
31
|
+
//
|
|
32
|
+
// --pins is the COMPLETE frozen set; nothing here reads a head. It is split once, before anything is fetched: a
|
|
33
|
+
// sources/<name> key the definition declares is the customer's own, every other key but "definition-repository" goes
|
|
34
|
+
// to the vendor step (which refuses what its offering does not declare). A source name both the offering and the
|
|
35
|
+
// definition declare is refused before the vendor step: its one pin could not say whose source it is.
|
|
36
|
+
|
|
37
|
+
import { execFileSync, spawnSync } from 'node:child_process';
|
|
38
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
39
|
+
import { tmpdir } from 'node:os';
|
|
40
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
41
|
+
import { fileURLToPath } from 'node:url';
|
|
42
|
+
|
|
43
|
+
import { sha256Digest } from './canonical-evidence-json.mjs';
|
|
44
|
+
import { CATALOGUE_PIN_FILE } from './catalogue-pin.mjs';
|
|
45
|
+
import { CATALOGUE_FILE, parseCatalogue } from './catalogue.mjs';
|
|
46
|
+
import { customerPreviousSeal, customerSourceImages, declaredSourceNamesOf, resolveCustomerChannelEdition } from './customer-edition-verbs.mjs';
|
|
47
|
+
import { DEFINITION_AT_FLAG, OFFERING_FLAG, OFFERING_PREVIOUS_LOCK_FLAG, OFFERING_PREVIOUS_LOCK_SHA256_FLAG } from './customer-flags.mjs';
|
|
48
|
+
import { assertDefinitionCheckout, parseFollowDefinition } from './develop-follow.mjs';
|
|
49
|
+
import { customerDevelopGitBundleVerifier } from './develop-git-bundles.mjs';
|
|
50
|
+
import { assertCanonicalDistributionId } from './distribution-paths.mjs';
|
|
51
|
+
import { LOCAL_WORKFLOW, parseEditionDescriptor, snapshotEditionTarget, workflowFromEnv, writeEditionArtifact } from './edition-artifact.mjs';
|
|
52
|
+
import { resetEditionWorkspaceConfig } from './edition-config.mjs';
|
|
53
|
+
import { CATALOGUE_PIN_KEY, DEFINITION_PIN_KEY, DEFINITION_REPOSITORY_PIN_KEY, EDITION_DESCRIPTOR_FILE, EditionChannel, EditionKind, VENDOR_MEMBER_PIN_PATTERN } from './edition-constants.mjs';
|
|
54
|
+
import { WorkspaceRoot, cacheDir, configureEditionWorkspace, configuredWorkspace, editionsRoot, workspaceArgv } from './edition-workspace.mjs';
|
|
55
|
+
import { descriptorEvidenceTrust } from './evidence-trust-policy.mjs';
|
|
56
|
+
import { materializeGitSource } from './external-source-walk.mjs';
|
|
57
|
+
import { serializeJson } from './lib.mjs';
|
|
58
|
+
import { dataPinKey, isSourcePinKey, sortedSnapshotPins, sourceNameOfPinKey } from './pin-source.mjs';
|
|
59
|
+
import { parseStrictJson, readStrictJsonFile } from './strict-json.mjs';
|
|
60
|
+
|
|
61
|
+
export { DEFINITION_REPOSITORY_PIN_KEY };
|
|
62
|
+
/** The definition file in a definition directory (the editions-root layout). */
|
|
63
|
+
const DEFINITION_FILE = 'xema-distribution.json';
|
|
64
|
+
const SHA_PATTERN = /^[0-9a-f]{40}$/;
|
|
65
|
+
const ENTRY = join(dirname(fileURLToPath(import.meta.url)), 'develop-edition.mjs');
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* `sha256:<hex>` of the exact catalogue.json bytes the vendor step wrote (the catalogue layer digest convention): the
|
|
69
|
+
* lock's catalogueContentDigest, never a pin.
|
|
70
|
+
*/
|
|
71
|
+
export const catalogueContentDigestOf = (bytes) => sha256Digest(bytes);
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* --pins checked and split once, before anything is fetched: `{ definitionRepositoryCommit, members }`, the members
|
|
75
|
+
* every other key. "catalogue" and "definition" are computed, never input.
|
|
76
|
+
*/
|
|
77
|
+
export function splitCatalogueFreePins(pins) {
|
|
78
|
+
if (pins === null || typeof pins !== 'object' || Array.isArray(pins)) {throw new Error('--pins must be an object mapping pin key -> 40-hex commit');}
|
|
79
|
+
for (const computed of [CATALOGUE_PIN_KEY, DEFINITION_PIN_KEY]) {
|
|
80
|
+
if (computed in pins) {throw new Error(`--pins names "${computed}": a catalogue-free resolve computes the definition digest and pins no catalogue`);}
|
|
81
|
+
}
|
|
82
|
+
const { [DEFINITION_REPOSITORY_PIN_KEY]: definitionRepositoryCommit, ...members } = pins;
|
|
83
|
+
if (typeof definitionRepositoryCommit !== 'string' || !SHA_PATTERN.test(definitionRepositoryCommit)) {
|
|
84
|
+
throw new Error(`--pins "${DEFINITION_REPOSITORY_PIN_KEY}" must be the 40-hex commit the definition repository is read at, got ${JSON.stringify(definitionRepositoryCommit)}`);
|
|
85
|
+
}
|
|
86
|
+
if (!Object.keys(members).some((key) => VENDOR_MEMBER_PIN_PATTERN.test(key))) {
|
|
87
|
+
throw new Error('--pins carries no vendor member pin (repos/<x> or submodules/<x>): a catalogue-free resolve resolves the vendor offering from them');
|
|
88
|
+
}
|
|
89
|
+
return { definitionRepositoryCommit, members };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The members of --pins between the two steps: `{ own, vendor }`, own the sources/<name> keys the definition declares.
|
|
94
|
+
* `offering` and `definition` are `{ label, sourceNames }`: a source name both declare is refused, naming both.
|
|
95
|
+
*/
|
|
96
|
+
export function partitionCatalogueFreePins(members, { offering, definition }) {
|
|
97
|
+
const vendorNames = new Set(offering.sourceNames);
|
|
98
|
+
const shared = [...new Set(definition.sourceNames)].filter((name) => vendorNames.has(name));
|
|
99
|
+
if (shared.length > 0) {
|
|
100
|
+
throw new Error(`the source${shared.length === 1 ? '' : 's'} ${shared.map((name) => `"${name}"`).join(', ')} ${shared.length === 1 ? 'is' : 'are'} declared by both the offering ${offering.label} and the definition ${definition.label}: a catalogue-free resolve splits each sources/<name> pin to one of them, so a source name is declared once`);
|
|
101
|
+
}
|
|
102
|
+
const ownNames = new Set(definition.sourceNames);
|
|
103
|
+
const own = {};
|
|
104
|
+
const vendor = {};
|
|
105
|
+
for (const [key, commit] of Object.entries(members)) {
|
|
106
|
+
if (isSourcePinKey(key) && ownNames.has(sourceNameOfPinKey(key))) {own[key] = commit;} else {vendor[key] = commit;}
|
|
107
|
+
}
|
|
108
|
+
return { own, vendor: sortedSnapshotPins(vendor) };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** The definition repository at `commit`, through the source walk's fetch by exact commit; returns its (shared cache) directory. */
|
|
112
|
+
export function fetchDefinitionRepository({ repository, commit, env }) {
|
|
113
|
+
const source = { kind: 'git', name: DEFINITION_REPOSITORY_PIN_KEY, url: `https://github.com/${repository}.git`, ref: commit };
|
|
114
|
+
return materializeGitSource({ source, cacheRoot: join(cacheDir(), 'definition-repositories', ...repository.split('/')), env }).directory;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The tree of `commit` in the repository at `repository`, written into the new directory `into` from the object store
|
|
119
|
+
* (a temporary index beside `into`, never the repository's own index or worktree; symlinks as plain files, so nothing
|
|
120
|
+
* read from `into` resolves outside it). Returns `into`.
|
|
121
|
+
*/
|
|
122
|
+
export function materializeDefinitionCommit({ repository, commit, into }) {
|
|
123
|
+
mkdirSync(into);
|
|
124
|
+
const env = { ...process.env, GIT_INDEX_FILE: `${into}.index`, GIT_NO_LAZY_FETCH: '1' };
|
|
125
|
+
const git = (args) => execFileSync('git', ['-C', repository, '-c', 'core.symlinks=false', ...args], { env, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
126
|
+
try {
|
|
127
|
+
git(['read-tree', `${commit}^{tree}`]);
|
|
128
|
+
git(['checkout-index', '--all', `--prefix=${into}/`]);
|
|
129
|
+
} finally {
|
|
130
|
+
rmSync(`${into}.index`, { force: true });
|
|
131
|
+
}
|
|
132
|
+
return into;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** The argv of the vendor step's child `develop resolve` (after the node binary). */
|
|
136
|
+
export function vendorResolveArgs({ offeringId, pinsFile, outDir, options, roots }) {
|
|
137
|
+
// Always --local: the child is a step of this call, not a release workflow; its descriptor never leaves the scratch
|
|
138
|
+
// directory, and the edition this call writes records this call's own workflow (LOCAL_WORKFLOW only under --local).
|
|
139
|
+
// The child finds and proves the engine itself (--engine-repository/--engine-signer), and seals against the OFFERING's
|
|
140
|
+
// own previous seal (the images lock `develop follow` froze), held by the child to its sha256 and to the offering's id.
|
|
141
|
+
return [
|
|
142
|
+
ENTRY, 'resolve', offeringId, '--pins', pinsFile, '--out', outDir, '--registry', options['--registry'],
|
|
143
|
+
'--engine-repository', options['--engine-repository'], '--engine-signer', options['--engine-signer'], '--cosign', options['--cosign'],
|
|
144
|
+
'--previous-lock', options[OFFERING_PREVIOUS_LOCK_FLAG], '--previous-lock-sha256', options[OFFERING_PREVIOUS_LOCK_SHA256_FLAG],
|
|
145
|
+
'--local', ...roots,
|
|
146
|
+
];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** The vendor step: a child `develop resolve` of this CLI on the vendor roots, writing into `outDir`. */
|
|
150
|
+
export function runVendorResolve({ offeringId, pinsFile, outDir, options, env, roots }) {
|
|
151
|
+
const result = spawnSync(process.execPath, vendorResolveArgs({ offeringId, pinsFile, outDir, options, roots }), { env, stdio: ['ignore', 'ignore', 'inherit'] });
|
|
152
|
+
if (result.error !== undefined || result.status !== 0) {
|
|
153
|
+
throw new Error(`the vendor resolve of the offering "${offeringId}" failed (${result.error?.message ?? `exit ${result.status ?? result.signal}`}); its refusal is above`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* The catalogue the vendor step wrote, its digest, the engine it proved, and the proof the vendor artifact is that offering
|
|
159
|
+
* at those pins. (The digest is of `bytes` as read, the same bytes parsed: never a re-serialisation.)
|
|
160
|
+
*/
|
|
161
|
+
export function readVendorCatalogue({ outDir, offeringId, vendorPins }) {
|
|
162
|
+
const path = join(outDir, CATALOGUE_FILE);
|
|
163
|
+
const bytes = readFileSync(path);
|
|
164
|
+
const catalogue = parseCatalogue(parseStrictJson(bytes.toString('utf8'), path));
|
|
165
|
+
if (catalogue.offeringId !== offeringId || catalogue.channel !== EditionChannel.Develop) {
|
|
166
|
+
throw new Error(`the vendor step wrote the catalogue of "${catalogue.offeringId}" (${catalogue.channel}), not of the develop offering "${offeringId}"`);
|
|
167
|
+
}
|
|
168
|
+
const descriptor = parseEditionDescriptor(readStrictJsonFile(join(outDir, EDITION_DESCRIPTOR_FILE)));
|
|
169
|
+
if (serializeJson(sortedSnapshotPins(descriptor.provenance.pins)) !== serializeJson(vendorPins)) {
|
|
170
|
+
throw new Error(`the vendor step resolved "${offeringId}" at other pins than the vendor pins it was given`);
|
|
171
|
+
}
|
|
172
|
+
return { catalogue, catalogueContentDigest: catalogueContentDigestOf(bytes), engineImage: descriptor.engine.image };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Run `body` with the workspace on `roots`, restoring the previous roots after. */
|
|
176
|
+
async function onRoots(roots, body) {
|
|
177
|
+
const restored = configuredWorkspace();
|
|
178
|
+
resetEditionWorkspaceConfig();
|
|
179
|
+
configureEditionWorkspace(roots);
|
|
180
|
+
try {
|
|
181
|
+
return await body();
|
|
182
|
+
} finally {
|
|
183
|
+
resetEditionWorkspaceConfig();
|
|
184
|
+
configureEditionWorkspace(restored);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* The catalogue-free customer develop resolve (see the header). Writes the artifact to --out and returns
|
|
190
|
+
* `{ id, pins, target, catalogueContentDigest, engineImage }`. The edition records the engine the vendor step proved, and
|
|
191
|
+
* carries the customer's own composed generations from --previous-lock (or --first-seal) under the customer's id.
|
|
192
|
+
* `fetchDefinition`, `vendorResolve`, `resolveCustomer`, `evidenceTrust` and `write` are replaced by tests; `print`
|
|
193
|
+
* receives the target.
|
|
194
|
+
*/
|
|
195
|
+
export async function resolveCatalogueFree({
|
|
196
|
+
options,
|
|
197
|
+
env = process.env,
|
|
198
|
+
fetchDefinition = fetchDefinitionRepository,
|
|
199
|
+
vendorResolve = runVendorResolve,
|
|
200
|
+
resolveCustomer = resolveCustomerChannelEdition,
|
|
201
|
+
evidenceTrust = descriptorEvidenceTrust,
|
|
202
|
+
write = writeEditionArtifact,
|
|
203
|
+
print = (line) => console.log(line),
|
|
204
|
+
}) {
|
|
205
|
+
const definition = parseFollowDefinition(options[DEFINITION_AT_FLAG], DEFINITION_AT_FLAG);
|
|
206
|
+
const offering = parseFollowDefinition(options[OFFERING_FLAG], OFFERING_FLAG);
|
|
207
|
+
const rawPins = readStrictJsonFile(options['--pins']);
|
|
208
|
+
const offeringId = assertDefinitionCheckout({ definition: offering, dataSha: rawPins?.[dataPinKey()], flag: OFFERING_FLAG, shaLabel: `--pins "${dataPinKey()}"` });
|
|
209
|
+
const { definitionRepositoryCommit, members } = splitCatalogueFreePins(rawPins);
|
|
210
|
+
const offeringLabel = `${offering.repository}:${offering.path}/${DEFINITION_FILE}`;
|
|
211
|
+
const offeringSourceNames = declaredSourceNamesOf(readStrictJsonFile(join(editionsRoot(), offeringId, DEFINITION_FILE)));
|
|
212
|
+
const vendorRoots = configuredWorkspace();
|
|
213
|
+
const scratch = mkdtempSync(join(tmpdir(), 'xema-catalogue-free-'));
|
|
214
|
+
try {
|
|
215
|
+
const cached = fetchDefinition({ repository: definition.repository, commit: definitionRepositoryCommit, env });
|
|
216
|
+
const checkout = materializeDefinitionCommit({ repository: cached, commit: definitionRepositoryCommit, into: join(scratch, DEFINITION_REPOSITORY_PIN_KEY) });
|
|
217
|
+
const definitionDir = join(checkout, definition.path);
|
|
218
|
+
const definitionLabel = `${definition.repository}:${definition.path}/${DEFINITION_FILE}`;
|
|
219
|
+
if (existsSync(join(definitionDir, CATALOGUE_PIN_FILE))) {
|
|
220
|
+
throw new Error(`${definition.repository}:${definition.path} carries ${CATALOGUE_PIN_FILE}: a definition is resolved against its pinned catalogue (--definition <file>) or catalogue-free, never both`);
|
|
221
|
+
}
|
|
222
|
+
const rawDefinition = readStrictJsonFile(join(definitionDir, DEFINITION_FILE));
|
|
223
|
+
assertCanonicalDistributionId(rawDefinition?.id);
|
|
224
|
+
if (rawDefinition.id !== basename(definition.path)) {
|
|
225
|
+
throw new Error(`${definitionLabel} is "${rawDefinition.id}", not the directory's "${basename(definition.path)}"`);
|
|
226
|
+
}
|
|
227
|
+
const split = partitionCatalogueFreePins(members, {
|
|
228
|
+
offering: { label: offeringLabel, sourceNames: offeringSourceNames },
|
|
229
|
+
definition: { label: definitionLabel, sourceNames: declaredSourceNamesOf(rawDefinition) },
|
|
230
|
+
});
|
|
231
|
+
const sourceImages = customerSourceImages(options, rawDefinition);
|
|
232
|
+
const vendorOut = join(scratch, 'vendor');
|
|
233
|
+
const pinsFile = join(scratch, 'vendor-pins.json');
|
|
234
|
+
writeFileSync(pinsFile, serializeJson(split.vendor));
|
|
235
|
+
vendorResolve({ offeringId, pinsFile, outDir: vendorOut, options, env, roots: workspaceArgv() });
|
|
236
|
+
const { catalogue, catalogueContentDigest, engineImage } = readVendorCatalogue({ outDir: vendorOut, offeringId, vendorPins: split.vendor });
|
|
237
|
+
const sources = join(scratch, 'sources');
|
|
238
|
+
mkdirSync(sources);
|
|
239
|
+
const customerRoots = {
|
|
240
|
+
[WorkspaceRoot.Data]: join(checkout, definition.dataRoot),
|
|
241
|
+
[WorkspaceRoot.Editions]: dirname(definitionDir),
|
|
242
|
+
[WorkspaceRoot.Sources]: sources,
|
|
243
|
+
[WorkspaceRoot.Cache]: vendorRoots[WorkspaceRoot.Cache],
|
|
244
|
+
};
|
|
245
|
+
return await onRoots(customerRoots, async () => {
|
|
246
|
+
const { id, pins, ...resolved } = await resolveCustomer({
|
|
247
|
+
channel: EditionChannel.Develop,
|
|
248
|
+
rawDefinition,
|
|
249
|
+
catalogue,
|
|
250
|
+
catalogueContentDigest,
|
|
251
|
+
pins: split.own,
|
|
252
|
+
vendorPins: { ...split.vendor, [DEFINITION_REPOSITORY_PIN_KEY]: definitionRepositoryCommit },
|
|
253
|
+
previousSeal: customerPreviousSeal(EditionChannel.Develop, options),
|
|
254
|
+
sourceImages,
|
|
255
|
+
env,
|
|
256
|
+
// The customer's git-sourced OCI bundles, verified under the definition's own pinned policy (its data root).
|
|
257
|
+
developGitBundles: customerDevelopGitBundleVerifier({ channel: EditionChannel.Develop, cosignBinary: options['--cosign'], env }),
|
|
258
|
+
});
|
|
259
|
+
const evidenceTrustPolicy = await evidenceTrust({ kind: EditionKind.Resolved, channel: EditionChannel.Develop, consumesCatalogue: true });
|
|
260
|
+
const target = snapshotEditionTarget(EditionChannel.Develop, id, pins, { registry: options['--registry'], engineImage, sourceImages });
|
|
261
|
+
write({
|
|
262
|
+
outDir: resolve(options['--out']),
|
|
263
|
+
channel: EditionChannel.Develop,
|
|
264
|
+
distributionId: id,
|
|
265
|
+
pins,
|
|
266
|
+
workflow: options['--local'] ? LOCAL_WORKFLOW : workflowFromEnv(env),
|
|
267
|
+
evidenceTrustPolicy,
|
|
268
|
+
engineImage,
|
|
269
|
+
distributionLock: resolved.distributionLock,
|
|
270
|
+
imageLock: resolved.imageLock,
|
|
271
|
+
authorizationLock: resolved.authorizationLock,
|
|
272
|
+
chartSetLock: resolved.chartSetLock,
|
|
273
|
+
});
|
|
274
|
+
print(target);
|
|
275
|
+
return { id, pins, target, catalogueContentDigest, engineImage };
|
|
276
|
+
});
|
|
277
|
+
} finally {
|
|
278
|
+
rmSync(scratch, { recursive: true, force: true });
|
|
279
|
+
}
|
|
280
|
+
}
|
|
@@ -23,6 +23,23 @@
|
|
|
23
23
|
// declares (`freeze-sources --definition` writes it); the catalogue digest is frozen into the descriptor's
|
|
24
24
|
// provenance pins as `catalogue`. The id is the definition's. Takes --data-root, --editions-root,
|
|
25
25
|
// --sources-root, --cache-dir (no --deploy-root, no --kernel: the kernel is the catalogue's, from npm).
|
|
26
|
+
// It takes the customer edition's OWN previous seal, exactly one of --previous-lock <file> --previous-lock-sha256 <hex>
|
|
27
|
+
// (the customer edition's last images lock: sha256 of its bytes, its id the definition's) or --first-seal; it carries
|
|
28
|
+
// the generations of the customer's own composed artifacts forward (vendor rows are the catalogue's, unchanged).
|
|
29
|
+
// resolve --definition-at <owner>/<repo>,<path>,<dataRoot> --offering <owner>/<repo>,<path>,<dataRoot>
|
|
30
|
+
// --pins <file> --out <dir> --registry <root> --engine-repository <repository> --engine-signer <certificate identity>
|
|
31
|
+
// --cosign <absolute-path> --offering-previous-lock <file> --offering-previous-lock-sha256 <hex>
|
|
32
|
+
// (--previous-lock <file> --previous-lock-sha256 <hex> | --first-seal) [--source-image <source>=<registry>/<prefix>]... [--local]
|
|
33
|
+
// a CATALOGUE-FREE customer develop edition (develop-catalogue-free-resolve.mjs): the vendor offering named by
|
|
34
|
+
// --offering (its directory in the vendor data checkout) is resolved first by a child `resolve` on the vendor
|
|
35
|
+
// roots, sealed against the OFFERING's previous seal (--offering-previous-lock, as `follow` froze it, held to its
|
|
36
|
+
// sha256 and the offering's id) with the engine it finds and proves; then the definition, read from its repository at
|
|
37
|
+
// --pins "definition-repository", is resolved against the catalogue that child wrote, on its own roots, carrying the
|
|
38
|
+
// customer's own composed generations from --previous-lock (or --first-seal). --pins is the complete frozen set (the
|
|
39
|
+
// vendor pins, the definition's sources/<name>, "definition-repository"); no head is read and nothing is pushed. The
|
|
40
|
+
// edition pins those and the definition digest, no catalogue (the catalogue's content digest is in the distribution
|
|
41
|
+
// lock only), and records the vendor step's engine. catalogue.pin.json beside the definition is refused.
|
|
42
|
+
// Takes the vendor roots.
|
|
26
43
|
// engine <id> --pins <file> --engine-repository <repository> --engine-signer <certificate identity> --cosign <absolute-path>
|
|
27
44
|
// print <repository>@sha256:<digest>: the engine xema-deploy CI published (tag sha-<12 hex>) for the
|
|
28
45
|
// snapshot's xema-deploy commit, its signature verified offline against the pinned trust root as made by
|
|
@@ -87,6 +104,22 @@
|
|
|
87
104
|
// part 07 passes the flag.
|
|
88
105
|
// Residual: anyone who can delete registry tags can remove newer records, and this then pins the newest remaining
|
|
89
106
|
// valid one; the guard is part 07's, deploy-latest's owner refuses a snapshot that goes backwards.
|
|
107
|
+
// follow --definition-at <owner>/<repo>,<path>,<dataRoot> --data-sha <sha> --kernel-sha <sha> --deploy-sha <upper bound>
|
|
108
|
+
// --engine-repository <registry>/<path> --registry <root> --cosign <absolute-path> (--first | --current-pins <file>) --out <file>
|
|
109
|
+
// the pins a dev deployment's follow policy selects (develop-follow.mjs), written once (wx) as the COMPLETE frozen
|
|
110
|
+
// set `resolve` reads; nothing else is read or written. The data root checkout is the definition: the data root's
|
|
111
|
+
// declared repository, clean at --data-sha, its data root and distribution directory at dataRoot and path, and the
|
|
112
|
+
// editions root that same repository; the kernel checkout is at --kernel-sha. Members: select-records' newest-commit
|
|
113
|
+
// rule over their signed build records (the same verifier), with the --current-pins as the published pins (behind or
|
|
114
|
+
// equal keeps the current pin, diverged is refused by name), pinned as freeze-graph --member-pins pins them (a member
|
|
115
|
+
// that keeps its current resolution at its develop head). Deploy root: --deploy-sha is an UPPER BOUND, the pin the
|
|
116
|
+
// newest first-parent commit of the deploy checkout at or below it whose engine <--engine-repository>:sha-<12 hex> is
|
|
117
|
+
// published (unpublished skipped and counted, none within the walk limit refused by name). Tooling roots:
|
|
118
|
+
// freeze-graph's rules, then each classified against its current pin by ancestry in its own checkout (behind,
|
|
119
|
+
// diverged or absent from the checkout is refused by type). Declared sources: their develop heads, newer-wins
|
|
120
|
+
// against the current pin by the same ancestry rule. Prints {distributionId, definition, follow: first |
|
|
121
|
+
// from-current, tooling, advanced, kept, added, dropped, deploy: {deploySha, upperBound, skipped}}: --first says so
|
|
122
|
+
// and classifies nothing. Takes the roots `resolve` takes.
|
|
90
123
|
// image-record <id> --selection <file> --pins <file> --out <file>
|
|
91
124
|
// write the edition's image build record (the file `resolve --images-from` reads) from a select-records selection:
|
|
92
125
|
// the subjects are the checked-out snapshot's first-party image subjects (enumerated as `coverage` does). A subject of
|
|
@@ -120,10 +153,12 @@ import { resolve } from 'node:path';
|
|
|
120
153
|
import { writeCatalogue } from './catalogue.mjs';
|
|
121
154
|
import { CUSTOMER_ROOTS, FIRST_SEAL_FLAG, IMAGES_FROM_FLAG, PREVIOUS_LOCK_FLAG, PREVIOUS_LOCK_SHA256_FLAG, headResolver, parseEditionVerbArgs, runCustomerVerb, writeOnce } from './customer-edition-verbs.mjs';
|
|
122
155
|
import { DEFINITION_FLAG } from './customer-flags.mjs';
|
|
156
|
+
import { resolveCatalogueFree } from './develop-catalogue-free-resolve.mjs';
|
|
123
157
|
import { runDevelopCoverage } from './develop-coverage-run.mjs';
|
|
124
158
|
import { CoverageExit } from './develop-coverage.mjs';
|
|
125
159
|
import { resolveDevelopEdition } from './develop-edition-resolve.mjs';
|
|
126
160
|
import { resolveDevelopEngineImage } from './develop-engine-image.mjs';
|
|
161
|
+
import { runFollow } from './develop-follow.mjs';
|
|
127
162
|
import { assertNoToolingOverride, freezeGraph, freezeSources } from './develop-freeze.mjs';
|
|
128
163
|
import { developGraphMembers } from './develop-graph.mjs';
|
|
129
164
|
import { runImageRecord } from './develop-image-record.mjs';
|
|
@@ -189,7 +224,7 @@ async function imageBuildRecordTagInput(options) {
|
|
|
189
224
|
}
|
|
190
225
|
|
|
191
226
|
async function run(argv, env) {
|
|
192
|
-
const { command, id, options, customer } = parseDevelopEditionArgs(argv);
|
|
227
|
+
const { command, id, options, customer, catalogueFree } = parseDevelopEditionArgs(argv);
|
|
193
228
|
switch (command) {
|
|
194
229
|
case 'target': {
|
|
195
230
|
if (customer) {return runCustomerVerb({ channel: EditionChannel.Develop, command, options, env });}
|
|
@@ -198,6 +233,10 @@ async function run(argv, env) {
|
|
|
198
233
|
}
|
|
199
234
|
case 'resolve': {
|
|
200
235
|
if (customer) {return runCustomerVerb({ channel: EditionChannel.Develop, command, options, env });}
|
|
236
|
+
if (catalogueFree) {
|
|
237
|
+
await resolveCatalogueFree({ options, env });
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
201
240
|
const pins = readStrictJsonFile(options['--pins']);
|
|
202
241
|
const workflow = options['--local'] ? LOCAL_WORKFLOW : workflowFromEnv(env);
|
|
203
242
|
const evidenceTrustPolicy = await descriptorEvidenceTrust({ kind: EditionKind.Resolved, channel: EditionChannel.Develop });
|
|
@@ -262,6 +301,9 @@ async function run(argv, env) {
|
|
|
262
301
|
json: options['--json'] === true,
|
|
263
302
|
env,
|
|
264
303
|
});
|
|
304
|
+
case 'follow':
|
|
305
|
+
await runFollow({ options, env });
|
|
306
|
+
return;
|
|
265
307
|
case 'select-records':
|
|
266
308
|
await runSelectRecords({ options, env });
|
|
267
309
|
return;
|