@xemahq/xema 0.32.0 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/edition.d.ts.map +1 -1
- package/dist/commands/edition.js +3 -1
- package/dist/commands/edition.js.map +1 -1
- package/dist/edition/verify-image-evidence.bundle.mjs +243 -160
- package/dist/edition/verify-image-evidence.bundle.provenance.json +45 -39
- package/dist/runtime/edition/verb-map.d.ts +3 -1
- package/dist/runtime/edition/verb-map.d.ts.map +1 -1
- package/dist/runtime/edition/verb-map.js +4 -0
- package/dist/runtime/edition/verb-map.js.map +1 -1
- package/edition/README.md +19 -0
- package/edition/checkout-indexed-source-repositories.mjs +57 -2
- package/edition/customer-definition.mjs +8 -0
- package/edition/develop-snapshot-inputs.mjs +4 -15
- package/edition/dist-check.mjs +11 -7
- package/edition/edition-biome-manifests.mjs +3 -2
- 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-source-pins.mjs +209 -0
- package/edition/edition-unpin.mjs +16 -0
- package/edition/image-evidence.mjs +13 -7
- package/edition/indexes-at-pins.mjs +29 -0
- package/edition/release-catalogue.mjs +9 -8
- package/edition/release-edition-artifact.mjs +3 -2
- package/edition/resolve-distribution.mjs +13 -25
- package/edition/resolve-images.mjs +12 -11
- package/edition/seal-image-digests.mjs +12 -5
- package/edition/source-authority.mjs +1 -1
- package/package.json +2 -2
|
@@ -21,13 +21,14 @@ import { assertCanonicalDistributionId } from './distribution-paths.mjs';
|
|
|
21
21
|
import { LOCAL_WORKFLOW, assertEngineImage, editionTarget, workflowFromEnv, writeEditionArtifact } from './edition-artifact.mjs';
|
|
22
22
|
import { assertRosteredChartSetLock } from './edition-chart-set.mjs';
|
|
23
23
|
import { EditionChannel, EditionLockFile } from './edition-constants.mjs';
|
|
24
|
+
import { editionPinnedInputs } from './edition-pinned-inputs.mjs';
|
|
24
25
|
import { WorkspaceRoot, configureEditionWorkspaceFromProcess, editionsRoot, isEntryModule } from './edition-workspace.mjs';
|
|
25
26
|
import { descriptorEvidenceTrust } from './evidence-trust-policy.mjs';
|
|
26
27
|
import { loadKernelDistributionSchemas, validateImmutableImageLock } from './lib.mjs';
|
|
27
28
|
import { releaseCatalogue } from './release-catalogue.mjs';
|
|
28
29
|
import { assertReleaseEditionTree } from './release-channel-guard.mjs';
|
|
29
30
|
import { externalSourceTrees, sourceTreeLocator } from './resolve-images.mjs';
|
|
30
|
-
import {
|
|
31
|
+
import { createPinnedSourceAuthority } from './source-authority.mjs';
|
|
31
32
|
|
|
32
33
|
if (isEntryModule(import.meta.url)) {
|
|
33
34
|
configureEditionWorkspaceFromProcess([WorkspaceRoot.Data, WorkspaceRoot.Editions, WorkspaceRoot.Sources, WorkspaceRoot.Kernel]);
|
|
@@ -110,7 +111,7 @@ export async function readCommittedEdition(id, root = editionsRoot()) {
|
|
|
110
111
|
{ cause: error },
|
|
111
112
|
);
|
|
112
113
|
}
|
|
113
|
-
const authority = createPinnedSourceAuthority({ pins:
|
|
114
|
+
const authority = createPinnedSourceAuthority({ pins: (await editionPinnedInputs(id, { schemas })).repositoryPins });
|
|
114
115
|
await verifyBiomeContentHashes({
|
|
115
116
|
schemas,
|
|
116
117
|
lock: sealed,
|
|
@@ -77,7 +77,9 @@ import {
|
|
|
77
77
|
import { resolveDispatchedRuntimeComponents } from './dispatched-runtime-components.mjs';
|
|
78
78
|
import { fleetChartSetLockPath, writeEditionChartSet } from './edition-chart-set.mjs';
|
|
79
79
|
import { workspaceSection } from './edition-config.mjs';
|
|
80
|
-
import {
|
|
80
|
+
import { editionPinnedInputs } from './edition-pinned-inputs.mjs';
|
|
81
|
+
import { splitEditionDefinition } from './edition-source-pins.mjs';
|
|
82
|
+
import { WorkspaceRoot, configureEditionWorkspaceFromProcess, editionsRoot, isEntryModule, kernelPackageDir } from './edition-workspace.mjs';
|
|
81
83
|
import { walkDeclaredSources } from './external-source-walk.mjs';
|
|
82
84
|
import {
|
|
83
85
|
biomeTierOrder,
|
|
@@ -260,7 +262,7 @@ export function loadEditionManifest(parseDistribution, id) {
|
|
|
260
262
|
"(core class, the classes the edition's include names, and services a shipped biome's API requires)",
|
|
261
263
|
);
|
|
262
264
|
}
|
|
263
|
-
const manifest = parseDistribution(raw);
|
|
265
|
+
const manifest = parseDistribution(splitEditionDefinition(raw, id).document);
|
|
264
266
|
if (manifest.id !== id) {
|
|
265
267
|
throw new Error(`distribution at ${path} declares id "${manifest.id}", but it was addressed as "${id}"`);
|
|
266
268
|
}
|
|
@@ -314,26 +316,6 @@ export function composeManifest(manifest, pinSource = PUBLISHED_PIN_SOURCE) {
|
|
|
314
316
|
};
|
|
315
317
|
}
|
|
316
318
|
|
|
317
|
-
function readBiomeIndex() {
|
|
318
|
-
if (!existsSync(biomeIndexPath())) {
|
|
319
|
-
throw new Error(
|
|
320
|
-
`${biomeIndexPath()} not found — run \`${commandFor('build-biome-index.mjs')}\` first`,
|
|
321
|
-
);
|
|
322
|
-
}
|
|
323
|
-
return JSON.parse(readFileSync(biomeIndexPath(), 'utf8'));
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
function readPlatformServiceIndex(parsePlatformServiceSourceIndex) {
|
|
327
|
-
if (!existsSync(platformServiceIndexPath())) {
|
|
328
|
-
throw new Error(
|
|
329
|
-
`${platformServiceIndexPath()} not found — run the trusted platform-service index generator first`,
|
|
330
|
-
);
|
|
331
|
-
}
|
|
332
|
-
return parsePlatformServiceSourceIndex(
|
|
333
|
-
JSON.parse(readFileSync(platformServiceIndexPath(), 'utf8')),
|
|
334
|
-
);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
319
|
/** Unique key for an index/lock entry (id+target+repo distinguishes cultivar/reference + web/server pairs). */
|
|
338
320
|
function entryKey(e) {
|
|
339
321
|
return JSON.stringify([e.id, e.target, e.repo]);
|
|
@@ -2229,8 +2211,14 @@ export async function resolveDistributionWithIndex(id, { materialize, inputs, re
|
|
|
2229
2211
|
// THE RELEASE READS THESE FROM DISK; A DEVELOP SNAPSHOT INJECTS THEM
|
|
2230
2212
|
// (`develop-snapshot-inputs.mjs`). Either way they pass through the one
|
|
2231
2213
|
// validation below, so there is a single resolve path and two sources.
|
|
2232
|
-
|
|
2233
|
-
|
|
2214
|
+
// The edition's own indexes (editionPinnedInputs): the committed ones, or
|
|
2215
|
+
// rebuilt at the repositories it holds. Read only when the caller injected
|
|
2216
|
+
// neither.
|
|
2217
|
+
const pinned =
|
|
2218
|
+
inputs?.platformServiceIndex === undefined || inputs?.biomeIndex === undefined
|
|
2219
|
+
? await editionPinnedInputs(id, { schemas })
|
|
2220
|
+
: undefined;
|
|
2221
|
+
const claimedPlatformServiceIndex = inputs?.platformServiceIndex ?? pinned.platformServiceIndex;
|
|
2234
2222
|
// OWNERSHIP IS THE SERVICE'S OWN STATEMENT: each entry's capability domains
|
|
2235
2223
|
// are re-derived from its pinned source manifest (hash-verified), so a
|
|
2236
2224
|
// hand-edited index cannot move where grants land. Only the customer resolve
|
|
@@ -2242,7 +2230,7 @@ export async function resolveDistributionWithIndex(id, { materialize, inputs, re
|
|
|
2242
2230
|
? claimedPlatformServiceIndex
|
|
2243
2231
|
: verifiedPlatformServiceIndex(claimedPlatformServiceIndex, { readManifest });
|
|
2244
2232
|
const platformIndex = validateServiceDependencyOwnerParity(
|
|
2245
|
-
validateImmutableBiomeIndex(inputs?.biomeIndex ??
|
|
2233
|
+
validateImmutableBiomeIndex(inputs?.biomeIndex ?? pinned.biomeIndex),
|
|
2246
2234
|
platformServiceIndex,
|
|
2247
2235
|
);
|
|
2248
2236
|
const { entries, sourceRecords } = await walkDeclaredSources({
|
|
@@ -45,7 +45,8 @@ import {
|
|
|
45
45
|
readBoundDistributionFile,
|
|
46
46
|
} from './distribution-paths.mjs';
|
|
47
47
|
import { editionWorkspaceConfig } from './edition-config.mjs';
|
|
48
|
-
import {
|
|
48
|
+
import { editionPinnedInputs } from './edition-pinned-inputs.mjs';
|
|
49
|
+
import { dataRoot, editionsRoot, WorkspaceRoot, configureEditionWorkspaceFromProcess, isEntryModule, sourcePath, sourcesRoot } from './edition-workspace.mjs';
|
|
49
50
|
import {
|
|
50
51
|
externalSourceCacheDir,
|
|
51
52
|
materializeGitSource,
|
|
@@ -68,7 +69,6 @@ import {
|
|
|
68
69
|
} from './lib.mjs';
|
|
69
70
|
import { assertReleaseContent } from './release-channel-guard.mjs';
|
|
70
71
|
import { composeEdition } from './resolve-distribution.mjs';
|
|
71
|
-
import { readStrictJsonFile } from './strict-json.mjs';
|
|
72
72
|
|
|
73
73
|
if (isEntryModule(import.meta.url)) {
|
|
74
74
|
configureEditionWorkspaceFromProcess([WorkspaceRoot.Data, WorkspaceRoot.Editions, WorkspaceRoot.Sources, WorkspaceRoot.Kernel, WorkspaceRoot.Cache]);
|
|
@@ -164,10 +164,6 @@ const FIRST_PARTY_TREES = (repo) => ({ dir: sourcePath(repo), base: sourcesRoot(
|
|
|
164
164
|
const imageBuildInputHashByIdentity = new Map();
|
|
165
165
|
const sourceManifestHashByIdentity = new Map();
|
|
166
166
|
|
|
167
|
-
function readJson(path) {
|
|
168
|
-
return readStrictJsonFile(path);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
167
|
/**
|
|
172
168
|
* Memoized image build-input closure hash.
|
|
173
169
|
*
|
|
@@ -1489,8 +1485,13 @@ export function resolveCrossRepoInputsForRoster({
|
|
|
1489
1485
|
return crossRepoInputsByConsumer;
|
|
1490
1486
|
}
|
|
1491
1487
|
|
|
1492
|
-
/**
|
|
1493
|
-
|
|
1488
|
+
/**
|
|
1489
|
+
* The release's inputs: the COMMITTED lock, and the edition's own indexes
|
|
1490
|
+
* (`editionPinnedInputs`: the committed ones, or rebuilt at the repositories the
|
|
1491
|
+
* edition holds).
|
|
1492
|
+
*/
|
|
1493
|
+
async function releaseImageInputs(schemas, id) {
|
|
1494
|
+
const pinned = await editionPinnedInputs(id, { schemas });
|
|
1494
1495
|
return {
|
|
1495
1496
|
lock: validateImmutableDistributionLock(
|
|
1496
1497
|
readBoundDistributionFile({
|
|
@@ -1501,8 +1502,8 @@ function releaseImageInputs(schemas, id) {
|
|
|
1501
1502
|
parse: (raw) => parseResolvedDistributionLock(schemas, raw),
|
|
1502
1503
|
}).value,
|
|
1503
1504
|
),
|
|
1504
|
-
runtimeSourceIndex:
|
|
1505
|
-
biomeIndex:
|
|
1505
|
+
runtimeSourceIndex: pinned.runtimeImageSourceIndex,
|
|
1506
|
+
biomeIndex: pinned.biomeIndex,
|
|
1506
1507
|
requiredPlatforms: resolveDistributionRequiredPlatforms(schemas, id),
|
|
1507
1508
|
declaredSources: resolveDistributionDeclaredSources(schemas, id),
|
|
1508
1509
|
};
|
|
@@ -1544,7 +1545,7 @@ export async function resolveImageRows(id, { inputs, lock } = {}) {
|
|
|
1544
1545
|
biomeIndex,
|
|
1545
1546
|
requiredPlatforms,
|
|
1546
1547
|
declaredSources,
|
|
1547
|
-
} = inputs === undefined ? releaseImageInputs(schemas, id) : snapshotImageInputs(schemas, { inputs, lock });
|
|
1548
|
+
} = inputs === undefined ? await releaseImageInputs(schemas, id) : snapshotImageInputs(schemas, { inputs, lock });
|
|
1548
1549
|
// Materialise every EXTERNAL source at the commit the lock pins, and resolve
|
|
1549
1550
|
// every `repo` handle through one locator from here down.
|
|
1550
1551
|
const locate = sourceTreeLocator(externalSourceTrees(distributionLock));
|
|
@@ -118,6 +118,7 @@ import { fetchCatalogue } from './catalogue-fetch.mjs';
|
|
|
118
118
|
import { readCataloguePin } from './catalogue-pin.mjs';
|
|
119
119
|
import { CATALOGUE_TOKEN_ENV_FLAG, CATALOGUE_USERNAME_FLAG, DEFINITION_FLAG } from './customer-flags.mjs';
|
|
120
120
|
import { EditionChannel } from './edition-constants.mjs';
|
|
121
|
+
import { editionPinnedInputs } from './edition-pinned-inputs.mjs';
|
|
121
122
|
import { WorkspaceRoot, biomeIndexPath, configureEditionWorkspaceFromProcess, editionsRoot, isEntryModule, runtimeImageSourceIndexPath } from './edition-workspace.mjs';
|
|
122
123
|
import {
|
|
123
124
|
assertCoordinatesFinal,
|
|
@@ -508,11 +509,14 @@ export async function sealEditionLocks({ distributionId, root, digests = new Map
|
|
|
508
509
|
}
|
|
509
510
|
const raw = JSON.parse(readFileSync(lockPath, 'utf8'));
|
|
510
511
|
const imageLock = schemas.parseImageLock(rawImages);
|
|
511
|
-
|
|
512
|
+
// A release seal reads the EDITION's indexes and pins (a held repository is sealed at the held commit); a
|
|
513
|
+
// customer seal (catalogue) and a develop seal (injected `vendoredCommit`) are not vendor release editions.
|
|
514
|
+
const editionInputs = catalogue === undefined && vendoredCommit === undefined ? await editionPinnedInputs(distributionId, { schemas }) : undefined;
|
|
515
|
+
const pinned = vendoredCommit ?? ((repo) => createPinnedSourceAuthority({ pins: editionInputs?.repositoryPins ?? committedRepositoryPins() }).vendoredSourceCommitForRepository(repo));
|
|
512
516
|
const lockDocument = raw !== null && typeof raw === 'object' && 'unsealedSchema' in raw ? schemas.parseUnsealedDistributionLock(raw) : schemas.parseDistributionLock(raw);
|
|
513
517
|
if (lockMountsAdapters(lockDocument)) {
|
|
514
518
|
// The adapter host's row: the verified catalogue's for a customer, the data root's biome index for the vendor.
|
|
515
|
-
const rows = catalogue
|
|
519
|
+
const rows = catalogue !== undefined ? catalogue.biomes : (editionInputs?.biomeIndex ?? JSON.parse(readFileSync(biomeIndexPath(), 'utf8')));
|
|
516
520
|
await assertAdapterBundlesMatchHost({ lock: lockDocument, hostRow: adapterHostRow(rows), fetchManifest });
|
|
517
521
|
}
|
|
518
522
|
const result = await sealOrVerifyLock({
|
|
@@ -762,9 +766,12 @@ async function main() {
|
|
|
762
766
|
),
|
|
763
767
|
imageLock,
|
|
764
768
|
distributionId,
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
769
|
+
// A vendor edition grades the pins it RESOLVES at (a held repository is graded at the held commit);
|
|
770
|
+
// a customer seal grades the data root's committed index.
|
|
771
|
+
runtimeSourceIndex:
|
|
772
|
+
catalogue === undefined
|
|
773
|
+
? (await editionPinnedInputs(distributionId, { schemas: await loadKernelDistributionSchemas() })).runtimeImageSourceIndex
|
|
774
|
+
: JSON.parse(readFileSync(runtimeImageSourceIndexPath(), 'utf8')),
|
|
768
775
|
});
|
|
769
776
|
|
|
770
777
|
const { writes, unchanged } = planDigestSeal({
|
|
@@ -184,7 +184,7 @@ function ledgerCommits(repo, ledger) {
|
|
|
184
184
|
* positive — each is a record that a build actually happened — while either
|
|
185
185
|
* alone has been measured missing whole repositories.
|
|
186
186
|
*/
|
|
187
|
-
function builtCommits(directory, ref, repo, ledger) {
|
|
187
|
+
export function builtCommits(directory, ref, repo, ledger) {
|
|
188
188
|
const fromTree = evidenceTreeCommits(directory, ref);
|
|
189
189
|
const fromLedger = ledgerCommits(repo, ledger);
|
|
190
190
|
// Null from BOTH means neither record has ever heard of this repository, so
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xemahq/xema",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.33.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/xema-dev/xema-cli.git",
|
|
@@ -92,6 +92,6 @@
|
|
|
92
92
|
"typecheck": "tsc --noEmit"
|
|
93
93
|
},
|
|
94
94
|
"xemaRelease": {
|
|
95
|
-
"sourceDigest": "sha256:
|
|
95
|
+
"sourceDigest": "sha256:ec5750fec69809f0c18307f59b655b145349cffd670741ac33dbb1aac0029d62"
|
|
96
96
|
}
|
|
97
97
|
}
|