@xemahq/xema 0.28.0 → 0.29.1

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 (55) hide show
  1. package/dist/commands/edition.d.ts.map +1 -1
  2. package/dist/commands/edition.js +8 -7
  3. package/dist/commands/edition.js.map +1 -1
  4. package/dist/edition/verify-image-evidence.bundle.mjs +88 -11
  5. package/dist/edition/verify-image-evidence.bundle.provenance.json +48 -42
  6. package/dist/runtime/edition/verb-map.d.ts +2 -1
  7. package/dist/runtime/edition/verb-map.d.ts.map +1 -1
  8. package/dist/runtime/edition/verb-map.js +2 -0
  9. package/dist/runtime/edition/verb-map.js.map +1 -1
  10. package/dist/runtime/runner/chart-set-lock.d.ts +4 -1
  11. package/dist/runtime/runner/chart-set-lock.d.ts.map +1 -1
  12. package/dist/runtime/runner/chart-set-lock.js +39 -4
  13. package/dist/runtime/runner/chart-set-lock.js.map +1 -1
  14. package/edition/README.md +69 -32
  15. package/edition/build-biome-index.mjs +1 -1
  16. package/edition/build-platform-service-index.mjs +1 -1
  17. package/edition/build-runtime-image-source-index.mjs +64 -20
  18. package/edition/catalogue-fetch.mjs +36 -9
  19. package/edition/catalogue-pin.mjs +71 -0
  20. package/edition/catalogue.mjs +11 -0
  21. package/edition/chart-set-source-commit.mjs +17 -7
  22. package/edition/checkout-indexed-source-repositories.mjs +17 -17
  23. package/edition/customer-flags.mjs +0 -1
  24. package/edition/develop-customer-edition.mjs +23 -5
  25. package/edition/develop-edition.mjs +147 -48
  26. package/edition/develop-freeze.mjs +69 -25
  27. package/edition/develop-seal.mjs +39 -25
  28. package/edition/dist-check.mjs +1 -1
  29. package/edition/edition-artifact.mjs +65 -18
  30. package/edition/edition-biome-manifests.mjs +2 -3
  31. package/edition/edition-chart-set.mjs +59 -6
  32. package/edition/edition-config.mjs +28 -2
  33. package/edition/edition-constants.mjs +10 -4
  34. package/edition/edition-contract-parse.mjs +52 -0
  35. package/edition/edition-contract.json +10 -1
  36. package/edition/edition-workspace.mjs +12 -9
  37. package/edition/evidence-trust-policy.mjs +26 -0
  38. package/edition/external-source-walk.mjs +48 -61
  39. package/edition/git-source-auth.mjs +104 -25
  40. package/edition/image-evidence.mjs +20 -10
  41. package/edition/kernel-contract-authority.mjs +23 -1
  42. package/edition/lib.mjs +13 -24
  43. package/edition/oras.mjs +32 -10
  44. package/edition/pin-source.mjs +6 -1
  45. package/edition/registry-credentials.mjs +11 -1
  46. package/edition/release-edition-artifact.mjs +3 -3
  47. package/edition/resolve-chart-set.mjs +50 -30
  48. package/edition/resolve-customer-edition.mjs +63 -7
  49. package/edition/resolve-distribution.mjs +1 -1
  50. package/edition/resolve-edition.mjs +12 -11
  51. package/edition/resolve-images.mjs +36 -15
  52. package/edition/seal-image-digests.mjs +31 -7
  53. package/edition/source-closure.mjs +1 -2
  54. package/edition/trust-policy-arming-verb.mjs +68 -0
  55. package/package.json +3 -3
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CHART_DIGEST_PATTERN = exports.CHART_SET_LOCK_SCHEMA_VERSION = exports.CHART_SET_LOCK_FILENAME = exports.CHART_SET_LOCK_ENV = void 0;
3
+ exports.CHART_DIGEST_PATTERN = exports.DEVELOP_CHART_VERSION_PATTERN = exports.DEVELOP_CHART_SHA_LENGTH = exports.DEVELOP_CHART_VERSION_PREFIX = exports.CHART_SET_LOCK_SCHEMA_VERSION = exports.CHART_SET_LOCK_FILENAME = exports.CHART_SET_LOCK_ENV = void 0;
4
4
  exports.defaultChartSetLockPath = defaultChartSetLockPath;
5
5
  exports.loadChartSetLock = loadChartSetLock;
6
6
  exports.isMissingPath = isMissingPath;
@@ -8,7 +8,10 @@ const node_fs_1 = require("node:fs");
8
8
  const appliance_context_1 = require("./appliance-context");
9
9
  exports.CHART_SET_LOCK_ENV = 'XEMA_CHART_SET_LOCK';
10
10
  exports.CHART_SET_LOCK_FILENAME = 'chart-set.lock.json';
11
- exports.CHART_SET_LOCK_SCHEMA_VERSION = 3;
11
+ exports.CHART_SET_LOCK_SCHEMA_VERSION = 4;
12
+ exports.DEVELOP_CHART_VERSION_PREFIX = '0.0.0-dev.';
13
+ exports.DEVELOP_CHART_SHA_LENGTH = 12;
14
+ exports.DEVELOP_CHART_VERSION_PATTERN = new RegExp(`^${exports.DEVELOP_CHART_VERSION_PREFIX.replaceAll('.', '\\.')}[0-9a-f]{${exports.DEVELOP_CHART_SHA_LENGTH}}$`);
12
15
  exports.CHART_DIGEST_PATTERN = /^sha256:[a-f0-9]{64}$/;
13
16
  function defaultChartSetLockPath(env = process.env) {
14
17
  const declared = env[exports.CHART_SET_LOCK_ENV];
@@ -45,8 +48,9 @@ function parseChartSetLock(parsed, path) {
45
48
  }
46
49
  if (root.schemaVersion !== exports.CHART_SET_LOCK_SCHEMA_VERSION) {
47
50
  throw new Error(`${path} has schemaVersion ${JSON.stringify(root.schemaVersion)}; only ` +
48
- `schemaVersion ${String(exports.CHART_SET_LOCK_SCHEMA_VERSION)} (a digest per ` +
49
- 'chart) is read. Re-seal the lock; older locks are refused, not migrated.');
51
+ `schemaVersion ${String(exports.CHART_SET_LOCK_SCHEMA_VERSION)} (an index digest ` +
52
+ 'per publisher, a digest per chart) is read. Re-resolve the chart set with ' +
53
+ '`xema edition chart-set resolve`; older locks are refused, not migrated.');
50
54
  }
51
55
  if (typeof root.registry !== 'string' || root.registry.length === 0) {
52
56
  throw new Error(`${path} carries no 'registry'; refusing the chart-set lock.`);
@@ -61,6 +65,7 @@ function parseChartSetLock(parsed, path) {
61
65
  if (publisher === null || typeof index !== 'string' || index.length === 0) {
62
66
  throw new Error(`${path} has a publisher entry without an 'index'.`);
63
67
  }
68
+ assertIndexDigest(publisher, index, path);
64
69
  const charts = asRecord(publisher.charts);
65
70
  if (charts === null || Object.keys(charts).length === 0) {
66
71
  throw new Error(`${path}: publisher '${index}' carries no 'charts' map.`);
@@ -75,6 +80,36 @@ function parseChartSetLock(parsed, path) {
75
80
  }
76
81
  return { path, registry: root.registry, members };
77
82
  }
83
+ function assertIndexDigest(publisher, index, path) {
84
+ const where = `${path}: publisher '${index}'`;
85
+ if (!Object.hasOwn(publisher, 'indexDigest')) {
86
+ throw new Error(`${where} carries no 'indexDigest' key (required: sha256:<64 hex> for a ` +
87
+ 'release index, null for a develop snapshot version).');
88
+ }
89
+ const { version, indexDigest } = publisher;
90
+ if (typeof version === 'string' &&
91
+ version.startsWith(exports.DEVELOP_CHART_VERSION_PREFIX) &&
92
+ !exports.DEVELOP_CHART_VERSION_PATTERN.test(version)) {
93
+ throw new Error(`${where} has malformed develop chart version ${JSON.stringify(version)}; ` +
94
+ `a develop version is exactly ${exports.DEVELOP_CHART_VERSION_PREFIX}<${exports.DEVELOP_CHART_SHA_LENGTH} lowercase hex>.`);
95
+ }
96
+ const develop = typeof version === 'string' && exports.DEVELOP_CHART_VERSION_PATTERN.test(version);
97
+ if (indexDigest === null) {
98
+ if (!develop) {
99
+ throw new Error(`${where} has indexDigest null with version ${JSON.stringify(version)}; ` +
100
+ `null is only for a develop snapshot version (${exports.DEVELOP_CHART_VERSION_PREFIX}<sha12>).`);
101
+ }
102
+ return;
103
+ }
104
+ if (develop) {
105
+ throw new Error(`${where} has an indexDigest with develop snapshot version ${String(version)}; ` +
106
+ 'a develop snapshot has no index chart, so indexDigest must be null.');
107
+ }
108
+ if (typeof indexDigest !== 'string' || !exports.CHART_DIGEST_PATTERN.test(indexDigest)) {
109
+ throw new Error(`${where} has a malformed indexDigest (${JSON.stringify(indexDigest)}); ` +
110
+ 'expected sha256:<64 hex>.');
111
+ }
112
+ }
78
113
  function parseMember(name, value, path) {
79
114
  const record = asRecord(value);
80
115
  if (record === null) {
@@ -1 +1 @@
1
- {"version":3,"file":"chart-set-lock.js","sourceRoot":"","sources":["../../../src/runtime/runner/chart-set-lock.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"chart-set-lock.js","sourceRoot":"","sources":["../../../src/runtime/runner/chart-set-lock.ts"],"names":[],"mappings":"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"}
package/edition/README.md CHANGED
@@ -19,7 +19,6 @@ no defaults.
19
19
  | `--kernel` | the BUILT `kernel-contracts` package directory |
20
20
  | `--deploy-root` | the `xema-deploy` checkout |
21
21
  | `--cache-dir` | a writable directory for fetched artifacts |
22
- | `--source-hash` | the BUILT `@xemahq/distribution-source-hash` package directory (`packages/sdks/distribution-source-hash` of `xema-runtime-core`, after its build). Required by the entries that resolve, build, index or seal; the evidence verifier never reads it. The CLI ships without the package (a devDependency for its tests only). On first load its algorithm ids are held identical to the kernel's `ImageSourceHashAlgorithm`. |
23
22
 
24
23
  ## Entry scripts
25
24
 
@@ -27,16 +26,17 @@ Each takes the roots below plus its own arguments (`--check`, an edition id, ...
27
26
 
28
27
  | Script | Roots |
29
28
  | --- | --- |
30
- | `resolve-distribution.mjs` | data, editions, sources, kernel, source-hash |
31
- | `resolve-images.mjs` | data, editions, sources, kernel, cache, source-hash |
29
+ | `resolve-distribution.mjs` | data, editions, sources, kernel |
30
+ | `resolve-images.mjs` | data, editions, sources, kernel, cache |
32
31
  | `resolve-chart-set.mjs` | data, editions, deploy, kernel |
33
- | `edition-biome-manifests.mjs` | data, editions, sources, source-hash, kernel |
34
- | `seal-image-digests.mjs` | data, editions, kernel, source-hash |
32
+ | `edition-biome-manifests.mjs` | data, editions, sources, kernel |
33
+ | `seal-image-digests.mjs` | data, editions, kernel |
35
34
  | `verify-image-platforms.mjs` | data, editions, kernel |
36
- | `dist-check.mjs` | data, editions, sources, kernel, deploy, cache, source-hash |
37
- | `develop-edition.mjs` | data, editions, sources, kernel, deploy, cache, source-hash |
35
+ | `dist-check.mjs` | data, editions, sources, kernel, deploy, cache |
36
+ | `develop-edition.mjs` | data, editions, sources, kernel, deploy, cache |
38
37
  | `release-edition-artifact.mjs` (`xema edition artifact`) | data, editions, kernel |
39
- | `build-biome-index.mjs`, `build-platform-service-index.mjs`, `build-runtime-image-source-index.mjs` | data, sources, kernel, source-hash |
38
+ | `trust-policy-arming-verb.mjs` (`xema edition arming`) | data, kernel |
39
+ | `build-biome-index.mjs`, `build-platform-service-index.mjs`, `build-runtime-image-source-index.mjs` | data, sources, kernel |
40
40
  | `checkout-indexed-source-repositories.mjs` | data, sources |
41
41
  | `why.mjs` | editions, kernel |
42
42
  | `repin-evidence-trust.mjs` | data, kernel |
@@ -60,21 +60,21 @@ Read strictly: every key is required, an unknown key or a duplicate key is refus
60
60
  ```
61
61
 
62
62
  - `imageRegistry`: `<host>/<namespace>`, where first-party images are addressed (`<imageRegistry>/<subject-id>`).
63
- - `chartRegistry`: `oci://<host>/<path>`, where chart-set indexes and charts are published.
64
- - `chartPublishers`: the chart-set indexes the pin covers; `repo` is recorded in `chart-set.lock.json`.
63
+ - `chartRegistry`: `oci://<host>/<path>`, where chart-set indexes and charts are published. VENDOR-ONLY, paired with `chartPublishers`: both declared or both absent (one alone is refused); a customer data root declares neither, and vendor chart-set code on such a root is refused naming the file and both keys.
64
+ - `chartPublishers`: the chart-set indexes the pin covers; `repo` is recorded in `chart-set.lock.json`. VENDOR-ONLY (see `chartRegistry`).
65
65
  - `sourceRepositoryUrlPrefix`: `https://<host>/<organisation>/`, the prefix every first-party `repositoryUrl` must equal (with `<repo>.git`).
66
- - `sourceAuthUsername`: the non-empty git username first-party source tokens authenticate as (GitHub: `x-access-token`; other forges differ). Required, no default.
66
+ - `sourceAuthUsername`: the non-empty git username source tokens authenticate as (GitHub: `x-access-token`; other forges differ). Required, no default. With `sourceRepositoryUrlPrefix` it is the only declaration of source credentials: the token is read from the environment variable `EVIDENCE_REPOSITORIES_TOKEN`, and it is sent only to repositories under the prefix. A definition source carrying `authTokenEnv` or `authUsername` is refused; a source outside the prefix is fetched with no credential (environment-injected git config and credential helpers are disabled for it). When the definition and `edition-workspace.json` live in the same repository, a pull request can edit both, so the file is no control against the pull request's author: the control is that resolves triggered by pull requests run without secrets.
67
67
  - `evidenceTrustPolicy` (mandatory): `{policy, pin}`, two data-root-relative POSIX paths (no `..`, regular no-follow files), or `null`, the explicit statement that this data root declares no policy. Any other key is refused as unknown (the former `required` switch included): no configuration admits a release edition without an armed policy. A data root that declares `null` serves develop editions only. See below.
68
68
 
69
69
  ## Operating notes
70
70
 
71
71
  - **Release order (edition protocol coupling).** The CLI is coupled to the kernel's edition protocol, not to the whole kernel package. `@xemahq/contracts` exports the protocol identity as data (`dist/edition-protocol/edition-protocol.json`: `{contentDigest, version}`, the kernel contract authority digest over every kernel source file the protocol's entry subpaths load at runtime, derived from the import graph; `edition-contract.schema.json` states that identity, the entry subpaths and the source paths). `edition-protocol.mjs` declares the versions this CLI supports, each pinned to one digest and vendored under `vendored/`; the library loads no kernel subpath outside the vendored entry subpaths (`edition-protocol.test.mjs`). Every loader that executes the `--kernel`, `xema edition check` first, and the evidence verifier compare only that identity, and refuse an unsupported one naming the versions this CLI supports and the version the kernel declares. The digest is a CONSERVATIVE, deterministic stand-in for protocol semantics. Any change to accepted or refused edition semantics MUST change it; a behaviour-preserving refactor MAY change it too, and the cost is a repin, which is accepted. Semantic equivalence isn't computable, so it is never detected. A kernel change outside the protocol's source files needs no CLI release. An edition protocol change does: the CLI release that supports it goes FIRST; only then does the data root repin its kernel authority to a kernel that speaks it.
72
72
  - **`xema edition resolve <id> --trusted-write-v3`** writes the lock set, and the flag is the operator's explicit acknowledgement that the images lock is rewritten unsealed (`xema edition seal` writes the digests afterwards). **`xema edition resolve <id> --check`** writes nothing committed (it resolves into a scratch copy) and compares like with like: the resolve timestamp is ignored and the committed images lock is read without its sealed `imageDigest`s, so a sealed lock is not drift.
73
- - **Git credentials.** `git-source-auth.mjs` is the one place a token becomes git authentication (Basic `<username>:<token>`, `GIT_CONFIG_*`, never argv; the username is a required declared argument with no default: `sourceAuthUsername` for first-party sources, and for a customer git source its own `authUsername`, which the kernel's `GitBiomeSourceSchema` requires whenever `authTokenEnv` is set and refuses without it). The header is scoped to the origin of the URL it is for: `sourceRepositoryUrlPrefix` for first-party sources, a customer source's own `url`. A token that cannot be applied (an `authTokenEnv` that is unset, or a URL that is not https with a host) is refused; there is no ambient-credential fallback. The CLI's runtime has its own TypeScript implementation (`src/runtime/sources/kinds/git.ts`) of the same shape, because this library is plain ESM that cannot import compiled TypeScript.
74
- - **Edition artifacts (one layout for develop and release).** An edition is an OCI artifact at `<registry>/xema-edition-<id>`, addressed by DIGEST, with artifactType `application/vnd.xema.edition.v1+json` and titled layers (`org.opencontainers.image.title` = file name): `xema-edition.json` (the descriptor) plus `distribution.lock.json`, `images.lock.json`, `authorization.lock.json` and the chart-set lock. `--registry` is the caller's namespace only (`registry.example.com/acme`); the CLI appends `xema-edition-<id>` itself and refuses a registry whose last segment already starts with `xema-edition-` or is the retired develop-only segment. Tags are for humans only: develop `<snapshot hash>`, release `<coordinate>`. The CLI writes the files to a scratch directory and prints the target; the workflow does `oras push` and `cosign sign` on the digest. Develop: `xema edition develop resolve <id> --pins <file> --out <dir> --registry <ns> --engine-image <repo>@sha256:<digest> [--local]`. Release: `xema edition artifact <id> --channel release --registry <ns> --coordinate <tag> --engine-image <repo>@sha256:<digest> --out <dir> [--local]`, written from the committed locks and refused when the images lock is not sealed.
73
+ - **Git credentials.** `git-source-auth.mjs` is the one place a token becomes git authentication (Basic `<username>:<token>`, `GIT_CONFIG_*`, never argv; the username is a required declared argument with no default). Only the data root declares source credentials: `sourceRepositoryUrlPrefix` and `sourceAuthUsername` in `edition-workspace.json`, with the token in `EVIDENCE_REPOSITORIES_TOKEN`. `holdSource` and `withSourceGitEnv` are the hold and the git environment every network git call on a source passes (the develop freeze's `ls-remote`, the source walk, the indexed checkout), before anything is spawned: a source carrying `authTokenEnv` or `authUsername` is refused, a URL that is not a canonical public https URL is refused, a source under the prefix gets the header scoped to the prefix's origin (an unset token is refused; there is no ambient-credential fallback), and any other source is fetched with no credential: environment-injected git config (`GIT_CONFIG_*`) and askpass programs are removed and the credential helper list is reset. Git runs isolated from the user's own configuration (`GIT_CONFIG_GLOBAL=/dev/null`, `GIT_CONFIG_NOSYSTEM=1`, `HOME` and `XDG_CONFIG_HOME` an empty directory that exists only for the call) and does not follow redirects for a credentialed fetch. The CLI's runtime has its own TypeScript implementation (`src/runtime/sources/kinds/git.ts`) of the same shape, because this library is plain ESM that cannot import compiled TypeScript.
74
+ - **Edition artifacts (one layout for develop and release).** An edition is an OCI artifact at `<registry root>/<channel segment>/xema-edition-<id>` (release: `<root>/editions/xema-edition-<id>`, develop: `<root>/develop-editions/xema-edition-<id>`; both segments and the prefix come from `edition-contract.json` `repositories`), addressed by DIGEST, with artifactType `application/vnd.xema.edition.v1+json` and titled layers (`org.opencontainers.image.title` = file name): `xema-edition.json` (the descriptor) plus `distribution.lock.json`, `images.lock.json`, `authorization.lock.json` and the chart-set lock. `--registry` is the registry ROOT (`<host>[:port]/<namespace>[/...]`, e.g. `registry.example.com/acme`); the CLI appends the channel segment AND `xema-edition-<id>` itself and refuses a root whose last segment is either channel segment or starts with `xema-edition-`. `parseEditionRepository` is the one parser of `<root>/<segment>/xema-edition-<id>`; every reader that fetches an edition or offering artifact by reference refuses a reference whose repository channel is not the channel being resolved, and an artifact whose own stated channel (the catalogue's `channel`) differs from the channel of the repository it was fetched from, before using any content. Tags are for humans only: develop `<snapshot hash>`, release `<coordinate>`. The CLI writes the files to a scratch directory and prints the target; the workflow does `oras push` and `cosign sign` on the digest. Develop: `xema edition develop resolve <id> --pins <file> --out <dir> --registry <root> --engine-image <repo>@sha256:<digest> [--local]`. Release: `xema edition artifact <id> --channel release --registry <root> --coordinate <tag> --engine-image <repo>@sha256:<digest> --out <dir> [--local]`, written from the committed locks and refused when the images lock is not sealed.
75
75
  - **The descriptor** (`xema.edition/v1`, strict parse, no other key): `{schema, distributionId, locks[], provenance: {channel: "develop" | "release", pins, workflow: {repository, runId}}, evidenceTrustPolicy: "armed" | "not-armed", engine: {image: "<repository>@sha256:<64 hex>"}}`. `engine.image` is required, digest form only, filled from `--engine-image` (the producer workflow resolves the engine tag with oras; the CLI never resolves tags). `evidenceTrustPolicy` records `armed` when the data root declares a policy and it is armed, `not-armed` otherwise. A release edition's `provenance.pins` is empty (its sources are recorded in its locks). Admission rests on the cosign signature of the manifest digest: readers never branch on `provenance.channel`, and no tool refuses an edition for a channel label, schema name or file name.
76
- - **Contract.** `edition-contract.json` (`xema.edition-contract/v1`) holds the artifactType, the repository prefix, the descriptor file and schema, the lock list and the develop image namespace segment; it ships in the package and xema-deploy vendors it byte for byte.
77
- - **Chart set.** ONE `chart-set.lock.json` (rostered, `schemaVersion: 3`: `publishers[]` with each chart's version and registry manifest digest) serves both channels. A develop edition pins the charts xema-deploy publishes as OCI for the frozen deploy SHA, at version `0.0.0-dev.<first 12 hex of the SHA>` (also the OCI tag). Each producer validates the chart coordinates of its own channel: a release chart set accepts only `-h<16 hex>` versions, a develop one only `-dev.<12 hex>`; a develop chart-set lock copied into a release is refused by `assertReleaseContent`, and `0.0.0-dev.*` index tags are never release candidates; the resolver reads each chart's manifest digest from the chart registry, exactly as a release does, and the lock lists only the publisher whose repository is the deploy pin role's. The CLI computes no chart content digest.
76
+ - **Contract.** `edition-contract.json` (`xema.edition-contract/v2`) holds the artifactType, the repository prefix, `repositories` (the namespace segment of each channel: `release` and `develop`), the descriptor file and schema, the lock list, the develop image namespace segment and a `vendoring` statement; it ships in the package. A consumer (xema-deploy, nexus) vendors it BYTE-IDENTICAL from a stated xema-cli commit, never edits it, reads both namespace segments and the prefix only from it, and refuses a contract without `repositories`.
77
+ - **Chart set.** ONE `chart-set.lock.json` (rostered, `schemaVersion: 4`: `publishers[]` with each publisher's `indexDigest`, the registry manifest digest of its index chart, and each chart's version and registry manifest digest; `indexDigest` is `null` exactly for a develop snapshot version `0.0.0-dev.<sha12>`, which has no index chart, and a release lock refuses `null`; a lock of another schema is refused by every reader: re-resolve the chart set with `xema edition chart-set resolve`) serves both channels. A develop edition pins the charts xema-deploy publishes as OCI for the frozen deploy SHA, at version `0.0.0-dev.<first 12 hex of the SHA>` (also the OCI tag). Each producer validates the chart coordinates of its own channel: a release chart set accepts only `-h<16 hex>` versions, a develop one only `-dev.<12 hex>`; a develop chart-set lock copied into a release is refused by `assertReleaseContent`, and `0.0.0-dev.*` index tags are never release candidates; the resolver reads each chart's manifest digest from the chart registry, exactly as a release does, and the lock lists only the publisher whose repository is the deploy pin role's. The CLI computes no chart content digest.
78
78
  - **Release lock set.** `assertReleaseEditionTree` checks that the companion chart-set lock is PRESENT beside the distribution and authorization locks. It does not bind the set by a digest, and is not described as hash-bound.
79
79
 
80
80
  ## Evidence trust
@@ -193,9 +193,10 @@ Common to all verbs: root flags `--data-root`, `--editions-root`, `--sources-roo
193
193
 
194
194
  | Verb | Arguments | Result |
195
195
  |---|---|---|
196
- | `xema edition chart-set resolve [--check]` | roots `--data-root --editions-root --deploy-root --kernel` | what `node resolve-chart-set.mjs` did: pins `chart-set.lock.json` and every edition's copy to the charts the registry serves; `--check` writes nothing and fails when the pin is stale |
197
- | `xema edition chart-set source-commit` | roots `--data-root --kernel`, env the registry credential variables the data root declares | read-only: for each publisher of the committed `chart-set.lock.json` it fetches the index chart at the LOCKED version and prints `{"schema":"xema.edition-chart-set-source/v1","publishers":[{"index","repo","version","sourceCommit"}]}` (the `xema.dev/chart-set-source-sha` annotation). It prints nothing and exits non-zero when the annotation is absent or not 40 lowercase hex, or when the index's roster differs from the charts the lock records for that publisher |
198
- | `xema edition biome-manifests <id> --json` | `--data-root --editions-root --sources-root --source-hash --kernel` | `{"schema":"xema.edition-biome-manifests/v1","distributionId","biomes":[{"id","repo","sourceCommit","manifest"}]}` sorted by id: each biome's `xema-biome.json` read at the commit its lock pins; an absent lock or an unreadable manifest is refused with nothing on stdout |
196
+ | `xema edition chart-set resolve [--check]` | roots `--data-root --editions-root --deploy-root --kernel` | what `node resolve-chart-set.mjs` did: pins `chart-set.lock.json` and every edition's copy to the charts the registry serves; `--check` writes nothing and fails when the pin is stale. The index chart is pinned by the manifest digest the registry served at resolve, and every later read is by that digest; WHO signed the index is not verified by the CLI (the control is the reviewed lock commit) |
197
+ | `xema edition chart-set source-commit` | roots `--data-root --kernel`, env the registry credential variables the data root declares | read-only: for each publisher of the committed `chart-set.lock.json` it fetches the index chart by the LOCKED `indexDigest` (a develop publisher, `indexDigest: null`, is refused) and prints `{"schema":"xema.edition-chart-set-source/v1","publishers":[{"index","repo","version","sourceCommit"}]}` (the `xema.dev/chart-set-source-sha` annotation). It prints nothing and exits non-zero when the registry serves another digest, when the annotation is absent or not 40 lowercase hex, or when the index's roster differs from the charts the lock records for that publisher |
198
+ | `xema edition biome-manifests <id> --json` | `--data-root --editions-root --sources-root --kernel` | `{"schema":"xema.edition-biome-manifests/v1","distributionId","biomes":[{"id","repo","sourceCommit","manifest"}]}` sorted by id: each biome's `xema-biome.json` read at the commit its lock pins; an absent lock or an unreadable manifest is refused with nothing on stdout |
199
+ | `xema edition arming --channel <release\|develop>` | `--data-root --kernel` | read-only: `{"channel","state":"armed\|unarmed\|partial","unmet":[…]}` on one stdout line; exits 0 only when the declared evidence trust policy is `armed` on that channel, 1 otherwise (each unmet entry on stderr; a data root declaring no policy is `unarmed`), 2 for a usage error |
199
200
  | `xema edition list` | `--editions-root` | `{"schemaVersion":1,"editions":["<id>",…]}`; an empty root is refused |
200
201
  | `xema edition locks <id>` | `--data-root --editions-root --kernel` | `{"schemaVersion":1,"distributionId","contractAuthority","distributionLock","imageLock","repositoryPins":[…]}`, validated as the verifier validates them |
201
202
  | `xema edition evidence assemble-input` | `--output <file> --image-repository --manifest-digest --subject-kind --subject-id --source-repo --source-path --source-commit --provenance <file> [--source-manifest-hash] [--source-tree-hash] [--ttl-seconds]`, roots `--data-root --sources-root --kernel`, env `GITHUB_*` workflow context and the registry credential variables the data root declares | writes the producer-input JSON (mode 0600, never overwrites); reads the registry with the pinned oras |
@@ -210,8 +211,8 @@ Common to all verbs: root flags `--data-root`, `--editions-root`, `--sources-roo
210
211
  | `xema edition evidence oci attach-plan` | `--evidence-directory <abs dir> [--input <file>]`, roots `--data-root --kernel` | validates a produced evidence directory (with `--input`, against the statements that input builds) and prints `{"schemaVersion":1,"kind":"oci-attach-plan","evidenceDirectory","imageRepository","subjectDigest","subject","artifactType","distributionSpec","annotations":{…},"files":[{"path","mediaType"}]}`: the operands of `oras attach`, run from the evidence directory |
211
212
  | `xema edition evidence oci validate` | `--evidence-directory <abs dir> --manifest <file> --subject-digest <digest> [--input <file> --cosign <abs path> [--now <epoch-ms>]]`, roots `--data-root --kernel` | holds a pulled artifact (`oras pull`) to its manifest (`oras manifest fetch`); prints `{"schemaVersion":1,"kind":"oci-evidence","subjectDigest","created","subjects":[…],"matchesInput","remainingSeconds","reusable"}`. The last three are `null` without `--input`; with it every bundle is verified against the input's signer and a bundle that does not verify is a refusal |
212
213
  | `xema edition evidence oci backfill-plan` | `--distribution <id>… --producer-repository-root <abs dir>…`, roots `--data-root --editions-root --kernel`, env the registry credential variables the data root declares | prints `{"schemaVersion":1,"kind":"oci-backfill-plan","items":[{"status":"missing-git\|present\|missing","subjectKind","subjectId","imageRepository","subjectDigest","artifactDigest","attach"?}]}`; a `missing` item carries its attach plan; exit 2 unless every item is `present` |
213
- | `xema edition index runtime-image-source --repin-contract-authority` | roots `--data-root --sources-root --kernel --source-hash` | replaces ONLY `contractAuthority` of the committed `runtime-image-source-index.json` with the authority trusted from the `--kernel` checkout (clean, at the expected origin, else refused) and validates and rewrites it; every repository pin and subject is identical before and after. It writes the move on stdout and the owed follow-ups on stderr: `xema edition repin-evidence-trust`, re-resolving every edition (each `images.lock.json` carries the authority), and that signed producer statements made under the old authority no longer verify |
214
- | `xema edition source-closure` | `--repo <repo> --path <dir> --commit <40 hex>`, roots `--sources-root --source-hash --kernel` (the hash implementation is held to the kernel) | `{"schemaVersion":1,"repo","directory","commit","treeAlgorithm","treeHash","buildable","lockfileBlobId","closure":[{"directory","treeHash"}]}`: the facts a content coordinate is framed from |
214
+ | `xema edition index runtime-image-source --repin-contract-authority --kernel-commit <40-hex>` | roots `--data-root --sources-root --kernel`; `--kernel-commit` REQUIRED (the kernel commit the authority moves to; refused with the usage when absent); `[--kernel-root <absolute-checkout>]` | replaces ONLY `contractAuthority` of the committed `runtime-image-source-index.json` with the authority read from the kernel checkout AT THE STATED COMMIT: the checkout must be clean, at the expected origin, and its HEAD must equal `--kernel-commit` (else refused, naming both commits), and that commit is what `contractAuthority.sourceCommit` records and what `xema edition repin-evidence-trust` then pins as `kernelContractAuthority.sourceCommit`. A bootstrap `--trusted-write` (no committed contract authority) chooses a commit the same way and takes the same required flag; every other mode refuses it. It validates and rewrites the index; every repository pin and subject is identical before and after. It writes the move on stdout and the owed follow-ups on stderr: `xema edition repin-evidence-trust`, re-resolving every edition (each `images.lock.json` carries the authority), and that signed producer statements made under the old authority no longer verify. `--kernel-commit` holds the checkout's HEAD to the stated commit and nothing more: there is NO ancestry or remote check, so a never-pushed local commit passes if its SHA is stated; the control is the reviewed data-root diff that carries the new `sourceCommit` |
215
+ | `xema edition source-closure` | `--repo <repo> --path <dir> --commit <40 hex>`, roots `--sources-root --kernel` (the hash implementation is held to the kernel) | `{"schemaVersion":1,"repo","directory","commit","treeAlgorithm","treeHash","buildable","lockfileBlobId","closure":[{"directory","treeHash"}]}`: the facts a content coordinate is framed from |
215
216
  | `xema edition verify-evidence <id>` | see Evidence trust; `--descriptor <absolute path of the edition descriptor being verified>` (required: its `distributionId` must be the requested id and its `provenance.channel` is the channel whose signers may vouch for the evidence; it is held outside the graded checkouts like `--state`); plus `[--json]` (stdout is then exactly one `{"schema":"xema.edition-evidence-verification/v1","distributionId","channel","releaseSequence","statements":[{"predicateType","subject","statementDigest"}]}` document, where `statementDigest` is the digest of the verified statement, not of the image; the human text goes to stderr, and a failed verification prints no JSON); plus, given together or not at all, `--entitlement <evidence-root-relative path of an entitlement.json> --entitlement-workflow-sha <40-hex git sha> --customer <id> --mode <mode>` and optionally `--target-registry <registry>`: the delivery entitlement `accept` wrote is verified with the release (`--entitlement-workflow-sha` is the exact commit of the workflow run that signed the entitlement, the one its certificate is held to; it is stated by the caller and never read from the evidence; the statement and bundle are read from the directory holding `entitlement.json`), and the verification document then ends with the entitlement statement | the in-repository gate and the release-checkout grading are this verifier; there is no second one. Its result is its exit status, the advanced replay state and, with `--json`, the verification document on stdout: it writes no "already verified" file and has no file-output flag. A delivery entitlement verifies only as part of this run: a refused entitlement refuses the whole verification and the replay state does not advance. `entitlement.json` is `{schemaVersion: 2, distributionId, customerId, mode, targetRegistry, idempotencyKey, distributionStatementDigest, evidence}` with exactly those keys (`evidence` is the Sigstore evidence descriptor); every field but `evidence` is an unauthenticated convenience that must agree with the flags and the verified completeness statement, and the signed statement stays the authority |
216
217
 
217
218
  The signing plan (stdout of every `stage`):
@@ -241,10 +242,21 @@ A customer edition is resolved against a CATALOGUE: `catalogue.json`, a titled l
241
242
  the chart-set, and never adds a component. The resolved set is the customer's `{biome}` selections, every
242
243
  `installationClass: core` entry and the transitive declared dependencies.
243
244
 
244
- xema edition resolve --definition <file> --catalogue oci://<registry>/<repo>@sha256:<digest> --channel release \
245
- --data-root <dir> --sources-root <dir> --kernel <dir> --cache-dir <dir> --source-hash <dir> --out <dir> \
245
+ xema edition resolve --definition <file> --channel <release|develop> \
246
+ --data-root <dir> --sources-root <dir> --kernel <dir> --cache-dir <dir> --out <dir> \
246
247
  --source-image <source>=<registry>/<prefix> [--catalogue-username <u> --catalogue-token-env <VAR>]
247
248
 
249
+ The catalogue is the one pinned for the channel in `catalogue.pin.json`, in the SAME directory as the definition file:
250
+ `{"schemaVersion": 1, "develop": "oci://<repository>@sha256:<64 hex>" | null, "release": "oci://<repository>@sha256:<64 hex>" | null}`
251
+ (exact keys, both channel keys present, null = not produced on that channel; the repository of the reference under a key must be
252
+ that channel's repository). A null pin for the requested channel is refused. There is no `--catalogue` flag; passing one is an unknown-flag error.
253
+ `catalogue.pin.json` is written by the customer (or for it, by a commit to its repository), and until the kernel repin the
254
+ catalogue it names carries no vendor signature and no identity check: the digest in the pin is the only thing that binds it.
255
+ `develop freeze-sources --definition` asks only source URLs that are canonical HTTPS repository URLs under the data root's
256
+ `sourceRepositoryUrlPrefix`; a definition naming any other origin is refused before a network call, so the source token is never sent there.
257
+ A definition source carrying `authTokenEnv` or `authUsername` is refused on every path that fetches it: source credentials are declared only by the data root (`sourceRepositoryUrlPrefix` + `sourceAuthUsername`, token `EVIDENCE_REPOSITORIES_TOKEN`), and `resolve` fetches a source outside the prefix with no credential.
258
+ `--registry` refuses a root with a `.` or `..` path segment.
259
+
248
260
  `--out` is the customer's editions root (`<out>/<id>/` receives the four locks). `--data-root` holds the customer's
249
261
  `edition-workspace.json`. The definition allows `{biome}` selectors (the same per-biome options a vendor offering's selector takes in the kernel
250
262
  schema, `installPolicy` and `mandatory`, and nothing else), `sources[]` (no registry), `trustCeiling` and
@@ -266,8 +278,8 @@ The vendor's trust domain is closed to a customer edition, by three refusals the
266
278
  assigns that biome when it names it, otherwise any domain the catalogue assigns to nothing. A definition that names another domain is refused, naming the biome and both domains.
267
279
  A `null` member of `ceiling` bounds nothing.
268
280
 
269
- Handoff to the customer's registry: `oras cp` the catalogue artifact and the vendor images by digest, switch
270
- `EDITION_CATALOGUE_REF`, and declare `imageRegistryMirror: {complete: true, map: [{source, target}]}`. Prefixes match whole
281
+ Handoff to the customer's registry: `oras cp` the catalogue artifact and the vendor images by digest, pin the mirrored
282
+ catalogue in `catalogue.pin.json`, and declare `imageRegistryMirror: {complete: true, map: [{source, target}]}`. Prefixes match whole
271
283
  path segments; the digest never changes and no signature is re-verified at the mirrored ref (the customer's seal is the
272
284
  trust anchor and deploy is digest-anchored).
273
285
 
@@ -287,27 +299,52 @@ biome's tree id at its pinned commit, written from the real tree by `edition cat
287
299
  customer's seal takes that tree id as an INPUT ON THE CATALOGUE'S AUTHORITY: it is not source verification, and the
288
300
  refusal text says so. A vendor biome absent from the catalogue is refused, not skipped.
289
301
 
290
- xema edition seal <id> --catalogue oci://<registry>/<repo>@sha256:<digest> [--catalogue-username <u> --catalogue-token-env <VAR>] \
291
- --data-root <dir> --editions-root <dir> --sources-root <dir> --kernel <dir> --cache-dir <dir> --source-hash <dir>
302
+ xema edition seal <id> [--definition <file> [--catalogue-username <u> --catalogue-token-env <VAR>]] \
303
+ --data-root <dir> --editions-root <dir> --sources-root <dir> --kernel <dir> --cache-dir <dir>
292
304
 
293
305
  Mirror runbook (`imageRegistryMirror`): 1. `oras cp` the catalogue artifact and every vendor image BY DIGEST into the
294
- customer's registry. 2. Point the definition at the mirrored catalogue digest. 3. Declare
306
+ customer's registry. 2. Pin the mirrored catalogue digest in `catalogue.pin.json`. 3. Declare
295
307
  `imageRegistryMirror: {complete: true, map: [{source, target}]}` (whole path segments; `complete: true` refuses a vendor row no
296
308
  entry covers). 4. Resolve and seal: digests are unchanged, so the seal's hashes are unchanged; no signature is
297
309
  re-verified at the mirrored ref.
298
310
 
299
311
  ### Develop customer edition
300
312
 
301
- xema edition develop resolve --definition <file> --catalogue oci://<registry>/<repo>@sha256:<digest> \
302
- --pins <file> --out <dir> --registry <namespace> --engine-image <repository>@sha256:<digest> \
303
- --data-root <dir> --editions-root <dir> --sources-root <dir> --kernel <dir> --cache-dir <dir> --source-hash <dir> \
313
+ A customer-owned develop edition is produced with CLI verbs only. Each takes `--definition <file>`; the develop catalogue is the
314
+ `develop` pin of `catalogue.pin.json` beside it.
315
+
316
+ xema edition develop freeze-sources --definition <file> --out <file>
317
+ xema edition develop kernel-commit --definition <file> [--catalogue-username <u> --catalogue-token-env <VAR>] --data-root <dir>
318
+ xema edition develop resolve --definition <file> --pins <file> --out <dir> --registry <root> --engine-image <repository>@sha256:<digest> \
319
+ --data-root <dir> --editions-root <dir> --sources-root <dir> --kernel <dir> --cache-dir <dir> \
304
320
  [--source-image <source>=<registry>/<prefix>]... [--catalogue-username <u> --catalogue-token-env <VAR>] [--local]
321
+ xema edition develop target --definition <file> --pins <file> --registry <root>
322
+ xema edition develop check-existing --definition <file> --pins <file> --artifact <dir> --engine-image <repository>@sha256:<digest> \
323
+ --data-root <dir> --kernel <dir>
324
+
325
+ `freeze-sources` writes (exclusively) the `--pins` file: exactly one `sources/<name>` commit per source the definition declares,
326
+ the head of `refs/heads/develop` of each source repository, resolved once (token from `EVIDENCE_REPOSITORIES_TOKEN`).
327
+ `kernel-commit` fetches the pinned develop catalogue and prints its `contractAuthority.sourceCommit` (one 40-hex line): the commit
328
+ a workflow checks the kernel contracts out at; it needs no `--kernel` and no `--sources-root`. `target` prints the develop target
329
+ (the tag covers the pinned catalogue digest). `check-existing` does for a customer artifact what it does for a vendor one, and
330
+ additionally refuses a descriptor whose `catalogue` provenance pin is not the pinned develop catalogue digest.
331
+
332
+ Every customer DEVELOP verb works on a data root holding only `edition-workspace.json`, and none reads the vendor's indices,
333
+ pins or pin roles. The release seal of a customer edition (`xema edition seal <id> --definition ...`) still reads the vendor's
334
+ `runtime-image-source-index.json` from the data root and is not supported before the kernel repin. It takes `--data-root`,
335
+ `--editions-root`, `--sources-root`, `--kernel` and `--cache-dir`, and refuses any other root flag (for example `--deploy-root`),
336
+ naming the roots it takes. A customer whose sources build images also commits `image-registry-credentials.json` in its editions root
337
+ (the registries its own images are verified against); when its own image rows are sealed and that file is absent, the resolve
338
+ is refused naming the file and the root. A customer with no sources, or whose sources build no image, needs no file in its
339
+ editions root. A customer verb refuses a root flag it does not take, naming the roots it takes (`target` takes none).
305
340
 
306
341
  The id is the definition's (no positional id). `--pins` holds exactly one `sources/<name>` commit per source the
307
342
  definition declares (the customer's own repositories; the vendor's pins are the catalogue's). The digest of the catalogue
308
343
  artifact is frozen into the descriptor's `provenance.pins` as `catalogue: "sha256:<64 hex>"` (typed per key: every other
309
344
  pin is a 40-hex commit), so the snapshot-hash tag covers it. The artifact is the one edition layout (`xema.edition/v1`,
310
- the descriptor plus the four locks) at `<namespace>/xema-edition-<id>`. Vendor image rows arrive sealed from the
311
- catalogue; only the customer's own rows are sealed against the develop image repositories. A customer artifact is not an
345
+ the descriptor plus the four locks) at `<root>/<develop segment>/xema-edition-<id>`. Vendor image rows arrive sealed from the
346
+ catalogue and are neither re-homed nor re-verified; only the customer's own rows are re-homed to the develop image repositories,
347
+ verified there for their required platforms and sealed with the digest the registry served. The images lock is parsed once, as
348
+ the merged lock (vendor rows plus own rows). On the release channel the own rows stay unsealed in the merged lock. A customer artifact is not an
312
349
  offering and carries no `catalogue.json`. `edition-contract.json` lists `catalogue.json` under `offeringLayers` (a layer
313
350
  of an offering artifact, not a lock) and the `catalogue` pin under `provenancePins`.
@@ -38,7 +38,7 @@ import {
38
38
  } from './source-authority.mjs';
39
39
 
40
40
  if (isEntryModule(import.meta.url)) {
41
- configureEditionWorkspaceFromProcess([WorkspaceRoot.Data, WorkspaceRoot.Sources, WorkspaceRoot.Kernel, WorkspaceRoot.SourceHash]);
41
+ configureEditionWorkspaceFromProcess([WorkspaceRoot.Data, WorkspaceRoot.Sources, WorkspaceRoot.Kernel]);
42
42
  }
43
43
 
44
44
  /**
@@ -30,7 +30,7 @@ import {
30
30
  import { readStrictJsonFile } from './strict-json.mjs';
31
31
 
32
32
  if (isEntryModule(import.meta.url)) {
33
- configureEditionWorkspaceFromProcess([WorkspaceRoot.Data, WorkspaceRoot.Sources, WorkspaceRoot.Kernel, WorkspaceRoot.SourceHash]);
33
+ configureEditionWorkspaceFromProcess([WorkspaceRoot.Data, WorkspaceRoot.Sources, WorkspaceRoot.Kernel]);
34
34
  }
35
35
 
36
36
  const TRUSTED_WRITE_FLAG = '--trusted-write';
@@ -7,8 +7,10 @@ import { isAbsolute, join } from 'node:path';
7
7
  import { connectorCrossRepoBuildInputs } from './connector-source-authority.mjs';
8
8
  import { editionWorkspaceConfig, workspaceSection } from './edition-config.mjs';
9
9
  import { biomeIndexPath, dataRoot, runtimeImageSourceIndexPath, WorkspaceRoot, configureEditionWorkspaceFromProcess, isEntryModule, sourcePath } from './edition-workspace.mjs';
10
+ import { COMMIT_PATTERN } from './evidence-grammars.mjs';
10
11
  import {
11
12
  buildTrustedKernelContractAuthority,
13
+ KERNEL_COMMIT_FLAG,
12
14
  describeKernelContractAuthorityDrift,
13
15
  validateKernelContractAuthorityFromGit,
14
16
  } from './kernel-contract-authority.mjs';
@@ -31,7 +33,7 @@ import {
31
33
  import { readStrictJsonFile } from './strict-json.mjs';
32
34
 
33
35
  if (isEntryModule(import.meta.url)) {
34
- configureEditionWorkspaceFromProcess([WorkspaceRoot.Data, WorkspaceRoot.Sources, WorkspaceRoot.Kernel, WorkspaceRoot.SourceHash]);
36
+ configureEditionWorkspaceFromProcess([WorkspaceRoot.Data, WorkspaceRoot.Sources, WorkspaceRoot.Kernel]);
35
37
  }
36
38
 
37
39
  const runtimeImageSources = () =>
@@ -243,17 +245,29 @@ function assertVendoredSourcesResolve(crossRepoBuildInputs, resolveIdentity) {
243
245
  * contract as a side effect — two operations behind one command, and the one
244
246
  * nobody asked for is the one that took the lock.
245
247
  *
246
- * BOOTSTRAP is the single case where local HEAD is the honest source: there is
247
- * no committed index to preserve, so there is no prior decision to respect.
248
+ * BOOTSTRAP is the single case with no committed authority to preserve: it
249
+ * CHOOSES a commit exactly as the re-pin does, so it obeys the re-pin's one
250
+ * rule — the caller states the commit (`--kernel-commit`) and the checkout's
251
+ * HEAD must equal it. Stating a commit when a committed authority exists is
252
+ * refused: it would be read by nobody.
248
253
  */
249
- async function resolveContractAuthority(schemas, kernelRoot) {
254
+ async function resolveContractAuthority(schemas, kernelRoot, kernelCommit) {
250
255
  const repositoryDirectory = kernelRoot ?? undefined;
251
- if (!existsSync(runtimeImageSourceIndexPath())) {
252
- return buildTrustedKernelContractAuthority({ schemas });
253
- }
254
- const committed = readJson(runtimeImageSourceIndexPath());
256
+ const committed = existsSync(runtimeImageSourceIndexPath())
257
+ ? readJson(runtimeImageSourceIndexPath())
258
+ : undefined;
255
259
  if (!committed?.contractAuthority) {
256
- return buildTrustedKernelContractAuthority({ schemas });
260
+ return buildTrustedKernelContractAuthority({
261
+ ...(repositoryDirectory === undefined ? {} : { repositoryDirectory }),
262
+ schemas,
263
+ kernelCommit,
264
+ });
265
+ }
266
+ if (kernelCommit !== undefined) {
267
+ throw new Error(
268
+ `${KERNEL_COMMIT_FLAG} states the commit of a NEW contract authority, and the committed index already pins ` +
269
+ `${committed.contractAuthority.sourceCommit}: this command preserves it. Move it with ${REPIN_FLAG} ${KERNEL_COMMIT_FLAG} <40-hex>.`,
270
+ );
257
271
  }
258
272
  return validateKernelContractAuthorityFromGit({
259
273
  authority: committed.contractAuthority,
@@ -357,9 +371,9 @@ export async function buildTrustedIndex(
357
371
  sourceAuthority,
358
372
  kernelRoot,
359
373
  repoScope,
360
- { biomeIndex, platformServiceIndex },
374
+ { biomeIndex, platformServiceIndex, kernelCommit },
361
375
  ) {
362
- const contractAuthority = await resolveContractAuthority(schemas, kernelRoot);
376
+ const contractAuthority = await resolveContractAuthority(schemas, kernelRoot, kernelCommit);
363
377
  const repositoryIdentities = new Map();
364
378
  if (repoScope !== undefined) {
365
379
  for (const pin of committedRepositoryPinsToPreserve(repoScope)) {
@@ -622,7 +636,8 @@ export const REPIN_CONTRACT_AUTHORITY_FOLLOW_UPS = Object.freeze([
622
636
 
623
637
  /**
624
638
  * Replace ONLY `contractAuthority` of the committed runtime image source index
625
- * with the one trusted from the kernel checkout, and write it back.
639
+ * with the one trusted from the kernel checkout at the commit the caller
640
+ * states (`kernelCommit`, required; HEAD must equal it), and write it back.
626
641
  *
627
642
  * The document is read as raw strict JSON and the field replaced BEFORE it is
628
643
  * parsed: the kernel's schema refuses an index whose authority `sourcePaths`
@@ -631,7 +646,7 @@ export const REPIN_CONTRACT_AUTHORITY_FOLLOW_UPS = Object.freeze([
631
646
  * is not asserted: this repin produces no pin, and requiring coverage would
632
647
  * make a stale index unrepairable.
633
648
  */
634
- export async function repinContractAuthority(schemas, kernelRoot) {
649
+ export async function repinContractAuthority(schemas, kernelRoot, kernelCommit) {
635
650
  const path = runtimeImageSourceIndexPath();
636
651
  if (!existsSync(path)) {
637
652
  throw new Error(`${path} does not exist: there is no index whose contract authority could be re-pinned`);
@@ -640,6 +655,7 @@ export async function repinContractAuthority(schemas, kernelRoot) {
640
655
  const contractAuthority = await buildTrustedKernelContractAuthority({
641
656
  ...(kernelRoot === undefined ? {} : { repositoryDirectory: kernelRoot }),
642
657
  schemas,
658
+ kernelCommit,
643
659
  });
644
660
  const candidate = schemas.parseRuntimeImageSourceIndex({ ...raw, contractAuthority });
645
661
  await validateRuntimeImageSourceIndex(candidate, schemas, {
@@ -710,12 +726,37 @@ function parseKernelRoot(argv) {
710
726
  };
711
727
  }
712
728
 
729
+ /**
730
+ * `--kernel-commit <40-hex>`: the kernel commit a NEW contract authority is
731
+ * taken from. Required wherever one is chosen (the re-pin, and a bootstrap
732
+ * `--trusted-write`), refused everywhere else.
733
+ */
734
+ function parseKernelCommit(argv) {
735
+ const index = argv.indexOf(KERNEL_COMMIT_FLAG);
736
+ if (index === -1) {return { kernelCommit: undefined, remaining: argv };}
737
+ const value = argv[index + 1];
738
+ if (!value || !COMMIT_PATTERN.test(value)) {
739
+ throw new Error(`${KERNEL_COMMIT_FLAG} requires a full 40-character commit sha`);
740
+ }
741
+ return {
742
+ kernelCommit: value,
743
+ remaining: [...argv.slice(0, index), ...argv.slice(index + 2)],
744
+ };
745
+ }
746
+
713
747
  const REPIN_FLAG = '--repin-contract-authority';
748
+ const USAGE =
749
+ 'usage: build-runtime-image-source-index.mjs (--check | --check-sources | --trusted-write | ' +
750
+ '--repin-contract-authority --kernel-commit <40-hex-commit>) ' +
751
+ '[--source published|workspace] [--kernel-root <absolute-checkout>] ' +
752
+ '[--repo <repository> [--at <40-hex-commit>]] ' +
753
+ '(--trusted-write on a data root with no committed contract authority also requires --kernel-commit)';
714
754
 
715
755
  async function main() {
716
- const { kernelRoot, remaining: afterKernelRoot } = parseKernelRoot(
756
+ const { kernelRoot, remaining: afterKernelRootFlag } = parseKernelRoot(
717
757
  process.argv.slice(2),
718
758
  );
759
+ const { kernelCommit, remaining: afterKernelRoot } = parseKernelCommit(afterKernelRootFlag);
719
760
  const { repoScope, remaining: afterRepoScope } = parseRepoScope(afterKernelRoot);
720
761
  const { exactCommit, remaining: argv } = parseExactCommit(afterRepoScope);
721
762
  const { mode, remaining } = parseSourceModeArgs(argv);
@@ -727,18 +768,20 @@ async function main() {
727
768
  remaining.length !== 1 ||
728
769
  !['--check', '--check-sources', '--trusted-write', REPIN_FLAG].includes(flag)
729
770
  ) {
730
- throw new Error(
731
- 'usage: build-runtime-image-source-index.mjs (--check | --check-sources | --trusted-write | --repin-contract-authority) ' +
732
- '[--source published|workspace] [--kernel-root <absolute-checkout>] ' +
733
- '[--repo <repository> [--at <40-hex-commit>]]',
734
- );
771
+ throw new Error(USAGE);
772
+ }
773
+ if (kernelCommit !== undefined && ![REPIN_FLAG, '--trusted-write'].includes(flag)) {
774
+ throw new Error(`${KERNEL_COMMIT_FLAG} states the commit of a new contract authority: ${flag} chooses none\n${USAGE}`);
735
775
  }
736
776
  const schemas = await loadKernelDistributionSchemas();
737
777
  if (flag === REPIN_FLAG) {
738
778
  if (repoScope !== undefined || exactCommit !== undefined) {
739
779
  throw new Error(`${REPIN_FLAG} replaces the contract authority only: it takes no --repo or --at`);
740
780
  }
741
- const { path, previous, current } = await repinContractAuthority(schemas, kernelRoot);
781
+ if (kernelCommit === undefined) {
782
+ throw new Error(`${REPIN_FLAG} requires ${KERNEL_COMMIT_FLAG} <40-hex-commit>: the commit the authority moves to is stated, never read from the checkout\n${USAGE}`);
783
+ }
784
+ const { path, previous, current } = await repinContractAuthority(schemas, kernelRoot, kernelCommit);
742
785
  console.log(
743
786
  `[runtime-image-source-index] wrote ${path} (contract authority ${previous?.sourceCommit ?? 'absent'} -> ${current.sourceCommit}; every repository pin and subject preserved)`,
744
787
  );
@@ -784,6 +827,7 @@ async function main() {
784
827
  {
785
828
  biomeIndex: readJson(biomeIndexPath()),
786
829
  platformServiceIndex: readJson(join(dataRoot(), 'platform-service-index.json')),
830
+ kernelCommit,
787
831
  },
788
832
  ),
789
833
  );
@@ -1,7 +1,11 @@
1
1
  // catalogue-fetch — read the catalogue layer of an offering artifact, by digest.
2
2
  //
3
- // The reference is `oci://<registry>/<repository>@sha256:<digest>`: a tag is
4
- // refused, because a catalogue is pinned, not followed. The manifest and the
3
+ // The reference is `oci://<registry root>/<channel segment>/xema-edition-<id>@sha256:<digest>`
4
+ // (it comes from catalogue.pin.json): a tag is refused, because a catalogue is pinned, not
5
+ // followed. The repository is parsed by `parseEditionRepository`; a reference whose
6
+ // repository channel is not the channel being resolved is refused before any fetch, and a
7
+ // catalogue that states another channel than the repository it came from is refused before
8
+ // it is used. The manifest and the
5
9
  // layer are fetched with the one OCI client (`oras.mjs`, the pinned oras), which verifies that
6
10
  // every body hashes to the digest it was asked for. Credentials are named by
7
11
  // environment variable (never inlined): `--catalogue-username` with
@@ -10,6 +14,7 @@
10
14
  import { Buffer } from 'node:buffer';
11
15
 
12
16
  import { CATALOGUE_FILE, CatalogueError, parseCatalogue } from './catalogue.mjs';
17
+ import { parseEditionRepository } from './edition-artifact.mjs';
13
18
  import { EDITION_ARTIFACT_TYPE } from './edition-constants.mjs';
14
19
  import { OrasRegistry } from './oras.mjs';
15
20
  import { splitImageRef } from './registry-credentials.mjs';
@@ -18,14 +23,23 @@ const REF = /^oci:\/\/([^@]+)@(sha256:[a-f0-9]{64})$/;
18
23
  const CATALOGUE_MAX_BYTES = 32 * 1024 * 1024;
19
24
  const TITLE = 'org.opencontainers.image.title';
20
25
 
21
- /** `{ authority, repository, digest }` of an `oci://<repository>@sha256:<digest>` reference; a tag is refused. */
26
+ /**
27
+ * `{ authority, repository, digest, edition }` of an `oci://<repository>@sha256:<digest>` reference, where
28
+ * `edition` is the parsed repository (`{ registryRoot, channel, distributionId }`); a tag is refused.
29
+ */
22
30
  export function parseCatalogueRef(ref) {
23
31
  const match = REF.exec(String(ref));
24
32
  if (match === null) {
25
- throw new CatalogueError(`--catalogue ${JSON.stringify(ref)} is not oci://<registry>/<repository>@sha256:<64 hex>: a catalogue is pinned by digest, never by tag`);
33
+ throw new CatalogueError(`catalogue reference ${JSON.stringify(ref)} is not oci://<registry>/<repository>@sha256:<64 hex>: a catalogue is pinned by digest, never by tag`);
26
34
  }
27
35
  const { authority, repository } = splitImageRef(match[1]);
28
- return { authority, repository, digest: match[2] };
36
+ let edition;
37
+ try {
38
+ edition = parseEditionRepository(match[1]);
39
+ } catch (error) {
40
+ throw new CatalogueError(`catalogue reference ${JSON.stringify(ref)}: ${error.message}`, { cause: error });
41
+ }
42
+ return { authority, repository, digest: match[2], edition };
29
43
  }
30
44
 
31
45
  /** The credential the client needs: named by env var, or anonymous. */
@@ -41,9 +55,15 @@ export function catalogueCredential({ authority, username, tokenEnv, env = proce
41
55
  return { authority, access: 'authenticated', basicAuth: Buffer.from(`${username}:${token}`, 'utf8').toString('base64') };
42
56
  }
43
57
 
44
- /** Fetch and strictly parse the catalogue of the artifact at `ref`. `client` is injectable for tests. */
45
- export async function fetchCatalogue({ ref, username, tokenEnv, env = process.env, client }) {
46
- const { authority, repository, digest } = parseCatalogueRef(ref);
58
+ /** Fetch and strictly parse the catalogue of the artifact at `ref`, resolving for `channel`. `client` is injectable for tests. */
59
+ export async function fetchCatalogue({ ref, channel, username, tokenEnv, env = process.env, client }) {
60
+ const { authority, repository, digest, edition } = parseCatalogueRef(ref);
61
+ if (edition.channel !== channel) {
62
+ throw new CatalogueError(
63
+ `${ref}: its repository ${authority}/${repository} is a ${edition.channel} repository, but this resolve is for the ${channel} channel; ` +
64
+ 'a catalogue is read only from the repository of the channel being resolved',
65
+ );
66
+ }
47
67
  const registry = client ?? new OrasRegistry({ credential: catalogueCredential({ authority, username, tokenEnv, env }) });
48
68
  const manifest = await registry.getManifestByDigest(repository, digest);
49
69
  if (manifest.isIndex || manifest.document.artifactType !== EDITION_ARTIFACT_TYPE) {
@@ -60,5 +80,12 @@ export async function fetchCatalogue({ ref, username, tokenEnv, env = process.en
60
80
  } catch (error) {
61
81
  throw new CatalogueError(`${ref}: ${CATALOGUE_FILE} is not JSON: ${error.message}`);
62
82
  }
63
- return { catalogue: parseCatalogue(document), digest };
83
+ const catalogue = parseCatalogue(document);
84
+ if (catalogue.channel !== edition.channel) {
85
+ throw new CatalogueError(
86
+ `${ref}: ${CATALOGUE_FILE} states channel ${catalogue.channel}, but it was fetched from the ${edition.channel} repository ${authority}/${repository}; ` +
87
+ 'an artifact whose stated channel differs from its repository is refused',
88
+ );
89
+ }
90
+ return { catalogue, digest };
64
91
  }