@xemahq/xema 0.41.0 → 0.42.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.js +1 -1
- package/dist/commands/edition.js.map +1 -1
- package/dist/edition/verify-image-evidence.bundle.mjs +1297 -850
- package/dist/edition/verify-image-evidence.bundle.provenance.json +498 -456
- package/edition/README.md +3 -1
- package/edition/biome-content.mjs +4 -0
- package/edition/customer-edition-verbs.mjs +1 -0
- package/edition/develop-edition.mjs +22 -2
- package/edition/dispatched-runtime-components.mjs +91 -1
- package/edition/edition-protocol.mjs +36 -9
- package/edition/edition-workspace.mjs +2 -0
- package/edition/kernel-protocol-choice.mjs +239 -0
- package/edition/lib.mjs +113 -14
- package/edition/operation-grant-admission.mjs +65 -1
- package/edition/operation-requirements.mjs +18 -1
- package/edition/resolve-customer-edition.mjs +4 -3
- package/edition/resolve-distribution.mjs +16 -0
- package/edition/resolve-images.mjs +164 -20
- package/package.json +4 -4
package/edition/lib.mjs
CHANGED
|
@@ -20,6 +20,12 @@ import { readBiomeAuthorizationModuleAt } from './authorization-base.mjs';
|
|
|
20
20
|
import {
|
|
21
21
|
isAuthoritativeProductionBiomeSource,
|
|
22
22
|
} from './connector-source-authority.mjs';
|
|
23
|
+
import {
|
|
24
|
+
compareDispatchedEdges,
|
|
25
|
+
dispatchedEdgeIdentity,
|
|
26
|
+
isSupplementalEdge,
|
|
27
|
+
projectDispatchedTarget,
|
|
28
|
+
} from './dispatched-runtime-components.mjs';
|
|
23
29
|
import { assertKernelEditionProtocol } from './edition-protocol.mjs';
|
|
24
30
|
import {
|
|
25
31
|
kernelDistPath,
|
|
@@ -34,6 +40,7 @@ import { isKernelCapabilityDomain } from './kernel-capability-domain.mjs';
|
|
|
34
40
|
import {
|
|
35
41
|
PRINCIPAL_PRIVILEGE_FIELDS,
|
|
36
42
|
assertPrivilegesHavePrincipals,
|
|
43
|
+
componentInstalledOperationRequests,
|
|
37
44
|
componentOperationRequirements,
|
|
38
45
|
componentOptionalOperationRequirements,
|
|
39
46
|
componentOrganizationSweepRequirements,
|
|
@@ -680,6 +687,64 @@ const kernelDistributionDist = () => kernelDistPath('distribution', 'index.js');
|
|
|
680
687
|
// through. A sibling of the distribution dist, loaded beside it.
|
|
681
688
|
const kernelProcessTopologyDist = () => kernelDistPath('process-topology', 'index.js');
|
|
682
689
|
|
|
690
|
+
/** A composed-process subject, on a kernel that cannot write image-lock schemaVersion 4. */
|
|
691
|
+
export class ComposedProcessNeedsLockV4Error extends Error {
|
|
692
|
+
constructor(message) {
|
|
693
|
+
super(message);
|
|
694
|
+
this.name = 'ComposedProcessNeedsLockV4Error';
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/** The image-lock revision of a kernel that predates `IMAGE_LOCK_WRITE_SCHEMA_VERSION` (edition protocol < v14). */
|
|
699
|
+
const PRE_V14_IMAGE_LOCK_SCHEMA_VERSION = 3;
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* Refuse a composed-process subject when the loaded kernel predates image-lock
|
|
703
|
+
* schemaVersion 4 (it exports no `IMAGE_LOCK_WRITE_SCHEMA_VERSION`): such a
|
|
704
|
+
* kernel has no `members[]`, so it cannot say which services the composed
|
|
705
|
+
* image carries. There is no fallback.
|
|
706
|
+
*/
|
|
707
|
+
export function assertKernelSealsComposedMembership(schemas, subjects, label) {
|
|
708
|
+
if (Number.isInteger(schemas.IMAGE_LOCK_WRITE_SCHEMA_VERSION)) {return;}
|
|
709
|
+
const composed = (subjects ?? []).find((subject) => subject.kind === 'composed-process');
|
|
710
|
+
if (composed === undefined) {return;}
|
|
711
|
+
throw new ComposedProcessNeedsLockV4Error(
|
|
712
|
+
`${label}: composed-process subject "${composed.id}" — a composed-process subject needs a kernel that ` +
|
|
713
|
+
'writes image-lock schemaVersion 4 (edition protocol >= v14); the loaded kernel ' +
|
|
714
|
+
`(${kernelRepoDir()}) exports no IMAGE_LOCK_WRITE_SCHEMA_VERSION`,
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* The READ side of the same rule, for a kernel before image-lock
|
|
720
|
+
* schemaVersion 4 (no `ImageLockSchemaVersion`): it reads only schemaVersion
|
|
721
|
+
* 3, which names no composed members, so a v3 lock carrying a composed-process
|
|
722
|
+
* subject cannot say which services run from it and is refused (worded as the
|
|
723
|
+
* kernel's own `LegacyComposedImageLockError`).
|
|
724
|
+
*/
|
|
725
|
+
export function assertLegacyLockHasNoComposedSubject(lock) {
|
|
726
|
+
const composed = (lock.subjects ?? []).find((subject) => subject.kind === 'composed-process');
|
|
727
|
+
if (composed === undefined) {return;}
|
|
728
|
+
throw new ComposedProcessNeedsLockV4Error(
|
|
729
|
+
`distribution "${lock.distributionId}": subjects "${composed.id}" is a schemaVersion ` +
|
|
730
|
+
`${String(lock.schemaVersion)} composed-process subject, which names no members: regenerate with a CLI ` +
|
|
731
|
+
'that writes image-lock schemaVersion 4, against a kernel at edition protocol >= v14',
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* The image-lock schemaVersion a writer emits for `subjects`: the loaded
|
|
737
|
+
* kernel's `IMAGE_LOCK_WRITE_SCHEMA_VERSION`. A kernel before edition protocol
|
|
738
|
+
* v14 exports none and writes only schemaVersion 3, which is allowed only when
|
|
739
|
+
* there is NO composed-process subject
|
|
740
|
+
* ({@link ComposedProcessNeedsLockV4Error} otherwise).
|
|
741
|
+
*/
|
|
742
|
+
export function imageLockWriteSchemaVersion(schemas, subjects, label = 'images.lock.json') {
|
|
743
|
+
assertKernelSealsComposedMembership(schemas, subjects, label);
|
|
744
|
+
const version = schemas.IMAGE_LOCK_WRITE_SCHEMA_VERSION;
|
|
745
|
+
return Number.isInteger(version) ? version : PRE_V14_IMAGE_LOCK_SCHEMA_VERSION;
|
|
746
|
+
}
|
|
747
|
+
|
|
683
748
|
/**
|
|
684
749
|
* Dynamically import the BUILT kernel distribution schemas. The kernel package
|
|
685
750
|
* is NOT linked into the repo-root node_modules (it lives in its own sub-
|
|
@@ -731,6 +796,8 @@ export async function loadKernelDistributionSchemas() {
|
|
|
731
796
|
parseOciDigest,
|
|
732
797
|
parseGitTreeId,
|
|
733
798
|
parseImageLock,
|
|
799
|
+
IMAGE_LOCK_WRITE_SCHEMA_VERSION,
|
|
800
|
+
ImageLockSchemaVersion,
|
|
734
801
|
parseImageProducerStatement,
|
|
735
802
|
parseDistributionImagesStatement,
|
|
736
803
|
parseImageDeliveryEntitlementStatement,
|
|
@@ -783,6 +850,8 @@ export async function loadKernelDistributionSchemas() {
|
|
|
783
850
|
admitOperationGrants,
|
|
784
851
|
admitOrganizationSweeps,
|
|
785
852
|
contractRequiredServiceTargets,
|
|
853
|
+
admitInstalledOperationCeiling,
|
|
854
|
+
admitStoreWorkloadCeiling,
|
|
786
855
|
} = mod;
|
|
787
856
|
let topology;
|
|
788
857
|
try {
|
|
@@ -880,6 +949,11 @@ export async function loadKernelDistributionSchemas() {
|
|
|
880
949
|
typeof admitOperationGrants !== 'function' ||
|
|
881
950
|
typeof admitOrganizationSweeps !== 'function' ||
|
|
882
951
|
typeof contractRequiredServiceTargets !== 'function' ||
|
|
952
|
+
// The installed-operation ceiling and the edition's store-workload ceiling,
|
|
953
|
+
// both admitted at the lock (`assertInstalledOperationCeilingAdmitted`,
|
|
954
|
+
// `assertStoreWorkloadCeilingAdmitted`).
|
|
955
|
+
typeof admitInstalledOperationCeiling !== 'function' ||
|
|
956
|
+
typeof admitStoreWorkloadCeiling !== 'function' ||
|
|
883
957
|
typeof distributionImagesPredicateLockRefusal !== 'function' ||
|
|
884
958
|
typeof distributionLockDigest !== 'function' ||
|
|
885
959
|
typeof imageDeliveryEntitlementRefusal !== 'function' ||
|
|
@@ -901,7 +975,7 @@ export async function loadKernelDistributionSchemas() {
|
|
|
901
975
|
`resolveRuntimeImagePlatforms/RuntimeImagePlatformKey/` +
|
|
902
976
|
`kernel contract-authority digest SSOT/compareLockedServiceResources/resolveNexusBindings/describeNexusBindingRefusal/canonicalNexusFacts/` +
|
|
903
977
|
`positive closure (resolveBiomeClosure/platformInvariantRoots/DistributionResolutionMode/provenance)/` +
|
|
904
|
-
`deriveWorkflowSubjects/deriveTrustCeiling (>= 0.71.0)/admitOperationGrants/admitOrganizationSweeps/` +
|
|
978
|
+
`deriveWorkflowSubjects/deriveTrustCeiling (>= 0.71.0)/admitOperationGrants/admitOrganizationSweeps/admitInstalledOperationCeiling/admitStoreWorkloadCeiling/` +
|
|
905
979
|
`defaultTrustDomain/FIRST_PARTY_TRUST_DOMAIN/ProcessTrustPolicySchema/DistributionTrustPolicySchema/LockedStorePublisherSchema/` +
|
|
906
980
|
`resolveFirstPartyCapabilityDomains/DistributionVisibility/` +
|
|
907
981
|
`BiomeInstallPolicy/BiomeLaunchMode/ImageSourceHashAlgorithm/runtime-image enums — ` +
|
|
@@ -927,6 +1001,8 @@ export async function loadKernelDistributionSchemas() {
|
|
|
927
1001
|
parseOciDigest,
|
|
928
1002
|
parseGitTreeId,
|
|
929
1003
|
parseImageLock,
|
|
1004
|
+
IMAGE_LOCK_WRITE_SCHEMA_VERSION,
|
|
1005
|
+
ImageLockSchemaVersion,
|
|
930
1006
|
parseImageProducerStatement,
|
|
931
1007
|
parseDistributionImagesStatement,
|
|
932
1008
|
parseImageDeliveryEntitlementStatement,
|
|
@@ -986,6 +1062,8 @@ export async function loadKernelDistributionSchemas() {
|
|
|
986
1062
|
admitOperationGrants,
|
|
987
1063
|
admitOrganizationSweeps,
|
|
988
1064
|
contractRequiredServiceTargets,
|
|
1065
|
+
admitInstalledOperationCeiling,
|
|
1066
|
+
admitStoreWorkloadCeiling,
|
|
989
1067
|
distributionImagesPredicateLockRefusal,
|
|
990
1068
|
imageDeliveryEntitlementRefusal,
|
|
991
1069
|
producerWorkflowPinRefusal,
|
|
@@ -1617,7 +1695,7 @@ export async function manifestToIndexEntry({ raw, generatedIndex, rel, repo, pat
|
|
|
1617
1695
|
'resolve no worker image: a worker declares artifact.kind "oci-image" or "shared-oci-image"',
|
|
1618
1696
|
);
|
|
1619
1697
|
}
|
|
1620
|
-
// A privilege (`requiresOperations`, `optionalOperations`, `requiresOrganizationSweeps`) is admitted for a lock workload
|
|
1698
|
+
// A privilege (`requiresOperations`, `optionalOperations`, `requestsInstalledOperations`, `requiresOrganizationSweeps`) is admitted for a lock workload
|
|
1621
1699
|
// principal only; on any other component it is refused, never dropped.
|
|
1622
1700
|
assertPrivilegesHavePrincipals(components, imageComponents, rel);
|
|
1623
1701
|
// A biome must declare its images in EXACTLY ONE shape. Declaring both a
|
|
@@ -1668,6 +1746,10 @@ export async function manifestToIndexEntry({ raw, generatedIndex, rel, repo, pat
|
|
|
1668
1746
|
// (`optionalOperations`), keyed like `operationRequirements`. Declared
|
|
1669
1747
|
// intent only: the seal grants, leaves ungranted or refuses each one.
|
|
1670
1748
|
const optionalOperationRequirements = {};
|
|
1749
|
+
// `component → the operations it asks to exercise for the installing
|
|
1750
|
+
// organization` (`requestsInstalledOperations`), keyed like
|
|
1751
|
+
// `operationRequirements`. Declared intent only: the lock admits the ceiling.
|
|
1752
|
+
const installedOperationRequests = {};
|
|
1671
1753
|
// `component → the organization sweeps it may hold`
|
|
1672
1754
|
// (`requiresOrganizationSweeps`), keyed like `operationRequirements`.
|
|
1673
1755
|
// Declared intent only: the seal admits or refuses each one.
|
|
@@ -1861,6 +1943,8 @@ export async function manifestToIndexEntry({ raw, generatedIndex, rel, repo, pat
|
|
|
1861
1943
|
if (operations !== null) {operationRequirements[name] = operations;}
|
|
1862
1944
|
const optionalOperations = componentOptionalOperationRequirements(component, where);
|
|
1863
1945
|
if (optionalOperations !== null) {optionalOperationRequirements[name] = optionalOperations;}
|
|
1946
|
+
const installed = componentInstalledOperationRequests(component, where);
|
|
1947
|
+
if (installed !== null) {installedOperationRequests[name] = installed;}
|
|
1864
1948
|
const sweeps = componentOrganizationSweepRequirements(component, where);
|
|
1865
1949
|
if (sweeps !== null) {organizationSweepRequirements[name] = sweeps;}
|
|
1866
1950
|
const queues = componentWorkerTaskQueues(component, protocol, where);
|
|
@@ -2037,16 +2121,10 @@ export async function manifestToIndexEntry({ raw, generatedIndex, rel, repo, pat
|
|
|
2037
2121
|
...new Map(
|
|
2038
2122
|
parsedIndex.biome.edges
|
|
2039
2123
|
.filter((edge) => edge.kind === kernelBiome.BiomeGeneratedEdgeKind.RuntimeImage)
|
|
2040
|
-
.map((edge) =>
|
|
2041
|
-
|
|
2042
|
-
{ biomeId: edge.target.biomeId, componentKey: edge.target.componentKey },
|
|
2043
|
-
]),
|
|
2124
|
+
.map((edge) => projectDispatchedTarget(id, edge.target))
|
|
2125
|
+
.map((target) => [dispatchedEdgeIdentity(target), target]),
|
|
2044
2126
|
).values(),
|
|
2045
|
-
].sort(
|
|
2046
|
-
(a, b) =>
|
|
2047
|
-
compareStrings(a.biomeId, b.biomeId) ||
|
|
2048
|
-
compareStrings(a.componentKey, b.componentKey),
|
|
2049
|
-
);
|
|
2127
|
+
].sort(compareDispatchedEdges);
|
|
2050
2128
|
|
|
2051
2129
|
// Optional server biomes a web biome can integrate with but does not require
|
|
2052
2130
|
// (never cause an ORPHANED resolution). Carried separately from `requires`.
|
|
@@ -2311,6 +2389,17 @@ export async function manifestToIndexEntry({ raw, generatedIndex, rel, repo, pat
|
|
|
2311
2389
|
),
|
|
2312
2390
|
}
|
|
2313
2391
|
: {}),
|
|
2392
|
+
// The operations each workload component asks to exercise for the
|
|
2393
|
+
// installing organization; omitted when none declares any.
|
|
2394
|
+
...(Object.keys(installedOperationRequests).length > 0
|
|
2395
|
+
? {
|
|
2396
|
+
installedOperationRequests: Object.fromEntries(
|
|
2397
|
+
Object.entries(installedOperationRequests).sort(([a], [b]) =>
|
|
2398
|
+
compareStrings(a, b),
|
|
2399
|
+
),
|
|
2400
|
+
),
|
|
2401
|
+
}
|
|
2402
|
+
: {}),
|
|
2314
2403
|
// The organization sweeps each workload component may hold; omitted when
|
|
2315
2404
|
// none declares any.
|
|
2316
2405
|
...(Object.keys(organizationSweepRequirements).length > 0
|
|
@@ -2795,10 +2884,12 @@ export function validateImmutableBiomeIndex(index) {
|
|
|
2795
2884
|
}
|
|
2796
2885
|
const dispatched = new Set();
|
|
2797
2886
|
for (const edge of biome.dispatchesRuntimeComponents ?? []) {
|
|
2798
|
-
const edgeKey =
|
|
2887
|
+
const edgeKey = dispatchedEdgeIdentity(projectDispatchedTarget(biome.id, edge));
|
|
2799
2888
|
if (dispatched.has(edgeKey)) {
|
|
2800
2889
|
throw new Error(
|
|
2801
|
-
|
|
2890
|
+
isSupplementalEdge(edge)
|
|
2891
|
+
? `biome "${biome.id}" dispatches ${edgeKey} more than once`
|
|
2892
|
+
: `biome "${biome.id}" dispatches runtime component "${edge.componentKey}" of biome "${edge.biomeId}" more than once`,
|
|
2802
2893
|
);
|
|
2803
2894
|
}
|
|
2804
2895
|
dispatched.add(edgeKey);
|
|
@@ -2918,8 +3009,16 @@ export function validateImmutableImageLock(lock, schemas) {
|
|
|
2918
3009
|
if (!schemas) {
|
|
2919
3010
|
throw new Error('trusted kernel distribution schemas are required');
|
|
2920
3011
|
}
|
|
3012
|
+
assertKernelSealsComposedMembership(schemas, lock.subjects, `distribution "${lock.distributionId}"`);
|
|
3013
|
+
// The revisions the loaded kernel reads (`ImageLockSchemaVersion`: 3 without
|
|
3014
|
+
// composed membership, 4 with it); a kernel before image-lock v4 reads only 3.
|
|
3015
|
+
if (schemas.ImageLockSchemaVersion === undefined) {assertLegacyLockHasNoComposedSubject(lock);}
|
|
3016
|
+
const readableVersions =
|
|
3017
|
+
schemas.ImageLockSchemaVersion === undefined
|
|
3018
|
+
? [PRE_V14_IMAGE_LOCK_SCHEMA_VERSION]
|
|
3019
|
+
: Object.values(schemas.ImageLockSchemaVersion).filter((version) => Number.isInteger(version));
|
|
2921
3020
|
if (
|
|
2922
|
-
lock.schemaVersion
|
|
3021
|
+
!readableVersions.includes(lock.schemaVersion) ||
|
|
2923
3022
|
lock.sourceHashAlgorithm !== schemas.ImageSourceHashAlgorithm.ImageInputV2
|
|
2924
3023
|
) {
|
|
2925
3024
|
throw new Error(
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
* `operationRequirements`); the kernel's `admitOperationGrants` admits or
|
|
6
6
|
* refuses every requirement, and a refusal is never a warning. The resolve
|
|
7
7
|
* and the seal both call {@link assertOperationGrantsAdmitted}, so a lock with
|
|
8
|
-
* a refused requirement is neither written nor sealed.
|
|
8
|
+
* a refused requirement is neither written nor sealed. The installed-operation
|
|
9
|
+
* ceiling (`requestsInstalledOperations`, carried as `installedOperationRequests`)
|
|
10
|
+
* and the edition's store-workload ceiling are admitted the same way.
|
|
9
11
|
*/
|
|
10
12
|
|
|
11
13
|
/** Thrown when one or more declared operation requirements are not admitted. */
|
|
@@ -83,3 +85,65 @@ export function assertOrganizationSweepsAdmitted(schemas, lock) {
|
|
|
83
85
|
}
|
|
84
86
|
return grants;
|
|
85
87
|
}
|
|
88
|
+
|
|
89
|
+
/** Thrown when one or more requested installed operations are not admitted into the ceiling. */
|
|
90
|
+
export class InstalledOperationCeilingAdmissionError extends Error {
|
|
91
|
+
/**
|
|
92
|
+
* @param {string} distributionId
|
|
93
|
+
* @param {ReadonlyArray<{code: string, biome: string, component: string, operation: string, message: string}>} refusals
|
|
94
|
+
*/
|
|
95
|
+
constructor(distributionId, refusals) {
|
|
96
|
+
super(
|
|
97
|
+
`distribution "${distributionId}" declares installed operation requests that are not admitted:\n` +
|
|
98
|
+
refusals.map((refusal) => `[${refusal.code}] ${refusal.message}`).join('\n'),
|
|
99
|
+
);
|
|
100
|
+
this.name = 'InstalledOperationCeilingAdmissionError';
|
|
101
|
+
this.distributionId = distributionId;
|
|
102
|
+
this.refusals = refusals;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Admit every installed-operation request of `lock` through the kernel
|
|
108
|
+
* (`admitInstalledOperationCeiling`), or throw
|
|
109
|
+
* {@link InstalledOperationCeilingAdmissionError} listing every refusal.
|
|
110
|
+
* Returns the admitted ceiling rows.
|
|
111
|
+
*/
|
|
112
|
+
export function assertInstalledOperationCeilingAdmitted(schemas, lock) {
|
|
113
|
+
const { ceilings, refusals } = schemas.admitInstalledOperationCeiling(lock);
|
|
114
|
+
if (refusals.length > 0) {
|
|
115
|
+
throw new InstalledOperationCeilingAdmissionError(lock.distributionId, refusals);
|
|
116
|
+
}
|
|
117
|
+
return ceilings;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Thrown when one or more domains of the edition's store-workload ceiling are not admitted. */
|
|
121
|
+
export class StoreWorkloadCeilingAdmissionError extends Error {
|
|
122
|
+
/**
|
|
123
|
+
* @param {string} distributionId
|
|
124
|
+
* @param {ReadonlyArray<{code: string, domain: string, message: string}>} refusals
|
|
125
|
+
*/
|
|
126
|
+
constructor(distributionId, refusals) {
|
|
127
|
+
super(
|
|
128
|
+
`distribution "${distributionId}" declares a store-workload ceiling that is not admitted:\n` +
|
|
129
|
+
refusals.map((refusal) => `[${refusal.code}] ${refusal.message}`).join('\n'),
|
|
130
|
+
);
|
|
131
|
+
this.name = 'StoreWorkloadCeilingAdmissionError';
|
|
132
|
+
this.distributionId = distributionId;
|
|
133
|
+
this.refusals = refusals;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Admit the store-workload ceiling of `lock` through the kernel
|
|
139
|
+
* (`admitStoreWorkloadCeiling`), or throw
|
|
140
|
+
* {@link StoreWorkloadCeilingAdmissionError} listing every refusal. Returns the
|
|
141
|
+
* resolved ceiling.
|
|
142
|
+
*/
|
|
143
|
+
export function assertStoreWorkloadCeilingAdmitted(schemas, lock) {
|
|
144
|
+
const { ceiling, refusals } = schemas.admitStoreWorkloadCeiling(lock);
|
|
145
|
+
if (refusals.length > 0) {
|
|
146
|
+
throw new StoreWorkloadCeilingAdmissionError(lock.distributionId, refusals);
|
|
147
|
+
}
|
|
148
|
+
return ceiling;
|
|
149
|
+
}
|
|
@@ -81,6 +81,18 @@ export function componentOptionalOperationRequirements(component, where) {
|
|
|
81
81
|
return declaredPrivilegeList(component, where, 'optionalOperations', OperationIdSchema, 'operation id');
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
/**
|
|
85
|
+
* The operations one workload component asks to exercise for the organization
|
|
86
|
+
* that installs its biome (`requestsInstalledOperations`), graded by the same
|
|
87
|
+
* operation-id grammar. Declared INTENT only; the lock admits the ceiling of
|
|
88
|
+
* the request (`admitInstalledOperationCeiling`), and an installed operation is
|
|
89
|
+
* never a deployment grant.
|
|
90
|
+
*/
|
|
91
|
+
export function componentInstalledOperationRequests(component, where) {
|
|
92
|
+
const { OperationIdSchema } = loadKernelPrivilegeGrammars();
|
|
93
|
+
return declaredPrivilegeList(component, where, 'requestsInstalledOperations', OperationIdSchema, 'operation id');
|
|
94
|
+
}
|
|
95
|
+
|
|
84
96
|
/**
|
|
85
97
|
* The organization sweeps one workload component declares it may hold
|
|
86
98
|
* (`requiresOrganizationSweeps`), each a member of the kernel's closed
|
|
@@ -96,7 +108,12 @@ export function componentOrganizationSweepRequirements(component, where) {
|
|
|
96
108
|
* The privilege declarations a component may carry only if it is a workload
|
|
97
109
|
* principal in the lock.
|
|
98
110
|
*/
|
|
99
|
-
export const PRINCIPAL_PRIVILEGE_FIELDS = Object.freeze([
|
|
111
|
+
export const PRINCIPAL_PRIVILEGE_FIELDS = Object.freeze([
|
|
112
|
+
'requiresOperations',
|
|
113
|
+
'optionalOperations',
|
|
114
|
+
'requestsInstalledOperations',
|
|
115
|
+
'requiresOrganizationSweeps',
|
|
116
|
+
]);
|
|
100
117
|
|
|
101
118
|
/**
|
|
102
119
|
* Thrown when a component that has no workload principal in the lock declares
|
|
@@ -37,7 +37,7 @@ import { assertRosteredChartSetLock } from './edition-chart-set.mjs';
|
|
|
37
37
|
import { EditionLockFile } from './edition-constants.mjs';
|
|
38
38
|
import { editionsRoot } from './edition-workspace.mjs';
|
|
39
39
|
import { adoptCatalogueKernel } from './kernel-package.mjs';
|
|
40
|
-
import { compareStrings, serializeJson, validateImmutableImageLock } from './lib.mjs';
|
|
40
|
+
import { compareStrings, imageLockWriteSchemaVersion, serializeJson, validateImmutableImageLock } from './lib.mjs';
|
|
41
41
|
import { pinnedSource, snapshotPinSource } from './pin-source.mjs';
|
|
42
42
|
import { composeAuthorizationBase, composeManifest, resolveDistributionWithIndex, UnknownSelectionError } from './resolve-distribution.mjs';
|
|
43
43
|
import { resolveImageRows } from './resolve-images.mjs';
|
|
@@ -238,13 +238,14 @@ export async function resolveCustomerImages({ id, catalogue, lock, composed, sch
|
|
|
238
238
|
const seen = new Set(vendorSubjects.map(subjectKey));
|
|
239
239
|
const clash = own.subjects.find((subject) => seen.has(subjectKey(subject)));
|
|
240
240
|
if (clash !== undefined) {throw new Error(`the customer's own image ${clash.kind}:${clash.id} collides with a catalogue image of the same identity`);}
|
|
241
|
+
const mergedSubjects = [...vendorSubjects, ...own.subjects].sort(compareSubjects);
|
|
241
242
|
const merged = validateImmutableImageLock(
|
|
242
243
|
schemas.parseImageLock({
|
|
243
|
-
schemaVersion:
|
|
244
|
+
schemaVersion: imageLockWriteSchemaVersion(schemas, mergedSubjects, `distribution "${id}"`),
|
|
244
245
|
sourceHashAlgorithm: catalogue.images.sourceHashAlgorithm,
|
|
245
246
|
distributionId: id,
|
|
246
247
|
contractAuthority: catalogue.contractAuthority,
|
|
247
|
-
subjects:
|
|
248
|
+
subjects: mergedSubjects,
|
|
248
249
|
platformServiceBindings: [...vendor.platformServiceBindings, ...own.platformServiceBindings].sort((a, b) => compareStrings(a.platformService, b.platformService)),
|
|
249
250
|
webBiomes: [...vendor.webBiomes, ...own.webBiomes].sort((a, b) => compareStrings(a.biomeId, b.biomeId)),
|
|
250
251
|
}),
|
|
@@ -95,8 +95,10 @@ import {
|
|
|
95
95
|
validateServiceDependencyOwnerParity,
|
|
96
96
|
} from './lib.mjs';
|
|
97
97
|
import {
|
|
98
|
+
assertInstalledOperationCeilingAdmitted,
|
|
98
99
|
assertOperationGrantsAdmitted,
|
|
99
100
|
assertOrganizationSweepsAdmitted,
|
|
101
|
+
assertStoreWorkloadCeilingAdmitted,
|
|
100
102
|
} from './operation-grant-admission.mjs';
|
|
101
103
|
import { PUBLISHED_PIN_SOURCE, pinnedSource } from './pin-source.mjs';
|
|
102
104
|
import { verifiedPlatformServiceIndex } from './platform-service-ownership.mjs';
|
|
@@ -307,6 +309,7 @@ export function composeManifest(manifest, pinSource = PUBLISHED_PIN_SOURCE) {
|
|
|
307
309
|
// Visibility is a RESTRICTION; the caller (which holds the kernel enum)
|
|
308
310
|
// reduces the declarations.
|
|
309
311
|
declaredVisibilities: manifest.visibility === undefined ? [] : [manifest.visibility],
|
|
312
|
+
storeWorkloadCeiling: manifest.storeWorkloadCeiling,
|
|
310
313
|
declaredSourceNames: new Set(sources.map((source) => source.name)),
|
|
311
314
|
declaredSources: sources.map((source) => pinnedSource(source, pinSource)),
|
|
312
315
|
owner: manifest.owner,
|
|
@@ -1159,6 +1162,7 @@ export function resolveDistributionFromInputs(
|
|
|
1159
1162
|
trustPolicy,
|
|
1160
1163
|
trustCeiling,
|
|
1161
1164
|
declaredVisibilities,
|
|
1165
|
+
storeWorkloadCeiling,
|
|
1162
1166
|
owner,
|
|
1163
1167
|
leaf,
|
|
1164
1168
|
// Absent on a selector-model composition — including every `composed` a
|
|
@@ -2047,6 +2051,13 @@ export function resolveDistributionFromInputs(
|
|
|
2047
2051
|
Object.keys(e.optionalOperationRequirements).length > 0
|
|
2048
2052
|
? { optionalOperationRequirements: e.optionalOperationRequirements }
|
|
2049
2053
|
: {}),
|
|
2054
|
+
// The operations each workload component asks to exercise for the
|
|
2055
|
+
// installing organization, forwarded verbatim; the lock admits their
|
|
2056
|
+
// ceiling (`admitInstalledOperationCeiling`).
|
|
2057
|
+
...(e.installedOperationRequests !== undefined &&
|
|
2058
|
+
Object.keys(e.installedOperationRequests).length > 0
|
|
2059
|
+
? { installedOperationRequests: e.installedOperationRequests }
|
|
2060
|
+
: {}),
|
|
2050
2061
|
// The organization sweeps each workload component may hold, forwarded
|
|
2051
2062
|
// verbatim; the seal admits them (`admitOrganizationSweeps`).
|
|
2052
2063
|
...(e.organizationSweepRequirements !== undefined &&
|
|
@@ -2202,6 +2213,9 @@ export function resolveDistributionFromInputs(
|
|
|
2202
2213
|
// last for the same reason: an edition without it carries exactly the bytes it
|
|
2203
2214
|
// always did. Not in `inputHash`; the kernel lock schema validates it.
|
|
2204
2215
|
if (composed?.storePublisherTrust !== undefined) {lock.storePublisherTrust = composed.storePublisherTrust;}
|
|
2216
|
+
// The edition's store-workload ceiling — written ONLY when the edition
|
|
2217
|
+
// declares one (the kernel refuses an empty list), carried unchanged.
|
|
2218
|
+
if (storeWorkloadCeiling !== undefined) {lock.storeWorkloadCeiling = storeWorkloadCeiling;}
|
|
2205
2219
|
|
|
2206
2220
|
// (7) Re-derive the ownership index through the KERNEL so the table this
|
|
2207
2221
|
// resolver emits is validated by the exact code every consumer will read
|
|
@@ -2221,6 +2235,8 @@ export function resolveDistributionFromInputs(
|
|
|
2221
2235
|
// never fails (the resolve and `edition check` both run through here).
|
|
2222
2236
|
assertOperationGrantsAdmitted(schemas, parsed, { report: (line) => console.log(`[operations] ${line}`) });
|
|
2223
2237
|
assertOrganizationSweepsAdmitted(schemas, parsed);
|
|
2238
|
+
assertInstalledOperationCeilingAdmitted(schemas, parsed);
|
|
2239
|
+
assertStoreWorkloadCeilingAdmitted(schemas, parsed);
|
|
2224
2240
|
return validateImmutableDistributionLock(lock);
|
|
2225
2241
|
}
|
|
2226
2242
|
|