@xemahq/xema 0.38.0 → 0.40.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.
Files changed (63) hide show
  1. package/README.md +1 -1
  2. package/dist/assets/distribution-assets.manifest.json +2 -70
  3. package/dist/commands/biome-dev.js +2 -3
  4. package/dist/commands/biome-dev.js.map +1 -1
  5. package/dist/commands/dev.js +2 -3
  6. package/dist/commands/dev.js.map +1 -1
  7. package/dist/commands/doctor.d.ts.map +1 -1
  8. package/dist/commands/doctor.js +3 -2
  9. package/dist/commands/doctor.js.map +1 -1
  10. package/dist/commands/init.js +21 -9
  11. package/dist/commands/init.js.map +1 -1
  12. package/dist/commands/installation.d.ts.map +1 -1
  13. package/dist/commands/installation.js +7 -5
  14. package/dist/commands/installation.js.map +1 -1
  15. package/dist/commands/license.d.ts.map +1 -1
  16. package/dist/commands/license.js +2 -1
  17. package/dist/commands/license.js.map +1 -1
  18. package/dist/commands/serve.js +1 -1
  19. package/dist/commands/serve.js.map +1 -1
  20. package/dist/commands/up.d.ts.map +1 -1
  21. package/dist/commands/up.js +3 -5
  22. package/dist/commands/up.js.map +1 -1
  23. package/dist/edition/verify-image-evidence.bundle.mjs +14 -2
  24. package/dist/edition/verify-image-evidence.bundle.provenance.json +41 -41
  25. package/dist/runtime/config/init-presets.d.ts.map +1 -1
  26. package/dist/runtime/config/init-presets.js +3 -5
  27. package/dist/runtime/config/init-presets.js.map +1 -1
  28. package/dist/runtime/config/loader.d.ts +2 -2
  29. package/dist/runtime/config/loader.d.ts.map +1 -1
  30. package/dist/runtime/config/loader.js +1 -1
  31. package/dist/runtime/config/loader.js.map +1 -1
  32. package/dist/runtime/config/schema.d.ts +149 -1
  33. package/dist/runtime/config/schema.d.ts.map +1 -1
  34. package/dist/runtime/config/schema.js +17 -4
  35. package/dist/runtime/config/schema.js.map +1 -1
  36. package/dist/runtime/installation/setup-profile.d.ts +1 -1
  37. package/dist/runtime/installation/setup-profile.d.ts.map +1 -1
  38. package/dist/runtime/installation/setup-profile.js +1 -1
  39. package/dist/runtime/installation/setup-profile.js.map +1 -1
  40. package/edition/biome-content.mjs +35 -6
  41. package/edition/build-platform-service-index.mjs +1 -1
  42. package/edition/develop-edition-resolve.mjs +58 -20
  43. package/edition/develop-image-probe.mjs +117 -0
  44. package/edition/develop-seal.mjs +4 -12
  45. package/edition/dispatched-runtime-components.mjs +1 -1
  46. package/edition/dist-check.mjs +3 -2
  47. package/edition/lib.mjs +1 -1
  48. package/edition/map-with-concurrency.mjs +19 -0
  49. package/edition/oras-process.mjs +35 -0
  50. package/edition/oras.mjs +123 -94
  51. package/edition/resolve-chart-set.mjs +12 -8
  52. package/edition/resolve-images.mjs +119 -34
  53. package/edition/verify-image-platforms.mjs +28 -26
  54. package/edition/workspace-sections.mjs +22 -1
  55. package/package.json +2 -2
  56. package/dist/assets/cloud/distribution.lock.json +0 -6447
  57. package/dist/assets/cloud/images.lock.json +0 -1831
  58. package/dist/assets/minimal/distribution.lock.json +0 -3297
  59. package/dist/assets/minimal/images.lock.json +0 -1189
  60. package/dist/assets/oss/distribution.lock.json +0 -6266
  61. package/dist/assets/oss/images.lock.json +0 -1750
  62. package/dist/assets/starter/distribution.lock.json +0 -4165
  63. package/dist/assets/starter/images.lock.json +0 -1454
@@ -35,6 +35,7 @@
35
35
  import { execFileSync } from 'node:child_process';
36
36
  import { writeFileSync } from 'node:fs';
37
37
  import { join, relative, sep } from 'node:path';
38
+ import { setImmediate as yieldToEventLoop } from 'node:timers/promises';
38
39
 
39
40
  import { assertBuildLedgerComplete } from './build-ledger-completeness.mjs';
40
41
  import { BUILD_LEDGER_FILE, readBuildLedger } from './build-ledger.mjs';
@@ -45,7 +46,7 @@ import {
45
46
  distributionOutputPath,
46
47
  readBoundDistributionFile,
47
48
  } from './distribution-paths.mjs';
48
- import { editionWorkspaceConfig } from './edition-config.mjs';
49
+ import { editionWorkspaceConfig, workspaceSection } from './edition-config.mjs';
49
50
  import { editionPinnedInputs } from './edition-pinned-inputs.mjs';
50
51
  import { dataRoot, editionsRoot, WorkspaceRoot, configureEditionWorkspaceFromProcess, isEntryModule, sourcePath, sourcesRoot } from './edition-workspace.mjs';
51
52
  import {
@@ -398,6 +399,14 @@ function missingArtifactDependencyMessage(lock, serviceName, binding, biomeIndex
398
399
  * `dispatchesRuntimeComponents`: the runtime components that biome launches,
399
400
  * whose images enter this roster as runtime-component rows although their
400
401
  * biomes are not rostered (see `dispatched-runtime-components.mjs`).
402
+ *
403
+ * `options.supplementalSources` is the edition workspace's
404
+ * `runtimeImageSources.supplementalSources`: the declarations the source
405
+ * index's supplemental subjects were built from, carrying each one's
406
+ * `launchedBy`. A subject launched by a biome ships, owned by that biome, in
407
+ * every edition whose roster contains it; one a platform service binds ships
408
+ * with that service. A declared subject with neither is refused by name: it
409
+ * could never enter any edition, and it used to be dropped silently.
401
410
  */
402
411
  export function resolveImageStructure(
403
412
  lock,
@@ -405,7 +414,7 @@ export function resolveImageStructure(
405
414
  enums,
406
415
  requiredPlatforms,
407
416
  declaredSources,
408
- { biomeIndex } = {},
417
+ { biomeIndex, supplementalSources } = {},
409
418
  ) {
410
419
  if (!Array.isArray(requiredPlatforms) || requiredPlatforms.length === 0) {
411
420
  throw new Error(
@@ -420,6 +429,12 @@ export function resolveImageStructure(
420
429
  'the runtime components a rostered biome dispatches, so a roster without it can omit images',
421
430
  );
422
431
  }
432
+ if (!Array.isArray(supplementalSources)) {
433
+ throw new Error(
434
+ `distribution "${lock.distributionId}" resolved its images without the supplemental source declarations — ` +
435
+ 'they say which biome launches a supplemental subject, so a roster without them can omit images',
436
+ );
437
+ }
423
438
  const subjects = new Map();
424
439
  const webBiomes = [];
425
440
  const platformServiceBindings = [];
@@ -436,6 +451,28 @@ export function resolveImageStructure(
436
451
  }
437
452
  return pin;
438
453
  };
454
+ // A supplemental subject of the runtime-image source index, built at its
455
+ // repository pin, owned by whatever brings it into this edition.
456
+ const addSupplementalSubject = (supplemental, owners) => {
457
+ const repository = requireRepositoryPin(supplemental.repo);
458
+ if (supplemental.sourceCommit !== repository.sourceCommit) {
459
+ throw new Error(
460
+ `runtime subject "${supplemental.kind}:${supplemental.id}" source commit does not match its repository pin`,
461
+ );
462
+ }
463
+ addSubject(subjects, {
464
+ kind: supplemental.kind,
465
+ id: supplemental.id,
466
+ imageRef: supplemental.imageRef,
467
+ repo: supplemental.repo,
468
+ path: supplemental.path,
469
+ sourceCommit: repository.sourceCommit,
470
+ sourceManifestHash: supplemental.sourceManifestHash,
471
+ sourceManifestPath: supplemental.sourceManifestPath,
472
+ requiredPlatforms,
473
+ owners,
474
+ });
475
+ };
439
476
 
440
477
  for (const biome of lock.biomes) {
441
478
  // WHERE THIS BIOME'S IMAGES LIVE, and WHICH COMMIT THEY ARE BUILT FROM.
@@ -674,6 +711,50 @@ export function resolveImageStructure(
674
711
  ]),
675
712
  );
676
713
 
714
+ // Supplemental subjects a BIOME launches (`launchedBy`): sealed, owned by
715
+ // that biome, in every edition whose roster contains it. The catalogue
716
+ // checks run for every edition, rostered launcher or not.
717
+ const boundSubjects = new Set(
718
+ runtimeSourceIndex.platformServiceBindings.map((binding) => bindingKey(binding)),
719
+ );
720
+ const launchers = new Map(
721
+ supplementalSources
722
+ .filter((source) => source.launchedBy !== undefined)
723
+ .map((source) => [subjectKey(source), source.launchedBy]),
724
+ );
725
+ for (const key of launchers.keys()) {
726
+ if (!supplementalSubjects.has(key)) {
727
+ throw new Error(
728
+ `supplemental source "${key.replace('\0', ':')}" declares a launcher, but the runtime image source index ` +
729
+ 'has no such supplemental subject — rebuild the index from the current declarations',
730
+ );
731
+ }
732
+ }
733
+ for (const [key, supplemental] of supplementalSubjects) {
734
+ const launcher = launchers.get(key);
735
+ if (launcher === undefined) {
736
+ if (!boundSubjects.has(key)) {
737
+ throw new Error(
738
+ `supplemental subject "${supplemental.kind}:${supplemental.id}" has neither a launchedBy nor a ` +
739
+ 'platform-service binding, so no edition could ever carry it — declare what launches it ' +
740
+ '(runtimeImageSources.supplementalSources[].launchedBy) or bind it to a platform service',
741
+ );
742
+ }
743
+ continue;
744
+ }
745
+ if (!biomeIndex.some((entry) => entry.id === launcher.biomeId && entry.source === undefined)) {
746
+ throw new Error(
747
+ `supplemental subject "${supplemental.kind}:${supplemental.id}" is launched by biome ` +
748
+ `"${launcher.biomeId}", which the first-party biome index does not carry`,
749
+ );
750
+ }
751
+ if (lock.biomes.some((biome) => biome.id === launcher.biomeId)) {
752
+ addSupplementalSubject(supplemental, [
753
+ { kind: enums.RuntimeImageOwnerKind.Biome, biomeId: launcher.biomeId },
754
+ ]);
755
+ }
756
+ }
757
+
677
758
  for (const service of lock.platformServices ?? []) {
678
759
  let binding;
679
760
  // The discriminator is the DECLARED runtime binding, never "does the lock
@@ -726,29 +807,12 @@ export function resolveImageStructure(
726
807
  const key = bindingKey(binding);
727
808
  const supplemental = supplementalSubjects.get(key);
728
809
  if (supplemental) {
729
- const repository = requireRepositoryPin(supplemental.repo);
730
- if (supplemental.sourceCommit !== repository.sourceCommit) {
731
- throw new Error(
732
- `runtime subject "${supplemental.kind}:${supplemental.id}" source commit does not match its repository pin`,
733
- );
734
- }
735
- addSubject(subjects, {
736
- kind: supplemental.kind,
737
- id: supplemental.id,
738
- imageRef: supplemental.imageRef,
739
- repo: supplemental.repo,
740
- path: supplemental.path,
741
- sourceCommit: repository.sourceCommit,
742
- sourceManifestHash: supplemental.sourceManifestHash,
743
- sourceManifestPath: supplemental.sourceManifestPath,
744
- requiredPlatforms,
745
- owners: [
746
- {
747
- kind: enums.RuntimeImageOwnerKind.PlatformService,
748
- platformService: service.name,
749
- },
750
- ],
751
- });
810
+ addSupplementalSubject(supplemental, [
811
+ {
812
+ kind: enums.RuntimeImageOwnerKind.PlatformService,
813
+ platformService: service.name,
814
+ },
815
+ ]);
752
816
  } else {
753
817
  const target = subjects.get(key);
754
818
  if (!target) {
@@ -1523,6 +1587,18 @@ function snapshotImageInputs(schemas, { inputs, lock }) {
1523
1587
  };
1524
1588
  }
1525
1589
 
1590
+ /**
1591
+ * The supplemental source declarations an image resolve reads beside
1592
+ * `runtimeSourceIndex`. An index with no supplemental subjects (a customer
1593
+ * edition's own rows) needs none, and its workspace need not declare the
1594
+ * section; any other reads the edition workspace's.
1595
+ */
1596
+ export function supplementalSourcesFor(runtimeSourceIndex) {
1597
+ return runtimeSourceIndex.supplementalSubjects.length === 0
1598
+ ? []
1599
+ : workspaceSection('runtimeImageSources', 'resolving supplemental image subjects').supplementalSources;
1600
+ }
1601
+
1526
1602
  /**
1527
1603
  * The image ROWS of one edition: its subjects (hashed at the commits the lock
1528
1604
  * pins), its platform-service bindings and its bundled web biomes, after every
@@ -1531,7 +1607,7 @@ function snapshotImageInputs(schemas, { inputs, lock }) {
1531
1607
  * of one (a customer's own rows, merged with the catalogue's) parses the lock
1532
1608
  * it completes. `resolveImages` is these rows parsed as the whole lock.
1533
1609
  */
1534
- export async function resolveImageRows(id, { inputs, lock } = {}) {
1610
+ export async function resolveImageRows(id, { inputs, lock, onSubjectHashed } = {}) {
1535
1611
  assertCanonicalDistributionId(id);
1536
1612
  if ((inputs === undefined) !== (lock === undefined)) {
1537
1613
  throw new Error('resolveImages takes the snapshot `inputs` and its distribution `lock` together, or neither');
@@ -1555,7 +1631,7 @@ export async function resolveImageRows(id, { inputs, lock } = {}) {
1555
1631
  schemas,
1556
1632
  requiredPlatforms,
1557
1633
  declaredSources,
1558
- { biomeIndex },
1634
+ { biomeIndex, supplementalSources: supplementalSourcesFor(runtimeSourceIndex) },
1559
1635
  ),
1560
1636
  schemas,
1561
1637
  requiredPlatforms,
@@ -1587,8 +1663,8 @@ export async function resolveImageRows(id, { inputs, lock } = {}) {
1587
1663
  // made that edition-independent quantity depend on the edition, which failed
1588
1664
  // in both directions at once:
1589
1665
  // • it is UNCOMPUTABLE for an edition that does not itself resolve the
1590
- // source repo — `appliance-lean` and `minimal` do not ship the xema-store
1591
- // biome, so re-resolving either one threw here and their locks could not
1666
+ // source repo — `appliance-lean` does not ship the xema-store
1667
+ // biome, so re-resolving it threw here and their locks could not
1592
1668
  // be regenerated at all, even though the image they name is the very one
1593
1669
  // the other six editions resolve fine;
1594
1670
  // • had it not thrown it would have been WORSE, silently minting a second
@@ -1629,14 +1705,15 @@ export async function resolveImageRows(id, { inputs, lock } = {}) {
1629
1705
  ...(crossRepoInputsByConsumer.get(`${subject.repo}\u0000`) ?? []),
1630
1706
  ...(crossRepoInputsByConsumer.get(`${subject.repo}\u0000${subject.id}`) ?? []),
1631
1707
  ];
1632
- const subjects = structure.subjects.map((subject) => {
1708
+ const subjects = [];
1709
+ for (const subject of structure.subjects) {
1633
1710
  const sourceCommit = commitsBefore.get(subject.repo);
1634
1711
  const repoDirectory = locate(subject.repo).dir;
1635
1712
  const servicePath = subjectServicePath(subject, sourceCommit, locate);
1636
1713
  const { sourceManifestPath: ignoredSourceManifestPath, ...lockedSubject } =
1637
1714
  subject;
1638
1715
  void ignoredSourceManifestPath;
1639
- return {
1716
+ const hashed = {
1640
1717
  ...lockedSubject,
1641
1718
  sourceCommit,
1642
1719
  contentHash: exactImageBuildInputHash(
@@ -1652,7 +1729,15 @@ export async function resolveImageRows(id, { inputs, lock } = {}) {
1652
1729
  imageInputSourceHashAlgorithm(),
1653
1730
  ),
1654
1731
  };
1655
- });
1732
+ subjects.push(hashed);
1733
+ // The hash is the last thing a registry probe waits for: hand the subject
1734
+ // over now, and let the event loop run so the probes of the subjects
1735
+ // already hashed progress while the rest hash (hashing is synchronous).
1736
+ if (onSubjectHashed !== undefined) {
1737
+ onSubjectHashed(hashed);
1738
+ await yieldToEventLoop();
1739
+ }
1740
+ }
1656
1741
  // Release lane only: a develop snapshot or customer overlay is not covered by
1657
1742
  // the committed ledger.
1658
1743
  if (inputs === undefined) {
@@ -1690,8 +1775,8 @@ export async function resolveImageRows(id, { inputs, lock } = {}) {
1690
1775
  }
1691
1776
 
1692
1777
  /** One edition's COMPLETE image lock: its rows, parsed and validated as the kernel's image lock. */
1693
- export async function resolveImages(id, { inputs, lock } = {}) {
1694
- const { subjects, platformServiceBindings, webBiomes, contractAuthority, commits } = await resolveImageRows(id, { inputs, lock });
1778
+ export async function resolveImages(id, { inputs, lock, onSubjectHashed } = {}) {
1779
+ const { subjects, platformServiceBindings, webBiomes, contractAuthority, commits } = await resolveImageRows(id, { inputs, lock, onSubjectHashed });
1695
1780
  const schemas = await loadKernelDistributionSchemas();
1696
1781
  const imageLock = validateImmutableImageLock(
1697
1782
  schemas.parseImageLock({
@@ -46,6 +46,7 @@ import {
46
46
  loadKernelDistributionSchemas,
47
47
  validateImmutableImageLock,
48
48
  } from './lib.mjs';
49
+ import { mapWithConcurrency } from './map-with-concurrency.mjs';
49
50
  import { OrasRegistry } from './oras.mjs';
50
51
  import {
51
52
  REGISTRY_CREDENTIALS_FILE,
@@ -284,18 +285,20 @@ function missingPlatformMessage({ distributionId, subject, coordinate, missing,
284
285
  );
285
286
  }
286
287
 
287
- async function mapWithConcurrency(items, limit, worker) {
288
- const results = new Array(items.length);
289
- let cursor = 0;
290
- const runners = Array.from({ length: Math.min(limit, items.length) }, async () => {
291
- while (cursor < items.length) {
292
- const index = cursor;
293
- cursor += 1;
294
- results[index] = await worker(items[index], index);
295
- }
296
- });
297
- await Promise.all(runners);
298
- return results;
288
+ /**
289
+ * One `OrasRegistry` per registry authority of `subjects`, every credential
290
+ * resolved BEFORE the first request: a run that would die on subject 300 for a
291
+ * missing token must die on subject 0 instead.
292
+ */
293
+ export function registryClientsFor(subjects, { declarations, env, fetchImpl, run }) {
294
+ const clients = new Map();
295
+ for (const subject of subjects) {
296
+ const { authority } = splitImageRef(subject.imageRef);
297
+ if (clients.has(authority)) {continue;}
298
+ const credential = resolveRegistryCredential({ authority, declarations, env });
299
+ clients.set(authority, new OrasRegistry({ credential, fetchImpl, ...(run ? { run } : {}) }));
300
+ }
301
+ return clients;
299
302
  }
300
303
 
301
304
  /**
@@ -335,30 +338,29 @@ export async function verifyDistributionPlatforms({
335
338
  // successes, so a caller can name every missing coordinate at once. It never
336
339
  // swallows one: the caller decides what each failure means.
337
340
  collectFailures = false,
341
+ // `(line) => void`: one line per verified subject, with its elapsed ms; null is silent.
342
+ progress = null,
338
343
  }) {
339
344
  const declarations = loadRegistryCredentialDeclarations(distributionsRoot);
340
345
 
341
- // Resolve every credential BEFORE the first request. A run that would die on
342
- // subject 300 for a missing token must die on subject 0 instead.
343
- const clients = new Map();
344
- for (const subject of imageLock.subjects) {
345
- const { authority } = splitImageRef(subject.imageRef);
346
- if (clients.has(authority)) {continue;}
347
- const credential = resolveRegistryCredential({ authority, declarations, env });
348
- clients.set(
349
- authority,
350
- new OrasRegistry({ credential, fetchImpl, ...(run ? { run } : {}) }),
351
- );
352
- }
346
+ const clients = registryClientsFor(imageLock.subjects, { declarations, env, fetchImpl, run });
353
347
 
354
348
  const failures = [];
355
349
  const verified = await mapWithConcurrency(
356
350
  imageLock.subjects,
357
351
  concurrency,
358
- async (subject) => {
352
+ async (subject, index) => {
353
+ const startedAt = Date.now();
359
354
  try {
360
- return await verifySubject(subject);
355
+ const verifiedSubject = await verifySubject(subject);
356
+ progress?.(
357
+ `[seal] ${index + 1}/${imageLock.subjects.length} ${subject.kind}:${subject.id} ok ${Date.now() - startedAt}ms`,
358
+ );
359
+ return verifiedSubject;
361
360
  } catch (error) {
361
+ progress?.(
362
+ `[seal] ${index + 1}/${imageLock.subjects.length} ${subject.kind}:${subject.id} FAILED ${Date.now() - startedAt}ms`,
363
+ );
362
364
  if (!collectFailures) {throw error;}
363
365
  failures.push({ subject, error });
364
366
  return undefined;
@@ -73,6 +73,24 @@ function subjectKind(value, where) {
73
73
  return value;
74
74
  }
75
75
 
76
+ /**
77
+ * What launches a supplemental subject that no platform service binds: the
78
+ * owner its image-lock row is sealed under. Closed, one member until a second
79
+ * real case exists: a BIOME, which launches the subject's image itself, so
80
+ * the subject ships in every edition whose roster contains that biome.
81
+ */
82
+ export const SupplementalLauncherKind = Object.freeze({
83
+ Biome: 'biome',
84
+ });
85
+
86
+ const LAUNCHER_KINDS = Object.values(SupplementalLauncherKind);
87
+
88
+ function supplementalLauncher(value, where) {
89
+ exactObject(value, where, ['kind', 'biomeId']);
90
+ if (!LAUNCHER_KINDS.includes(value.kind)) {fail(`${where}.kind`, `must be one of ${LAUNCHER_KINDS.join(', ')}`);}
91
+ return Object.freeze({ kind: value.kind, biomeId: text(value.biomeId, `${where}.biomeId`) });
92
+ }
93
+
76
94
  export function parseFirstPartySources(value) {
77
95
  const where = '"firstPartySources"';
78
96
  if (!isObject(value) || Object.keys(value).length === 0) {fail(where, 'must be a non-empty {repo handle: source id} object');}
@@ -138,13 +156,16 @@ export function parseRuntimeImageSources(value) {
138
156
  const where = '"runtimeImageSources"';
139
157
  exactObject(value, where, ['supplementalSources', 'crossRepoBuildInputs', 'buildStepContextDirectories']);
140
158
  const supplementalSources = list(value.supplementalSources, `${where}.supplementalSources`, (item, at) => {
141
- exactObject(item, at, ['kind', 'id', 'repo', 'path', 'sourceManifestPath']);
159
+ exactObject(item, at, ['kind', 'id', 'repo', 'path', 'sourceManifestPath'], ['launchedBy']);
142
160
  return {
143
161
  kind: subjectKind(item.kind, `${at}.kind`),
144
162
  id: text(item.id, `${at}.id`),
145
163
  repo: repoHandle(item.repo, `${at}.repo`),
146
164
  path: text(item.path, `${at}.path`),
147
165
  sourceManifestPath: text(item.sourceManifestPath, `${at}.sourceManifestPath`),
166
+ ...(Object.hasOwn(item, 'launchedBy')
167
+ ? { launchedBy: supplementalLauncher(item.launchedBy, `${at}.launchedBy`) }
168
+ : {}),
148
169
  };
149
170
  });
150
171
  uniqueBy(supplementalSources, `${where}.supplementalSources`, (item) => `${item.kind}/${item.id}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xemahq/xema",
3
- "version": "0.38.0",
3
+ "version": "0.40.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:6ceee7eaad77b410aeb26998f61349944a56481549d146a3bc42a7a325ab3890"
95
+ "sourceDigest": "sha256:e3d77e6ac0d871437aec5ec5749446091e4a65c86ce04df7900b22954ae71308"
96
96
  }
97
97
  }