@xemahq/edition-tooling 0.0.0-stage → 0.1.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/LICENSE +175 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +47 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/edition.d.ts +4 -0
- package/dist/commands/edition.d.ts.map +1 -0
- package/dist/commands/edition.js +58 -0
- package/dist/commands/edition.js.map +1 -0
- package/dist/edition/verify-image-evidence.bundle.mjs +72104 -0
- package/dist/edition/verify-image-evidence.bundle.provenance.json +4119 -0
- package/dist/lib/exit-codes.d.ts +6 -0
- package/dist/lib/exit-codes.d.ts.map +1 -0
- package/dist/lib/exit-codes.js +21 -0
- package/dist/lib/exit-codes.js.map +1 -0
- package/dist/runtime/edition/run-entry.d.ts +10 -0
- package/dist/runtime/edition/run-entry.d.ts.map +1 -0
- package/dist/runtime/edition/run-entry.js +49 -0
- package/dist/runtime/edition/run-entry.js.map +1 -0
- package/dist/runtime/edition/verb-map.d.ts +70 -0
- package/dist/runtime/edition/verb-map.d.ts.map +1 -0
- package/dist/runtime/edition/verb-map.js +166 -0
- package/dist/runtime/edition/verb-map.js.map +1 -0
- package/edition/README.md +291 -0
- package/edition/adapter-bundled-versions.mjs +128 -0
- package/edition/adapter-host-versions.mjs +76 -0
- package/edition/assemble-producer-input.mjs +1423 -0
- package/edition/assemble-third-party-input.mjs +348 -0
- package/edition/authored-source-check.mjs +141 -0
- package/edition/authority-binding.mjs +23 -0
- package/edition/authorization-base.mjs +357 -0
- package/edition/biome-content.mjs +327 -0
- package/edition/build-biome-index.mjs +180 -0
- package/edition/build-ledger-completeness.mjs +67 -0
- package/edition/build-ledger.mjs +107 -0
- package/edition/build-platform-service-index.mjs +292 -0
- package/edition/build-runtime-image-source-index.mjs +1197 -0
- package/edition/built-verifier.mjs +55 -0
- package/edition/candidate-edition.mjs +735 -0
- package/edition/canonical-evidence-json.mjs +82 -0
- package/edition/canonical-repository-url.mjs +61 -0
- package/edition/catalogue-edition.mjs +49 -0
- package/edition/catalogue-evidence.mjs +228 -0
- package/edition/catalogue-fetch.mjs +124 -0
- package/edition/catalogue-lock-field.mjs +41 -0
- package/edition/catalogue-pin.mjs +71 -0
- package/edition/catalogue.mjs +237 -0
- package/edition/chart-set-source-commit.mjs +97 -0
- package/edition/check-committed-image-evidence.mjs +412 -0
- package/edition/checkout-indexed-source-repositories.mjs +283 -0
- package/edition/collected-refusals.mjs +59 -0
- package/edition/comment-blanker.mjs +75 -0
- package/edition/connector-source-authority.mjs +60 -0
- package/edition/contract-authority.mjs +122 -0
- package/edition/cosign-certificate-constraints.mjs +50 -0
- package/edition/cross-repo-build-inputs.mjs +158 -0
- package/edition/customer-definition.mjs +72 -0
- package/edition/customer-edition-verbs.mjs +610 -0
- package/edition/customer-flags.mjs +15 -0
- package/edition/definition-checkout.mjs +96 -0
- package/edition/develop-build-record.mjs +122 -0
- package/edition/develop-catalogue-free-resolve.mjs +282 -0
- package/edition/develop-catalogue-free-vendor.mjs +118 -0
- package/edition/develop-channel-evidence.mjs +23 -0
- package/edition/develop-channel-record.mjs +49 -0
- package/edition/develop-channel-satellites.mjs +65 -0
- package/edition/develop-chart-roster.mjs +96 -0
- package/edition/develop-chart-set.mjs +147 -0
- package/edition/develop-current-resolution.mjs +29 -0
- package/edition/develop-edition-invariants.mjs +142 -0
- package/edition/develop-edition-resolve.mjs +250 -0
- package/edition/develop-edition.mjs +332 -0
- package/edition/develop-engine-image.mjs +97 -0
- package/edition/develop-freeze.mjs +304 -0
- package/edition/develop-git-bundles.mjs +296 -0
- package/edition/develop-graph.mjs +34 -0
- package/edition/develop-image-coordinate.mjs +85 -0
- package/edition/develop-image-probe.mjs +122 -0
- package/edition/develop-image-record.mjs +260 -0
- package/edition/develop-resolve-report.mjs +159 -0
- package/edition/develop-select-records.mjs +90 -0
- package/edition/develop-selection-line.mjs +29 -0
- package/edition/develop-selection-pins.mjs +36 -0
- package/edition/develop-snapshot-inputs.mjs +112 -0
- package/edition/develop-store-root.mjs +20 -0
- package/edition/dispatched-runtime-components.mjs +172 -0
- package/edition/dist-check.mjs +1174 -0
- package/edition/distribution-evidence-set.mjs +1064 -0
- package/edition/distribution-paths.mjs +191 -0
- package/edition/edition-artifact.mjs +529 -0
- package/edition/edition-biome-manifests.mjs +85 -0
- package/edition/edition-ceiling.mjs +182 -0
- package/edition/edition-channel.mjs +27 -0
- package/edition/edition-chart-set.mjs +104 -0
- package/edition/edition-config.mjs +374 -0
- package/edition/edition-constants.mjs +110 -0
- package/edition/edition-contract-parse.mjs +179 -0
- package/edition/edition-contract.json +100 -0
- package/edition/edition-image-seal.mjs +115 -0
- package/edition/edition-kind.mjs +10 -0
- package/edition/edition-list.mjs +35 -0
- package/edition/edition-locks.mjs +78 -0
- package/edition/edition-pin-verbs.mjs +183 -0
- package/edition/edition-pin.mjs +17 -0
- package/edition/edition-pinned-inputs.mjs +116 -0
- package/edition/edition-protocol.mjs +285 -0
- package/edition/edition-publisher.mjs +21 -0
- package/edition/edition-seal.mjs +257 -0
- package/edition/edition-source-pins.mjs +211 -0
- package/edition/edition-unpin.mjs +16 -0
- package/edition/edition-workspace.mjs +205 -0
- package/edition/evidence-checkout.mjs +439 -0
- package/edition/evidence-grammars.mjs +18 -0
- package/edition/evidence-input-mode.mjs +136 -0
- package/edition/evidence-trust-pin.mjs +392 -0
- package/edition/evidence-trust-policy.mjs +374 -0
- package/edition/external-source-walk.mjs +486 -0
- package/edition/first-party-source-ids.mjs +62 -0
- package/edition/fleet-coordinate-finality.mjs +363 -0
- package/edition/fulcio-certificate-extensions.mjs +278 -0
- package/edition/git-json.mjs +54 -0
- package/edition/git-source-auth.mjs +131 -0
- package/edition/image-delivery-entitlement-descriptor.mjs +106 -0
- package/edition/image-evidence-replay-state.mjs +242 -0
- package/edition/image-evidence.mjs +1328 -0
- package/edition/image-lock-service-rows.mjs +26 -0
- package/edition/image-subject-identity.mjs +19 -0
- package/edition/images-from-record.mjs +216 -0
- package/edition/indexes-at-pins.mjs +13 -0
- package/edition/infra-images-lock.mjs +105 -0
- package/edition/kernel-capability-domain.mjs +30 -0
- package/edition/kernel-contract-authority.mjs +526 -0
- package/edition/kernel-contract-source-path-set.mjs +96 -0
- package/edition/kernel-package.mjs +237 -0
- package/edition/kernel-pin-source.mjs +63 -0
- package/edition/kernel-protocol-choice.mjs +239 -0
- package/edition/kernel-signer-kind.mjs +23 -0
- package/edition/lib.mjs +3322 -0
- package/edition/locked-bundle-signer.mjs +297 -0
- package/edition/map-with-concurrency.mjs +19 -0
- package/edition/no-follow-file.mjs +94 -0
- package/edition/oci-evidence-verbs.mjs +421 -0
- package/edition/oci-image-source-evidence.mjs +377 -0
- package/edition/operation-grant-admission.mjs +285 -0
- package/edition/operation-requirements.mjs +282 -0
- package/edition/oras-process.mjs +35 -0
- package/edition/oras.mjs +664 -0
- package/edition/pin-source.mjs +242 -0
- package/edition/pinned-cosign.mjs +86 -0
- package/edition/plan-image-evidence-refresh.mjs +428 -0
- package/edition/platform-lock-readability.mjs +903 -0
- package/edition/platform-service-class.mjs +85 -0
- package/edition/platform-service-ownership.mjs +91 -0
- package/edition/platform-service-source-authority.mjs +105 -0
- package/edition/positive-closure.mjs +162 -0
- package/edition/previous-image-lock.mjs +25 -0
- package/edition/produce-distribution-completeness-evidence.mjs +317 -0
- package/edition/produce-edition-catalogue-evidence.mjs +252 -0
- package/edition/produce-image-delivery-entitlement-evidence.mjs +302 -0
- package/edition/produce-image-source-evidence.mjs +788 -0
- package/edition/producer-evidence-bundle.mjs +201 -0
- package/edition/producer-evidence-index-file.mjs +4 -0
- package/edition/record-refused.mjs +11 -0
- package/edition/registry-client-factory.mjs +21 -0
- package/edition/registry-credentials.mjs +225 -0
- package/edition/release-catalogue.mjs +48 -0
- package/edition/release-channel-guard.mjs +108 -0
- package/edition/release-edition-artifact.mjs +152 -0
- package/edition/release-edition.mjs +54 -0
- package/edition/remedy-command.mjs +89 -0
- package/edition/repin-evidence-trust.mjs +109 -0
- package/edition/required-resource-provider.mjs +58 -0
- package/edition/resolve-chart-set.mjs +376 -0
- package/edition/resolve-customer-edition.mjs +374 -0
- package/edition/resolve-distribution.mjs +2642 -0
- package/edition/resolve-edition.mjs +192 -0
- package/edition/resolve-images.mjs +2344 -0
- package/edition/resolve-infra-images.mjs +88 -0
- package/edition/seal-image-digests.mjs +954 -0
- package/edition/sealed-distribution-lock.mjs +22 -0
- package/edition/sealed-evidence-accept.mjs +82 -0
- package/edition/shallow-checkout.mjs +18 -0
- package/edition/sigstore-bundle-media-type.mjs +4 -0
- package/edition/source-authority.mjs +713 -0
- package/edition/source-closure.mjs +115 -0
- package/edition/store-publisher-trust.mjs +40 -0
- package/edition/strict-json.mjs +121 -0
- package/edition/string-order.mjs +5 -0
- package/edition/third-party-admission.mjs +118 -0
- package/edition/third-party-bundle-registry.mjs +349 -0
- package/edition/third-party-certificate-extensions.mjs +58 -0
- package/edition/third-party-image-pins.mjs +41 -0
- package/edition/third-party-pins-trusted.mjs +24 -0
- package/edition/third-party-producer-evidence.mjs +76 -0
- package/edition/third-party-source-check.mjs +28 -0
- package/edition/third-party-source-context.mjs +41 -0
- package/edition/third-party-source-evidence.mjs +326 -0
- package/edition/third-party-source-flags.mjs +148 -0
- package/edition/third-party-source-pins.mjs +270 -0
- package/edition/third-party-source-uniqueness.mjs +54 -0
- package/edition/third-party-source-walk.mjs +118 -0
- package/edition/third-party-source-writer.mjs +228 -0
- package/edition/third-party-subjects.mjs +128 -0
- package/edition/trust-policy-arming-verb.mjs +60 -0
- package/edition/trust-policy-arming.mjs +144 -0
- package/edition/trust-policy-image-ownership.mjs +85 -0
- package/edition/trusted-executable.mjs +166 -0
- package/edition/verifier-binding.mjs +45 -0
- package/edition/verifier-digest-pin.mjs +17 -0
- package/edition/verifier-files.mjs +8 -0
- package/edition/verifier-kernel-schemas.mjs +22 -0
- package/edition/verifier-provenance.mjs +64 -0
- package/edition/verify-catalogue-evidence.mjs +247 -0
- package/edition/verify-image-evidence.mjs +493 -0
- package/edition/verify-image-platforms.mjs +523 -0
- package/edition/why.mjs +56 -0
- package/edition/workspace-sections.mjs +287 -0
- package/package.json +83 -4
- package/README.md +0 -3
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
# edition library
|
|
2
|
+
|
|
3
|
+
Resolves, checks, seals and develop-snapshots an edition: a named, positive set
|
|
4
|
+
of biomes and platform services, from its definition (`<id>/xema-distribution.json`)
|
|
5
|
+
to its locks (`distribution`, `authorization`, `images`, `chart-set`). Plain ESM
|
|
6
|
+
`.mjs`, run with `node`; tests run with `node --test`.
|
|
7
|
+
|
|
8
|
+
The library derives nothing from where it sits. Every directory is stated by the
|
|
9
|
+
caller, once, and a root that was not stated throws, naming its flag. There are
|
|
10
|
+
no defaults.
|
|
11
|
+
|
|
12
|
+
## Roots
|
|
13
|
+
|
|
14
|
+
| Flag | Meaning |
|
|
15
|
+
| --- | --- |
|
|
16
|
+
| `--data-root` | catalogue indices (`biome-index.json`, `platform-service-index.json`, `runtime-image-source-index.json`), `chart-set.lock.json`, and `edition-workspace.json` |
|
|
17
|
+
| `--editions-root` | the `<edition-id>/` definitions and locks |
|
|
18
|
+
| `--sources-root` | source checkouts at `repos/<r>` and `submodules/<r>` |
|
|
19
|
+
| `--kernel` | the BUILT `kernel-contracts` package directory (vendor verbs only; a customer verb takes none) |
|
|
20
|
+
| `--deploy-root` | the `xema-deploy` checkout |
|
|
21
|
+
| `--cache-dir` | a writable directory for fetched artifacts |
|
|
22
|
+
|
|
23
|
+
## Entry scripts
|
|
24
|
+
|
|
25
|
+
Each takes the roots below plus its own arguments (`--check`, an edition id, ...).
|
|
26
|
+
|
|
27
|
+
| Script | Roots |
|
|
28
|
+
| --- | --- |
|
|
29
|
+
| `resolve-distribution.mjs` | data, editions, sources, kernel |
|
|
30
|
+
| `resolve-images.mjs` | data, editions, sources, kernel, cache |
|
|
31
|
+
| `resolve-chart-set.mjs` | data, editions, kernel |
|
|
32
|
+
| `edition-biome-manifests.mjs` | data, editions, sources, kernel |
|
|
33
|
+
| `seal-image-digests.mjs` | data, editions, kernel |
|
|
34
|
+
| `verify-image-platforms.mjs` | data, editions, kernel |
|
|
35
|
+
| `dist-check.mjs` | data, editions, sources, kernel, deploy, cache |
|
|
36
|
+
| `authored-source-check.mjs` (`xema-edition check-authored`) | data, editions, sources, kernel |
|
|
37
|
+
| `develop-edition.mjs` | data, editions, sources, kernel, deploy, cache |
|
|
38
|
+
| `release-edition.mjs` (`xema-edition release`) | per verb: `freeze-sources` data; `target` none; `check-existing` data, cache; `resolve` data, editions, sources, cache |
|
|
39
|
+
| `release-edition-artifact.mjs` (`xema-edition artifact`) | data, editions, kernel |
|
|
40
|
+
| `trust-policy-arming-verb.mjs` (`xema-edition arming`) | data, kernel |
|
|
41
|
+
| `contract-authority.mjs` (`xema-edition contract-authority`) | none (the `@xemahq/contracts` this CLI ships) |
|
|
42
|
+
| `build-biome-index.mjs`, `build-platform-service-index.mjs`, `build-runtime-image-source-index.mjs` | data, sources, kernel |
|
|
43
|
+
| `checkout-indexed-source-repositories.mjs` | data, sources |
|
|
44
|
+
| `why.mjs` | editions, kernel |
|
|
45
|
+
| `repin-evidence-trust.mjs` | data, kernel |
|
|
46
|
+
| `verify-image-evidence.mjs` (run as the BUILT bundle, `xema-edition verify-evidence`) | data, editions, kernel |
|
|
47
|
+
|
|
48
|
+
## `<data-root>/edition-workspace.json`
|
|
49
|
+
|
|
50
|
+
The deployment's coordinates, so the library names no organisation or registry.
|
|
51
|
+
Read strictly: every key is required, an unknown key or a duplicate key is refused.
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"schemaVersion": 1,
|
|
56
|
+
"imageRegistry": "registry.example.com/acme",
|
|
57
|
+
"chartRegistry": "oci://registry.example.com/acme/charts",
|
|
58
|
+
"chartPublishers": [{ "index": "<chart-set-index-name>", "repo": "<owner>/<repo>" }],
|
|
59
|
+
"sourceRepositoryUrlPrefix": "https://git.example.com/acme/",
|
|
60
|
+
"sourceAuthUsername": "x-access-token",
|
|
61
|
+
"evidenceTrustPolicy": { "policy": "image-evidence-trust-policy.json", "pin": "image-evidence-trust-policy.pin.json" }
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
- `imageRegistry`: `<host>/<namespace>` where first-party images are addressed.
|
|
66
|
+
- `chartRegistry`, `chartPublishers`: where chart-set indexes and charts are published. Vendor only; declare both or neither.
|
|
67
|
+
- `sourceRepositoryUrlPrefix`, `sourceAuthUsername`: the prefix every first-party source URL must match and the git username source tokens authenticate as. The token is read from `EVIDENCE_REPOSITORIES_TOKEN` and sent only to repositories under the prefix.
|
|
68
|
+
- `evidenceTrustPolicy`: `{policy, pin}` (data-root-relative paths) or `null` for a data root that declares no policy. A `null` data root serves develop editions only.
|
|
69
|
+
|
|
70
|
+
## Common commands
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
xema-edition resolve <id> [--check] # write or verify the lock set
|
|
74
|
+
xema-edition resolve-digests <id> # write the published image digests into the locks
|
|
75
|
+
xema-edition seal --resolved <repo>@sha256:<d> --manifest <file> --artifact <dir> --out <dir> --cosign <abs> --resolve-workflow-sha <40-hex> # seal one pulled resolved RELEASE edition: cosign-verified offline (contract issuer; publisher only from edition-workspace.json editionPublishers.resolved.release, in the repository its provenance records), kind sealed, sealOf; armed only
|
|
76
|
+
xema-edition check # committed locks and indices are current
|
|
77
|
+
xema-edition check-authored # the authored source, offline: definitions, indexes, policies, chart-set pin
|
|
78
|
+
xema-edition why <id> <biome> # why a biome is in an edition
|
|
79
|
+
xema-edition list --editions-root <dir>
|
|
80
|
+
xema-edition locks <id> --channel <release|develop> # validated locks as JSON; an unsealed distribution lock is read on develop only
|
|
81
|
+
xema-edition artifact <id> --channel release --registry <ns> --coordinate <tag> --engine-image <repo>@sha256:<digest> --out <dir>
|
|
82
|
+
xema-edition develop <freeze-sources|resolve|target|check-existing> ...
|
|
83
|
+
# develop tag = sha256 of { cli, engine, pins, sourceImages }; target and check-existing take --engine-image, and a customer's --source-image
|
|
84
|
+
xema-edition develop select-records --branch develop --channel <tag> [--registry <root>, accepted and unused] --out <file> # the build record per graph member that the registry channel tag points at (`build-records/<repo>:<channel>`, read by digest, never signature-verified: the registry write ACL is the trust) -> { pins, snapshot, channel, images, currentResolution }, written once (a member whose firstPartySources entry declares `currentResolution: "<ticket>"`, xema-docs until XEMA-2373, is skipped with no registry read, listed in currentResolution and printed as `xema-docs: current resolution (leaves the edition, XEMA-2373)`); images[] entries carry `member`; a missing or unreadable record refuses the run by member
|
|
85
|
+
xema-edition develop resolve <id> --pins <file> --out <dir> --registry <root> --engine-image <repo>@sha256:<64 hex> --images-from <record> --channel <tag> --selection <file> (--previous-lock <lock> --previous-lock-sha256 <hex> | --first-seal) <roots> # the channel (dev/prod) vendor resolve: the engine is the caller's digest, every first-party image the record's digest, satellites checked only for consistency with the producer records the selection pinned; no cosign, no rehash, no bundle attestation, no evidence-trust policy (descriptor records not-armed). --engine-repository, --engine-signer and --cosign are refused by name
|
|
86
|
+
xema-edition develop resolve --definition-at <owner>/<repo>,<path>,<dataRoot> --offering <owner>/<repo>,<path>,<dataRoot> --pins <file> --out <dir> --registry <root> --engine-repository <registry>/<path> --engine-signer <identity> --cosign <abs path> --offering-previous-lock <lock> --offering-previous-lock-sha256 <hex> (--previous-lock <lock> --previous-lock-sha256 <hex> | --first-seal) [--source-image ...] # catalogue-free customer resolve (official lane, verified: the vendor step is an internal entry, develop-catalogue-free-vendor.mjs, that finds and cosign-verifies the engine, probes and attests every image and bundle): --pins is the frozen vendor pins plus "definition-repository" (the definition's commit); the vendor step resolves the offering at the vendor pins under the offering's id and previous seal (the frozen offering lock), the customer step reads the definition at its commit and seals its own composed rows against the customer's previous lock. Reads no heads, pushes nothing; the same frozen inputs give the same bytes
|
|
87
|
+
xema-edition develop image-record <id> --selection <file> --pins <file> --out <file> --channel <tag> [--registry <root>, accepted and unused] # the edition's image build record (what `resolve --images-from` reads) from a select-records selection: subjects are enumerated with no hash computed; each takes the digest AND contentHash its develop repository's selected record states (selected commit must equal the snapshot's) and goes in `satellites`. Declared exception (XEMA-2373): a current-resolution member (xema-docs) has no build record, so its subjects are hashed and probed and go in `images`. Platforms read by digest; any gap refuses and writes nothing; written once
|
|
88
|
+
xema-edition develop check-overrides --overrides <json> --data-root <dir> # data root only
|
|
89
|
+
xema-edition ceiling --distribution <id> --at <distributions sha> --producer-commit <owner/repo>=<sha> [...] --data-root <dir> --sources-root <dir> --kernel <dir> # the dev ceiling (DevelopCeilingSchema from @xemahq/contracts/develop-ceiling): selection, origin, repo and trustCeiling from the definition and indexes at --at; version, services, runtimeComponents and serviceProcessFacts from each producer's xema-biome.json and xema-biome.index.json at its --producer-commit; a missing commit or manifest refuses, with no fallback
|
|
90
|
+
xema-edition chart-set <resolve|source-commit> ...
|
|
91
|
+
xema-edition arming --channel <release|develop> --data-root <dir> --kernel <dir>
|
|
92
|
+
xema-edition verify-evidence <id> --descriptor <abs path> [--json]
|
|
93
|
+
xema-edition evidence <assemble-input|check-committed|plan-refresh|statement|assemble-set|oci> ...
|
|
94
|
+
xema-edition contract-authority > contract-authority.json # the full kernel contract authority of the @xemahq/contracts this CLI ships, canonical JSON: the --contract-authority <file> of a publisher's public-input run, no root flags (see Third-party sources)
|
|
95
|
+
xema-edition evidence assemble-third-party-input --bundle <oci repository>@sha256:<hex> --contract-authority <file> --image <image repository>@sha256:<hex> --subject-id <component image name> --provenance <absolute exported SLSA v1 predicate> --registry-credentials <file> --output <path> [--ttl-seconds <n>] # a third-party publisher's producer input, no root flags (see Third-party sources)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Results are JSON on stdout and diagnostics on stderr. Exit 0 is success and 1 is a refusal. `xema-edition <verb> --help` names the roots and flags each verb takes.
|
|
99
|
+
|
|
100
|
+
## Authored-source check
|
|
101
|
+
|
|
102
|
+
`xema-edition check-authored` grades what is authored, offline, and reads no release state. `xema-edition check` is the full release-path check; the authored check is a separate entry, not that check with parts turned off. It resolves no edition.
|
|
103
|
+
|
|
104
|
+
It refuses on:
|
|
105
|
+
|
|
106
|
+
- the kernel's edition protocol;
|
|
107
|
+
- `biome-index.json`: immutable entries, deterministic order, no duplicate source identity;
|
|
108
|
+
- `platform-service-index.json` and `runtime-image-source-index.json`, validated offline;
|
|
109
|
+
- the evidence trust policy, its pin and its arming (an unarmed policy counts the editions that hold a release lock, by presence only);
|
|
110
|
+
- the data-root readers (store source repositories, publisher trust);
|
|
111
|
+
- the fleet chart-set pin and the pinned kernel's authorization composer;
|
|
112
|
+
- the ownership self-tests: the kernel's capability-domain reader refuses two owners of one domain and an invalid domain, and counts no unlisted domain as first-party;
|
|
113
|
+
- every edition definition's structure: the kernel parses it, `sourcePins` included, and it composes.
|
|
114
|
+
|
|
115
|
+
It reads no lock content (the committed `distribution`, `images`, `authorization` and per-edition `chart-set` locks; the trust-arming rule counts lock files by presence) and no source checkout at a pinned commit. The sources root is read only for the kernel contract checkout the runtime-image-source index check verifies its authority against. `--check-sources` is refused: it is the release check's source audit.
|
|
116
|
+
|
|
117
|
+
## Develop graph
|
|
118
|
+
|
|
119
|
+
The develop graph is the data root's first-party repositories, declared in `edition-workspace.json`:
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
"firstPartySources": {
|
|
123
|
+
"repos/xema-base": { "sourceId": "xema-base", "repositoryUrl": "https://git.example.com/acme/xema-base.git", "imageOrigin": "workspace" },
|
|
124
|
+
"submodules/xema-host-web": { "sourceId": "xema-host-web", "repositoryUrl": "https://git.example.com/acme/xema-host-web.git", "imageOrigin": "satellite" }
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
- Each key is a repository handle (`repos/<x>` or `submodules/<x>`); `sourceId` is its kebab-case source id; `repositoryUrl` is canonical HTTPS under `sourceRepositoryUrlPrefix`. Both are unique. The retired string form (`"repos/x": "x"`) is refused with `RetiredFirstPartySourceShapeError`.
|
|
129
|
+
- `developImageRegistry` (required) is the source's develop store root, a lowercase `<host[:port]>/<path>` with no scheme, tag, digest, trailing slash or trailing `/dev` (e.g. `registry.example.com/acme-dev`). It replaces `<host>/<owner>` in every develop ref of the source: the develop image of a subject whose release ref is `<host>/<owner>/<name...>` is `<root>/dev/<name...>` (`developImageRefFor`), and the build records of member `<name>` are `<root>/build-records/<name>` (`select-records` and `image-record`'s satellite verifier read them there; `--registry` is still accepted on both for one release, so a workflow can drop it first, and is unused). A source whose develop images still live beside its release images declares that owner root explicitly; a missing key is refused naming the source. Structurally a develop ref is any ref with a `dev` segment after the root.
|
|
130
|
+
- `imageOrigin` says where the source's images come from on the everyday lane: `workspace` (built by the job in the aggregator workspace) or `satellite` (a producer outside it, such as `submodules/xema-host-web`, that publishes its own signed develop digest). The parse accepts a source without it and fills in no default; `--images-from` refuses such a source (see below).
|
|
131
|
+
- `xema-edition develop freeze-graph` pins every declared repository at the `develop` head of its declared URL; overrides may name any of them. With `--member-pins <selection file>` (a select-records selection; only `.pins`, 40-hex, is read) each member is pinned at its selected record commit instead: the selection must pin exactly the graph minus its `currentResolution` members (equal to the graph members' declared `currentResolution`; a missing or extra member is refused by name), a `currentResolution` member (xema-docs, XEMA-2373) is pinned at its develop head, and `--member-pins` with `--overrides` is refused. The snapshot's `repositoryPins` (`develop resolve`, `checkout-indexed-source-repositories.mjs --snapshot-pins`) are exactly these repositories. The runtime image source index's `repositoryPins` are release pins and are not read on develop.
|
|
132
|
+
- A first-party repository is in the develop graph before any release pins it. The converse is checked offline: every `repositoryPins` entry must be declared first-party at the same `repositoryUrl`.
|
|
133
|
+
|
|
134
|
+
## Develop images from a build record
|
|
135
|
+
|
|
136
|
+
`xema-edition develop resolve <id> ... --images-from <file>` takes every first-party image of the edition from the everyday job's build record instead of the registry probe: no probe, no rehash, and no digest read from the registry. The record (`images-from-record.mjs`, schema version 1, strict: unknown keys refused, each list sorted by `subject`, a subject in at most one entry):
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"schemaVersion": 1,
|
|
141
|
+
"images": [
|
|
142
|
+
{
|
|
143
|
+
"subject": "service:biome-host-api",
|
|
144
|
+
"repository": "registry.example.com/acme/dev/biome-host-api",
|
|
145
|
+
"contentHash": "<64 hex: the image-input hash, the tag>",
|
|
146
|
+
"digest": "sha256:<64 hex: the manifest or index digest>",
|
|
147
|
+
"platforms": [{ "os": "linux", "architecture": "amd64" }],
|
|
148
|
+
"sourceSha": "<40 hex>"
|
|
149
|
+
}
|
|
150
|
+
],
|
|
151
|
+
"satellites": [ { "subject": "host:xema-host-web", "...": "the same six fields" } ]
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
- `subject` is `<kind>:<id>`, the key of the subject in `images.lock.json`. `repository` is the subject's develop repository. `platforms` must include every platform the edition requires. `sourceSha` must be the commit the snapshot pins for the subject's repository.
|
|
156
|
+
- `images` are the images the job built or reused by content-hash tag. `satellites` are digests of first-party sources the job does not build (for example `submodules/xema-host-web`), taken from the producer's build record that the channel tag points at. With `--channel` each satellite is checked only for consistency: its producer's build record must list the exact `repository` and `digest` at `sourceSha`. The record is the one `select-records` pinned (the selection's `snapshot` run), read by its own record tag `build-records/<repo>:<branch>-<run>-<attempt>`, so the channel tag moving after selection (a newer green build) does not affect the run. `image-record` and `resolve` both take the selection (`--selection <file>`, required) and never read the channel tag for it. No signature is verified: on the channel lane the registry write ACL is the trust. The records live under `--registry`'s `build-records/<repo>`.
|
|
157
|
+
- Coverage is exact: a subject missing from the record, an entry that is not a first-party subject of the edition, or any contradicted field refuses the resolve, all named at once, with no probe fallback. `contentHash` and `sourceSha` are written to the lock as given; the lock's `imageRef` and `imageDigest` are the record's `repository` and `digest`. A third-party subject is not recorded and keeps its pinned seal.
|
|
158
|
+
- Which list an entry sits in is not the record's choice. Each entry must be in the list its source's declared `imageOrigin` names: `workspace` sources in `images`, `satellite` sources in `satellites`. An entry in the other list (for example a satellite listed under `images`, which would skip the channel consistency check), or an entry whose source declares no `imageOrigin`, refuses the resolve, with every problem named at once.
|
|
159
|
+
- The develop target tag takes the record's digest (sha256 of its canonical JSON, entries by subject and platforms sorted) as one more input, `imageBuildRecord`, so the same pins with another satellite digest are another artifact. `develop target --images-from <file>` prints the same target the resolve does.
|
|
160
|
+
- The vendor `develop resolve` requires `--images-from` and `--channel` (refused by name without them). Only the catalogue-free customer resolve's internal vendor step probes the registry. A customer resolve or target refuses the flag.
|
|
161
|
+
|
|
162
|
+
## Develop plane invariants
|
|
163
|
+
|
|
164
|
+
`xema-edition develop resolve` asserts the capability-domain reservation table and biome confinement on the candidate it resolves, against the develop graph it froze, before any later phase. On develop they are asserted only there; `xema-edition check` asserts them over the committed locks.
|
|
165
|
+
|
|
166
|
+
- Reservation: the candidate's `capabilityDomainOwners` is non-empty, accepted by the kernel's reader, and exactly the table the frozen graph's full biome index builds, so every candidate over one graph carries the same table.
|
|
167
|
+
- Confinement: each `confinedBiomes` declaration names a biome the frozen index carries and a substantive `consequence`; the candidate ships a confined biome only when admitted, and an admission of the candidate for a biome it does not ship is refused as stale.
|
|
168
|
+
|
|
169
|
+
A violation throws `DevelopEditionInvariantError`, whose `violations` carry a `DevelopEditionInvariantReason` each.
|
|
170
|
+
|
|
171
|
+
## Evidence trust
|
|
172
|
+
|
|
173
|
+
The edition library builds, validates and verifies evidence statements; it never signs. Signing is done by the caller with `cosign`.
|
|
174
|
+
|
|
175
|
+
- An edition descriptor records `evidenceTrustPolicy` as `armed` or `not-armed`. A release edition requires `armed`; writing one otherwise is refused.
|
|
176
|
+
- A policy is armed on a channel when it authorizes a signer for each predicate on that channel and pins `cosign` and `git`. The develop channel admits `not-armed`.
|
|
177
|
+
- The pin file (`image-evidence-trust-policy.pin.json`) binds the policy, the built verifier bundle, the kernel contract authority and the trusted `oras` binaries. Write it with `xema-edition repin-evidence-trust [--trusted-oras-digest sha256:<64 hex>]`.
|
|
178
|
+
- A customer edition (one resolved against a catalogue) also needs the edition-catalogue (E4) row on the release channel: one arming rule, `assessTrustPolicyArming(policy, schemas, channel, {consumesCatalogue})`.
|
|
179
|
+
- `xema-edition evidence statement catalogue stage|accept` (vendor) derives the E4 statement from `--artifact oci://<repository>@sha256:<digest>` and `--offering <id>`, and `accept` attaches the signed statement and bundle to the artifact as a referrer. A release catalogue is verified before it is read, by the data root's pinned built verifier (`catalogue-evidence arming|verify` mode of the bundle, which uses its own embedded kernel because the catalogue's kernel is not adopted yet); `xema-edition release resolve` and `check-existing` take `--cosign <absolute path>` for it.
|
|
180
|
+
- `oras` is located with `XEMA_ORAS_BIN` or `PATH` and must hash to a pinned digest. It runs with a scrubbed environment, so proxy and CA variables do not reach it.
|
|
181
|
+
|
|
182
|
+
## Previous seal
|
|
183
|
+
|
|
184
|
+
A write of `images.lock.json` (`resolve-images`, `xema-edition develop resolve`) takes exactly one previous seal, whose composed artifacts' generations carry forward:
|
|
185
|
+
|
|
186
|
+
- `--previous-lock <lock> --previous-lock-sha256 <hex>`: the previous edition's images.lock layer, taken by the caller; the CLI refuses unless the bytes hash to the digest.
|
|
187
|
+
|
|
188
|
+
A customer develop resolve (`--definition`, or the catalogue-free form) takes `--previous-lock`/`--previous-lock-sha256` or `--first-seal`, under the customer's id, and it feeds only the customer's own composed rows; the catalogue rows are the catalogue's. In the catalogue-free form the vendor rows carry forward from the offering's own seal, `--offering-previous-lock`/`--offering-previous-lock-sha256`, under the offering's id. A release customer resolve takes none.
|
|
189
|
+
- `--first-seal`: generation 1, only for a distribution that has never had an edition. The CLI never infers it.
|
|
190
|
+
|
|
191
|
+
The in-repo resolve and pin/unpin take the committed `images.lock.json` at the target path as the previous lock (first seal when absent).
|
|
192
|
+
|
|
193
|
+
## Kernel protocol and pins
|
|
194
|
+
|
|
195
|
+
The CLI is coupled to the kernel's edition protocol, not to the kernel package. `@xemahq/contracts` exports the protocol identity as data (`dist/edition-protocol/edition-protocol.json`: `{contentDigest, provisional, version}`). This CLI supports protocols v28, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40 and v41 (no published kernel declares v29; a CLI lists a protocol only while every kernel published under it exports what the CLI imports; v27 and older kernels lack `serviceImageSubjects`, so a data root whose pinned kernel speaks v27 or older stays on an earlier CLI), pinned to its digest and vendored, and REFUSES an identity with `provisional: true`; 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 supported and the declared versions. A protocol change needs a CLI release first; only then does the data root repin its kernel authority.
|
|
196
|
+
|
|
197
|
+
A develop edition does not take the kernel's develop head blindly: `xema-edition develop choose-kernel --kernel <package dir> [--kernel-pin <40-hex>] --out <file>` reads the declared protocol of each commit on the checkout's first-parent history (from source, unbuilt) and chooses the newest one this CLI supports. It writes `{chosenSha, headSha, held, reason}` (`reason`: `head-supported` | `head-ahead` | `explicit-pin`; `held` is `head-ahead`), and when held prints `kernel held at v<N> (<sha12>), head at v<M> (<sha12>): raise the CLI` (stdout only, never persisted). The head is the checkout's `refs/remotes/origin/develop` (a full-history checkout). An explicit pin wins but is still checked, and must be on that head's first-parent history (`not-on-develop` otherwise); when no commit is supported it refuses (`EditionProtocolMismatchError`, `no-supported-commit`; the walk ends at the first commit with no declaration file) and never falls back to the head; a declaration that exists but does not parse, at the head or any walked commit, is refused by name (`unparseable`), never skipped. `freeze-graph` applies the same check to the kernel pin, which on develop is the pin of the xema-base member whose checkout `--kernel` sits in (the kernel contracts at the folded path); `--kernel-sha` is refused on develop.
|
|
198
|
+
|
|
199
|
+
- `xema-edition evidence statement entitlement stage` writes entitlement predicate v2 (`lockDigest`, `catalogueDigest`, read from the edition's committed locks), so it takes `--editions-root` besides `--data-root` and `--kernel`.
|
|
200
|
+
- A source repository JOINS a data root that has committed pins with one command: `xema-edition index runtime-image-source --trusted-write --repo <repository> [--at <40-hex>] [--kernel-root <checkout>]`. The repository is resolved once and the runtime image source, biome and platform-service indexes are all written from the committed pins plus that one pin; every committed pin is kept field for field (a differing one is refused by name), a second declared repository with no pin is refused, and the summary names each pin added, moved and dropped. Then `xema-edition index biome --check`, `xema-edition index runtime-image-source --check` and `pnpm check`. The biome builder takes no `--repo`: it would resolve the repository a second time.
|
|
201
|
+
- A THIRD-PARTY source (a biome published from an organisation outside the first-party fleet) joins with `build-runtime-image-source-index.mjs --trusted-write --third-party <name>` plus the flags `--help` of that script lists (the repository, its numeric repository and owner ids, the bundle and image references, the signer policy ids, the biome id, the source commit). The writer clones nothing: it verifies the bundle (`cosign verify` and `verify-attestation --type slsaprovenance1`, offline, the pinned trusted root, exactly the bundle-signer row's identity and issuer, never `--insecure-ignore-tlog`), takes the bundle's SLSA provenance statement from the envelope `verify-attestation` verified (its predicate type and its subject, the bundle digest, held again) and commits it with the classic `.att` layer's certificate and Rekor entry as a Sigstore bundle v0.1, and each image's image-source/v2 statement (an OCI referrer) against the trusted cosign; holds every artifact to its OWN exact trust row (the bundle to its one bundle-signer row, each image to the one release image-source row listing its repository), all rows of the one repository, signed from `refs/heads/main` at the one source commit, while the workflows may differ (one signs the bundle, another builds the images; the pin records each, `bundle.signerWorkflow` and each image's `producerWorkflow`), the `sourceManifestHash` and the pinned contract authority; reads the Fulcio extensions .15, .17 and .11 of every certificate and holds .11 (Runner Environment) to the source's declared runner environment (`--runner-environment self-hosted`, recorded as the pin's `runnerEnvironment`; absent means `github-hosted`), and for a self-hosted source also holds .9 (Build Signer URI) and .18 (Build Config URI) to the signing row's exact workflow identity (the declared workflow ran as the top-level workflow). Fulcio carries no runner label and no code reads the workflow's `runs-on`: whoever passes `--runner-environment self-hosted` must verify BY HAND that the signer workflows pin `runs-on: signing` (the dedicated signing runner label) at the pinned source commit, and the writer's summary prints the declaration so the `repin-evidence-trust` reviewer sees it; and checks uniqueness and the first-party exclusions. It then writes `third-party-source-pins.json` (canonical JSON), the evidence under `evidence/sha256-*/`, and the index with the repository's `producerWorkflowPins` entry through the generator's own merge. It does not repin the evidence trust pin: `xema-edition repin-evidence-trust` must follow. `build-runtime-image-source-index.mjs --check-third-party [--cosign <path>]` (or `XEMA_COSIGN_BIN`) re-verifies every pin offline; `dist-check` runs it next to `--check`, and it passes trivially when the data root has no pin file (zero-config).
|
|
202
|
+
A distribution declares such a source as `{kind: oci, name, registry}`; the source walk (`external-source-walk.mjs`) holds it to the pin of that name (no pin file, an empty one or an unknown name refuses with the loader's reason), re-verifies the committed evidence offline with the same verifier (cosign from `XEMA_COSIGN_BIN`), pulls the bundle BY DIGEST, and writes a lock row with origin `third-party`, an OCI `bundleSource` and NO lock `source`. `resolve-images` takes the row's image subjects from the pin (`imageRef`, `sourceCommit`, `contentHash` = the publisher's `sourceTreeHash`, never built or hashed here) and prints that hash as `contentHash (publisher-attested)`; admission derives the repository pin from the pin file, refuses a repository under `repos/` or `submodules/` or one that collides with an index pin or git source, and for these subjects asserts the signing row's repository, the subject `repo` and the predicate's `source.repositoryUrl` are one repository.
|
|
203
|
+
|
|
204
|
+
- The runtime image source index is v2 and carries operator-stated `producerWorkflowPins` (the workflow commit a producer certificate must be signed from, per repository). `xema-edition index runtime-image-source [--producer-workflow-pin <owner/repo>=<40-hex>]...` preserves the committed pins exactly; each flag sets one repository's pin (repeatable; a repeated or non-GitHub `owner/repo` is refused; only `--trusted-write` writes an index, so the flag with any other mode, `--check-sources` included, is refused); a bootstrap index with none writes `[]`. A v1 index is refused, never migrated: delete it and bootstrap a new one with `--kernel-commit`.
|
|
205
|
+
|
|
206
|
+
## Third-party sources
|
|
207
|
+
|
|
208
|
+
- **Zero-config.** A data root with no `third-party-source-pins.json`, an empty one or an unparsable one has NO third-party source: every `kind: oci` declared source and every third-party image subject is refused, naming the loader's reason. The pin file is the explicit grant; a trust-policy row alone grants nothing. When the file exists, its sha256 is part of the evidence trust pin, so it moves only together with `xema-edition repin-evidence-trust`.
|
|
209
|
+
- **Publisher input.** The publisher's own workflow (GitHub-hosted, or self-hosted when its pin declares so), on `refs/heads/main`, assembles the producer input of each image with `xema-edition evidence assemble-third-party-input` (usage above), one run per image. It runs on public inputs only: this CLI and the `@xemahq/contracts` it ships (both from npm), the publisher's checkout at `GITHUB_WORKSPACE` (holding `GITHUB_SHA`), its own registry credential declaration (`--registry-credentials`, the `image-registry-credentials.json` format, naming environment variables, never a secret) and the `GITHUB_*` context. It takes no root flag and reads no data root. `--contract-authority` is the kernel contract authority object the vendor's evidence trust pin names; it must be the kernel this CLI ships, so the publisher installs the matching CLI version. The subject is the bundle's (`--bundle`, read by digest) `oci-image` service or worker component named `--subject-id`; `path` is the directory of the one `xema-biome.json` at `GITHUB_SHA` whose hash is the bundle's; `sourceTreeHash` is hashed over the checkout with no cross-repo inputs (publisher-attested); the image is read by digest (`--image`) and must carry its cosign SLSA v1 and SBOM attestations; the builder digests come from the exported predicate (`--provenance`, VCS revision `GITHUB_SHA`). The lifetime is `--ttl-seconds` or the first-party default (72h); the trust policy's `maxProducerTtlSeconds` ceiling is enforced by the vendor's third-party pin writer (`index runtime-image-source --trusted-write --third-party`) and by `verify-evidence`, and the signer rows and image ownership are checked by the pin writer. It refuses unless `RUNNER_ENVIRONMENT` is the declared runner environment (`--runner-environment self-hosted` when the vendor's pin declares it, else `github-hosted`) and `GITHUB_REF` is `refs/heads/main`. The output is the exact input `xema-edition evidence statement producer` accepts.
|
|
210
|
+
- **Publisher contract authority.** The `--contract-authority <file>` comes from `xema-edition contract-authority`, also on public inputs only: it prints the full `KernelContractAuthority` of the `@xemahq/contracts` this CLI ships, read from that package's own export `@xemahq/contracts/edition-protocol/contract-authority.json` (the kernel's release derives it at publish time; `sourceCommit` is the commit as the kernel release states it, carried, not proven, while the digest and `sourcePaths` are proven against the package). It is parsed with `KernelContractAuthoritySchema`, refused unless its `contentDigest` is the bundled edition protocol digest and the bundled kernel is that authority, and printed as canonical JSON. A kernel without the file (a dev build, or a release older than the file) is refused by name; there is no fallback.
|
|
211
|
+
- **Publisher evidence (public-input mode).** The rest of the publisher's run uses the same verbs as first party with `--contract-authority <file>` in place of `--data-root`/`--kernel`: `xema-edition evidence statement producer stage` (no OIDC), a keyless `cosign attest-blob --new-bundle-format` of each staged statement in an `id-token: write` job, `statement producer accept --cosign <abs path>` (no OIDC), then `oci attach-plan`, `oras attach` and `oci validate`. Passing both modes, or neither, is refused by name. The authority and schemas come from the file and the `@xemahq/contracts` this CLI bundles, through the one resolution `assemble-third-party-input` uses. Accept verifies every bundle's signature but holds cosign to no version: the vendor's admission re-verifies with its pinned cosign. The publisher installs the xema CLI whose bundled `@xemahq/contracts` matches the authority; an authority whose contracts digest is not the bundled one is refused, naming both digests.
|
|
212
|
+
- **Seal.** A third-party image subject is sealed BY DIGEST from its pin: it keeps the publisher's `imageRef`, its platforms are read at `<imageRef>@<manifestDigest>`, and its `contentHash` (the publisher's `sourceTreeHash`) is never used as a tag, a develop coordinate, a cache key or a build-ledger key. It is excluded from the first-party fleet-coordinate verdict. Its producer evidence in the edition's evidence set is the committed evidence the pin records, checked byte for byte against the pin's digests.
|
|
213
|
+
- **Revocation.** A sealed lock keeps its seal-time verdict; there is no expiry machinery. To revoke a source: remove its pin and its trust-policy rows, regenerate the runtime image source index, run `xema-edition repin-evidence-trust`, and reseal every edition that includes the biome. An edition already delivered is not recalled.
|
|
214
|
+
- **What this does not prove.**
|
|
215
|
+
- `sourceTreeHash`/`contentHash` are publisher-attested and never recomputed: the image is proven to come from repository@commit through that organisation's GitHub-hosted workflow, not from these exact inputs.
|
|
216
|
+
- Nothing about the CONTENT of the repository or the workflow at that commit: the certificate identity proves who built, not what was built.
|
|
217
|
+
- The bundle and the images share one commit, but the bundle's files are not proven to be the images' build input.
|
|
218
|
+
- No revocation after sealing (above). A private registry needs a read credential at mirror and install time.
|
|
219
|
+
|
|
220
|
+
## Build ledger completeness
|
|
221
|
+
|
|
222
|
+
`build-ledger.json` schema 2 records, per repository, the commits that published images and each `imageRef:contentHash` with its platforms and digest (schema 1, a bare commit list, is still read and is removed once the data root is re-projected). With a v2 ledger, `resolve-images` (and so `dist:check` and an exact-commit `--at` pin) refuses an edition when any resolved subject lacks its contentHash or one of that edition's required platforms in the ledger, listing every missing coordinate with the repository pin that produced it. There is no override; a v1 ledger gets no check.
|
|
223
|
+
|
|
224
|
+
## Per-edition source pins
|
|
225
|
+
|
|
226
|
+
A vendor edition's `xema-distribution.json` may carry the CLI-owned key `sourcePins: [{ "repo": "repos/<name>", "ref": "<40-hex commit>", "until": "<exit condition>" }]`: the edition resolves that repository at the exact commit instead of the data root's shared `repositoryPins`. The key is strict (non-empty array, exactly `{repo, ref, until}`, a graph repository, an exact lowercase commit, a non-empty `until`, no repository twice), is removed before the kernel parse, and is refused in a customer definition. The kernel contract repository cannot be held: its authority stays data-root-wide.
|
|
227
|
+
|
|
228
|
+
Holds are sparse overrides: an entry exists only for a repository the edition holds back, so an entry equal to the shared `repositoryPin` is refused ("not a hold"). Each entry carries its exit condition in `until`, and the parser refuses an entry without one. Do not edit `sourcePins` by hand; the verbs are the supported way:
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
xema-edition pin <edition> <repo> <ref> --until "<exit condition>" --data-root <dir> --editions-root <dir> --sources-root <dir> --kernel <dir>
|
|
232
|
+
xema-edition unpin <edition> [<repo>] --data-root <dir> --editions-root <dir> --sources-root <dir> --kernel <dir>
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
`pin` applies the checks below (and the graph, kernel and not-a-hold rules) before it writes, adds or replaces the entry in the edition's `xema-distribution.json` keeping the file's formatting and key order, and re-resolves the edition; a failed re-resolve restores the edition directory (definition and locks) byte for byte and rethrows. `unpin` removes one hold (or all of the edition's), drops the key when none is left, and re-resolves. `xema-edition check` prints every active hold, informationally: `[dist:check] hold: <edition> <repo> @<ref12> until: <exit condition>`, then `N hold(s) active` or `no holds`.
|
|
236
|
+
|
|
237
|
+
A held commit is authorized, never trusted, and nothing overrides it: the commit exists in the source checkout; it is contained in `refs/remotes/origin/main` (a missing ref refuses); and it is a built commit (in the build ledger or the repository's evidence tree; a repository neither knows publishes no images and needs containment only). Every refusal is listed at once.
|
|
238
|
+
|
|
239
|
+
`edition-pinned-inputs.mjs` is the one per-edition source of the three indexes and the pins: an edition holding nothing reads the committed data-root files unchanged; a holding edition has them rebuilt at its effective pins by the release builders (`indexes-at-pins.mjs`, the builders a develop snapshot uses). Resolve, images, check, seal, catalogue, artifact and biome manifests all read through it, so `images.lock.json` `sourceCommit`, the content hashes and the catalogue rows carry the held commit and `xema-edition check` reports a held edition stale exactly when a fresh resolve at its own pins differs. Index currency checks and the index builders stay on the shared pins. The evidence reader (`xema-edition locks`, the verifier) grades each subject against the edition's effective pins, so the evidence verifier bundle changes and the data root repins evidence trust when it adopts this CLI. No lock schema changes.
|
|
240
|
+
|
|
241
|
+
`sourcePins` are not inherited and do not apply on the develop channel: a snapshot replaces every pin. `checkout-indexed-source-repositories.mjs --editions-root <dir>` additionally fetches every edition's held commits and the main history of the held repositories, so containment can be checked in CI.
|
|
242
|
+
|
|
243
|
+
## Customer editions
|
|
244
|
+
|
|
245
|
+
A customer edition is resolved against a catalogue pinned in `catalogue.pin.json` beside the definition file. The catalogue is the ceiling: it supplies availability, pins and digests and never adds a component. It also names the kernel: `kernel` is `{name, version, integrity}` (`@xemahq/contracts`, an exact `x.y.z`, the npm `sha512-` integrity of that tarball), and a catalogue without it is refused. Its `ceiling.storePublisherTrust` is the offering lock's Store publishers (or `null`); a definition selects among them with `storePublisherAnchorKeys`, the resolve copies the selected entries into the customer lock, and the kernel's `customerLockCeilingIssues` refuses, at resolve, seal and verify, an entry the definition does not select, a key the catalogue does not vouch for, or an entry that differs from the catalogue's.
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
xema-edition resolve --definition <file> --channel release \
|
|
249
|
+
--data-root <dir> --sources-root <dir> --cache-dir <dir> --out <dir> \
|
|
250
|
+
--source-image <source>=<registry>/<prefix> [--catalogue-username <u> --catalogue-token-env <VAR>]
|
|
251
|
+
xema-edition release resolve --definition <file> --cosign <abs path> ... # release: E4 evidence verified before the catalogue is read
|
|
252
|
+
xema-edition resolve-digests <id> --definition <file> --data-root <dir> --editions-root <dir> --sources-root <dir> --cache-dir <dir>
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
`--channel develop` is refused on this verb: it takes no previous seal, so a customer's own composed rows would restart at generation 1. Resolve a develop customer edition with `xema-edition develop resolve --definition <file> (--previous-lock <file> --previous-lock-sha256 <hex> | --first-seal) ...`.
|
|
256
|
+
|
|
257
|
+
To use a customer's own registry, copy the catalogue artifact and vendor images by digest with `oras cp`, pin the mirrored catalogue in `catalogue.pin.json`, and declare `imageRegistryMirror: {complete: true, map: [{source, target}]}` in the definition.
|
|
258
|
+
|
|
259
|
+
A customer verb takes no `--kernel` (it is refused) and needs no kernel checkout and no credential for the kernel. After it fetches its catalogue it downloads `@xemahq/contracts@<catalogue.kernel.version>` from registry.npmjs.org into `<--cache-dir>/kernel-npm/`, holds the tarball to the catalogue's npm integrity before extracting it, and proves the package is the catalogue's contract authority (final protocol identity carrying `contractAuthority.contentDigest`) before it loads any schema.
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
### Adapter bundles against the adapter host
|
|
263
|
+
|
|
264
|
+
The adapter host checks at mount an adapter's packed `package.json` `peerDependencies` against its own installed copies of the packages it provides (`@xemahq/contracts`, `@xemahq/biome-sdk`): a declared peer must be a package the host provides, with a valid semver range that has an upper bound, which the host's version satisfies (prerelease-aware, so a range can admit a develop build). An adapter that declares no peers carries its own inlined copies and gets no version check; its `xemaBundled` is provenance, not compared. The edition build applies the same rule first, by the same implementation (`checkAdapterPeers` of `@xemahq/biome-sdk/adapter-peers`), in `adapter-bundled-versions.mjs`: every lock biome host module of the `xema-adapter-host` ABI must carry an npm bundle source, whose published manifest is read from `https://registry.npmjs.org/<name>/<version>` without a credential. The host's versions are the `hostPackageVersions` of the `biome-adapter-host` row of the biome index (read from the host source's `pnpm-lock.yaml` when the index is built; the distribution lock never carries it) and, for a customer, of the same row in the catalogue. Resolve (vendor, develop, customer) and seal (vendor and `--definition`) run the check and report all refusals together, each with one of `adapter-peer-unsatisfied` (the host's version does not satisfy a declared range), `adapter-peer-declaration` (a peer the host does not provide, not a semver range, no upper bound, or `peerDependencies` not an object), `adapter-host-unpinned` (no host row, no `hostPackageVersions`, or no host version of a declared peer), `adapter-not-published` (the registry answers 404) or `unsupported-bundle-source` (not npm).
|
|
265
|
+
|
|
266
|
+
## Candidate editions (a boot test, never a deploy)
|
|
267
|
+
|
|
268
|
+
`@xemahq/edition-tooling/candidate-edition` re-seals a pinned develop edition with ONE repository's candidate commit, so that repository's CI can boot the composed member set through the production path against locks that match the candidate. Two calls:
|
|
269
|
+
|
|
270
|
+
```js
|
|
271
|
+
import { fetchPinnedEditionCheckouts, resealCandidateEdition } from '@xemahq/edition-tooling/candidate-edition';
|
|
272
|
+
const candidate = { repo: 'repos/xema-base', checkoutDir: '/abs/xema-base' };
|
|
273
|
+
const checkouts = await fetchPinnedEditionCheckouts({ tripleDir, cacheDir, dataRootDir, candidate, env });
|
|
274
|
+
await resealCandidateEdition({ tripleDir, checkouts, candidate, outDir, deployment: { id, runtimeNamespace } });
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
- `tripleDir` is a pulled develop edition artifact (`xema-edition.json` plus its locks); its `provenance.pins` are the pins. A customer or release edition is refused.
|
|
278
|
+
- `fetchPinnedEditionCheckouts` is the ONLY network step. A cached checkout is built in a staging directory and renamed into place only once it is complete (the kernel once its build succeeded), so a failed run never leaves a partial checkout to be reused. Local git commands run with PATH and HOME only. It clones every checkout the develop resolve reads at its pin (the kernel, built as `develop-edition.yaml` builds it, and every develop-graph repository but the candidate, with the extra commits the resolve reads) into `cacheDir/checkouts/<pin key>/<commit>`, and every declared source into the external-source cache. The data root is cloned locally from `dataRootDir`, the caller's checkout of it at its pin, because the data root declares every other repository and the credential that reads them. Credentials are read only from the `env` the caller passes. A warm cache runs no network command and no build. Every cached checkout must be at its pin and clean.
|
|
279
|
+
- The calls configure the process-global edition workspace while they run: run one at a time in a process.
|
|
280
|
+
- `resealCandidateEdition` runs OFFLINE. The candidate checkout must be clean (tracked files at HEAD), and the candidate commit replaces its pin. It reruns the develop resolve's distribution steps (snapshot inputs, resolve, bundle signers, plane invariants, operation admission) and `composeAuthorizationBase` (`XEMA_FGA_CLI`). It seals the distribution lock with the kernel's `sealDistributionLock` and carries `images.lock.json` byte for byte. It does not run the steps that read a registry: the images resolve and seal, the adapter-bundle check and the chart set.
|
|
281
|
+
- The output directory holds `distribution.lock.json`, `images.lock.json`, `authorization.lock.json`, `resolved-deployment.json` and `candidate-content.json`. The resolved deployment is the kernel's `resolveUnsplitDeployment` over exactly those lock bytes. It names the registry `candidate.invalid` and the version `0.0.0-candidate`.
|
|
282
|
+
- **Unsigned and not deployable.** The output is a plain directory, never an edition. It has no `xema-edition.json`, is never pushed as an OCI artifact and is never signed, and admission needs the cosign-signed edition artifact. Its resolved deployment is the UNSPLIT variant, which `verifyResolvedDeployment` refuses (`unsplit-not-accepted`) unless the caller opts in with `{ accept: 'unsplit' }`.
|
|
283
|
+
- **The candidate content record.** A biome that ships images from its own repository has its content read where its images were built. The carried images were built at the pinned commit, so `candidate-content.json` records `{ <repo>: <candidate commit> }`, bound to the distribution lock bytes by `distributionLockSha256`, to the edition by `distributionId`, and to the candidate: it holds exactly one entry, the candidate repository at its checkout's HEAD, and any other record is refused. The seal and `verifyCandidateContentHashes` pass the record to biome-content.mjs as `contentCommits`. A record that names a commit whose content is not the lock's is refused. A triple without a record is verified exactly as any lock is. The record is a file beside the locks, because the kernel's lock schema is strict and has no field for it.
|
|
284
|
+
|
|
285
|
+
## Tests
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
pnpm run test:edition # node --test "edition/*.test.mjs" "edition/*-contract-tests.mjs"
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
The sealing tests run the real `openfga` CLI and fail, naming `XEMA_FGA_CLI`, when it is unset.
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// adapter-bundled-versions — the edition build refuses what the adapter host
|
|
2
|
+
// would refuse at mount, before it ships.
|
|
3
|
+
//
|
|
4
|
+
// The adapter host (xema-base `biome-adapter-host`) checks at mount the adapter's
|
|
5
|
+
// packed package.json `peerDependencies` against its own installed copies of the
|
|
6
|
+
// packages it provides. The edition applies the same rule, the one shared
|
|
7
|
+
// implementation `checkAdapterPeers` of `@xemahq/biome-sdk/adapter-peers`, to the
|
|
8
|
+
// package.json the registry serves for the pinned bundle version. An adapter that
|
|
9
|
+
// declares no peers inlines its own copies (`xemaBundled` is provenance, not
|
|
10
|
+
// compared) and passes, exactly as it mounts. The mount check stays the last
|
|
11
|
+
// line; this is the first.
|
|
12
|
+
|
|
13
|
+
import { AdapterPeerProblemKind, checkAdapterPeers } from '@xemahq/biome-sdk/adapter-peers';
|
|
14
|
+
|
|
15
|
+
import { ADAPTER_HOST_BIOME_ID, HOST_PROVIDED_PACKAGES } from './adapter-host-versions.mjs';
|
|
16
|
+
import { RefusalError } from './collected-refusals.mjs';
|
|
17
|
+
import { loadKernelBiomeContracts } from './lib.mjs';
|
|
18
|
+
import { compareStrings } from './string-order.mjs';
|
|
19
|
+
|
|
20
|
+
export const AdapterBundleVersionErrorCode = Object.freeze({
|
|
21
|
+
/** The host's version does not satisfy a declared peer range (the host's AdapterPeerUnsatisfiedError). */
|
|
22
|
+
PeerUnsatisfied: 'adapter-peer-unsatisfied',
|
|
23
|
+
/** A peer declaration the host cannot honour (the host's AdapterPeerDeclarationError). */
|
|
24
|
+
PeerDeclaration: 'adapter-peer-declaration',
|
|
25
|
+
Unpinned: 'adapter-host-unpinned',
|
|
26
|
+
NotPublished: 'adapter-not-published',
|
|
27
|
+
UnsupportedSource: 'unsupported-bundle-source',
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export class AdapterBundleVersionError extends RefusalError {
|
|
31
|
+
constructor({ code, biomeId, packageName, detail, range = null, host = null }) {
|
|
32
|
+
super(`${code}: biome "${biomeId}" package "${packageName}": ${detail}`);
|
|
33
|
+
this.name = 'AdapterBundleVersionError';
|
|
34
|
+
this.code = code;
|
|
35
|
+
this.biomeId = biomeId;
|
|
36
|
+
this.packageName = packageName;
|
|
37
|
+
this.detail = detail;
|
|
38
|
+
this.range = range;
|
|
39
|
+
this.host = host;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The refusal fields for a shared-rule problem. */
|
|
44
|
+
function peerRefusal(problem) {
|
|
45
|
+
const fields = { packageName: problem.packageName ?? 'peerDependencies', detail: problem.detail, range: problem.range ?? null };
|
|
46
|
+
switch (problem.kind) {
|
|
47
|
+
case AdapterPeerProblemKind.Unsatisfied:
|
|
48
|
+
return { ...fields, code: AdapterBundleVersionErrorCode.PeerUnsatisfied, host: problem.hostVersion };
|
|
49
|
+
case AdapterPeerProblemKind.HostVersionMissing:
|
|
50
|
+
return { ...fields, code: AdapterBundleVersionErrorCode.Unpinned, detail: 'the biome index pins no host version of it' };
|
|
51
|
+
default:
|
|
52
|
+
return { ...fields, code: AdapterBundleVersionErrorCode.PeerDeclaration };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The adapter host's row of a biome index or catalogue (undefined when the edition does not carry it). */
|
|
57
|
+
export function adapterHostRow(rows) {
|
|
58
|
+
return rows.find((row) => row.id === ADAPTER_HOST_BIOME_ID);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Does the lock mount any adapter host module? */
|
|
62
|
+
export function lockMountsAdapters(lock) {
|
|
63
|
+
const adapterAbi = loadKernelBiomeContracts().BiomeComponentHostAbi.AdapterHost;
|
|
64
|
+
return (lock.biomes ?? []).some((biome) => (biome.hostModules ?? []).some((hostModule) => hostModule.hostAbi?.name === adapterAbi));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Fetch a package version's manifest from the public registry (no credential); null when unpublished. */
|
|
68
|
+
export async function fetchRegistryManifest(name, version, fetchImpl = fetch) {
|
|
69
|
+
const response = await fetchImpl(
|
|
70
|
+
`https://registry.npmjs.org/${name.replace('/', '%2F')}/${encodeURIComponent(version)}`,
|
|
71
|
+
{ headers: { accept: 'application/json' } },
|
|
72
|
+
);
|
|
73
|
+
if (response.status === 404) {return null;}
|
|
74
|
+
if (!response.ok) {
|
|
75
|
+
throw new Error(`npm registry answered ${response.status} for ${name}@${version}`);
|
|
76
|
+
}
|
|
77
|
+
return response.json();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Refuse every adapter host module the host would refuse at mount: its pinned
|
|
82
|
+
* bundle's package.json `peerDependencies` fail the host's rule against the
|
|
83
|
+
* host versions the biome index pins. `fetchManifest(name, version)` resolves
|
|
84
|
+
* the manifest or null (unpublished). All refusals are collected and thrown
|
|
85
|
+
* together, sorted.
|
|
86
|
+
*/
|
|
87
|
+
export async function assertAdapterBundlesMatchHost({ lock, hostRow, fetchManifest }) {
|
|
88
|
+
const adapterAbi = loadKernelBiomeContracts().BiomeComponentHostAbi.AdapterHost;
|
|
89
|
+
const refusals = [];
|
|
90
|
+
const manifests = new Map();
|
|
91
|
+
const hostVersions = hostRow?.hostPackageVersions;
|
|
92
|
+
for (const biome of lock.biomes ?? []) {
|
|
93
|
+
for (const hostModule of biome.hostModules ?? []) {
|
|
94
|
+
if (hostModule.hostAbi?.name !== adapterAbi) {continue;}
|
|
95
|
+
const base = { biomeId: biome.id };
|
|
96
|
+
if (hostVersions === undefined) {
|
|
97
|
+
refusals.push({ ...base, code: AdapterBundleVersionErrorCode.Unpinned, packageName: ADAPTER_HOST_BIOME_ID, detail: 'the biome index pins no host package versions' });
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const source = hostModule.bundleSource;
|
|
101
|
+
if (source?.kind !== 'npm') {
|
|
102
|
+
refusals.push({ ...base, code: AdapterBundleVersionErrorCode.UnsupportedSource, packageName: String(source?.kind), detail: 'the bundle source is not npm' });
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const key = `${source.name}@${source.version}`;
|
|
106
|
+
if (!manifests.has(key)) {
|
|
107
|
+
manifests.set(key, fetchManifest(source.name, source.version));
|
|
108
|
+
}
|
|
109
|
+
const manifest = await manifests.get(key);
|
|
110
|
+
if (manifest === null) {
|
|
111
|
+
refusals.push({ ...base, code: AdapterBundleVersionErrorCode.NotPublished, packageName: key, detail: 'the registry has no such version' });
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const verdict = checkAdapterPeers({ peerDependencies: manifest.peerDependencies, hostProvided: HOST_PROVIDED_PACKAGES, hostVersions });
|
|
115
|
+
if (!verdict.ok) {
|
|
116
|
+
refusals.push({ ...base, ...peerRefusal(verdict.problem) });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (refusals.length === 0) {return;}
|
|
121
|
+
const errors = refusals
|
|
122
|
+
.map((fields) => new AdapterBundleVersionError(fields))
|
|
123
|
+
.sort((a, b) => compareStrings(a.message, b.message));
|
|
124
|
+
const thrown = new AdapterBundleVersionError(errors[0]);
|
|
125
|
+
thrown.message = errors.map((error) => error.message).join('\n');
|
|
126
|
+
thrown.refusals = errors;
|
|
127
|
+
throw thrown;
|
|
128
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// adapter-host-versions — what the edition knows of the adapter host's own copies of the packages it provides to
|
|
2
|
+
// adapter bundles: the package names, and the lockfile read that yields the host's versions when the biome index is
|
|
3
|
+
// built. The version rule itself is shared with the host (@xemahq/biome-sdk/adapter-peers). No kernel load, so the
|
|
4
|
+
// index build can import it.
|
|
5
|
+
|
|
6
|
+
import { posix } from 'node:path';
|
|
7
|
+
|
|
8
|
+
/** The id of the biome that ships the adapter host (the service of BiomeComponentHostAbi.AdapterHost). */
|
|
9
|
+
export const ADAPTER_HOST_BIOME_ID = 'biome-adapter-host';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Exactly the packages the adapter host provides to adapter bundles (xema-base biomes/biome-adapter-host/api/
|
|
13
|
+
* biome-adapter-host/src/loader/adapter-peer-preload.json `hostProvidedPackages`; a test holds the two equal).
|
|
14
|
+
*/
|
|
15
|
+
export const HOST_PROVIDED_PACKAGES = Object.freeze(['@xemahq/contracts', '@xemahq/biome-sdk']);
|
|
16
|
+
|
|
17
|
+
const LINK_PREFIX = 'link:';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The version of a workspace package the importer links (`link:<rel>`): the `version` of `<importer>/<rel>/package.json`
|
|
21
|
+
* in the same repository at the same commit (`readRepositoryText`). Refused by name when the link leaves the
|
|
22
|
+
* repository, or the package.json is missing or declares no version.
|
|
23
|
+
*/
|
|
24
|
+
function linkedPackageVersion({ name, link, importer, lockfilePath, readRepositoryText }) {
|
|
25
|
+
const where = `${lockfilePath} importer "${importer}" links ${name} at ${link}`;
|
|
26
|
+
const target = posix.normalize(posix.join(importer, link.slice(LINK_PREFIX.length)));
|
|
27
|
+
if (posix.isAbsolute(link.slice(LINK_PREFIX.length)) || target === '..' || target.startsWith('../')) {
|
|
28
|
+
throw new Error(`host package versions: ${where}, which leaves the repository`);
|
|
29
|
+
}
|
|
30
|
+
if (typeof readRepositoryText !== 'function') {
|
|
31
|
+
throw new Error(`host package versions: ${where}, and no repository reader was given to read its package.json`);
|
|
32
|
+
}
|
|
33
|
+
const manifestPath = posix.join(target, 'package.json');
|
|
34
|
+
const text = readRepositoryText(manifestPath);
|
|
35
|
+
if (text === null || text === undefined) {
|
|
36
|
+
throw new Error(`host package versions: ${where}, but ${manifestPath} does not exist at the pinned commit`);
|
|
37
|
+
}
|
|
38
|
+
let manifest;
|
|
39
|
+
try {
|
|
40
|
+
manifest = JSON.parse(text);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
throw new Error(`host package versions: ${where}, but ${manifestPath} is not JSON: ${error.message}`, { cause: error });
|
|
43
|
+
}
|
|
44
|
+
if (typeof manifest?.version !== 'string' || manifest.version === '') {
|
|
45
|
+
throw new Error(`host package versions: ${where}, but ${manifestPath} declares no version`);
|
|
46
|
+
}
|
|
47
|
+
if (manifest.name !== name) {
|
|
48
|
+
throw new Error(`host package versions: ${where}, but ${manifestPath} is ${JSON.stringify(manifest.name)}, not ${name}`);
|
|
49
|
+
}
|
|
50
|
+
return manifest.version;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The host component's resolved versions, read from the repository's pnpm-lock.yaml importer. A workspace link
|
|
55
|
+
* (`link:<rel>`) is read from the linked package's own package.json at the same commit (`readRepositoryText`).
|
|
56
|
+
*/
|
|
57
|
+
export function readHostPackageVersions({ lockfileText, importer, lockfilePath, parseYaml, readRepositoryText }) {
|
|
58
|
+
if (lockfileText === null) {
|
|
59
|
+
throw new Error(`host package versions: ${lockfilePath} does not exist`);
|
|
60
|
+
}
|
|
61
|
+
const dependencies = parseYaml(lockfileText)?.importers?.[importer]?.dependencies;
|
|
62
|
+
if (dependencies === undefined) {
|
|
63
|
+
throw new Error(`host package versions: ${lockfilePath} has no importer "${importer}" with dependencies`);
|
|
64
|
+
}
|
|
65
|
+
const versions = {};
|
|
66
|
+
for (const name of HOST_PROVIDED_PACKAGES) {
|
|
67
|
+
const raw = dependencies[name]?.version;
|
|
68
|
+
if (typeof raw !== 'string') {
|
|
69
|
+
throw new Error(`host package versions: ${lockfilePath} importer "${importer}" has no ${name} entry`);
|
|
70
|
+
}
|
|
71
|
+
versions[name] = raw.startsWith(LINK_PREFIX)
|
|
72
|
+
? linkedPackageVersion({ name, link: raw, importer, lockfilePath, readRepositoryText })
|
|
73
|
+
: raw.split('(')[0];
|
|
74
|
+
}
|
|
75
|
+
return versions;
|
|
76
|
+
}
|