@xemahq/xema 0.29.1 → 0.31.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/cli.d.ts.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/edition.js +3 -3
- package/dist/commands/edition.js.map +1 -1
- package/dist/dev/infra-preflight.js +1 -1
- package/dist/dev/infra-preflight.js.map +1 -1
- package/dist/edition/verify-image-evidence.bundle.mjs +10030 -1491
- package/dist/edition/verify-image-evidence.bundle.provenance.json +953 -419
- package/dist/runtime/runner/helm-cli.d.ts.map +1 -1
- package/dist/runtime/runner/helm-cli.js.map +1 -1
- package/dist/runtime/store-submit.d.ts.map +1 -1
- package/dist/runtime/store-submit.js.map +1 -1
- package/edition/README.md +50 -271
- package/edition/adapter-bundled-versions.mjs +107 -0
- package/edition/adapter-host-versions.mjs +41 -0
- package/edition/assemble-producer-input.mjs +1 -1
- package/edition/authorization-base.mjs +18 -9
- package/edition/biome-content.mjs +6 -0
- package/edition/build-platform-service-index.mjs +2 -0
- package/edition/build-runtime-image-source-index.mjs +97 -9
- package/edition/catalogue.mjs +20 -13
- package/edition/checkout-indexed-source-repositories.mjs +41 -6
- 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/dispatched-runtime-components.mjs +82 -0
- 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/external-source-walk.mjs +1 -1
- package/edition/image-evidence.mjs +134 -26
- package/edition/kernel-capability-domain.mjs +30 -0
- package/edition/kernel-package.mjs +193 -0
- package/edition/lib.mjs +192 -4
- package/edition/oci-evidence-verbs.mjs +2 -2
- package/edition/operation-grant-admission.mjs +70 -0
- package/edition/operation-requirements.mjs +128 -0
- package/edition/plan-image-evidence-refresh.mjs +2 -2
- package/edition/platform-lock-readability.mjs +2 -2
- package/edition/platform-service-class.mjs +42 -0
- package/edition/platform-service-ownership.mjs +78 -0
- 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/remedy-command.mjs +2 -2
- package/edition/resolve-chart-set.mjs +1 -1
- package/edition/resolve-customer-edition.mjs +33 -34
- package/edition/resolve-distribution.mjs +70 -16
- package/edition/resolve-edition.mjs +3 -2
- package/edition/resolve-images.mjs +65 -4
- package/edition/seal-image-digests.mjs +17 -5
- 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 +4 -4
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { sourcePath } from './edition-workspace.mjs';
|
|
2
|
+
import { readGitJsonAtCommit, serializeJson, sha256Hex } from './lib.mjs';
|
|
3
|
+
import { sourceOwnedCapabilityDomains } from './platform-service-class.mjs';
|
|
4
|
+
|
|
5
|
+
/** Why a platform service's capability-domain ownership was refused. */
|
|
6
|
+
export const PlatformServiceOwnershipFault = Object.freeze({
|
|
7
|
+
/** The pinned source manifest could not be read. */
|
|
8
|
+
ManifestUnavailable: 'manifest-unavailable',
|
|
9
|
+
/** The manifest bytes do not hash to the index entry's `sourceManifestHash`. */
|
|
10
|
+
ManifestHashMismatch: 'manifest-hash-mismatch',
|
|
11
|
+
/** The index entry's `ownsCapabilityDomains` differs from the source manifest's. */
|
|
12
|
+
OwnershipMismatch: 'ownership-mismatch',
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export class PlatformServiceOwnershipError extends Error {
|
|
16
|
+
constructor(fault, message) {
|
|
17
|
+
super(`[${fault}] ${message}`);
|
|
18
|
+
this.name = 'PlatformServiceOwnershipError';
|
|
19
|
+
this.fault = fault;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The source manifest of one index entry, at the commit the entry pins. The
|
|
25
|
+
* entry's `path` is workspace-relative (`<repo>/<manifestPath>`).
|
|
26
|
+
*/
|
|
27
|
+
export function readPinnedSourceManifest(entry) {
|
|
28
|
+
const manifestPath = entry.path.slice(`${entry.repo}/`.length);
|
|
29
|
+
try {
|
|
30
|
+
return readGitJsonAtCommit(sourcePath(entry.repo), manifestPath, entry.sourceCommit);
|
|
31
|
+
} catch (cause) {
|
|
32
|
+
throw new PlatformServiceOwnershipError(
|
|
33
|
+
PlatformServiceOwnershipFault.ManifestUnavailable,
|
|
34
|
+
`platform service "${entry.name}": cannot read ${entry.path} at ${entry.sourceCommit} ` +
|
|
35
|
+
`(${cause.message}); a resolve that reads the platform-service index needs the pinned sources`,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Re-derive each platform service's `ownsCapabilityDomains` from its pinned
|
|
42
|
+
* source manifest and refuse the index when an entry disagrees. The index is a
|
|
43
|
+
* hand-editable file; the SERVICE's own manifest is the authority for which
|
|
44
|
+
* service owns a domain, and so for where grants land.
|
|
45
|
+
*
|
|
46
|
+
* Returns the index rows with the RE-DERIVED value, never the index's.
|
|
47
|
+
*
|
|
48
|
+
* @param {{ services: object[] }} index
|
|
49
|
+
* @param {{ names?: Set<string>, readManifest?: (entry: object) => unknown }} [options]
|
|
50
|
+
* `names` restricts the check to those services; `readManifest` is the test seam.
|
|
51
|
+
*/
|
|
52
|
+
export function verifiedPlatformServiceIndex(index, { names, readManifest = readPinnedSourceManifest } = {}) {
|
|
53
|
+
const services = index.services.map((entry) => {
|
|
54
|
+
if (names !== undefined && !names.has(entry.name)) {return entry;}
|
|
55
|
+
const manifest = readManifest(entry);
|
|
56
|
+
const actualHash = sha256Hex(serializeJson(manifest));
|
|
57
|
+
if (actualHash !== entry.sourceManifestHash) {
|
|
58
|
+
throw new PlatformServiceOwnershipError(
|
|
59
|
+
PlatformServiceOwnershipFault.ManifestHashMismatch,
|
|
60
|
+
`platform service "${entry.name}": ${entry.path} at ${entry.sourceCommit} hashes to ${actualHash}, ` +
|
|
61
|
+
`but the index pins sourceManifestHash ${entry.sourceManifestHash}`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
const derived = sourceOwnedCapabilityDomains(manifest, `${entry.path} at ${entry.sourceCommit}`);
|
|
65
|
+
const claimed = entry.ownsCapabilityDomains;
|
|
66
|
+
if (serializeJson(claimed ?? null) !== serializeJson(derived.ownsCapabilityDomains ?? null)) {
|
|
67
|
+
throw new PlatformServiceOwnershipError(
|
|
68
|
+
PlatformServiceOwnershipFault.OwnershipMismatch,
|
|
69
|
+
`platform service "${entry.name}": the index claims ownsCapabilityDomains ${JSON.stringify(claimed ?? [])}, ` +
|
|
70
|
+
`its source manifest declares ${JSON.stringify(derived.ownsCapabilityDomains ?? [])} ` +
|
|
71
|
+
`(sourceManifestHash ${entry.sourceManifestHash})`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
const { ownsCapabilityDomains: _claimed, ...rest } = entry;
|
|
75
|
+
return { ...rest, ...derived };
|
|
76
|
+
});
|
|
77
|
+
return { ...index, services };
|
|
78
|
+
}
|
|
@@ -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
|
}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// cannot run — and the reader of a dist-check failure is standing in a FLAT
|
|
11
11
|
// checkout of this repository, which is exactly where the pnpm spelling does
|
|
12
12
|
// not resolve. A remedy that cannot be run where it is printed is a dead end
|
|
13
|
-
// (`.claude/rules/writing-boundary-checks.md
|
|
13
|
+
// (`.claude/rules/writing-boundary-checks.md`).
|
|
14
14
|
//
|
|
15
15
|
// Measured 2026-09-28 at `origin/develop`: 52 `pnpm dist:*` references in
|
|
16
16
|
// this repository, 19 such scripts declared in the aggregator, and ZERO
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
// That was the obvious fix and it is the wrong one: it would put 19 name→file
|
|
22
22
|
// mappings in two places, free to drift, and drift between them is invisible
|
|
23
23
|
// — a new script reachable from one side and not the other, with nothing able
|
|
24
|
-
// to say so short of a new gate.
|
|
24
|
+
// to say so short of a new gate. The rule forbids buying a fix with
|
|
25
25
|
// a permanent check.
|
|
26
26
|
//
|
|
27
27
|
// Deriving the command from the script's REAL PATH has one authority — the
|
|
@@ -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,14 +284,18 @@ 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;
|
|
304
|
-
|
|
295
|
+
// `sourcesAvailable: false`: a customer machine holds no vendor source checkouts, so the platform
|
|
296
|
+
// services' ownership cannot be re-derived here. It is verified where the catalogue is produced
|
|
297
|
+
// (`buildCatalogue`), and the catalogue is digest-pinned.
|
|
298
|
+
const inputs = { composed, biomeIndex: catalogue.biomes, platformServiceIndex: { schemaVersion: 1, services: catalogue.platformServices }, sourcesAvailable: false };
|
|
305
299
|
let resolved;
|
|
306
300
|
try {
|
|
307
301
|
resolved = await resolveDistributionWithIndex(id, { inputs, ...(materialize === undefined ? {} : { materialize }) });
|
|
@@ -312,9 +306,14 @@ export async function resolveCustomerDistribution({
|
|
|
312
306
|
throw error;
|
|
313
307
|
}
|
|
314
308
|
const { lock, index } = resolved;
|
|
315
|
-
assertTrustCeilingAssignsCustomerBiomes(
|
|
316
|
-
|
|
317
|
-
|
|
309
|
+
assertTrustCeilingAssignsCustomerBiomes(
|
|
310
|
+
schemas,
|
|
311
|
+
definition,
|
|
312
|
+
lock.biomes.filter((biome) => biome.source !== undefined).map((biome) => biome.id),
|
|
313
|
+
catalogueCeiling(catalogue, schemas).trustCeiling ?? NO_CATALOGUE_TRUST_CEILING,
|
|
314
|
+
);
|
|
315
|
+
assertLockWithinCatalogueCeiling({ catalogue, definition, lock, schemas });
|
|
316
|
+
await assertAdapterBundlesMatchHost({ lock, hostRow: adapterHostRow(catalogue.biomes), fetchManifest });
|
|
318
317
|
const authorizationLock = await composeAuthorizationBase(id, lock, index);
|
|
319
318
|
// A customer lock names the catalogue it was resolved against; seal and verify refuse any other.
|
|
320
319
|
return { id, schemas, composed, inputs, lock: { ...lock, catalogueDigest }, authorizationLock };
|
|
@@ -63,6 +63,7 @@
|
|
|
63
63
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
64
64
|
import { join } from 'node:path';
|
|
65
65
|
|
|
66
|
+
import { adapterHostRow, assertAdapterBundlesMatchHost, fetchRegistryManifest } from './adapter-bundled-versions.mjs';
|
|
66
67
|
import {
|
|
67
68
|
adoptedFromLock,
|
|
68
69
|
editionAuthorizationModuleInputs,
|
|
@@ -73,6 +74,7 @@ import {
|
|
|
73
74
|
sealEditionAuthorizationBase,
|
|
74
75
|
writeAuthorizationLock,
|
|
75
76
|
} from './authorization-base.mjs';
|
|
77
|
+
import { resolveDispatchedRuntimeComponents } from './dispatched-runtime-components.mjs';
|
|
76
78
|
import { fleetChartSetLockPath, writeEditionChartSet } from './edition-chart-set.mjs';
|
|
77
79
|
import { workspaceSection } from './edition-config.mjs';
|
|
78
80
|
import { WorkspaceRoot, biomeIndexPath, configureEditionWorkspaceFromProcess, editionsRoot, isEntryModule, kernelPackageDir, platformServiceIndexPath } from './edition-workspace.mjs';
|
|
@@ -91,7 +93,12 @@ import {
|
|
|
91
93
|
validateImmutableDistributionLock,
|
|
92
94
|
validateServiceDependencyOwnerParity,
|
|
93
95
|
} from './lib.mjs';
|
|
96
|
+
import {
|
|
97
|
+
assertOperationGrantsAdmitted,
|
|
98
|
+
assertOrganizationSweepsAdmitted,
|
|
99
|
+
} from './operation-grant-admission.mjs';
|
|
94
100
|
import { PUBLISHED_PIN_SOURCE, pinnedSource } from './pin-source.mjs';
|
|
101
|
+
import { verifiedPlatformServiceIndex } from './platform-service-ownership.mjs';
|
|
95
102
|
import { closureBiomeFacts } from './positive-closure.mjs';
|
|
96
103
|
import { assertReleaseContent, assertReleaseEditionTree } from './release-channel-guard.mjs';
|
|
97
104
|
import { commandFor } from './remedy-command.mjs';
|
|
@@ -1744,6 +1751,13 @@ export function resolveDistributionFromInputs(
|
|
|
1744
1751
|
// shipped provider. See `nexusBinder`.
|
|
1745
1752
|
const nexusBindings = nexusBinder(id, [...selected.values()], schemas);
|
|
1746
1753
|
|
|
1754
|
+
// (5d) Dispatched runtime components — every `runtime-image` edge of a
|
|
1755
|
+
// shipped biome must resolve to a runtime component of a biome in the index.
|
|
1756
|
+
// The target biome is not rostered (it enters no `biomes[]`, `requires` or
|
|
1757
|
+
// install set); the image plane adds its image row. Resolved here so an
|
|
1758
|
+
// unresolvable edge refuses the edition, not only its image lock.
|
|
1759
|
+
resolveDispatchedRuntimeComponents(`distribution "${id}"`, [...selected.values()], index);
|
|
1760
|
+
|
|
1747
1761
|
// (6) Build the lock — sort by (tier order, id, target, repo).
|
|
1748
1762
|
const biomes = [...selected.values()]
|
|
1749
1763
|
.map((e) => ({
|
|
@@ -1821,7 +1835,7 @@ export function resolveDistributionFromInputs(
|
|
|
1821
1835
|
// starts. Optional-in-the-new-schema and absent-from-the-old are different
|
|
1822
1836
|
// facts, and only the second one governs a deployed image.
|
|
1823
1837
|
//
|
|
1824
|
-
// MEASURED 2026-09-17. Emitting it took
|
|
1838
|
+
// MEASURED 2026-09-17. Emitting it took a customer environment's three
|
|
1825
1839
|
// distribution-lock consumers — biome-host-api, capability-registry-api,
|
|
1826
1840
|
// xema-capability-router — into CrashLoopBackOff on a sealed, signed,
|
|
1827
1841
|
// platform-verified edition. The seal was green; the lock was unparseable
|
|
@@ -1839,7 +1853,7 @@ export function resolveDistributionFromInputs(
|
|
|
1839
1853
|
// somebody raised the pin. Re-measured against the refs, not the comment:
|
|
1840
1854
|
// xema-base origin/main `'@xemahq/contracts': ^0.15.0`
|
|
1841
1855
|
// xema-base origin/develop `'@xemahq/contracts': ^0.16.0`
|
|
1842
|
-
// xema-base 2ef54a1 (the `sourceCommit`
|
|
1856
|
+
// xema-base 2ef54a1 (the `sourceCommit` a customer environment's images.lock.json
|
|
1843
1857
|
// names) resolves `@xemahq/contracts@0.15.0`, sole resolution.
|
|
1844
1858
|
// and against the published tarballs, two-sided, `npm pack` + grep:
|
|
1845
1859
|
// 0.9.2 -> 0 files name this key; 0.12.0 / 0.15.0 / 0.17.0 -> 2 each.
|
|
@@ -1847,7 +1861,7 @@ export function resolveDistributionFromInputs(
|
|
|
1847
1861
|
// past the deletion condition this comment states below.
|
|
1848
1862
|
//
|
|
1849
1863
|
// WHAT ACTUALLY BLOCKS IT IS THE ROLLOUT, and a pin is not a rollout.
|
|
1850
|
-
// Measured 2026-09-19 inside the running containers in `
|
|
1864
|
+
// Measured 2026-09-19 inside the running containers in `a customer environment`:
|
|
1851
1865
|
// `cat /app/node_modules/@xemahq/contracts/package.json` -> "0.9.2",
|
|
1852
1866
|
// one resolution each (`ls -d /app/node_modules/.pnpm/@xemahq+contracts@*`),
|
|
1853
1867
|
// and `dist/distribution/lib/distribution-lock.js` carries `.strict()`
|
|
@@ -1856,7 +1870,7 @@ export function resolveDistributionFromInputs(
|
|
|
1856
1870
|
// POSITIVE CONTROL, same tree, same probe: `grep -rl distribution` -> 64.
|
|
1857
1871
|
// So the zero is an absence, not a broken probe.
|
|
1858
1872
|
// And the three running digests are NOT the digests
|
|
1859
|
-
// `deployments
|
|
1873
|
+
// `deployments/<customer>-dev/images.lock.json` designates — the cluster is
|
|
1860
1874
|
// roughly one build behind its own lock, which is why a pin raised on the
|
|
1861
1875
|
// branch has not reached the parser.
|
|
1862
1876
|
//
|
|
@@ -1874,7 +1888,7 @@ export function resolveDistributionFromInputs(
|
|
|
1874
1888
|
// answers this. For each of biome-host-api, capability-registry-api and
|
|
1875
1889
|
// xema-capability-router:
|
|
1876
1890
|
//
|
|
1877
|
-
// kubectl -n
|
|
1891
|
+
// kubectl -n <customer>-dev exec deploy/<svc> -- \
|
|
1878
1892
|
// cat /app/node_modules/@xemahq/contracts/package.json
|
|
1879
1893
|
//
|
|
1880
1894
|
// must report >= 0.12.0 for ALL THREE. When it does, in ONE change:
|
|
@@ -1883,7 +1897,7 @@ export function resolveDistributionFromInputs(
|
|
|
1883
1897
|
// the withholding and will correctly go red;
|
|
1884
1898
|
// 3. re-seal the affected locks;
|
|
1885
1899
|
// 4. delete the `postgres.extensions` BRIDGE blocks from
|
|
1886
|
-
// `deployments
|
|
1900
|
+
// `deployments/<customer>-dev/secret-inputs.yaml` and
|
|
1887
1901
|
// `deployments/owl-xema-prod/secret-inputs.yaml` in xema-deploy —
|
|
1888
1902
|
// the deploy plane REFUSES that bridge by name the moment any biome
|
|
1889
1903
|
// in the lock carries this key, so 3 and 4 cannot be separated.
|
|
@@ -1999,6 +2013,20 @@ export function resolveDistributionFromInputs(
|
|
|
1999
2013
|
// throws), so an entry without the key is one whose manifest declares
|
|
2000
2014
|
// no such worker.
|
|
2001
2015
|
workerImages: e.workerImages ?? {},
|
|
2016
|
+
// The service-to-service operations each workload component requires,
|
|
2017
|
+
// forwarded verbatim from the index (already sorted and non-empty). The
|
|
2018
|
+
// seal admits them (`admitOperationGrants`). Omitted when none, so an
|
|
2019
|
+
// edition declaring none is byte-identical.
|
|
2020
|
+
...(e.operationRequirements !== undefined &&
|
|
2021
|
+
Object.keys(e.operationRequirements).length > 0
|
|
2022
|
+
? { operationRequirements: e.operationRequirements }
|
|
2023
|
+
: {}),
|
|
2024
|
+
// The organization sweeps each workload component may hold, forwarded
|
|
2025
|
+
// verbatim; the seal admits them (`admitOrganizationSweeps`).
|
|
2026
|
+
...(e.organizationSweepRequirements !== undefined &&
|
|
2027
|
+
Object.keys(e.organizationSweepRequirements).length > 0
|
|
2028
|
+
? { organizationSweepRequirements: e.organizationSweepRequirements }
|
|
2029
|
+
: {}),
|
|
2002
2030
|
// --- Catalog projection (consumed by biome-host's DistributionCatalogSource) ---
|
|
2003
2031
|
// Forwarded verbatim from the index so the lock is the SELF-CONTAINED
|
|
2004
2032
|
// single source of truth for the biome catalog: biome-host builds the
|
|
@@ -2068,7 +2096,15 @@ export function resolveDistributionFromInputs(
|
|
|
2068
2096
|
// widening it. That refusal is the right posture and this is the right
|
|
2069
2097
|
// side of it.
|
|
2070
2098
|
const { installationClass: _installationClass, ...locked } = service;
|
|
2071
|
-
return {
|
|
2099
|
+
return {
|
|
2100
|
+
...locked,
|
|
2101
|
+
// Copied verbatim from the index entry (sorted, unique, non-empty by
|
|
2102
|
+
// its schema); the grant admission reads it from the lock.
|
|
2103
|
+
...(source.ownsCapabilityDomains === undefined
|
|
2104
|
+
? {}
|
|
2105
|
+
: { ownsCapabilityDomains: source.ownsCapabilityDomains }),
|
|
2106
|
+
source,
|
|
2107
|
+
};
|
|
2072
2108
|
})
|
|
2073
2109
|
.sort((a, b) => {
|
|
2074
2110
|
const ta = TIER_WITH_EDGE.indexOf(a.tier);
|
|
@@ -2148,7 +2184,11 @@ export function resolveDistributionFromInputs(
|
|
|
2148
2184
|
schemas.resolveFirstPartyCapabilityDomains(lock);
|
|
2149
2185
|
|
|
2150
2186
|
// (8) Validate against the BUILT kernel schema — fail-fast.
|
|
2151
|
-
schemas.parseUnsealedDistributionLock(lock);
|
|
2187
|
+
const parsed = schemas.parseUnsealedDistributionLock(lock);
|
|
2188
|
+
// (9) Admit every declared service-to-service operation requirement; a
|
|
2189
|
+
// refusal throws, so a lock with one is never returned to be written.
|
|
2190
|
+
assertOperationGrantsAdmitted(schemas, parsed);
|
|
2191
|
+
assertOrganizationSweepsAdmitted(schemas, parsed);
|
|
2152
2192
|
return validateImmutableDistributionLock(lock);
|
|
2153
2193
|
}
|
|
2154
2194
|
|
|
@@ -2175,22 +2215,33 @@ export async function resolveDistribution(id) {
|
|
|
2175
2215
|
* `materialize` is the walk's one network seam, injectable for the contract
|
|
2176
2216
|
* suite exactly as it is on `walkDeclaredSources`.
|
|
2177
2217
|
*/
|
|
2178
|
-
export async function resolveDistributionWithIndex(id, { materialize, inputs } = {}) {
|
|
2218
|
+
export async function resolveDistributionWithIndex(id, { materialize, inputs, readManifest } = {}) {
|
|
2179
2219
|
const schemas = await loadKernelDistributionSchemas();
|
|
2220
|
+
// The manifest is composed FIRST, because it is what declares the sources and
|
|
2221
|
+
// what refuses a malformed definition before any other work. The walk then
|
|
2222
|
+
// runs once per resolve and its entries are merged into a per-resolution
|
|
2223
|
+
// index — never into `biome-index.json`, which is the platform's and is read
|
|
2224
|
+
// by every edition.
|
|
2225
|
+
const composed = inputs?.composed ?? composeEdition(schemas.parseDistribution, id);
|
|
2180
2226
|
// THE RELEASE READS THESE FROM DISK; A DEVELOP SNAPSHOT INJECTS THEM
|
|
2181
2227
|
// (`develop-snapshot-inputs.mjs`). Either way they pass through the one
|
|
2182
2228
|
// validation below, so there is a single resolve path and two sources.
|
|
2183
|
-
const
|
|
2229
|
+
const claimedPlatformServiceIndex =
|
|
2184
2230
|
inputs?.platformServiceIndex ?? readPlatformServiceIndex(schemas.parsePlatformServiceSourceIndex);
|
|
2231
|
+
// OWNERSHIP IS THE SERVICE'S OWN STATEMENT: each entry's capability domains
|
|
2232
|
+
// are re-derived from its pinned source manifest (hash-verified), so a
|
|
2233
|
+
// hand-edited index cannot move where grants land. Only the customer resolve
|
|
2234
|
+
// sets `sourcesAvailable: false` -- it has no vendor checkouts and trusts the
|
|
2235
|
+
// signed catalogue, which `buildCatalogue` verified where it was produced.
|
|
2236
|
+
// Any other caller without the sources is refused, never skipped.
|
|
2237
|
+
const platformServiceIndex =
|
|
2238
|
+
inputs?.sourcesAvailable === false
|
|
2239
|
+
? claimedPlatformServiceIndex
|
|
2240
|
+
: verifiedPlatformServiceIndex(claimedPlatformServiceIndex, { readManifest });
|
|
2185
2241
|
const platformIndex = validateServiceDependencyOwnerParity(
|
|
2186
2242
|
validateImmutableBiomeIndex(inputs?.biomeIndex ?? readBiomeIndex()),
|
|
2187
2243
|
platformServiceIndex,
|
|
2188
2244
|
);
|
|
2189
|
-
// The manifest is composed FIRST, because it is what declares the sources. The
|
|
2190
|
-
// walk then runs once per resolve and its entries are merged into a
|
|
2191
|
-
// per-resolution index — never into `biome-index.json`, which is the
|
|
2192
|
-
// platform's and is read by every edition.
|
|
2193
|
-
const composed = inputs?.composed ?? composeEdition(schemas.parseDistribution, id);
|
|
2194
2245
|
const { entries, sourceRecords } = await walkDeclaredSources({
|
|
2195
2246
|
sources: composed.declaredSources,
|
|
2196
2247
|
...(materialize === undefined ? {} : { materialize }),
|
|
@@ -2212,8 +2263,9 @@ export async function resolveDistributionWithIndex(id, { materialize, inputs } =
|
|
|
2212
2263
|
* this exact writer so an in-process refresh cannot drift from the standalone
|
|
2213
2264
|
* command's serialization or diagnostics.
|
|
2214
2265
|
*/
|
|
2215
|
-
export async function writeResolvedDistribution(id, { log = true, materialize } = {}) {
|
|
2266
|
+
export async function writeResolvedDistribution(id, { log = true, materialize, fetchManifest = fetchRegistryManifest } = {}) {
|
|
2216
2267
|
const { lock, index } = await resolveDistributionWithIndex(id, { materialize });
|
|
2268
|
+
await assertAdapterBundlesMatchHost({ lock, hostRow: adapterHostRow(index), fetchManifest });
|
|
2217
2269
|
const outPath = join(editionsRoot(), id, 'distribution.lock.json');
|
|
2218
2270
|
assertReleaseContent(lock, `the distribution lock for "${id}"`);
|
|
2219
2271
|
assertReleaseEditionTree(join(editionsRoot(), id));
|
|
@@ -2285,12 +2337,14 @@ export async function composeAuthorizationBase(id, lock, index) {
|
|
|
2285
2337
|
const kernel = await loadKernelAuthorizationComposer({ kernelPackageDir: kernelPackageDir() });
|
|
2286
2338
|
// Refuses an edition that carries a module under a pin with no composer.
|
|
2287
2339
|
const inputs = editionAuthorizationModuleInputs(lock.biomes, new Map(index.map((e) => [e.id, e])), kernel);
|
|
2340
|
+
const { biomeOriginsOfLock } = await loadKernelDistributionSchemas();
|
|
2288
2341
|
let sealed = null;
|
|
2289
2342
|
if (kernel !== null) {
|
|
2290
2343
|
sealed = sealEditionAuthorizationBase({
|
|
2291
2344
|
distributionId: id,
|
|
2292
2345
|
inputs,
|
|
2293
2346
|
kernel,
|
|
2347
|
+
biomeOrigins: biomeOriginsOfLock(lock),
|
|
2294
2348
|
adopted: adoptedFromLock(readAuthorizationLock(editionDir)),
|
|
2295
2349
|
runTests: fgaComposedModelTestRunner(resolveFgaCli()),
|
|
2296
2350
|
});
|
|
@@ -50,14 +50,15 @@ import { withoutSealedDigests } from './seal-image-digests.mjs';
|
|
|
50
50
|
import { readStrictJsonFile } from './strict-json.mjs';
|
|
51
51
|
|
|
52
52
|
// A customer resolve (`--definition`) has no vendor editions root: `--out` IS its
|
|
53
|
-
// editions root, the directory its `<id>/` locks are written under.
|
|
53
|
+
// editions root, the directory its `<id>/` locks are written under. It takes no kernel root:
|
|
54
|
+
// the kernel is the catalogue's, downloaded from npm into the cache root.
|
|
54
55
|
if (isEntryModule(import.meta.url)) {
|
|
55
56
|
if (process.argv.includes(DEFINITION_FLAG)) {
|
|
56
57
|
const at = process.argv.indexOf('--out');
|
|
57
58
|
if (at < 0 || process.argv[at + 1] === undefined) {throw new Error(`--out <dir> is required with ${DEFINITION_FLAG}`);}
|
|
58
59
|
mkdirSync(process.argv[at + 1], { recursive: true });
|
|
59
60
|
process.argv.splice(at, 1, '--editions-root');
|
|
60
|
-
configureEditionWorkspaceFromProcess([WorkspaceRoot.Data, WorkspaceRoot.Editions, WorkspaceRoot.Sources, WorkspaceRoot.
|
|
61
|
+
configureEditionWorkspaceFromProcess([WorkspaceRoot.Data, WorkspaceRoot.Editions, WorkspaceRoot.Sources, WorkspaceRoot.Cache], { refuseOthers: true });
|
|
61
62
|
} else {
|
|
62
63
|
configureEditionWorkspaceFromProcess([WorkspaceRoot.Data, WorkspaceRoot.Editions, WorkspaceRoot.Sources, WorkspaceRoot.Kernel, WorkspaceRoot.Cache]);
|
|
63
64
|
}
|