@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/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
- `${edge.target.biomeId}\0${edge.target.componentKey}`,
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 = `${edge.biomeId}\0${edge.componentKey}`;
2887
+ const edgeKey = dispatchedEdgeIdentity(projectDispatchedTarget(biome.id, edge));
2799
2888
  if (dispatched.has(edgeKey)) {
2800
2889
  throw new Error(
2801
- `biome "${biome.id}" dispatches runtime component "${edge.componentKey}" of biome "${edge.biomeId}" more than once`,
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 !== 3 ||
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(['requiresOperations', 'optionalOperations', 'requiresOrganizationSweeps']);
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: 3,
244
+ schemaVersion: imageLockWriteSchemaVersion(schemas, mergedSubjects, `distribution "${id}"`),
244
245
  sourceHashAlgorithm: catalogue.images.sourceHashAlgorithm,
245
246
  distributionId: id,
246
247
  contractAuthority: catalogue.contractAuthority,
247
- subjects: [...vendorSubjects, ...own.subjects].sort(compareSubjects),
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