@xemahq/xema 0.53.0 → 0.54.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 (47) hide show
  1. package/README.md +1 -0
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +2 -0
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/auth.d.ts +9 -0
  6. package/dist/commands/auth.d.ts.map +1 -0
  7. package/dist/commands/auth.js +66 -0
  8. package/dist/commands/auth.js.map +1 -0
  9. package/dist/commands/edition.d.ts.map +1 -1
  10. package/dist/commands/edition.js +1 -0
  11. package/dist/commands/edition.js.map +1 -1
  12. package/dist/edition/verify-image-evidence.bundle.mjs +916 -860
  13. package/dist/edition/verify-image-evidence.bundle.provenance.json +495 -489
  14. package/dist/runtime/edition/verb-map.d.ts +2 -1
  15. package/dist/runtime/edition/verb-map.d.ts.map +1 -1
  16. package/dist/runtime/edition/verb-map.js +2 -0
  17. package/dist/runtime/edition/verb-map.js.map +1 -1
  18. package/edition/README.md +54 -2
  19. package/edition/adapter-bundled-versions.mjs +2 -1
  20. package/edition/authorization-base.mjs +9 -8
  21. package/edition/biome-content.mjs +23 -29
  22. package/edition/candidate-edition.mjs +692 -0
  23. package/edition/checkout-indexed-source-repositories.mjs +6 -8
  24. package/edition/collected-refusals.mjs +59 -0
  25. package/edition/contract-authority.mjs +122 -0
  26. package/edition/cosign-certificate-constraints.mjs +18 -8
  27. package/edition/customer-edition-verbs.mjs +30 -8
  28. package/edition/develop-chart-set.mjs +7 -6
  29. package/edition/develop-edition-invariants.mjs +2 -1
  30. package/edition/develop-edition-resolve.mjs +109 -31
  31. package/edition/develop-edition.mjs +49 -8
  32. package/edition/develop-engine-image.mjs +95 -0
  33. package/edition/develop-image-probe.mjs +2 -1
  34. package/edition/develop-resolve-report.mjs +159 -0
  35. package/edition/develop-snapshot-inputs.mjs +4 -3
  36. package/edition/develop-verify-image-record.mjs +23 -4
  37. package/edition/edition-artifact.mjs +17 -7
  38. package/edition/edition-protocol.mjs +5 -2
  39. package/edition/images-from-record.mjs +362 -0
  40. package/edition/lib.mjs +1 -19
  41. package/edition/locked-bundle-signer.mjs +15 -3
  42. package/edition/operation-grant-admission.mjs +51 -9
  43. package/edition/resolve-distribution.mjs +95 -82
  44. package/edition/resolve-images.mjs +12 -5
  45. package/edition/shallow-checkout.mjs +18 -0
  46. package/edition/workspace-sections.mjs +23 -3
  47. package/package.json +7 -3
@@ -1787,11 +1787,15 @@ export function supplementalSourcesFor(runtimeSourceIndex) {
1787
1787
  * of one (a customer's own rows, merged with the catalogue's) parses the lock
1788
1788
  * it completes. `resolveImages` is these rows parsed as the whole lock.
1789
1789
  */
1790
- export async function resolveImageRows(id, { inputs, lock, onSubjectHashed } = {}) {
1790
+ export async function resolveImageRows(id, { inputs, lock, onSubjectHashed, recordedImages } = {}) {
1791
1791
  assertCanonicalDistributionId(id);
1792
1792
  if ((inputs === undefined) !== (lock === undefined)) {
1793
1793
  throw new Error('resolveImages takes the snapshot `inputs` and its distribution `lock` together, or neither');
1794
1794
  }
1795
+ // A build record (images-from-record.mjs) replaces the hashing on the snapshot lane only, and never beside a probe.
1796
+ if (recordedImages !== undefined && (inputs === undefined || onSubjectHashed !== undefined)) {
1797
+ throw new Error('resolveImages takes recorded images only on the snapshot lane, and never with onSubjectHashed');
1798
+ }
1795
1799
  const schemas = await loadKernelDistributionSchemas();
1796
1800
  const {
1797
1801
  lock: distributionLock,
@@ -1897,7 +1901,8 @@ export async function resolveImageRows(id, { inputs, lock, onSubjectHashed } = {
1897
1901
  const { sourceManifestPath: ignoredSourceManifestPath, ...lockedSubject } =
1898
1902
  subject;
1899
1903
  void ignoredSourceManifestPath;
1900
- const hashed = {
1904
+ // With a build record the content hash and source commit are the record's, carried and never recomputed.
1905
+ const hashed = recordedImages === undefined ? {
1901
1906
  ...lockedSubject,
1902
1907
  sourceCommit,
1903
1908
  contentHash: exactImageBuildInputHash(
@@ -1912,7 +1917,7 @@ export async function resolveImageRows(id, { inputs, lock, onSubjectHashed } = {
1912
1917
  },
1913
1918
  imageInputSourceHashAlgorithm(),
1914
1919
  ),
1915
- };
1920
+ } : { ...lockedSubject, ...recordedImages.take({ ...lockedSubject, sourceCommit }) };
1916
1921
  subjects.push(hashed);
1917
1922
  // The hash is the last thing a registry probe waits for: hand the subject
1918
1923
  // over now, and let the event loop run so the probes of the subjects
@@ -1922,6 +1927,8 @@ export async function resolveImageRows(id, { inputs, lock, onSubjectHashed } = {
1922
1927
  await yieldToEventLoop();
1923
1928
  }
1924
1929
  }
1930
+ // Every subject missing from, contradicted by or extra in the build record, refused at once.
1931
+ recordedImages?.settle();
1925
1932
  // Release lane only: a develop snapshot or customer overlay is not covered by
1926
1933
  // the committed ledger.
1927
1934
  if (inputs === undefined) {
@@ -1962,8 +1969,8 @@ export async function resolveImageRows(id, { inputs, lock, onSubjectHashed } = {
1962
1969
  }
1963
1970
 
1964
1971
  /** One edition's COMPLETE image lock: its rows, parsed and validated as the kernel's image lock. */
1965
- export async function resolveImages(id, { inputs, lock, onSubjectHashed } = {}) {
1966
- const { subjects, thirdPartySubjects, platformServiceBindings, webBiomes, contractAuthority, commits } = await resolveImageRows(id, { inputs, lock, onSubjectHashed });
1972
+ export async function resolveImages(id, { inputs, lock, onSubjectHashed, recordedImages } = {}) {
1973
+ const { subjects, thirdPartySubjects, platformServiceBindings, webBiomes, contractAuthority, commits } = await resolveImageRows(id, { inputs, lock, onSubjectHashed, recordedImages });
1967
1974
  const schemas = await loadKernelDistributionSchemas();
1968
1975
  const imageLock = validateImmutableImageLock(
1969
1976
  schemas.parseImageLock({
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The shallow git core every source checkout of the edition library runs: a checkout of exactly one commit
3
+ * and a depth-1 fetch of one more commit into it. `runGit(args, gitEnv)` spawns git; the caller owns the
4
+ * credential env (git-source-auth.mjs `withSourceGitEnv`) and verifies the result.
5
+ */
6
+
7
+ /** `target` becomes a detached checkout of `commit` from `url`, fetched at depth 1. */
8
+ export function shallowCheckout({ runGit, gitEnv, target, url, commit }) {
9
+ runGit(['init', '--quiet', target], gitEnv);
10
+ runGit(['-C', target, 'remote', 'add', 'origin', url], gitEnv);
11
+ shallowFetch({ runGit, gitEnv, target, commit });
12
+ runGit(['-C', target, 'checkout', '--quiet', '--detach', commit], gitEnv);
13
+ }
14
+
15
+ /** One more commit into an existing checkout, from its origin, at depth 1. */
16
+ export function shallowFetch({ runGit, gitEnv, target, commit }) {
17
+ runGit(['-C', target, 'fetch', '--quiet', '--depth=1', 'origin', commit], gitEnv);
18
+ }
@@ -106,13 +106,26 @@ export class RetiredFirstPartySourceShapeError extends Error {
106
106
  }
107
107
  }
108
108
 
109
+ /**
110
+ * Where a first-party source's images come from on the everyday lane: built by the job in the aggregator workspace,
111
+ * or a satellite producer's own signed develop digest (a source outside the workspace, such as
112
+ * submodules/xema-host-web). `--images-from` admits a record entry only from the list its source's origin names.
113
+ */
114
+ export const FirstPartyImageOrigin = Object.freeze({
115
+ Workspace: 'workspace',
116
+ Satellite: 'satellite',
117
+ });
118
+
119
+ const IMAGE_ORIGINS = Object.values(FirstPartyImageOrigin);
120
+
109
121
  /**
110
122
  * `firstPartySources`: every first-party repository, keyed by its source
111
123
  * checkout handle, with its declared source id (the identity a runtime subject
112
124
  * is named after) and its repository URL (where it lives: the develop graph
113
125
  * resolves its head there, and a release pin must name the same URL). The URL
114
126
  * is checked canonical and under `sourceRepositoryUrlPrefix` with the whole
115
- * config (edition-config.mjs).
127
+ * config (edition-config.mjs). `imageOrigin` (FirstPartyImageOrigin) is optional
128
+ * here and has no default: `--images-from` refuses a source that does not declare it.
116
129
  */
117
130
  export function parseFirstPartySources(value) {
118
131
  const where = '"firstPartySources"';
@@ -123,11 +136,18 @@ export function parseFirstPartySources(value) {
123
136
  repoHandle(repo, `${where} key`);
124
137
  if (typeof entry === 'string') {throw new RetiredFirstPartySourceShapeError(repo, entry);}
125
138
  const at = `${where}["${repo}"]`;
126
- exactObject(entry, at, ['sourceId', 'repositoryUrl']);
139
+ exactObject(entry, at, ['sourceId', 'repositoryUrl'], ['imageOrigin']);
127
140
  if (typeof entry.sourceId !== 'string' || !SOURCE_ID.test(entry.sourceId)) {
128
141
  fail(`${at}.sourceId`, `must be a lowercase kebab-case source id; got ${JSON.stringify(entry.sourceId)}`);
129
142
  }
130
- return [repo, Object.freeze({ sourceId: entry.sourceId, repositoryUrl: text(entry.repositoryUrl, `${at}.repositoryUrl`) })];
143
+ if (Object.hasOwn(entry, 'imageOrigin') && !IMAGE_ORIGINS.includes(entry.imageOrigin)) {
144
+ fail(`${at}.imageOrigin`, `must be one of ${IMAGE_ORIGINS.join(', ')}; got ${JSON.stringify(entry.imageOrigin)}`);
145
+ }
146
+ return [repo, Object.freeze({
147
+ sourceId: entry.sourceId,
148
+ repositoryUrl: text(entry.repositoryUrl, `${at}.repositoryUrl`),
149
+ ...(Object.hasOwn(entry, 'imageOrigin') ? { imageOrigin: entry.imageOrigin } : {}),
150
+ })];
131
151
  });
132
152
  uniqueBy(entries, `${where} sourceId`, ([, entry]) => entry.sourceId);
133
153
  uniqueBy(entries, `${where} repositoryUrl`, ([, entry]) => entry.repositoryUrl);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xemahq/xema",
3
- "version": "0.53.0",
3
+ "version": "0.54.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/xema-dev/xema-cli.git",
@@ -28,6 +28,10 @@
28
28
  "types": "./dist/publisher.d.ts",
29
29
  "require": "./dist/publisher.js",
30
30
  "default": "./dist/publisher.js"
31
+ },
32
+ "./candidate-edition": {
33
+ "import": "./edition/candidate-edition.mjs",
34
+ "default": "./edition/candidate-edition.mjs"
31
35
  }
32
36
  },
33
37
  "bin": {
@@ -57,7 +61,7 @@
57
61
  "@xemahq/distribution-source-hash": "^0.2.2",
58
62
  "@xemahq/dsl": "^0.28.0",
59
63
  "@xemahq/identity-client": "^0.36.0",
60
- "@xemahq/contracts": "^0.116.0",
64
+ "@xemahq/contracts": "^0.117.0",
61
65
  "@xemahq/kernel-state-runtime": "^0.8.0",
62
66
  "@xemahq/license-verifier": "^0.2.0",
63
67
  "@xemahq/release-policy": "^17.6.0",
@@ -96,6 +100,6 @@
96
100
  "typecheck": "tsc --noEmit"
97
101
  },
98
102
  "xemaRelease": {
99
- "sourceDigest": "sha256:86b6ce1a0758e2ed6ab8e9a2cd84c29c52f72582ff30c670e16e8e5982038894"
103
+ "sourceDigest": "sha256:7bcf18b69778e4e380b22bf9c301cf08ea6234a10c98ec06940abdde0f8920c1"
100
104
  }
101
105
  }