@xemahq/xema 0.29.1 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -389
- package/dist/commands/edition.js +3 -3
- package/dist/commands/edition.js.map +1 -1
- package/dist/edition/verify-image-evidence.bundle.mjs +8715 -807
- package/dist/edition/verify-image-evidence.bundle.provenance.json +873 -405
- package/edition/README.md +53 -273
- package/edition/adapter-bundled-versions.mjs +107 -0
- package/edition/adapter-host-versions.mjs +41 -0
- package/edition/authorization-base.mjs +18 -9
- package/edition/build-runtime-image-source-index.mjs +77 -4
- package/edition/catalogue.mjs +15 -12
- package/edition/customer-definition.mjs +3 -20
- package/edition/develop-customer-edition.mjs +2 -2
- package/edition/develop-edition-resolve.mjs +10 -2
- package/edition/develop-edition.mjs +56 -62
- package/edition/develop-freeze.mjs +14 -3
- package/edition/dist-check.mjs +1 -0
- package/edition/distribution-evidence-set.mjs +32 -5
- package/edition/edition-channel.mjs +15 -1
- package/edition/edition-locks.mjs +16 -6
- package/edition/edition-protocol.mjs +30 -8
- package/edition/edition-workspace.mjs +8 -0
- package/edition/image-evidence.mjs +133 -25
- package/edition/kernel-package.mjs +193 -0
- package/edition/lib.mjs +73 -1
- package/edition/oci-evidence-verbs.mjs +2 -2
- package/edition/plan-image-evidence-refresh.mjs +2 -2
- package/edition/produce-distribution-completeness-evidence.mjs +5 -0
- package/edition/produce-image-delivery-entitlement-evidence.mjs +11 -1
- package/edition/release-catalogue.mjs +10 -3
- package/edition/resolve-chart-set.mjs +1 -1
- package/edition/resolve-customer-edition.mjs +29 -33
- package/edition/resolve-distribution.mjs +5 -1
- package/edition/resolve-edition.mjs +3 -2
- package/edition/seal-image-digests.mjs +16 -4
- package/edition/trust-policy-arming-verb.mjs +2 -10
- package/edition/verify-image-evidence.mjs +1 -0
- package/examples/README.md +3 -2
- package/package.json +3 -3
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
// kernel-package — the customer edition's kernel: `@xemahq/contracts` taken from public npm.
|
|
2
|
+
//
|
|
3
|
+
// A catalogue names its kernel package (`kernel: {name, version, integrity}`, catalogue.mjs). This module is
|
|
4
|
+
// the ONE place that touches npm: it downloads that exact tarball (public, no credential, no .npmrc, no
|
|
5
|
+
// npm/pnpm process), holds it by the catalogue's integrity BEFORE anything is extracted, extracts it with the
|
|
6
|
+
// system `tar`, links its runtime dependencies to the copies this CLI itself ships, and proves the package is
|
|
7
|
+
// the contract authority the catalogue was resolved under: first by its edition protocol identity (data, no
|
|
8
|
+
// code executed), and only then, by loading its schemas, by the constants the authority was derived with.
|
|
9
|
+
|
|
10
|
+
import { spawnSync } from 'node:child_process';
|
|
11
|
+
import { createHash } from 'node:crypto';
|
|
12
|
+
import { mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, symlinkSync } from 'node:fs';
|
|
13
|
+
import { createRequire } from 'node:module';
|
|
14
|
+
import { tmpdir } from 'node:os';
|
|
15
|
+
import { dirname, join } from 'node:path';
|
|
16
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
17
|
+
|
|
18
|
+
import { readKernelEditionProtocol } from './edition-protocol.mjs';
|
|
19
|
+
import { cacheDir, stateKernelPackageDir } from './edition-workspace.mjs';
|
|
20
|
+
import { loadKernelDistributionSchemas } from './lib.mjs';
|
|
21
|
+
|
|
22
|
+
export const KERNEL_PACKAGE_NAME = '@xemahq/contracts';
|
|
23
|
+
const MAX_TARBALL_BYTES = 64 * 1024 * 1024;
|
|
24
|
+
const DEPENDENCY_NAME = /^(?:@[a-z0-9~-][a-z0-9._~-]*\/)?[a-z0-9~-][a-z0-9._~-]*$/;
|
|
25
|
+
const EXACT = /^(\d+)\.(\d+)\.(\d+)$/;
|
|
26
|
+
const CARET = /^\^(\d+)\.(\d+)\.(\d+)$/;
|
|
27
|
+
|
|
28
|
+
export class KernelPackageError extends Error {
|
|
29
|
+
constructor(message) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.name = 'KernelPackageError';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const kernelTarballUrl = (version) => `https://registry.npmjs.org/${KERNEL_PACKAGE_NAME}/-/contracts-${version}.tgz`;
|
|
36
|
+
|
|
37
|
+
/** The npm integrity string of `bytes`: `sha512-<base64>`. */
|
|
38
|
+
export const integrityOf = (bytes) => `sha512-${createHash('sha512').update(bytes).digest('base64')}`;
|
|
39
|
+
|
|
40
|
+
/** The published tarball of `@xemahq/contracts@version`, bounded in size. */
|
|
41
|
+
export async function fetchKernelTarball(version, { fetch = globalThis.fetch } = {}) {
|
|
42
|
+
const url = kernelTarballUrl(version);
|
|
43
|
+
const response = await fetch(url);
|
|
44
|
+
if (response.status === 404) {
|
|
45
|
+
throw new KernelPackageError(`${KERNEL_PACKAGE_NAME}@${version} is not published on npm (${url}): a catalogue names a published kernel`);
|
|
46
|
+
}
|
|
47
|
+
if (!response.ok) {throw new KernelPackageError(`${url} answered HTTP ${response.status}`);}
|
|
48
|
+
const declared = Number(response.headers.get('content-length') ?? 0);
|
|
49
|
+
if (declared > MAX_TARBALL_BYTES) {throw new KernelPackageError(`${url} declares ${declared} bytes, over the ${MAX_TARBALL_BYTES} byte bound`);}
|
|
50
|
+
const chunks = [];
|
|
51
|
+
let size = 0;
|
|
52
|
+
for await (const chunk of response.body) {
|
|
53
|
+
size += chunk.length;
|
|
54
|
+
if (size > MAX_TARBALL_BYTES) {throw new KernelPackageError(`${url} is over the ${MAX_TARBALL_BYTES} byte bound`);}
|
|
55
|
+
chunks.push(chunk);
|
|
56
|
+
}
|
|
57
|
+
return Buffer.concat(chunks);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Does `version` (exactly x.y.z) satisfy `range`? Exactly two range forms are supported: `x.y.z`, and
|
|
62
|
+
* `^x.y.z` (x > 0: >=x.y.z <(x+1).0.0; 0.y.z: >=0.y.z <0.(y+1).0). Any other form is refused.
|
|
63
|
+
*/
|
|
64
|
+
export function satisfiesDeclaredRange(version, range) {
|
|
65
|
+
const have = EXACT.exec(version)?.slice(1).map(Number);
|
|
66
|
+
if (have === undefined) {throw new KernelPackageError(`version ${JSON.stringify(version)} is not an exact x.y.z version`);}
|
|
67
|
+
const exact = EXACT.exec(range)?.slice(1).map(Number);
|
|
68
|
+
if (exact !== undefined) {return isDeepStrictEqual(have, exact);}
|
|
69
|
+
const caret = CARET.exec(range)?.slice(1).map(Number);
|
|
70
|
+
if (caret === undefined) {throw new KernelPackageError(`range ${JSON.stringify(range)} is not x.y.z or ^x.y.z, the only forms a kernel dependency may declare`);}
|
|
71
|
+
const compare = (a, b) => a[0] - b[0] || a[1] - b[1] || a[2] - b[2];
|
|
72
|
+
const upper = caret[0] > 0 ? [caret[0] + 1, 0, 0] : [0, caret[1] + 1, 0];
|
|
73
|
+
return compare(have, caret) >= 0 && compare(have, upper) < 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The directory of `name` as this CLI itself ships it: its own resolution first, then its installed kernel's. */
|
|
77
|
+
function shippedPackageDir(name) {
|
|
78
|
+
const own = createRequire(import.meta.url);
|
|
79
|
+
const attempts = [() => own, () => createRequire(realpathSync(own.resolve(`${KERNEL_PACKAGE_NAME}/package.json`)))];
|
|
80
|
+
for (const requireFrom of attempts) {
|
|
81
|
+
try {
|
|
82
|
+
return dirname(realpathSync(requireFrom().resolve(`${name}/package.json`)));
|
|
83
|
+
} catch (error) {
|
|
84
|
+
if (error?.code !== 'MODULE_NOT_FOUND' && error?.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED') {throw error;}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function linkRuntimeDependencies(packageDir) {
|
|
91
|
+
const manifest = JSON.parse(readFileSync(join(packageDir, 'package.json'), 'utf8'));
|
|
92
|
+
const nodeModules = dirname(manifest.name.startsWith('@') ? dirname(packageDir) : packageDir);
|
|
93
|
+
for (const [name, range] of Object.entries(manifest.dependencies ?? {})) {
|
|
94
|
+
if (!DEPENDENCY_NAME.test(name)) {throw new KernelPackageError(`${KERNEL_PACKAGE_NAME} declares a dependency named ${JSON.stringify(name)}, which is not a package name`);}
|
|
95
|
+
const shipped = shippedPackageDir(name);
|
|
96
|
+
if (shipped === undefined) {throw new KernelPackageError(`${KERNEL_PACKAGE_NAME} depends on ${name}@${range}, which this CLI does not ship`);}
|
|
97
|
+
const version = JSON.parse(readFileSync(join(shipped, 'package.json'), 'utf8')).version;
|
|
98
|
+
let ok;
|
|
99
|
+
try {
|
|
100
|
+
ok = satisfiesDeclaredRange(version, range);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
throw new KernelPackageError(`${KERNEL_PACKAGE_NAME} dependency ${name}: ${error.message} (this CLI ships ${version})`);
|
|
103
|
+
}
|
|
104
|
+
if (!ok) {throw new KernelPackageError(`${KERNEL_PACKAGE_NAME} depends on ${name}@${range}, but this CLI ships ${name}@${version}`);}
|
|
105
|
+
const link = join(nodeModules, name);
|
|
106
|
+
mkdirSync(dirname(link), { recursive: true });
|
|
107
|
+
symlinkSync(shipped, link, 'dir');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Download the catalogue's kernel, hold it to `kernel.integrity` (refused before anything is written), extract it
|
|
113
|
+
* under `<cacheDir>/kernel-npm/<version>-<16 hex of the sha512>/node_modules/@xemahq/contracts` (fresh every run)
|
|
114
|
+
* and link its runtime dependencies. Returns the package directory.
|
|
115
|
+
*/
|
|
116
|
+
export async function materializeKernelPackage({ kernel, cacheDir, fetchTarball = fetchKernelTarball }) {
|
|
117
|
+
const bytes = await fetchTarball(kernel.version);
|
|
118
|
+
const actual = integrityOf(bytes);
|
|
119
|
+
if (actual !== kernel.integrity) {
|
|
120
|
+
throw new KernelPackageError(`${KERNEL_PACKAGE_NAME}@${kernel.version} integrity ${actual} is not the catalogue's kernel.integrity ${kernel.integrity}`);
|
|
121
|
+
}
|
|
122
|
+
const hex = createHash('sha512').update(bytes).digest('hex').slice(0, 16);
|
|
123
|
+
const root = join(cacheDir, 'kernel-npm', `${kernel.version}-${hex}`);
|
|
124
|
+
const packageDir = join(root, 'node_modules', ...KERNEL_PACKAGE_NAME.split('/'));
|
|
125
|
+
rmSync(root, { recursive: true, force: true });
|
|
126
|
+
mkdirSync(packageDir, { recursive: true });
|
|
127
|
+
const extracted = spawnSync('tar', ['-xzf', '-', '-C', packageDir, '--strip-components', '1'], { input: bytes, stdio: ['pipe', 'ignore', 'pipe'] });
|
|
128
|
+
if (extracted.error !== undefined) {throw new KernelPackageError(`tar could not run: ${extracted.error.message}`);}
|
|
129
|
+
if (extracted.status !== 0) {throw new KernelPackageError(`tar failed extracting ${KERNEL_PACKAGE_NAME}@${kernel.version}: ${extracted.stderr.toString().trim()}`);}
|
|
130
|
+
const manifest = JSON.parse(readFileSync(join(packageDir, 'package.json'), 'utf8'));
|
|
131
|
+
if (manifest.name !== KERNEL_PACKAGE_NAME || manifest.version !== kernel.version) {
|
|
132
|
+
throw new KernelPackageError(`the tarball holds ${manifest.name}@${manifest.version}, not ${KERNEL_PACKAGE_NAME}@${kernel.version}`);
|
|
133
|
+
}
|
|
134
|
+
linkRuntimeDependencies(packageDir);
|
|
135
|
+
return packageDir;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const refuse = (field, wanted, got) => new KernelPackageError(`the kernel package is not the catalogue's contract authority: ${field} is ${JSON.stringify(got)}, the catalogue states ${JSON.stringify(wanted)}`);
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* The kernel package at `kernelPackageDir` is the contract authority of `catalogue`. First its identity, read as
|
|
142
|
+
* data (final, which the protocol read refuses otherwise, and the catalogue's digest); only then are its schemas loaded and held to the authority's constants.
|
|
143
|
+
* `sourceCommit` is provenance and is not compared. Returns the loaded schemas.
|
|
144
|
+
*/
|
|
145
|
+
export async function assertKernelPackageIsCatalogueAuthority({ catalogue, kernelPackageDir, loadSchemas }) {
|
|
146
|
+
const authority = catalogue.contractAuthority;
|
|
147
|
+
const identity = readKernelEditionProtocol(kernelPackageDir);
|
|
148
|
+
if (identity.contentDigest !== authority.contentDigest) {throw refuse('contentDigest', authority.contentDigest, identity.contentDigest);}
|
|
149
|
+
const schemas = await loadSchemas();
|
|
150
|
+
if (!isDeepStrictEqual(authority.sourcePaths, schemas.XEMA_KERNEL_CONTRACT_SOURCE_PATHS)) {
|
|
151
|
+
throw refuse('sourcePaths', authority.sourcePaths, schemas.XEMA_KERNEL_CONTRACT_SOURCE_PATHS);
|
|
152
|
+
}
|
|
153
|
+
const constants = [
|
|
154
|
+
['generatorIdentity', schemas.XEMA_KERNEL_CONTRACT_AUTHORITY_GENERATOR],
|
|
155
|
+
['repositoryUrl', schemas.XEMA_KERNEL_CONTRACT_REPOSITORY_URL],
|
|
156
|
+
['contentHashAlgorithm', schemas.KernelContractContentHashAlgorithm.LengthFramedGitPathModeBlobV1],
|
|
157
|
+
];
|
|
158
|
+
for (const [field, kernelValue] of constants) {
|
|
159
|
+
if (authority[field] !== kernelValue) {throw refuse(field, authority[field], kernelValue);}
|
|
160
|
+
}
|
|
161
|
+
return schemas;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* The `kernel` row a VENDOR catalogue carries: the published `@xemahq/contracts` the vendor's own kernel
|
|
166
|
+
* (`kernelPackageDir`) is, taken from npm, proved to be `contractAuthority` and held by its integrity.
|
|
167
|
+
* An unpublished version is refused. The downloaded package is extracted to a temporary directory and removed.
|
|
168
|
+
*/
|
|
169
|
+
export async function kernelPackageOfAuthority({ kernelPackageDir, contractAuthority, fetchTarball = fetchKernelTarball, loadSchemas = loadKernelDistributionSchemas }) {
|
|
170
|
+
const manifest = JSON.parse(readFileSync(join(kernelPackageDir, 'package.json'), 'utf8'));
|
|
171
|
+
if (manifest.name !== KERNEL_PACKAGE_NAME) {throw new KernelPackageError(`the vendor kernel is ${manifest.name}, not ${KERNEL_PACKAGE_NAME}`);}
|
|
172
|
+
const bytes = await fetchTarball(manifest.version);
|
|
173
|
+
const kernel = { name: KERNEL_PACKAGE_NAME, version: manifest.version, integrity: integrityOf(bytes) };
|
|
174
|
+
const scratch = mkdtempSync(join(tmpdir(), 'xema-kernel-npm-'));
|
|
175
|
+
try {
|
|
176
|
+
const packageDir = await materializeKernelPackage({ kernel, cacheDir: scratch, fetchTarball: async () => bytes });
|
|
177
|
+
await assertKernelPackageIsCatalogueAuthority({ catalogue: { contractAuthority }, kernelPackageDir: packageDir, loadSchemas });
|
|
178
|
+
} finally {
|
|
179
|
+
rmSync(scratch, { recursive: true, force: true });
|
|
180
|
+
}
|
|
181
|
+
return kernel;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* A customer verb's one step between fetching its catalogue and using any kernel schema: the catalogue's kernel is
|
|
186
|
+
* downloaded into the cache root, stated as the workspace's Kernel root, and proved to be the catalogue's contract
|
|
187
|
+
* authority. Returns the kernel's distribution schemas.
|
|
188
|
+
*/
|
|
189
|
+
export async function adoptCatalogueKernel(catalogue) {
|
|
190
|
+
const kernelPackageDir = await materializeKernelPackage({ kernel: catalogue.kernel, cacheDir: cacheDir() });
|
|
191
|
+
stateKernelPackageDir(kernelPackageDir);
|
|
192
|
+
return assertKernelPackageIsCatalogueAuthority({ catalogue, kernelPackageDir, loadSchemas: loadKernelDistributionSchemas });
|
|
193
|
+
}
|
package/edition/lib.mjs
CHANGED
|
@@ -14,6 +14,9 @@ import { createRequire } from 'node:module';
|
|
|
14
14
|
import { isIP } from 'node:net';
|
|
15
15
|
import { join } from 'node:path';
|
|
16
16
|
|
|
17
|
+
import { parse as parseYaml } from 'yaml';
|
|
18
|
+
|
|
19
|
+
import { ADAPTER_HOST_BIOME_ID, readHostPackageVersions } from './adapter-host-versions.mjs';
|
|
17
20
|
import { readBiomeAuthorizationModuleAt } from './authorization-base.mjs';
|
|
18
21
|
import {
|
|
19
22
|
isAuthoritativeProductionBiomeSource,
|
|
@@ -688,6 +691,18 @@ export async function loadKernelDistributionSchemas() {
|
|
|
688
691
|
`Underlying error: ${err.message}`,
|
|
689
692
|
);
|
|
690
693
|
}
|
|
694
|
+
// The Node-only half of the distribution contracts (`distribution/server`): the lock digest and the
|
|
695
|
+
// predicate checks that recompute it.
|
|
696
|
+
let server;
|
|
697
|
+
try {
|
|
698
|
+
server = await import(`file://${kernelDistPath('distribution', 'server', 'index.js')}`);
|
|
699
|
+
} catch (err) {
|
|
700
|
+
throw new Error(
|
|
701
|
+
`Cannot load the built kernel distribution/server module from ${kernelDistPath('distribution', 'server', 'index.js')}. ` +
|
|
702
|
+
`Rebuild the kernel checkout (${kernelRepoDir()}). Underlying error: ${err.message}`,
|
|
703
|
+
);
|
|
704
|
+
}
|
|
705
|
+
const { distributionLockDigest, distributionImagesPredicateLockRefusal, imageDeliveryEntitlementRefusal } = server;
|
|
691
706
|
const {
|
|
692
707
|
parseDistribution,
|
|
693
708
|
parseDistributionLock,
|
|
@@ -695,6 +710,8 @@ export async function loadKernelDistributionSchemas() {
|
|
|
695
710
|
parseUnsealedDistributionLock,
|
|
696
711
|
parseCustomerEditionDefinition,
|
|
697
712
|
customerTrustCeilingIssues,
|
|
713
|
+
customerLockCeilingIssues,
|
|
714
|
+
biomeOriginsOfLock,
|
|
698
715
|
sealDistributionLock,
|
|
699
716
|
unsealedProjection,
|
|
700
717
|
UNSEALED_DISTRIBUTION_LOCK_SCHEMA,
|
|
@@ -714,6 +731,7 @@ export async function loadKernelDistributionSchemas() {
|
|
|
714
731
|
SigstoreEvidenceDescriptorSchema,
|
|
715
732
|
parsePlatformServiceSourceIndex,
|
|
716
733
|
parseRuntimeImageSourceIndex,
|
|
734
|
+
producerWorkflowPinRefusal,
|
|
717
735
|
resolveRuntimeImagePlatforms,
|
|
718
736
|
BiomeInstallPolicy,
|
|
719
737
|
BiomeLaunchMode,
|
|
@@ -749,6 +767,7 @@ export async function loadKernelDistributionSchemas() {
|
|
|
749
767
|
describeProvenance,
|
|
750
768
|
describeHop,
|
|
751
769
|
deriveWorkflowSubjects,
|
|
770
|
+
distributionImagesPredicateChannelRefusal,
|
|
752
771
|
} = mod;
|
|
753
772
|
let topology;
|
|
754
773
|
try {
|
|
@@ -791,6 +810,7 @@ export async function loadKernelDistributionSchemas() {
|
|
|
791
810
|
typeof SigstoreEvidenceDescriptorSchema?.parse !== 'function' ||
|
|
792
811
|
typeof parsePlatformServiceSourceIndex !== 'function' ||
|
|
793
812
|
typeof parseRuntimeImageSourceIndex !== 'function' ||
|
|
813
|
+
typeof producerWorkflowPinRefusal !== 'function' ||
|
|
794
814
|
typeof resolveRuntimeImagePlatforms !== 'function' ||
|
|
795
815
|
typeof resolveFirstPartyCapabilityDomains !== 'function' ||
|
|
796
816
|
typeof DistributionVisibility !== 'object' ||
|
|
@@ -837,6 +857,12 @@ export async function loadKernelDistributionSchemas() {
|
|
|
837
857
|
// A biome's workflow subjects and its edition trust ceiling, each written
|
|
838
858
|
// through the kernel's one derivation (`@xemahq/contracts` >= 0.71.0).
|
|
839
859
|
typeof deriveWorkflowSubjects !== 'function' ||
|
|
860
|
+
// Predicate v2 checks (`@xemahq/contracts` >= 0.78.0): the channel the verifying policy row states and
|
|
861
|
+
// the lock digest the predicates bind.
|
|
862
|
+
typeof distributionImagesPredicateChannelRefusal !== 'function' ||
|
|
863
|
+
typeof distributionImagesPredicateLockRefusal !== 'function' ||
|
|
864
|
+
typeof distributionLockDigest !== 'function' ||
|
|
865
|
+
typeof imageDeliveryEntitlementRefusal !== 'function' ||
|
|
840
866
|
typeof deriveTrustCeiling !== 'function' ||
|
|
841
867
|
// The trust-domain vocabulary a customer edition is bounded by: the vendor's
|
|
842
868
|
// own domain, a biome's default domain, and the two ceiling grammars a
|
|
@@ -867,6 +893,8 @@ export async function loadKernelDistributionSchemas() {
|
|
|
867
893
|
parseUnsealedDistributionLock,
|
|
868
894
|
parseCustomerEditionDefinition,
|
|
869
895
|
customerTrustCeilingIssues,
|
|
896
|
+
customerLockCeilingIssues,
|
|
897
|
+
biomeOriginsOfLock,
|
|
870
898
|
sealDistributionLock,
|
|
871
899
|
unsealedProjection,
|
|
872
900
|
UNSEALED_DISTRIBUTION_LOCK_SCHEMA,
|
|
@@ -926,6 +954,11 @@ export async function loadKernelDistributionSchemas() {
|
|
|
926
954
|
FIRST_PARTY_TRUST_DOMAIN,
|
|
927
955
|
ProcessTrustPolicySchema,
|
|
928
956
|
DistributionTrustPolicySchema,
|
|
957
|
+
distributionLockDigest,
|
|
958
|
+
distributionImagesPredicateChannelRefusal,
|
|
959
|
+
distributionImagesPredicateLockRefusal,
|
|
960
|
+
imageDeliveryEntitlementRefusal,
|
|
961
|
+
producerWorkflowPinRefusal,
|
|
929
962
|
};
|
|
930
963
|
}
|
|
931
964
|
|
|
@@ -1086,6 +1119,10 @@ function listBiomeManifestsAt(directory, repo, commit) {
|
|
|
1086
1119
|
rel,
|
|
1087
1120
|
repo,
|
|
1088
1121
|
path,
|
|
1122
|
+
// The manifest's directory in its repository, and a reader of any file of
|
|
1123
|
+
// that repository at the SAME pinned commit (the host's pnpm-lock.yaml).
|
|
1124
|
+
manifestDirectory,
|
|
1125
|
+
readRepositoryText: (repoRelativePath) => readGitTextAtCommitOrNull(directory, repoRelativePath, commit),
|
|
1089
1126
|
...(authorizationModule === null ? {} : { authorizationModule }),
|
|
1090
1127
|
});
|
|
1091
1128
|
}
|
|
@@ -2058,6 +2095,16 @@ export async function manifestToIndexEntry({ raw, generatedIndex, rel, repo, pat
|
|
|
2058
2095
|
.filter((name) => typeof name === 'string' && name.length > 0),
|
|
2059
2096
|
),
|
|
2060
2097
|
].sort(compareStrings),
|
|
2098
|
+
// The capabilities the signed biome declares (a delegation
|
|
2099
|
+
// origination may lend only these): the manifest's
|
|
2100
|
+
// `xema.exposesCapabilities`; `requires*` are what it asks for.
|
|
2101
|
+
declaredCapabilities: [
|
|
2102
|
+
...new Set(
|
|
2103
|
+
(Array.isArray(xema.exposesCapabilities) ? xema.exposesCapabilities : []).filter(
|
|
2104
|
+
(ref) => typeof ref === 'string' && ref.length > 0,
|
|
2105
|
+
),
|
|
2106
|
+
),
|
|
2107
|
+
].sort(compareStrings),
|
|
2061
2108
|
},
|
|
2062
2109
|
},
|
|
2063
2110
|
}
|
|
@@ -2263,6 +2310,26 @@ export function validateServiceDependencyOwnerParity(
|
|
|
2263
2310
|
return entries;
|
|
2264
2311
|
}
|
|
2265
2312
|
|
|
2313
|
+
/**
|
|
2314
|
+
* The adapter host's resolved `@xemahq/contracts` / `@xemahq/biome-sdk` versions, read from its source
|
|
2315
|
+
* repository's pnpm-lock.yaml at the pinned commit. An INDEX field only: the distribution lock never carries it.
|
|
2316
|
+
*/
|
|
2317
|
+
function adapterHostPackageVersions(manifest) {
|
|
2318
|
+
const services = (manifest.raw.xema.components ?? []).filter(
|
|
2319
|
+
(component) => component?.kind === 'service' && component.artifact?.kind === 'oci-image',
|
|
2320
|
+
);
|
|
2321
|
+
if (services.length !== 1) {
|
|
2322
|
+
throw new Error(`${manifest.rel}: the adapter host declares ${services.length} oci-image service components; exactly one carries its package`);
|
|
2323
|
+
}
|
|
2324
|
+
const lockfilePath = 'pnpm-lock.yaml';
|
|
2325
|
+
return readHostPackageVersions({
|
|
2326
|
+
lockfileText: manifest.readRepositoryText(lockfilePath),
|
|
2327
|
+
importer: `${manifest.manifestDirectory}/${services[0].artifact.path}`,
|
|
2328
|
+
lockfilePath: `${manifest.repo}/${lockfilePath}`,
|
|
2329
|
+
parseYaml,
|
|
2330
|
+
});
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2266
2333
|
export async function buildBiomeIndex(
|
|
2267
2334
|
platformServiceIndex,
|
|
2268
2335
|
sourceRepositories = distributionBiomeSourceRepositories(),
|
|
@@ -2275,7 +2342,12 @@ export async function buildBiomeIndex(
|
|
|
2275
2342
|
});
|
|
2276
2343
|
const entries = [];
|
|
2277
2344
|
for (const manifest of await committedBiomeManifests(sourceRepositories, authority)) {
|
|
2278
|
-
|
|
2345
|
+
const entry = await manifestToIndexEntry(manifest);
|
|
2346
|
+
entries.push(
|
|
2347
|
+
entry.id === ADAPTER_HOST_BIOME_ID
|
|
2348
|
+
? { ...entry, hostPackageVersions: adapterHostPackageVersions(manifest) }
|
|
2349
|
+
: entry,
|
|
2350
|
+
);
|
|
2279
2351
|
}
|
|
2280
2352
|
resolveServiceDependencyOwners(entries, platformServiceIndex);
|
|
2281
2353
|
assertDeclaredDatabasesUnowned(entries);
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
isEntryModule,
|
|
33
33
|
} from './edition-workspace.mjs';
|
|
34
34
|
import { SHA256_DIGEST_PATTERN } from './evidence-grammars.mjs';
|
|
35
|
-
import { canonicalJson,
|
|
35
|
+
import { canonicalJson, loadValidatedImageLocks } from './image-evidence.mjs';
|
|
36
36
|
import { compareStrings, loadKernelDistributionSchemas, serializeJson } from './lib.mjs';
|
|
37
37
|
import {
|
|
38
38
|
IMAGE_SOURCE_EVIDENCE_ARTIFACT_TYPE,
|
|
@@ -230,7 +230,7 @@ export async function planOciEvidenceBackfill({
|
|
|
230
230
|
const missingGit = new Map();
|
|
231
231
|
for (const distributionId of distributionIds) {
|
|
232
232
|
assertCanonicalDistributionId(distributionId);
|
|
233
|
-
const { imageLock, repositoryPins } = await
|
|
233
|
+
const { imageLock, repositoryPins } = await loadValidatedImageLocks(distributionId, data, schemas, {
|
|
234
234
|
editionsRoot: editions,
|
|
235
235
|
});
|
|
236
236
|
for (const subject of imageLock.subjects) {
|
|
@@ -66,7 +66,7 @@ import {
|
|
|
66
66
|
} from './edition-workspace.mjs';
|
|
67
67
|
import {
|
|
68
68
|
canonicalJson,
|
|
69
|
-
|
|
69
|
+
loadValidatedImageLocks,
|
|
70
70
|
} from './image-evidence.mjs';
|
|
71
71
|
import { assertKernelContractAuthorityEqual } from './kernel-contract-authority.mjs';
|
|
72
72
|
import { compareStrings, loadKernelDistributionSchemas } from './lib.mjs';
|
|
@@ -142,7 +142,7 @@ export async function planImageEvidenceRefresh({
|
|
|
142
142
|
|
|
143
143
|
const subjects = [];
|
|
144
144
|
for (const distributionId of [...distributionIds].sort(compareStrings)) {
|
|
145
|
-
const { imageLock, repositoryPins, runtimeSourceIndex } = await
|
|
145
|
+
const { imageLock, repositoryPins, runtimeSourceIndex } = await loadValidatedImageLocks(
|
|
146
146
|
distributionId,
|
|
147
147
|
dataRootDirectory,
|
|
148
148
|
schemas,
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
assertPinnedCosignVersion,
|
|
22
22
|
buildDistributionImageEvidenceSet,
|
|
23
23
|
buildDistributionImagesStatement,
|
|
24
|
+
evidenceChannelOfSigner,
|
|
24
25
|
loadPinnedTrustPolicy,
|
|
25
26
|
parseEvidenceCliFlags,
|
|
26
27
|
resolveProducerEvidence,
|
|
@@ -59,6 +60,8 @@ export function githubWorkflowSigner({ repository, ref, path }) {
|
|
|
59
60
|
|
|
60
61
|
/** The producer evidence and the completeness statement these inputs resolve to; deterministic. */
|
|
61
62
|
async function deriveCompleteness(options, { outputRoot }) {
|
|
63
|
+
// The channel the signer signs for is the channel the lock is read on.
|
|
64
|
+
const channel = evidenceChannelOfSigner(options.signer, options.schemas);
|
|
62
65
|
const resolution = {
|
|
63
66
|
distributionId: options.distributionId,
|
|
64
67
|
dataRoot: options.dataRoot,
|
|
@@ -66,6 +69,7 @@ async function deriveCompleteness(options, { outputRoot }) {
|
|
|
66
69
|
schemas: options.schemas,
|
|
67
70
|
expectedContractAuthority: options.expectedContractAuthority,
|
|
68
71
|
registryClientFactory: options.registryClientFactory,
|
|
72
|
+
channel,
|
|
69
73
|
};
|
|
70
74
|
const staged = outputRoot === null
|
|
71
75
|
? await resolveProducerEvidence(resolution)
|
|
@@ -74,6 +78,7 @@ async function deriveCompleteness(options, { outputRoot }) {
|
|
|
74
78
|
distributionLock: staged.distributionLock,
|
|
75
79
|
imageLock: staged.imageLock,
|
|
76
80
|
images: staged.images,
|
|
81
|
+
channel,
|
|
77
82
|
releaseSequence: options.releaseSequence,
|
|
78
83
|
releaseSourceCommit: options.releaseSourceCommit,
|
|
79
84
|
runId: options.runId,
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
acceptSignedEvidenceStatement,
|
|
19
19
|
assertPinnedCosignVersion,
|
|
20
20
|
buildImageDeliveryEntitlementStatement,
|
|
21
|
+
evidenceChannelOfSigner,
|
|
21
22
|
loadPinnedTrustPolicy,
|
|
22
23
|
parseEvidenceCliFlags,
|
|
23
24
|
stageEvidenceStatement,
|
|
@@ -26,10 +27,11 @@ import {
|
|
|
26
27
|
WorkspaceRoot,
|
|
27
28
|
configureEditionWorkspaceFromArgv,
|
|
28
29
|
dataRoot,
|
|
30
|
+
editionsRoot,
|
|
29
31
|
isEntryModule,
|
|
30
32
|
} from './edition-workspace.mjs';
|
|
31
33
|
import { ENTITLEMENT_DESCRIPTOR_FILE, entitlementDescriptorDocument } from './image-delivery-entitlement-descriptor.mjs';
|
|
32
|
-
import { canonicalJsonBytes, readCanonicalJsonFromRoot } from './image-evidence.mjs';
|
|
34
|
+
import { canonicalJsonBytes, readCanonicalJsonFromRoot, readEditionDistributionLock } from './image-evidence.mjs';
|
|
33
35
|
import { serializeJson } from './lib.mjs';
|
|
34
36
|
import { githubWorkflowSigner } from './produce-distribution-completeness-evidence.mjs';
|
|
35
37
|
import {
|
|
@@ -81,6 +83,12 @@ function deriveEntitlement(options) {
|
|
|
81
83
|
schemas: options.schemas,
|
|
82
84
|
});
|
|
83
85
|
const statement = buildImageDeliveryEntitlementStatement({
|
|
86
|
+
distributionLock: readEditionDistributionLock(
|
|
87
|
+
options.editionsRoot,
|
|
88
|
+
options.distributionId,
|
|
89
|
+
options.schemas,
|
|
90
|
+
evidenceChannelOfSigner(options.signer, options.schemas),
|
|
91
|
+
),
|
|
84
92
|
distributionId: options.distributionId,
|
|
85
93
|
completenessStatementDigest,
|
|
86
94
|
customerId: options.customerId,
|
|
@@ -226,6 +234,7 @@ const SHARED_FLAGS = Object.freeze([
|
|
|
226
234
|
async function main() {
|
|
227
235
|
const [phase, ...args] = configureEditionWorkspaceFromArgv(process.argv.slice(2), [
|
|
228
236
|
WorkspaceRoot.Data,
|
|
237
|
+
WorkspaceRoot.Editions,
|
|
229
238
|
WorkspaceRoot.Kernel,
|
|
230
239
|
]);
|
|
231
240
|
if (phase !== 'stage' && phase !== 'accept') {
|
|
@@ -240,6 +249,7 @@ async function main() {
|
|
|
240
249
|
const options = {
|
|
241
250
|
distributionId: flags.require('distribution'),
|
|
242
251
|
evidenceRoot: resolve(flags.require('evidence-root')),
|
|
252
|
+
editionsRoot: editionsRoot(),
|
|
243
253
|
stagingDirectory: resolve(flags.require('staging')),
|
|
244
254
|
schemas,
|
|
245
255
|
policy: loadPinnedTrustPolicy({ dataRoot: dataRoot(), schemas }),
|
|
@@ -5,14 +5,15 @@ import { readFileSync } from 'node:fs';
|
|
|
5
5
|
import { biomeSourceTreeIds } from './biome-content.mjs';
|
|
6
6
|
import { buildCatalogue } from './catalogue.mjs';
|
|
7
7
|
import { EditionChannel } from './edition-constants.mjs';
|
|
8
|
-
import { biomeIndexPath, platformServiceIndexPath, runtimeImageSourceIndexPath } from './edition-workspace.mjs';
|
|
8
|
+
import { biomeIndexPath, kernelPackageDir, platformServiceIndexPath, runtimeImageSourceIndexPath } from './edition-workspace.mjs';
|
|
9
|
+
import { kernelPackageOfAuthority } from './kernel-package.mjs';
|
|
9
10
|
import { loadKernelDistributionSchemas } from './lib.mjs';
|
|
10
11
|
import { loadEditionManifest } from './resolve-distribution.mjs';
|
|
11
12
|
import { externalSourceTrees, sourceTreeLocator } from './resolve-images.mjs';
|
|
12
13
|
import { committedRepositoryPins, createPinnedSourceAuthority } from './source-authority.mjs';
|
|
13
14
|
|
|
14
15
|
/** The catalogue of release offering `id`, from the committed locks (`locks` as `readCommittedEdition` returns them). */
|
|
15
|
-
export async function releaseCatalogue(id, locks) {
|
|
16
|
+
export async function releaseCatalogue(id, locks, { fetchKernelTarball } = {}) {
|
|
16
17
|
const schemas = await loadKernelDistributionSchemas();
|
|
17
18
|
const authority = createPinnedSourceAuthority({ pins: committedRepositoryPins() });
|
|
18
19
|
// The WRITER of the catalogue's tree ids: the real `git rev-parse <commit>:<path>` of every biome.
|
|
@@ -23,6 +24,7 @@ export async function releaseCatalogue(id, locks) {
|
|
|
23
24
|
locate: sourceTreeLocator(externalSourceTrees(locks.distributionLock)),
|
|
24
25
|
vendoredCommit: (repo) => authority.vendoredSourceCommitForRepository(repo),
|
|
25
26
|
});
|
|
27
|
+
const contractAuthority = schemas.parseRuntimeImageSourceIndex(JSON.parse(readFileSync(runtimeImageSourceIndexPath(), 'utf8'))).contractAuthority;
|
|
26
28
|
return buildCatalogue({
|
|
27
29
|
sourceTreeIds,
|
|
28
30
|
offeringId: id,
|
|
@@ -32,7 +34,12 @@ export async function releaseCatalogue(id, locks) {
|
|
|
32
34
|
chartSetLock: locks.chartSetLock,
|
|
33
35
|
biomeIndex: JSON.parse(readFileSync(biomeIndexPath(), 'utf8')),
|
|
34
36
|
platformServiceIndex: schemas.parsePlatformServiceSourceIndex(JSON.parse(readFileSync(platformServiceIndexPath(), 'utf8'))),
|
|
35
|
-
contractAuthority
|
|
37
|
+
contractAuthority,
|
|
38
|
+
kernel: await kernelPackageOfAuthority({
|
|
39
|
+
kernelPackageDir: kernelPackageDir(),
|
|
40
|
+
contractAuthority,
|
|
41
|
+
...(fetchKernelTarball === undefined ? {} : { fetchTarball: fetchKernelTarball }),
|
|
42
|
+
}),
|
|
36
43
|
leaf: loadEditionManifest(schemas.parseDistribution, id),
|
|
37
44
|
});
|
|
38
45
|
}
|
|
@@ -82,7 +82,7 @@ import {
|
|
|
82
82
|
} from './registry-credentials.mjs';
|
|
83
83
|
|
|
84
84
|
if (isEntryModule(import.meta.url)) {
|
|
85
|
-
configureEditionWorkspaceFromProcess([WorkspaceRoot.Data, WorkspaceRoot.Editions, WorkspaceRoot.
|
|
85
|
+
configureEditionWorkspaceFromProcess([WorkspaceRoot.Data, WorkspaceRoot.Editions, WorkspaceRoot.Kernel], { refuseOthers: true });
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
export const CHART_SET_LOCK_FILE = 'chart-set.lock.json';
|
|
@@ -18,6 +18,7 @@ import { mkdirSync, writeFileSync } from 'node:fs';
|
|
|
18
18
|
import { join } from 'node:path';
|
|
19
19
|
import { isDeepStrictEqual } from 'node:util';
|
|
20
20
|
|
|
21
|
+
import { adapterHostRow, assertAdapterBundlesMatchHost, fetchRegistryManifest } from './adapter-bundled-versions.mjs';
|
|
21
22
|
import { AUTHORIZATION_LOCK_FILENAME, writeAuthorizationLock } from './authorization-base.mjs';
|
|
22
23
|
import { CATALOGUE_FILE, CatalogueError } from './catalogue.mjs';
|
|
23
24
|
import {
|
|
@@ -29,8 +30,8 @@ import {
|
|
|
29
30
|
import { assertRosteredChartSetLock } from './edition-chart-set.mjs';
|
|
30
31
|
import { EditionLockFile } from './edition-constants.mjs';
|
|
31
32
|
import { editionsRoot } from './edition-workspace.mjs';
|
|
32
|
-
import {
|
|
33
|
-
import { compareStrings,
|
|
33
|
+
import { adoptCatalogueKernel } from './kernel-package.mjs';
|
|
34
|
+
import { compareStrings, serializeJson, validateImmutableImageLock } from './lib.mjs';
|
|
34
35
|
import { pinnedSource, snapshotPinSource } from './pin-source.mjs';
|
|
35
36
|
import { composeAuthorizationBase, composeManifest, resolveDistributionWithIndex, UnknownSelectionError } from './resolve-distribution.mjs';
|
|
36
37
|
import { resolveImageRows } from './resolve-images.mjs';
|
|
@@ -63,11 +64,22 @@ function catalogueCeiling(catalogue, schemas) {
|
|
|
63
64
|
return { trustPolicy: read('trustPolicy', schemas.DistributionTrustPolicySchema), trustCeiling: read('trustCeiling', schemas.ProcessTrustPolicySchema) };
|
|
64
65
|
}
|
|
65
66
|
|
|
67
|
+
/** A catalogue whose ceiling states no trust-domain assignments reserves no domain: the kernel's required ceiling argument is the empty policy. */
|
|
68
|
+
const NO_CATALOGUE_TRUST_CEILING = Object.freeze({ assignments: [] });
|
|
69
|
+
|
|
70
|
+
/** The kernel's `customerLockCeilingIssues` is the one check of a customer lock against the catalogue's ceiling. */
|
|
71
|
+
function assertLockWithinCatalogueCeiling({ catalogue, definition, lock, schemas }) {
|
|
72
|
+
const issues = schemas.customerLockCeilingIssues(catalogueCeiling(catalogue, schemas).trustCeiling, lock.biomes);
|
|
73
|
+
if (issues.length > 0) {
|
|
74
|
+
throw new CatalogueCeilingError(`catalogue "${catalogue.offeringId}" (${catalogue.channel}): "${definition.id}" is outside the catalogue's trustCeiling: ${issues.join('; ')}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
66
78
|
/**
|
|
67
79
|
* The ceiling policy the customer's lock is written under: the catalogue's assignments, then
|
|
68
80
|
* the definition's for what the catalogue leaves open. An assignment of the definition that
|
|
69
81
|
* names a domain the catalogue assigns, or a biome the catalogue places, is not carried: the
|
|
70
|
-
* catalogue's reading stands, and `
|
|
82
|
+
* catalogue's reading stands, and the kernel's `customerLockCeilingIssues` refuses the difference in the lock.
|
|
71
83
|
*/
|
|
72
84
|
function ceilingUnderCatalogue(catalogueTrustCeiling, definitionTrustCeiling) {
|
|
73
85
|
if (catalogueTrustCeiling === undefined) {return definitionTrustCeiling;}
|
|
@@ -80,25 +92,6 @@ function ceilingUnderCatalogue(catalogueTrustCeiling, definitionTrustCeiling) {
|
|
|
80
92
|
return { assignments: [...catalogueTrustCeiling.assignments, ...open] };
|
|
81
93
|
}
|
|
82
94
|
|
|
83
|
-
/**
|
|
84
|
-
* Every customer-source biome sits in the domain its definition names, and that is the domain
|
|
85
|
-
* the catalogue's ceiling admits for it: the lock row carries the kernel's derivation
|
|
86
|
-
* (`deriveTrustCeiling`) under `ceilingUnderCatalogue`, so a definition that reaches into a
|
|
87
|
-
* domain the catalogue assigns, or moves a biome the catalogue places, differs from it here.
|
|
88
|
-
*/
|
|
89
|
-
function assertWithinCatalogueCeiling({ catalogue, definition, lock, schemas }) {
|
|
90
|
-
const stated = new Map((definition.trustCeiling?.assignments ?? []).flatMap((assignment) => assignment.biomes.map((biome) => [biome, assignment.domain])));
|
|
91
|
-
for (const biome of lock.biomes.filter((candidate) => candidate.source !== undefined)) {
|
|
92
|
-
const admitted = biome.trustCeiling ?? schemas.defaultTrustDomain(biome.id, biome.origin);
|
|
93
|
-
if (stated.get(biome.id) !== admitted) {
|
|
94
|
-
throw new CatalogueCeilingError(
|
|
95
|
-
`catalogue "${catalogue.offeringId}" (${catalogue.channel}): "${definition.id}" assigns biome "${biome.id}" to trust domain ` +
|
|
96
|
-
`"${stated.get(biome.id)}", and the catalogue's trustCeiling admits "${admitted}" for it`,
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
95
|
/** `{name -> {imageRegistry, imageNamePrefix}}` from repeated `<source>=<registry>/<prefix>` values; the prefix is the text after the last "/". */
|
|
103
96
|
export function parseSourceImages(values) {
|
|
104
97
|
const byName = new Map();
|
|
@@ -247,8 +240,10 @@ export async function resolveCustomerImages({ id, catalogue, lock, composed, sch
|
|
|
247
240
|
if (ownBiomes.length > 0) {
|
|
248
241
|
const ownLock = { ...lock, biomes: ownBiomes, platformServices: [] };
|
|
249
242
|
const runtimeImageSourceIndex = {
|
|
243
|
+
schemaVersion: 2,
|
|
250
244
|
contractAuthority: catalogue.contractAuthority,
|
|
251
245
|
repositoryPins: [],
|
|
246
|
+
producerWorkflowPins: [],
|
|
252
247
|
platformServiceBindings: [],
|
|
253
248
|
supplementalSubjects: [],
|
|
254
249
|
crossRepoBuildInputs: [],
|
|
@@ -278,14 +273,9 @@ export async function resolveCustomerImages({ id, catalogue, lock, composed, sch
|
|
|
278
273
|
return merged;
|
|
279
274
|
}
|
|
280
275
|
|
|
281
|
-
/** The kernel at --kernel must carry the contract the catalogue's offering was resolved under. */
|
|
282
|
-
export async function assertKernelAtCatalogueAuthority(catalogue, schemas) {
|
|
283
|
-
await validateKernelContractAuthorityFromGit({ authority: catalogue.contractAuthority, schemas, requireHeadMatch: false });
|
|
284
|
-
}
|
|
285
|
-
|
|
286
276
|
/**
|
|
287
277
|
* The distribution and authorization locks of the customer edition `rawDefinition`
|
|
288
|
-
* against `catalogue`. `catalogueDigest` is the digest of `catalogue`, written into the lock. `
|
|
278
|
+
* against `catalogue`. `catalogueDigest` is the digest of `catalogue`, written into the lock. `adoptKernel` (the catalogue's kernel from npm, proved its contract authority; returns the kernel schemas) and `materialize` are injectable for the test suites.
|
|
289
279
|
*/
|
|
290
280
|
export async function resolveCustomerDistribution({
|
|
291
281
|
rawDefinition,
|
|
@@ -294,10 +284,11 @@ export async function resolveCustomerDistribution({
|
|
|
294
284
|
sourceImages,
|
|
295
285
|
pins,
|
|
296
286
|
materialize,
|
|
297
|
-
|
|
287
|
+
adoptKernel = adoptCatalogueKernel,
|
|
288
|
+
fetchManifest = fetchRegistryManifest,
|
|
298
289
|
}) {
|
|
299
290
|
if (catalogueDigest === undefined) {throw new CatalogueError('a customer resolve names the digest of its catalogue');}
|
|
300
|
-
const schemas = await
|
|
291
|
+
const schemas = await adoptKernel(catalogue);
|
|
301
292
|
const { definition, mirror } = parseCustomerDefinition(schemas, rawDefinition);
|
|
302
293
|
const composed = composeCustomerEdition({ schemas, definition, mirror, catalogue, sourceImages, pins });
|
|
303
294
|
const id = composed.leaf.id;
|
|
@@ -312,9 +303,14 @@ export async function resolveCustomerDistribution({
|
|
|
312
303
|
throw error;
|
|
313
304
|
}
|
|
314
305
|
const { lock, index } = resolved;
|
|
315
|
-
assertTrustCeilingAssignsCustomerBiomes(
|
|
316
|
-
|
|
317
|
-
|
|
306
|
+
assertTrustCeilingAssignsCustomerBiomes(
|
|
307
|
+
schemas,
|
|
308
|
+
definition,
|
|
309
|
+
lock.biomes.filter((biome) => biome.source !== undefined).map((biome) => biome.id),
|
|
310
|
+
catalogueCeiling(catalogue, schemas).trustCeiling ?? NO_CATALOGUE_TRUST_CEILING,
|
|
311
|
+
);
|
|
312
|
+
assertLockWithinCatalogueCeiling({ catalogue, definition, lock, schemas });
|
|
313
|
+
await assertAdapterBundlesMatchHost({ lock, hostRow: adapterHostRow(catalogue.biomes), fetchManifest });
|
|
318
314
|
const authorizationLock = await composeAuthorizationBase(id, lock, index);
|
|
319
315
|
// A customer lock names the catalogue it was resolved against; seal and verify refuse any other.
|
|
320
316
|
return { id, schemas, composed, inputs, lock: { ...lock, catalogueDigest }, authorizationLock };
|