@xemahq/xema 0.28.0 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/edition.d.ts.map +1 -1
- package/dist/commands/edition.js +8 -7
- package/dist/commands/edition.js.map +1 -1
- package/dist/edition/verify-image-evidence.bundle.mjs +88 -11
- package/dist/edition/verify-image-evidence.bundle.provenance.json +48 -42
- package/dist/runtime/edition/verb-map.d.ts +2 -1
- package/dist/runtime/edition/verb-map.d.ts.map +1 -1
- package/dist/runtime/edition/verb-map.js +2 -0
- package/dist/runtime/edition/verb-map.js.map +1 -1
- package/dist/runtime/runner/chart-set-lock.d.ts +4 -1
- package/dist/runtime/runner/chart-set-lock.d.ts.map +1 -1
- package/dist/runtime/runner/chart-set-lock.js +39 -4
- package/dist/runtime/runner/chart-set-lock.js.map +1 -1
- package/edition/README.md +69 -32
- package/edition/build-biome-index.mjs +1 -1
- package/edition/build-platform-service-index.mjs +1 -1
- package/edition/build-runtime-image-source-index.mjs +64 -20
- package/edition/catalogue-fetch.mjs +36 -9
- package/edition/catalogue-pin.mjs +71 -0
- package/edition/catalogue.mjs +11 -0
- package/edition/chart-set-source-commit.mjs +17 -7
- package/edition/checkout-indexed-source-repositories.mjs +17 -17
- package/edition/customer-flags.mjs +0 -1
- package/edition/develop-customer-edition.mjs +23 -5
- package/edition/develop-edition.mjs +147 -48
- package/edition/develop-freeze.mjs +69 -25
- package/edition/develop-seal.mjs +39 -25
- package/edition/dist-check.mjs +1 -1
- package/edition/edition-artifact.mjs +65 -18
- package/edition/edition-biome-manifests.mjs +2 -3
- package/edition/edition-chart-set.mjs +59 -6
- package/edition/edition-config.mjs +28 -2
- package/edition/edition-constants.mjs +10 -4
- package/edition/edition-contract-parse.mjs +52 -0
- package/edition/edition-contract.json +10 -1
- package/edition/edition-workspace.mjs +12 -9
- package/edition/evidence-trust-policy.mjs +26 -0
- package/edition/external-source-walk.mjs +48 -61
- package/edition/git-source-auth.mjs +104 -25
- package/edition/image-evidence.mjs +20 -10
- package/edition/kernel-contract-authority.mjs +23 -1
- package/edition/lib.mjs +13 -24
- package/edition/pin-source.mjs +6 -1
- package/edition/registry-credentials.mjs +11 -1
- package/edition/release-edition-artifact.mjs +3 -3
- package/edition/resolve-chart-set.mjs +50 -30
- package/edition/resolve-customer-edition.mjs +63 -7
- package/edition/resolve-distribution.mjs +1 -1
- package/edition/resolve-edition.mjs +12 -11
- package/edition/resolve-images.mjs +36 -15
- package/edition/seal-image-digests.mjs +31 -7
- package/edition/source-closure.mjs +1 -2
- package/edition/trust-policy-arming-verb.mjs +68 -0
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chart-set-lock.js","sourceRoot":"","sources":["../../../src/runtime/runner/chart-set-lock.ts"],"names":[],"mappings":";;;
|
|
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
|
|
31
|
-
| `resolve-images.mjs` | data, editions, sources, kernel, cache
|
|
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,
|
|
34
|
-
| `seal-image-digests.mjs` | data, editions, kernel
|
|
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
|
|
37
|
-
| `develop-edition.mjs` | data, editions, sources, kernel, deploy, cache
|
|
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
|
-
| `
|
|
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
|
|
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`
|
|
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
|
|
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/
|
|
77
|
-
- **Chart set.** ONE `chart-set.lock.json` (rostered, `schemaVersion:
|
|
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
|
|
198
|
-
| `xema edition biome-manifests <id> --json` | `--data-root --editions-root --sources-root --
|
|
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
|
|
214
|
-
| `xema edition source-closure` | `--repo <repo> --path <dir> --commit <40 hex>`, roots `--sources-root --
|
|
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> --
|
|
245
|
-
--data-root <dir> --sources-root <dir> --kernel <dir> --cache-dir <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,
|
|
270
|
-
`
|
|
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> --
|
|
291
|
-
--data-root <dir> --editions-root <dir> --sources-root <dir> --kernel <dir> --cache-dir <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.
|
|
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
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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 `<
|
|
311
|
-
catalogue; only the customer's own rows are
|
|
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
|
|
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
|
|
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
|
|
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
|
|
247
|
-
*
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const committed = readJson(runtimeImageSourceIndexPath());
|
|
256
|
+
const committed = existsSync(runtimeImageSourceIndexPath())
|
|
257
|
+
? readJson(runtimeImageSourceIndexPath())
|
|
258
|
+
: undefined;
|
|
255
259
|
if (!committed?.contractAuthority) {
|
|
256
|
-
return buildTrustedKernelContractAuthority({
|
|
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
|
|
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:
|
|
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
|
-
|
|
732
|
-
|
|
733
|
-
|
|
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
|
-
|
|
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>/<
|
|
4
|
-
// refused, because a catalogue is pinned, not
|
|
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
|
-
/**
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// catalogue-pin — `catalogue.pin.json`, the catalogues a customer edition is resolved against.
|
|
2
|
+
//
|
|
3
|
+
// It sits in the SAME directory as the customer's definition file:
|
|
4
|
+
// { "schemaVersion": 1, "develop": "oci://<repository>@sha256:<64 hex>" | null, "release": ... | null }
|
|
5
|
+
// Exactly these keys, both channel keys present; null states that the edition is not produced on
|
|
6
|
+
// that channel. A reference is parsed by `parseCatalogueRef`, and the repository channel of the
|
|
7
|
+
// reference under a key must be that key. This is the only reader of the file: every customer
|
|
8
|
+
// resolve takes the catalogue reference of its channel from here.
|
|
9
|
+
|
|
10
|
+
import { existsSync } from 'node:fs';
|
|
11
|
+
import { dirname, join } from 'node:path';
|
|
12
|
+
|
|
13
|
+
import { parseCatalogueRef } from './catalogue-fetch.mjs';
|
|
14
|
+
import { CatalogueError } from './catalogue.mjs';
|
|
15
|
+
import { EditionChannel } from './edition-constants.mjs';
|
|
16
|
+
import { readStrictJsonFile } from './strict-json.mjs';
|
|
17
|
+
|
|
18
|
+
export const CATALOGUE_PIN_FILE = 'catalogue.pin.json';
|
|
19
|
+
export const CATALOGUE_PIN_SCHEMA_VERSION = 1;
|
|
20
|
+
|
|
21
|
+
const SCHEMA_VERSION_KEY = 'schemaVersion';
|
|
22
|
+
|
|
23
|
+
/** The catalogue pin file that sits beside `definitionFile`. */
|
|
24
|
+
export const cataloguePinFile = (definitionFile) => join(dirname(definitionFile), CATALOGUE_PIN_FILE);
|
|
25
|
+
|
|
26
|
+
/** The strict parse of a pin document read from `file`; returns it unchanged. */
|
|
27
|
+
export function parseCataloguePins(document, file) {
|
|
28
|
+
const channels = Object.values(EditionChannel);
|
|
29
|
+
const expected = [SCHEMA_VERSION_KEY, ...channels].sort().join();
|
|
30
|
+
if (document === null || typeof document !== 'object' || Array.isArray(document) || Object.keys(document).sort().join() !== expected) {
|
|
31
|
+
throw new CatalogueError(`${file} must be exactly {${[SCHEMA_VERSION_KEY, ...channels].join(', ')}}, every key present`);
|
|
32
|
+
}
|
|
33
|
+
if (document[SCHEMA_VERSION_KEY] !== CATALOGUE_PIN_SCHEMA_VERSION) {
|
|
34
|
+
throw new CatalogueError(`${file}: ${SCHEMA_VERSION_KEY} must be ${CATALOGUE_PIN_SCHEMA_VERSION}`);
|
|
35
|
+
}
|
|
36
|
+
for (const channel of channels) {
|
|
37
|
+
const ref = document[channel];
|
|
38
|
+
if (ref === null) {continue;}
|
|
39
|
+
if (typeof ref !== 'string') {
|
|
40
|
+
throw new CatalogueError(`${file}: "${channel}" must be an oci://<repository>@sha256:<64 hex> reference or null`);
|
|
41
|
+
}
|
|
42
|
+
let parsed;
|
|
43
|
+
try {
|
|
44
|
+
parsed = parseCatalogueRef(ref);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
throw new CatalogueError(`${file}: "${channel}": ${error.message}`, { cause: error });
|
|
47
|
+
}
|
|
48
|
+
if (parsed.edition.channel !== channel) {
|
|
49
|
+
throw new CatalogueError(
|
|
50
|
+
`${file}: "${channel}" pins ${ref}, whose repository is a ${parsed.edition.channel} repository; a ${channel} catalogue is pinned in a ${channel} repository`,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return document;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The catalogue reference pinned for `channel` beside `definitionFile`; a null pin or a missing file is refused. */
|
|
58
|
+
export function readCataloguePin({ definitionFile, channel }) {
|
|
59
|
+
const file = cataloguePinFile(definitionFile);
|
|
60
|
+
if (!existsSync(file)) {
|
|
61
|
+
throw new CatalogueError(`${file} does not exist: a customer edition pins its catalogues in ${CATALOGUE_PIN_FILE}, beside the definition`);
|
|
62
|
+
}
|
|
63
|
+
const ref = parseCataloguePins(readStrictJsonFile(file), file)[channel];
|
|
64
|
+
if (ref === undefined) {
|
|
65
|
+
throw new CatalogueError(`${file}: ${JSON.stringify(channel)} is not a channel (${Object.values(EditionChannel).join(' | ')})`);
|
|
66
|
+
}
|
|
67
|
+
if (ref === null) {
|
|
68
|
+
throw new CatalogueError(`${file}: no ${channel} catalogue is pinned for this edition`);
|
|
69
|
+
}
|
|
70
|
+
return ref;
|
|
71
|
+
}
|
package/edition/catalogue.mjs
CHANGED
|
@@ -133,3 +133,14 @@ export function writeCatalogue(outDir, catalogue) {
|
|
|
133
133
|
writeFileSync(path, serializeJson(catalogue));
|
|
134
134
|
return path;
|
|
135
135
|
}
|
|
136
|
+
|
|
137
|
+
const KERNEL_COMMIT = /^[0-9a-f]{40}$/;
|
|
138
|
+
|
|
139
|
+
/** The commit the catalogue's contract authority was taken at (40 hex); read without the kernel schemas, which are not checked out yet where this is used. */
|
|
140
|
+
export function contractAuthoritySourceCommit(catalogue) {
|
|
141
|
+
const commit = catalogue.contractAuthority.sourceCommit;
|
|
142
|
+
if (typeof commit !== 'string' || !KERNEL_COMMIT.test(commit)) {
|
|
143
|
+
throw new CatalogueError(`${CATALOGUE_FILE}: contractAuthority.sourceCommit is not a 40-hex commit: ${JSON.stringify(commit)}`);
|
|
144
|
+
}
|
|
145
|
+
return commit;
|
|
146
|
+
}
|