@xemahq/xema 0.49.0 → 0.50.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.
@@ -64,7 +64,7 @@ import {
64
64
  validateServiceDependencyOwnerParity,
65
65
  } from './lib.mjs';
66
66
  import { commandFor } from './remedy-command.mjs';
67
- import { resolveDistributionWithIndex } from './resolve-distribution.mjs';
67
+ import { assertReleaseGitSourcesDeclareNoBundle, resolveDistributionWithIndex } from './resolve-distribution.mjs';
68
68
  import {
69
69
  resolveDistributionDeclaredSources,
70
70
  resolveDistributionRequiredPlatforms,
@@ -160,6 +160,7 @@ export async function checkDistributionLock(id) {
160
160
  const lockPath = join(editionsRoot(), id, 'distribution.lock.json');
161
161
  try {
162
162
  const { lock: fresh, index } = await resolveDistributionWithIndex(id);
163
+ assertReleaseGitSourcesDeclareNoBundle(fresh, index);
163
164
  const verdict = await compareCommittedLock(lockPath, fresh);
164
165
  if (verdict === LockCheck.MISSING) {
165
166
  console.error(
@@ -3,7 +3,8 @@
3
3
  // Every `bundleSource: {kind: 'oci'}` row of a lock (a biome's, or one of its host modules') states WHO signs its bundle:
4
4
  // `bundleSigner: {policyId, signer}`, the kernel's `lockedBundleSignerOf` projection of ONE row of the data root's evidence
5
5
  // trust policy. The row is selected, never defaulted:
6
- // - a third-party row by its pin (`third-party-source-pins.json`), whose `bundle.signerPolicyId` names an
6
+ // - a git-sourced row (one with a lock `source`) carries no OCI bundle at all, and a lock stating one is refused;
7
+ // - a third-party row (no `source`, `isThirdPartyOciRow`) by its pin (`third-party-source-pins.json`), whose `bundle.signerPolicyId` names an
7
8
  // `oidc-certificate` row held to the checks `thirdPartySignerRow` makes;
8
9
  // - a first-party row by the data root's `firstPartyBundleSigner.policyId`, a `public-key` row whose key is bound to
9
10
  // no other row.
@@ -20,7 +21,7 @@ import { dataRoot, kernelDistPath, kernelPackageDir } from './edition-workspace.
20
21
  import { loadPinnedDeclaredTrustPolicy } from './evidence-trust-pin.mjs';
21
22
  import { loadTrustedThirdPartySourcePins } from './third-party-pins-trusted.mjs';
22
23
  import { thirdPartySignerRow } from './third-party-source-evidence.mjs';
23
- import { OCI_BUNDLE_SOURCE_KIND, isThirdPartyOciEntry } from './third-party-subjects.mjs';
24
+ import { OCI_BUNDLE_SOURCE_KIND, isThirdPartyOciRow } from './third-party-subjects.mjs';
24
25
 
25
26
  const requireFromHere = createRequire(import.meta.url);
26
27
 
@@ -47,6 +48,10 @@ export class BundleSignerKeyBoundElsewhereError extends BundleSignerError {}
47
48
  export class BundleSignerPolicyNotSelectedError extends BundleSignerError {}
48
49
  /** An OCI row of a lock states no `bundleSigner`. */
49
50
  export class BundleSignerMissingError extends BundleSignerError {}
51
+ /** A lock row drawn from a git `source` states an OCI `bundleSource`: a git-sourced biome is built from that source, never pulled as a bundle. */
52
+ export class BundleSourceOnGitRowError extends BundleSignerError {}
53
+ /** A resolve found an OCI host-module bundle on a git-sourced biome: such a bundle reaches an edition only through an `oci` source. */
54
+ export class GitSourcedHostModuleBundleError extends BundleSignerError {}
50
55
  /** The loaded kernel does not export what the bundle signer plane needs. */
51
56
  export class BundleSignerKernelUnsupportedError extends BundleSignerError {}
52
57
 
@@ -71,6 +76,16 @@ export function loadBundleSignerKernel() {
71
76
  const describeSubject = ({ biomeId, componentKey }) =>
72
77
  componentKey === undefined ? `biome "${biomeId}"` : `host module "${componentKey}" of biome "${biomeId}"`;
73
78
 
79
+ /** A git-sourced row carries no OCI bundle (resolve omits it), so no signer is selected for one; a lock stating one is refused. */
80
+ function assertNotGitSourced(subject) {
81
+ if (subject.source !== undefined) {
82
+ throw new BundleSourceOnGitRowError(
83
+ `${describeSubject(subject)} is drawn from git source "${subject.source?.name ?? subject.source}" and states an OCI bundleSource; a git-sourced biome carries none ` +
84
+ '(a third-party biome is an OCI bundle only as an `oci` source with a third-party pin)',
85
+ );
86
+ }
87
+ }
88
+
74
89
  const rowsOf = (policy, policyId) => policy.policies.filter((row) => row.policyId === policyId);
75
90
 
76
91
  /**
@@ -93,7 +108,7 @@ export function createBundleSignerSelection({ policy, thirdPartyPins, firstParty
93
108
  function requireKind(subject, row, kind) {
94
109
  if (row.kind !== kind) {
95
110
  throw new BundleSignerKindMismatchError(
96
- `${describeSubject(subject)}: trust policy row "${row.policyId}" is of kind ${JSON.stringify(row.kind)}, but a ${isThirdPartyOciEntry(subject) ? 'third-party' : 'first-party'} bundle is signed by a "${kind}" row`,
111
+ `${describeSubject(subject)}: trust policy row "${row.policyId}" is of kind ${JSON.stringify(row.kind)}, but a ${isThirdPartyOciRow(subject) ? 'third-party' : 'first-party'} bundle is signed by a "${kind}" row`,
97
112
  );
98
113
  }
99
114
  }
@@ -137,8 +152,9 @@ export function createBundleSignerSelection({ policy, thirdPartyPins, firstParty
137
152
  }
138
153
 
139
154
  function selectedRow(subject) {
155
+ assertNotGitSourced(subject);
140
156
  requirePolicy(subject);
141
- return isThirdPartyOciEntry(subject) ? thirdPartyRow(subject) : firstPartyRow(subject);
157
+ return isThirdPartyOciRow(subject) ? thirdPartyRow(subject) : firstPartyRow(subject);
142
158
  }
143
159
 
144
160
  const signerFor = (subject) => {
@@ -199,12 +215,12 @@ const isOci = (row) => row.bundleSource?.kind === OCI_BUNDLE_SOURCE_KIND;
199
215
  /** Every OCI subject of a lock: each biome and each host module whose `bundleSource` is OCI, with the biome's origin and repo. */
200
216
  export function lockBundleSubjects(lock) {
201
217
  return lock.biomes.flatMap((biome) => [
202
- ...(isOci(biome) ? [{ row: biome, subject: { biomeId: biome.id, origin: biome.origin, repo: biome.repo, bundleSource: biome.bundleSource } }] : []),
218
+ ...(isOci(biome) ? [{ row: biome, subject: { biomeId: biome.id, origin: biome.origin, repo: biome.repo, source: biome.source, bundleSource: biome.bundleSource } }] : []),
203
219
  ...(biome.hostModules ?? [])
204
220
  .filter(isOci)
205
221
  .map((hostModule) => ({
206
222
  row: hostModule,
207
- subject: { biomeId: biome.id, componentKey: hostModule.componentKey, origin: biome.origin, repo: biome.repo, bundleSource: hostModule.bundleSource },
223
+ subject: { biomeId: biome.id, componentKey: hostModule.componentKey, origin: biome.origin, repo: biome.repo, source: biome.source, bundleSource: hostModule.bundleSource },
208
224
  })),
209
225
  ]);
210
226
  }
@@ -219,6 +235,7 @@ export function assertLockBundleSigners({ lock, selection, schemas }) {
219
235
  if (subjects.length === 0) {return;}
220
236
  const chosen = selection ?? loadBundleSignerSelection({ dataRoot: dataRoot(), schemas });
221
237
  for (const { row, subject } of subjects) {
238
+ assertNotGitSourced(subject);
222
239
  chosen.assertSealed(subject, row.bundleSigner);
223
240
  }
224
241
  }
@@ -95,7 +95,7 @@ import {
95
95
  validateImmutableDistributionLock,
96
96
  validateServiceDependencyOwnerParity,
97
97
  } from './lib.mjs';
98
- import { BundleSignerPolicyAbsentError, loadBundleSignerSelection } from './locked-bundle-signer.mjs';
98
+ import { BundleSignerPolicyAbsentError, GitSourcedHostModuleBundleError, loadBundleSignerSelection } from './locked-bundle-signer.mjs';
99
99
  import {
100
100
  assertInstalledOperationCeilingAdmitted,
101
101
  assertOperationGrantsAdmitted,
@@ -1805,11 +1805,29 @@ export function resolveDistributionFromInputs(
1805
1805
  }
1806
1806
  return composed.bundleSigners.signerFor(subject);
1807
1807
  };
1808
- const bundleSignerFor = (e) => (e.bundleSource?.kind === 'oci' ? { bundleSigner: bundleSignerOf(e, e.bundleSource) } : {});
1809
- const hostModulesOf = (e) =>
1810
- (Array.isArray(e.hostModules) ? e.hostModules : []).map((hostModule) =>
1811
- hostModule.bundleSource?.kind === 'oci' ? { ...hostModule, bundleSigner: bundleSignerOf(e, hostModule.bundleSource, hostModule.componentKey) } : hostModule,
1812
- );
1808
+ // A row drawn from a GIT source carries no OCI bundle (biome-level; an OCI host-module bundle is refused below), hence no bundleSigner: it is built
1809
+ // from the pinned source, and the manifest's `bundleSource` names a release artifact not built from this commit.
1810
+ // Omitted here, at the copy point. (An entry of a declared `oci` source has a stamp but no record: not git.)
1811
+ const isGitSourced = (e) => e.source !== undefined && sourceRecords.has(e.source);
1812
+ const bundleSourceOf = (e) => (e.bundleSource === undefined || isGitSourced(e) ? {} : { bundleSource: e.bundleSource });
1813
+ const bundleSignerFor = (e) => (e.bundleSource?.kind === 'oci' && !isGitSourced(e) ? { bundleSigner: bundleSignerOf(e, e.bundleSource) } : {});
1814
+ const hostModuleOf = (e, hostModule) => {
1815
+ if (isGitSourced(e)) {
1816
+ // The lock schema requires a host module's bundleSource, so an OCI one cannot be omitted: it is refused, in every
1817
+ // channel. A non-OCI one (npm, ...) is untouched.
1818
+ if (hostModule.bundleSource?.kind === 'oci') {
1819
+ throw new GitSourcedHostModuleBundleError(
1820
+ `host module "${hostModule.componentKey}" of biome "${e.id}" (git source "${e.source}") declares an OCI bundleSource (${hostModule.bundleSource.ociRef}); ` +
1821
+ 'such a host module bundle reaches an edition only through an `oci` source with a third-party source pin',
1822
+ );
1823
+ }
1824
+ return hostModule;
1825
+ }
1826
+ return hostModule.bundleSource?.kind === 'oci'
1827
+ ? { ...hostModule, bundleSigner: bundleSignerOf(e, hostModule.bundleSource, hostModule.componentKey) }
1828
+ : hostModule;
1829
+ };
1830
+ const hostModulesOf = (e) => (Array.isArray(e.hostModules) ? e.hostModules : []).map((hostModule) => hostModuleOf(e, hostModule));
1813
1831
 
1814
1832
  // (5d) Dispatched runtime components — every `runtime-image` edge of a
1815
1833
  // shipped biome must resolve to a runtime component of a biome in the index.
@@ -1994,7 +2012,7 @@ export function resolveDistributionFromInputs(
1994
2012
  // unconditional key would move the bytes of every committed lock for a
1995
2013
  // fact none of their entries carries. A lock in which SOME biome carries
1996
2014
  // it still is the one v5 grammar once sealed.
1997
- ...(e.bundleSource === undefined ? {} : { bundleSource: e.bundleSource }),
2015
+ ...bundleSourceOf(e),
1998
2016
  ...bundleSignerFor(e),
1999
2017
  serviceDependencies: Array.isArray(e.serviceDependencies)
2000
2018
  ? e.serviceDependencies
@@ -2401,6 +2419,29 @@ export async function resolveDistributionWithIndex(id, { materialize, inputs, re
2401
2419
  return { lock, index };
2402
2420
  }
2403
2421
 
2422
+ /**
2423
+ * A release carries a third-party biome only as an `oci` source with a third-party pin. A git-sourced third-party biome
2424
+ * whose manifest declares an OCI bundleSource (biome-level or host module) names a main-release artifact that is not
2425
+ * built from the pinned commit, so a release resolve refuses it. `lock` is the resolved lock (a git row carries
2426
+ * `source`), `index` the index it was resolved from (which still holds the manifest's bundleSource).
2427
+ */
2428
+ export function assertReleaseGitSourcesDeclareNoBundle(lock, index) {
2429
+ const byId = new Map(index.map((entry) => [entry.id, entry]));
2430
+ for (const biome of lock.biomes) {
2431
+ if (biome.source === undefined) {continue;}
2432
+ const entry = byId.get(biome.id);
2433
+ const declared = [entry?.bundleSource, ...(entry?.hostModules ?? []).map((hostModule) => hostModule.bundleSource)].find(
2434
+ (bundleSource) => bundleSource?.kind === 'oci',
2435
+ );
2436
+ if (declared !== undefined) {
2437
+ throw new Error(
2438
+ `biome "${biome.id}" is drawn from git source "${biome.source.name}" and declares an OCI bundleSource (${declared.ociRef}); ` +
2439
+ 'a release does not carry it: declare the biome as an `oci` source with a third-party source pin instead',
2440
+ );
2441
+ }
2442
+ }
2443
+ }
2444
+
2404
2445
  /**
2405
2446
  * Resolve and persist one distribution lock. The CLI and the plane refresh use
2406
2447
  * this exact writer so an in-process refresh cannot drift from the standalone
@@ -2410,6 +2451,7 @@ export async function writeResolvedDistribution(id, { log = true, materialize, f
2410
2451
  const { lock, index } = await resolveDistributionWithIndex(id, { materialize });
2411
2452
  await assertAdapterBundlesMatchHost({ lock, hostRow: adapterHostRow(index), fetchManifest });
2412
2453
  const outPath = join(editionsRoot(), id, 'distribution.lock.json');
2454
+ assertReleaseGitSourcesDeclareNoBundle(lock, index);
2413
2455
  assertReleaseContent(lock, `the distribution lock for "${id}"`);
2414
2456
  assertReleaseEditionTree(join(editionsRoot(), id));
2415
2457
  mkdirSync(join(editionsRoot(), id), { recursive: true });
@@ -32,7 +32,8 @@ export const isThirdPartyOciEntry = (entry) => hasOciBundleSource(entry) && isTh
32
32
  /**
33
33
  * Refuse an OCI `bundleSource` on a row whose origin is not third-party: only a third-party biome is pulled from an OCI
34
34
  * bundle by digest, so the vendor's origin on such a row would place an unevidenced bundle in the vendor's trust domain.
35
- * A row drawn from a git `source` is held by the source walk and is not this key.
35
+ * A row drawn from a git `source` is not this key and carries no OCI `bundleSource` at all: resolve omits the manifest's
36
+ * (it names a release artifact not built from the pinned commit), and a lock stating one is refused (`locked-bundle-signer.mjs`).
36
37
  */
37
38
  export function assertOciBundleSourceOrigin(row, label) {
38
39
  if (row.source === undefined && hasOciBundleSource(row) && !isThirdPartyOrigin(row)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xemahq/xema",
3
- "version": "0.49.0",
3
+ "version": "0.50.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/xema-dev/xema-cli.git",
@@ -92,6 +92,6 @@
92
92
  "typecheck": "tsc --noEmit"
93
93
  },
94
94
  "xemaRelease": {
95
- "sourceDigest": "sha256:b0bda6e99522eaf8d1c8c8ad73de127d4c28dd617ca90a3563cbb150a0a980f8"
95
+ "sourceDigest": "sha256:b773e64002a4791ce49ea6b379942d02a68268b8cc6000f2d7f9fe6886732cb4"
96
96
  }
97
97
  }