@xemahq/xema 0.25.1 → 0.27.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/README.md +70 -37
- package/dist/assets/appliance-base/images.lock.json +126 -126
- package/dist/assets/appliance-lean/images.lock.json +123 -123
- package/dist/assets/cloud/images.lock.json +126 -126
- package/dist/assets/core/images.lock.json +120 -120
- package/dist/assets/distribution-assets.manifest.json +15 -16
- package/dist/assets/minimal/images.lock.json +108 -108
- package/dist/assets/oss/images.lock.json +126 -126
- package/dist/assets/starter/images.lock.json +123 -123
- package/dist/cli.js +2 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/dev.js +1 -0
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/down.d.ts.map +1 -1
- package/dist/commands/down.js +7 -1
- package/dist/commands/down.js.map +1 -1
- package/dist/commands/edition.d.ts +3 -0
- package/dist/commands/edition.d.ts.map +1 -0
- package/dist/commands/edition.js +46 -0
- package/dist/commands/edition.js.map +1 -0
- package/dist/commands/init.js +2 -2
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/serve.js +1 -0
- package/dist/commands/serve.js.map +1 -1
- package/dist/commands/up.js +1 -0
- package/dist/commands/up.js.map +1 -1
- package/dist/dev/env-defaults.d.ts.map +1 -1
- package/dist/dev/env-defaults.js +6 -1
- package/dist/dev/env-defaults.js.map +1 -1
- package/dist/dev/infra-preflight.d.ts +3 -2
- package/dist/dev/infra-preflight.d.ts.map +1 -1
- package/dist/dev/infra-preflight.js +18 -8
- package/dist/dev/infra-preflight.js.map +1 -1
- package/dist/edition/verify-image-evidence.bundle.mjs +54179 -0
- package/dist/edition/verify-image-evidence.bundle.provenance.json +2871 -0
- package/dist/runtime/backing-registration.d.ts.map +1 -1
- package/dist/runtime/backing-registration.js +7 -2
- package/dist/runtime/backing-registration.js.map +1 -1
- package/dist/runtime/config/schema.d.ts +1 -0
- package/dist/runtime/config/schema.d.ts.map +1 -1
- package/dist/runtime/custody/local-custody.d.ts +12 -0
- package/dist/runtime/custody/local-custody.d.ts.map +1 -0
- package/dist/runtime/custody/local-custody.js +22 -0
- package/dist/runtime/custody/local-custody.js.map +1 -0
- package/dist/runtime/custody/openbao-custody-bootstrap.d.ts +18 -0
- package/dist/runtime/custody/openbao-custody-bootstrap.d.ts.map +1 -0
- package/dist/runtime/custody/openbao-custody-bootstrap.js +50 -0
- package/dist/runtime/custody/openbao-custody-bootstrap.js.map +1 -0
- package/dist/runtime/custody/openbao-custody-store.d.ts +18 -0
- package/dist/runtime/custody/openbao-custody-store.d.ts.map +1 -0
- package/dist/runtime/custody/openbao-custody-store.js +55 -0
- package/dist/runtime/custody/openbao-custody-store.js.map +1 -0
- package/dist/runtime/custody/openbao-http-client.d.ts +34 -0
- package/dist/runtime/custody/openbao-http-client.d.ts.map +1 -0
- package/dist/runtime/custody/openbao-http-client.js +154 -0
- package/dist/runtime/custody/openbao-http-client.js.map +1 -0
- package/dist/runtime/custody/openbao-local-constants.d.ts +22 -0
- package/dist/runtime/custody/openbao-local-constants.d.ts.map +1 -0
- package/dist/runtime/custody/openbao-local-constants.js +48 -0
- package/dist/runtime/custody/openbao-local-constants.js.map +1 -0
- package/dist/runtime/custody/openbao-local-runner.d.ts +16 -0
- package/dist/runtime/custody/openbao-local-runner.d.ts.map +1 -0
- package/dist/runtime/custody/openbao-local-runner.js +86 -0
- package/dist/runtime/custody/openbao-local-runner.js.map +1 -0
- package/dist/runtime/edition/run-entry.d.ts +9 -0
- package/dist/runtime/edition/run-entry.d.ts.map +1 -0
- package/dist/runtime/edition/run-entry.js +36 -0
- package/dist/runtime/edition/run-entry.js.map +1 -0
- package/dist/runtime/edition/verb-map.d.ts +60 -0
- package/dist/runtime/edition/verb-map.d.ts.map +1 -0
- package/dist/runtime/edition/verb-map.js +146 -0
- package/dist/runtime/edition/verb-map.js.map +1 -0
- package/dist/runtime/env-seeder.d.ts +3 -0
- package/dist/runtime/env-seeder.d.ts.map +1 -1
- package/dist/runtime/env-seeder.js +20 -3
- package/dist/runtime/env-seeder.js.map +1 -1
- package/dist/runtime/launch.d.ts +3 -1
- package/dist/runtime/launch.d.ts.map +1 -1
- package/dist/runtime/launch.js +37 -8
- package/dist/runtime/launch.js.map +1 -1
- package/dist/runtime/paths.d.ts +1 -2
- package/dist/runtime/paths.d.ts.map +1 -1
- package/dist/runtime/paths.js +0 -10
- package/dist/runtime/paths.js.map +1 -1
- package/dist/runtime/runner/chart-local-provenance.d.ts +20 -0
- package/dist/runtime/runner/chart-local-provenance.d.ts.map +1 -0
- package/dist/runtime/runner/chart-local-provenance.js +114 -0
- package/dist/runtime/runner/chart-local-provenance.js.map +1 -0
- package/dist/runtime/runner/chart-preflight.d.ts +8 -8
- package/dist/runtime/runner/chart-preflight.d.ts.map +1 -1
- package/dist/runtime/runner/chart-preflight.js +30 -69
- package/dist/runtime/runner/chart-preflight.js.map +1 -1
- package/dist/runtime/runner/chart-pull.d.ts +14 -0
- package/dist/runtime/runner/chart-pull.d.ts.map +1 -0
- package/dist/runtime/runner/chart-pull.js +96 -0
- package/dist/runtime/runner/chart-pull.js.map +1 -0
- package/dist/runtime/runner/chart-resolver.d.ts +4 -12
- package/dist/runtime/runner/chart-resolver.d.ts.map +1 -1
- package/dist/runtime/runner/chart-resolver.js +16 -62
- package/dist/runtime/runner/chart-resolver.js.map +1 -1
- package/dist/runtime/runner/chart-set-lock.d.ts +6 -20
- package/dist/runtime/runner/chart-set-lock.d.ts.map +1 -1
- package/dist/runtime/runner/chart-set-lock.js +55 -99
- package/dist/runtime/runner/chart-set-lock.js.map +1 -1
- package/dist/runtime/runner/helm-cli.d.ts +3 -1
- package/dist/runtime/runner/helm-cli.d.ts.map +1 -1
- package/dist/runtime/runner/helm-cli.js +9 -7
- package/dist/runtime/runner/helm-cli.js.map +1 -1
- package/dist/runtime/runner/helm-runner.d.ts +4 -4
- package/dist/runtime/runner/helm-runner.d.ts.map +1 -1
- package/dist/runtime/runner/helm-runner.js +18 -16
- package/dist/runtime/runner/helm-runner.js.map +1 -1
- package/dist/runtime/sources/kinds/git.d.ts +1 -2
- package/dist/runtime/sources/kinds/git.d.ts.map +1 -1
- package/dist/runtime/sources/kinds/git.js +8 -5
- package/dist/runtime/sources/kinds/git.js.map +1 -1
- package/dist/runtime/sources/manifest-walk.d.ts +3 -1
- package/dist/runtime/sources/manifest-walk.d.ts.map +1 -1
- package/dist/runtime/sources/manifest-walk.js +8 -3
- package/dist/runtime/sources/manifest-walk.js.map +1 -1
- package/dist/runtime/sources/schema.d.ts +3 -0
- package/dist/runtime/sources/schema.d.ts.map +1 -1
- package/dist/runtime/sources/schema.js +6 -1
- package/dist/runtime/sources/schema.js.map +1 -1
- package/dist/runtime/supervisor.d.ts +3 -0
- package/dist/runtime/supervisor.d.ts.map +1 -1
- package/dist/runtime/supervisor.js +2 -0
- package/dist/runtime/supervisor.js.map +1 -1
- package/edition/README.md +315 -0
- package/edition/assemble-producer-input.mjs +1251 -0
- package/edition/authorization-base.mjs +347 -0
- package/edition/biome-content.mjs +268 -0
- package/edition/build-biome-index.mjs +175 -0
- package/edition/build-platform-service-index.mjs +288 -0
- package/edition/build-runtime-image-source-index.mjs +821 -0
- package/edition/built-verifier.mjs +55 -0
- package/edition/catalogue-edition.mjs +49 -0
- package/edition/catalogue-fetch.mjs +64 -0
- package/edition/catalogue.mjs +135 -0
- package/edition/chart-set-source-commit.mjs +92 -0
- package/edition/check-committed-image-evidence.mjs +383 -0
- package/edition/checkout-indexed-source-repositories.mjs +105 -0
- package/edition/comment-blanker.mjs +75 -0
- package/edition/connector-source-authority.mjs +60 -0
- package/edition/customer-definition.mjs +183 -0
- package/edition/customer-flags.mjs +7 -0
- package/edition/develop-customer-edition.mjs +99 -0
- package/edition/develop-edition-resolve.mjs +65 -0
- package/edition/develop-edition.mjs +274 -0
- package/edition/develop-freeze.mjs +173 -0
- package/edition/develop-image-coordinate.mjs +54 -0
- package/edition/develop-seal.mjs +77 -0
- package/edition/develop-snapshot-inputs.mjs +104 -0
- package/edition/dist-check.mjs +986 -0
- package/edition/distribution-evidence-set.mjs +987 -0
- package/edition/distribution-paths.mjs +191 -0
- package/edition/edition-artifact.mjs +270 -0
- package/edition/edition-biome-manifests.mjs +85 -0
- package/edition/edition-channel.mjs +6 -0
- package/edition/edition-chart-set.mjs +93 -0
- package/edition/edition-config.mjs +207 -0
- package/edition/edition-constants.mjs +57 -0
- package/edition/edition-contract.json +23 -0
- package/edition/edition-list.mjs +35 -0
- package/edition/edition-locks.mjs +69 -0
- package/edition/edition-protocol.mjs +228 -0
- package/edition/edition-workspace.mjs +176 -0
- package/edition/evidence-checkout.mjs +418 -0
- package/edition/evidence-grammars.mjs +16 -0
- package/edition/evidence-trust-pin.mjs +304 -0
- package/edition/evidence-trust-policy.mjs +256 -0
- package/edition/external-source-walk.mjs +463 -0
- package/edition/first-party-source-ids.mjs +59 -0
- package/edition/fleet-coordinate-finality.mjs +350 -0
- package/edition/git-source-auth.mjs +52 -0
- package/edition/image-evidence-replay-state.mjs +242 -0
- package/edition/image-evidence.mjs +1225 -0
- package/edition/kernel-contract-authority.mjs +443 -0
- package/edition/lib.mjs +3018 -0
- package/edition/no-follow-file.mjs +94 -0
- package/edition/oci-evidence-verbs.mjs +392 -0
- package/edition/oci-image-source-evidence.mjs +239 -0
- package/edition/oras.mjs +648 -0
- package/edition/pin-source.mjs +227 -0
- package/edition/plan-image-evidence-refresh.mjs +408 -0
- package/edition/platform-lock-readability.mjs +892 -0
- package/edition/platform-service-class.mjs +18 -0
- package/edition/platform-service-source-authority.mjs +105 -0
- package/edition/positive-closure.mjs +162 -0
- package/edition/produce-distribution-completeness-evidence.mjs +311 -0
- package/edition/produce-image-delivery-entitlement-evidence.mjs +294 -0
- package/edition/produce-image-source-evidence.mjs +765 -0
- package/edition/producer-evidence-bundle.mjs +157 -0
- package/edition/registry-credentials.mjs +193 -0
- package/edition/release-catalogue.mjs +39 -0
- package/edition/release-channel-guard.mjs +99 -0
- package/edition/release-edition-artifact.mjs +158 -0
- package/edition/remedy-command.mjs +89 -0
- package/edition/repin-evidence-trust.mjs +101 -0
- package/edition/required-resource-provider.mjs +58 -0
- package/edition/resolve-chart-set.mjs +415 -0
- package/edition/resolve-customer-edition.mjs +288 -0
- package/edition/resolve-distribution.mjs +2318 -0
- package/edition/resolve-edition.mjs +186 -0
- package/edition/resolve-images.mjs +1606 -0
- package/edition/seal-image-digests.mjs +855 -0
- package/edition/sealed-evidence-accept.mjs +82 -0
- package/edition/source-authority.mjs +725 -0
- package/edition/source-closure.mjs +116 -0
- package/edition/strict-json.mjs +121 -0
- package/edition/string-order.mjs +5 -0
- package/edition/trust-policy-arming.mjs +120 -0
- package/edition/trusted-executable.mjs +166 -0
- package/edition/verifier-binding.mjs +45 -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-image-evidence.mjs +430 -0
- package/edition/verify-image-platforms.mjs +495 -0
- package/edition/why.mjs +56 -0
- package/edition/workspace-sections.mjs +195 -0
- package/examples/06-biome-sources.yaml +1 -0
- package/package.json +18 -7
- package/dist/commands/dist.d.ts +0 -16
- package/dist/commands/dist.d.ts.map +0 -1
- package/dist/commands/dist.js +0 -182
- package/dist/commands/dist.js.map +0 -1
- package/dist/runtime/distribution/available-biome.d.ts +0 -10
- package/dist/runtime/distribution/available-biome.d.ts.map +0 -1
- package/dist/runtime/distribution/available-biome.js +0 -154
- package/dist/runtime/distribution/available-biome.js.map +0 -1
- package/dist/runtime/distribution/compose.d.ts +0 -23
- package/dist/runtime/distribution/compose.d.ts.map +0 -1
- package/dist/runtime/distribution/compose.js +0 -48
- package/dist/runtime/distribution/compose.js.map +0 -1
- package/dist/runtime/runner/chart-trust.d.ts +0 -57
- package/dist/runtime/runner/chart-trust.d.ts.map +0 -1
- package/dist/runtime/runner/chart-trust.js +0 -270
- package/dist/runtime/runner/chart-trust.js.map +0 -1
|
@@ -0,0 +1,821 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { execFileSync } from 'node:child_process';
|
|
4
|
+
import { existsSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { isAbsolute, join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
import { connectorCrossRepoBuildInputs } from './connector-source-authority.mjs';
|
|
8
|
+
import { editionWorkspaceConfig, workspaceSection } from './edition-config.mjs';
|
|
9
|
+
import { biomeIndexPath, dataRoot, runtimeImageSourceIndexPath, WorkspaceRoot, configureEditionWorkspaceFromProcess, isEntryModule, sourcePath } from './edition-workspace.mjs';
|
|
10
|
+
import {
|
|
11
|
+
buildTrustedKernelContractAuthority,
|
|
12
|
+
describeKernelContractAuthorityDrift,
|
|
13
|
+
validateKernelContractAuthorityFromGit,
|
|
14
|
+
} from './kernel-contract-authority.mjs';
|
|
15
|
+
import {
|
|
16
|
+
canonicalPublicHttpsRepositoryUrl,
|
|
17
|
+
compareStrings,
|
|
18
|
+
loadKernelDistributionSchemas,
|
|
19
|
+
readGitJsonAtCommit,
|
|
20
|
+
serializeJson,
|
|
21
|
+
sha256Hex,
|
|
22
|
+
} from './lib.mjs';
|
|
23
|
+
import {
|
|
24
|
+
platformServiceRuntimeBindings,
|
|
25
|
+
} from './platform-service-source-authority.mjs';
|
|
26
|
+
import {
|
|
27
|
+
createExactCommitSourceAuthority,
|
|
28
|
+
createSourceAuthority,
|
|
29
|
+
parseSourceModeArgs,
|
|
30
|
+
} from './source-authority.mjs';
|
|
31
|
+
import { readStrictJsonFile } from './strict-json.mjs';
|
|
32
|
+
|
|
33
|
+
if (isEntryModule(import.meta.url)) {
|
|
34
|
+
configureEditionWorkspaceFromProcess([WorkspaceRoot.Data, WorkspaceRoot.Sources, WorkspaceRoot.Kernel, WorkspaceRoot.SourceHash]);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const runtimeImageSources = () =>
|
|
38
|
+
workspaceSection('runtimeImageSources', 'building or validating the runtime image source index');
|
|
39
|
+
|
|
40
|
+
const sourceDeclarations = () => runtimeImageSources().supplementalSources;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Content a producing repository copies OUT OF ANOTHER repository into its build
|
|
44
|
+
* context before the image build.
|
|
45
|
+
*
|
|
46
|
+
* This is the ONLY image build input that is not derivable from the consuming
|
|
47
|
+
* repository at its own commit — nothing inside `xema-base@<sha>` names the
|
|
48
|
+
* bytes of `xema-cultivars/biomes/platform/mailops`. Every other input (the
|
|
49
|
+
* resolved Dockerfile, its `COPY` sources, the `workspace:` graph) is read
|
|
50
|
+
* straight out of the consuming commit, so this list is the whole hand-declared
|
|
51
|
+
* surface and stays small by construction.
|
|
52
|
+
*
|
|
53
|
+
* The list is the data root's (`runtimeImageSources.crossRepoBuildInputs`), plus
|
|
54
|
+
* one input per production connector derived from `connectorSources`; the
|
|
55
|
+
* history of why each entry is there lives with the data. Each entry pins the vendored bytes to the SOURCE repository's own
|
|
56
|
+
* `repositoryPins` commit — the revision this distribution has resolved, and
|
|
57
|
+
* therefore the revision the producing build must clone.
|
|
58
|
+
*
|
|
59
|
+
* CLOSED (was a KNOWN GAP): the producing workflows used to clone with
|
|
60
|
+
* `git clone --depth 1` and no ref — the source repository's default-branch HEAD
|
|
61
|
+
* at build time — so a built image was not a function of any commit pair and its
|
|
62
|
+
* published tag could describe bytes it did not contain. They now vendor through
|
|
63
|
+
* `tooling/release/vendor-cross-repo-inputs.mjs`, driven by a per-repository
|
|
64
|
+
* record the aggregator generates FROM THIS TABLE joined to `repositoryPins`
|
|
65
|
+
* (`tooling/release/generate-cross-repo-build-inputs.mjs`). The same record
|
|
66
|
+
* supplies `compute-image-tag.mjs`'s `--vendored` pins, so the bytes in the image
|
|
67
|
+
* and the bytes in the hash come from one place. The gate
|
|
68
|
+
* `cross-repo-build-inputs` fails on a stale record, and on a workflow that
|
|
69
|
+
* hand-rolls a clone or a copy instead of reading it.
|
|
70
|
+
*
|
|
71
|
+
* Consequence worth knowing: a change to a source repository's vendored content
|
|
72
|
+
* does NOT reach a consumer's image until this index is regenerated and the
|
|
73
|
+
* emitted record re-committed. That is the point — the vendored revision is
|
|
74
|
+
* declared, not whatever HEAD happened to be.
|
|
75
|
+
*/
|
|
76
|
+
export const crossRepoBuildInputs = () => [
|
|
77
|
+
...runtimeImageSources().crossRepoBuildInputs,
|
|
78
|
+
...connectorCrossRepoBuildInputs(),
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Context directories a build step creates, which exist in no commit. Declared
|
|
83
|
+
* (`runtimeImageSources.buildStepContextDirectories`) so a Dockerfile COPY of
|
|
84
|
+
* one is a REVIEWED absence rather than a silent miss.
|
|
85
|
+
*/
|
|
86
|
+
const buildStepContextDirectoriesDeclared = () => runtimeImageSources().buildStepContextDirectories;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* The logical-platform-service → actual-runtime-subject bindings that cannot be
|
|
90
|
+
* inferred. DERIVED from `platform-service-source-authority.mjs`, which is the
|
|
91
|
+
* one place the partition — own repository-root image vs. carried by another
|
|
92
|
+
* subject — is declared. It used to be a second copy here, and the two could
|
|
93
|
+
* only agree by hand.
|
|
94
|
+
*/
|
|
95
|
+
|
|
96
|
+
function readJson(path) {
|
|
97
|
+
return readStrictJsonFile(path);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function git(repo, args) {
|
|
101
|
+
return execFileSync('git', ['-C', repo, ...args], {
|
|
102
|
+
encoding: 'utf8',
|
|
103
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
104
|
+
}).trim();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function declaredSourceRepositories() {
|
|
108
|
+
return [
|
|
109
|
+
...new Set([
|
|
110
|
+
...readJson(biomeIndexPath()).map(({ repo }) => repo),
|
|
111
|
+
...readJson(
|
|
112
|
+
join(dataRoot(), 'platform-service-index.json'),
|
|
113
|
+
).services.map(({ repo }) => repo),
|
|
114
|
+
...sourceDeclarations().map(({ repo }) => repo),
|
|
115
|
+
...crossRepoBuildInputs().flatMap(({ consumerRepo, sourceRepo }) => [
|
|
116
|
+
consumerRepo,
|
|
117
|
+
sourceRepo,
|
|
118
|
+
]),
|
|
119
|
+
]),
|
|
120
|
+
];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* One repository, BOTH provenance axes, in ONE record.
|
|
125
|
+
*
|
|
126
|
+
* `sourceCommit` is the IMAGE axis and its meaning is unchanged: every image
|
|
127
|
+
* subject from this repository must carry exactly this commit, and
|
|
128
|
+
* `expectedProducerSource` throws when one does not. It is therefore
|
|
129
|
+
* constrained to commits with published image evidence.
|
|
130
|
+
*
|
|
131
|
+
* `vendoredSourceCommit` is the SOURCE axis — where vendored bytes come from.
|
|
132
|
+
* It is the published tip, because a vendored path needs its content to exist
|
|
133
|
+
* at a published immutable commit and does NOT need an image to have been
|
|
134
|
+
* built there. See `publishedSourceTip` in `source-authority.mjs` for the
|
|
135
|
+
* measured production failure that fusing the two produced.
|
|
136
|
+
*
|
|
137
|
+
* They are emitted together, from one resolution, deliberately: the release has
|
|
138
|
+
* ONE authority that answers both questions about a component, never two
|
|
139
|
+
* competing ones that can disagree about which commit a component is at.
|
|
140
|
+
*/
|
|
141
|
+
function repositoryIdentity(repo, sourceAuthority) {
|
|
142
|
+
const repoDirectory = sourcePath(repo);
|
|
143
|
+
const sourceCommit = sourceAuthority.commitForRepository(repo);
|
|
144
|
+
const vendoredSourceCommit =
|
|
145
|
+
sourceAuthority.vendoredSourceCommitForRepository(repo);
|
|
146
|
+
const repositoryUrl = canonicalPublicHttpsRepositoryUrl(
|
|
147
|
+
git(repoDirectory, ['remote', 'get-url', 'origin']),
|
|
148
|
+
`${repo} origin`,
|
|
149
|
+
);
|
|
150
|
+
return { repo, repositoryUrl, sourceCommit, vendoredSourceCommit };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Refuse to WRITE an index whose declared vendored source paths do not exist at
|
|
155
|
+
* the commit whose bytes the build will actually fetch.
|
|
156
|
+
*
|
|
157
|
+
* This assertion landed on `main` in `0dfbba7` and was removed by `a416f1e`
|
|
158
|
+
* ("Revert the develop merge on main — the CONSUMER landed before its
|
|
159
|
+
* PRODUCER") as COLLATERAL of reverting an unrelated cross-repo ordering
|
|
160
|
+
* failure — 93 lines of this file among 62 reverted files. It is re-landed here
|
|
161
|
+
* on its own merits, and on the SOURCE axis.
|
|
162
|
+
*
|
|
163
|
+
* Nothing reads the declared cross-repo build inputs at declaration time, so a path that
|
|
164
|
+
* has moved fails SILENTLY where it is written and LOUDLY somewhere else
|
|
165
|
+
* entirely: the consuming image build dies at "Fetch pinned cross-repo build
|
|
166
|
+
* inputs", in another repository, for a declaration nobody was editing. The
|
|
167
|
+
* defect belongs where it is authored, and an index that cannot be written is
|
|
168
|
+
* strictly better than one that can be written and cannot be built from.
|
|
169
|
+
*/
|
|
170
|
+
function assertVendoredSourcesResolve(crossRepoBuildInputs, resolveIdentity) {
|
|
171
|
+
const unresolved = [];
|
|
172
|
+
for (const input of crossRepoBuildInputs) {
|
|
173
|
+
// The SOURCE axis, not the image pin. A vendored path is a fact about the
|
|
174
|
+
// commit whose BYTES are vendored, and `vendor-cross-repo-inputs.mjs`
|
|
175
|
+
// fetches exactly that commit — so asking the image-evidence pin whether a
|
|
176
|
+
// source path exists answers about a commit the build never reads.
|
|
177
|
+
const { vendoredSourceCommit } = resolveIdentity(input.sourceRepo);
|
|
178
|
+
const found = git(sourcePath(input.sourceRepo), [
|
|
179
|
+
'ls-tree',
|
|
180
|
+
'--name-only',
|
|
181
|
+
vendoredSourceCommit,
|
|
182
|
+
'--',
|
|
183
|
+
input.sourcePath,
|
|
184
|
+
]);
|
|
185
|
+
if (found.length === 0) {
|
|
186
|
+
unresolved.push(
|
|
187
|
+
`${input.sourceRepo}@${vendoredSourceCommit}:${input.sourcePath} does not ` +
|
|
188
|
+
`exist (vendored into ${input.consumerRepo}` +
|
|
189
|
+
`${input.consumerService ? ` service "${input.consumerService}"` : ''} ` +
|
|
190
|
+
`at ${input.destinationPath})`,
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (unresolved.length > 0) {
|
|
195
|
+
throw new Error(
|
|
196
|
+
`the declared crossRepoBuildInputs name ${unresolved.length} source path(s) that do ` +
|
|
197
|
+
`not exist at the vendored source commit this index pins. A vendored path ` +
|
|
198
|
+
`is a fact about that PINNED commit, not about anybody's working tree or ` +
|
|
199
|
+
`develop branch — fix the declaration, or pin a commit that carries it:` +
|
|
200
|
+
`\n ${unresolved.join('\n ')}`,
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* The kernel-contract authority for a pin regeneration.
|
|
207
|
+
*
|
|
208
|
+
* ── WHY THIS IS NOT `buildTrustedKernelContractAuthority` ──
|
|
209
|
+
*
|
|
210
|
+
* This function regenerates `repositoryPins` — a PROJECTION of immutable
|
|
211
|
+
* remote facts. Every one of those pins is read from
|
|
212
|
+
* `refs/remotes/origin/main` of its repository, which is exactly right: a
|
|
213
|
+
* production composition must resolve from durable, published state.
|
|
214
|
+
*
|
|
215
|
+
* The kernel-contract authority was the ONE exception, and it was the wrong
|
|
216
|
+
* kind of exception. `buildTrustedKernelContractAuthority` takes its commit
|
|
217
|
+
* from the LOCAL HEAD of `repos/xema-kernel-contracts` and requires that
|
|
218
|
+
* checkout to be EXACTLY CLEAN — so regenerating thirteen pins that have
|
|
219
|
+
* nothing to do with the kernel contract failed outright whenever somebody
|
|
220
|
+
* had an uncommitted file in that one repository. Measured 2026-09-14: ten of
|
|
221
|
+
* twenty-eight repositories in a shared checkout held another agent's
|
|
222
|
+
* in-flight work, and the pins could not be regenerated at all.
|
|
223
|
+
*
|
|
224
|
+
* That is a development-state fact deciding a release-composition question,
|
|
225
|
+
* and it is the boundary this change moves. Whether a colleague's working
|
|
226
|
+
* tree is dirty must never determine whether we can resolve what production
|
|
227
|
+
* ships.
|
|
228
|
+
*
|
|
229
|
+
* ── WHAT IS PRESERVED, AND WHY IT IS NOT WEAKER ──
|
|
230
|
+
*
|
|
231
|
+
* The authority is read from the COMMITTED index and re-verified from git AT
|
|
232
|
+
* ITS OWN PINNED COMMIT: `validateKernelContractAuthorityFromGit` recomputes
|
|
233
|
+
* the content digest from `ls-tree` + `cat-file` blobs at `sourceCommit` and
|
|
234
|
+
* refuses anything that does not match byte for byte. No worktree file is
|
|
235
|
+
* read on that path — it never was — so the provenance guarantee is identical
|
|
236
|
+
* and the clean-checkout assertion was buying nothing for it.
|
|
237
|
+
*
|
|
238
|
+
* `requireHeadMatch: false` because MOVING the authority is a different
|
|
239
|
+
* operation with its own tool: `xema edition index runtime-image-source
|
|
240
|
+
* --repin-contract-authority`, which
|
|
241
|
+
* legitimately demands a deliberate clean checkout because it is CHOOSING a
|
|
242
|
+
* new commit. Re-deriving it here made pin regeneration silently re-pin the
|
|
243
|
+
* contract as a side effect — two operations behind one command, and the one
|
|
244
|
+
* nobody asked for is the one that took the lock.
|
|
245
|
+
*
|
|
246
|
+
* BOOTSTRAP is the single case where local HEAD is the honest source: there is
|
|
247
|
+
* no committed index to preserve, so there is no prior decision to respect.
|
|
248
|
+
*/
|
|
249
|
+
async function resolveContractAuthority(schemas, kernelRoot) {
|
|
250
|
+
const repositoryDirectory = kernelRoot ?? undefined;
|
|
251
|
+
if (!existsSync(runtimeImageSourceIndexPath())) {
|
|
252
|
+
return buildTrustedKernelContractAuthority({ schemas });
|
|
253
|
+
}
|
|
254
|
+
const committed = readJson(runtimeImageSourceIndexPath());
|
|
255
|
+
if (!committed?.contractAuthority) {
|
|
256
|
+
return buildTrustedKernelContractAuthority({ schemas });
|
|
257
|
+
}
|
|
258
|
+
return validateKernelContractAuthorityFromGit({
|
|
259
|
+
authority: committed.contractAuthority,
|
|
260
|
+
...(repositoryDirectory === undefined ? {} : { repositoryDirectory }),
|
|
261
|
+
schemas,
|
|
262
|
+
requireHeadMatch: false,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Re-resolve ONE repository pin and PRESERVE every other one byte for byte.
|
|
268
|
+
*
|
|
269
|
+
* ── WHY A WHOLE-INDEX REBUILD IS THE WRONG TOOL FOR A ONE-REPOSITORY FIX ──
|
|
270
|
+
*
|
|
271
|
+
* `--trusted-write` re-resolves all thirteen pins against each repository's
|
|
272
|
+
* `refs/remotes/origin/main`. That is exactly right when the question is "what
|
|
273
|
+
* does the fleet look like now", and exactly wrong when the question is "ship
|
|
274
|
+
* the fix that is already built in ONE repository": every other pin moves to
|
|
275
|
+
* whatever that repository has published since, and every subject whose
|
|
276
|
+
* identity depends on a moved pin is recomputed. Measured 2026-09-18 on this
|
|
277
|
+
* index, a full rebuild moves 5 of 13 pins and recomputes 54 of 62 subjects,
|
|
278
|
+
* 53 of them collateral — so a one-line fix arrives as a fleet-wide re-pin
|
|
279
|
+
* nobody asked for, and every one of those 53 subjects is a coordinate the
|
|
280
|
+
* registry must already be serving or delivery fails.
|
|
281
|
+
*
|
|
282
|
+
* The scope is a CHOICE somebody states, and it narrows: `--repo <repository>`
|
|
283
|
+
* says "this repository has moved; nothing else has". Every pin outside it is
|
|
284
|
+
* taken verbatim from the committed index, so it cannot drift by accident, and
|
|
285
|
+
* every subject derived from an unmoved pin is recomputed at the SAME commit
|
|
286
|
+
* and therefore lands byte-identical. Only the subjects whose identity actually
|
|
287
|
+
* depends on the named repository change.
|
|
288
|
+
*
|
|
289
|
+
* It is deliberately NOT a separate code path: the scope seeds the memo map
|
|
290
|
+
* `getRepositoryIdentity` already uses, so the index is produced by the one
|
|
291
|
+
* builder, passes the one validator, and is byte-comparable with a full
|
|
292
|
+
* rebuild's output for the parts that did not move. A second writer for
|
|
293
|
+
* "narrow" indexes would be a second answer to what an index IS.
|
|
294
|
+
*
|
|
295
|
+
* A repository this index does not source is a REFUSAL, never a silent no-op —
|
|
296
|
+
* a typo that quietly rewrote nothing would read in review as "the refresh ran".
|
|
297
|
+
*/
|
|
298
|
+
export function preserveOtherRepositoryPins(
|
|
299
|
+
repoScope,
|
|
300
|
+
committedIndex,
|
|
301
|
+
declaredRepositories,
|
|
302
|
+
) {
|
|
303
|
+
const declared = new Set(declaredRepositories);
|
|
304
|
+
if (!declared.has(repoScope)) {
|
|
305
|
+
throw new Error(
|
|
306
|
+
`--repo "${repoScope}" is not a source repository of this index. ` +
|
|
307
|
+
`Known repositories: ${[...declared].sort(compareStrings).join(', ')}`,
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
const pins = Array.isArray(committedIndex?.repositoryPins)
|
|
311
|
+
? committedIndex.repositoryPins
|
|
312
|
+
: [];
|
|
313
|
+
if (pins.length === 0) {
|
|
314
|
+
throw new Error(
|
|
315
|
+
'the committed runtime image source index declares no repositoryPins to preserve',
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
if (!pins.some((pin) => pin.repo === repoScope)) {
|
|
319
|
+
throw new Error(
|
|
320
|
+
`--repo "${repoScope}" has no committed pin to refresh. A repository ` +
|
|
321
|
+
`joining this index is a full --trusted-write, not a scoped refresh.`,
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
return pins
|
|
325
|
+
.filter((pin) => pin.repo !== repoScope)
|
|
326
|
+
.map((pin) => ({
|
|
327
|
+
repo: pin.repo,
|
|
328
|
+
repositoryUrl: pin.repositoryUrl,
|
|
329
|
+
sourceCommit: pin.sourceCommit,
|
|
330
|
+
vendoredSourceCommit: pin.vendoredSourceCommit,
|
|
331
|
+
}));
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function committedRepositoryPinsToPreserve(repoScope) {
|
|
335
|
+
if (!existsSync(runtimeImageSourceIndexPath())) {
|
|
336
|
+
throw new Error(
|
|
337
|
+
`--repo requires ${runtimeImageSourceIndexPath()} to exist: there is no ` +
|
|
338
|
+
`committed index whose other pins could be preserved. Bootstrap with ` +
|
|
339
|
+
`--trusted-write and no --repo.`,
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
return preserveOtherRepositoryPins(
|
|
343
|
+
repoScope,
|
|
344
|
+
readJson(runtimeImageSourceIndexPath()),
|
|
345
|
+
declaredSourceRepositories(),
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* `biomeIndex` and `platformServiceIndex` are the indexes this one is built
|
|
351
|
+
* over, INJECTED: the release CLI passes the committed ones read from disk
|
|
352
|
+
* (byte-identical to reading them here), a develop snapshot passes the ones it
|
|
353
|
+
* built at its own pins.
|
|
354
|
+
*/
|
|
355
|
+
export async function buildTrustedIndex(
|
|
356
|
+
schemas,
|
|
357
|
+
sourceAuthority,
|
|
358
|
+
kernelRoot,
|
|
359
|
+
repoScope,
|
|
360
|
+
{ biomeIndex, platformServiceIndex },
|
|
361
|
+
) {
|
|
362
|
+
const contractAuthority = await resolveContractAuthority(schemas, kernelRoot);
|
|
363
|
+
const repositoryIdentities = new Map();
|
|
364
|
+
if (repoScope !== undefined) {
|
|
365
|
+
for (const pin of committedRepositoryPinsToPreserve(repoScope)) {
|
|
366
|
+
repositoryIdentities.set(pin.repo, pin);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
const getRepositoryIdentity = (repo) => {
|
|
370
|
+
let identity = repositoryIdentities.get(repo);
|
|
371
|
+
if (!identity) {
|
|
372
|
+
identity = repositoryIdentity(repo, sourceAuthority);
|
|
373
|
+
repositoryIdentities.set(repo, identity);
|
|
374
|
+
}
|
|
375
|
+
return identity;
|
|
376
|
+
};
|
|
377
|
+
const supplementalSubjects = sourceDeclarations().map((declaration) => {
|
|
378
|
+
const repoDirectory = sourcePath(declaration.repo);
|
|
379
|
+
const { sourceCommit, repositoryUrl } = getRepositoryIdentity(
|
|
380
|
+
declaration.repo,
|
|
381
|
+
);
|
|
382
|
+
const sourceManifestJson = readGitJsonAtCommit(
|
|
383
|
+
repoDirectory,
|
|
384
|
+
declaration.sourceManifestPath,
|
|
385
|
+
sourceCommit,
|
|
386
|
+
);
|
|
387
|
+
return {
|
|
388
|
+
kind: declaration.kind,
|
|
389
|
+
id: declaration.id,
|
|
390
|
+
imageRef: `${editionWorkspaceConfig().imageRegistry}/${declaration.id}`,
|
|
391
|
+
repositoryUrl,
|
|
392
|
+
repo: declaration.repo,
|
|
393
|
+
path:
|
|
394
|
+
declaration.path === '.'
|
|
395
|
+
? declaration.repo
|
|
396
|
+
: `${declaration.repo}/${declaration.path}`,
|
|
397
|
+
sourceManifestPath: `${declaration.repo}/${declaration.sourceManifestPath}`,
|
|
398
|
+
sourceCommit,
|
|
399
|
+
sourceManifestHash: sha256Hex(serializeJson(sourceManifestJson)),
|
|
400
|
+
};
|
|
401
|
+
}).sort(compareSubject);
|
|
402
|
+
const sourceRepos = new Set([
|
|
403
|
+
...biomeIndex.map((biome) => biome.repo),
|
|
404
|
+
...platformServiceIndex.services.map((service) => service.repo),
|
|
405
|
+
...supplementalSubjects.map((subject) => subject.repo),
|
|
406
|
+
]);
|
|
407
|
+
const repositoryPins = [...sourceRepos]
|
|
408
|
+
.sort(compareStrings)
|
|
409
|
+
.map(getRepositoryIdentity);
|
|
410
|
+
const crossRepoBuildInputs = [...crossRepoBuildInputs()].sort(
|
|
411
|
+
(left, right) =>
|
|
412
|
+
compareStrings(
|
|
413
|
+
[left.consumerRepo, left.consumerService, left.destinationPath].join(
|
|
414
|
+
'\u0000',
|
|
415
|
+
),
|
|
416
|
+
[right.consumerRepo, right.consumerService, right.destinationPath].join(
|
|
417
|
+
'\u0000',
|
|
418
|
+
),
|
|
419
|
+
),
|
|
420
|
+
);
|
|
421
|
+
assertVendoredSourcesResolve(crossRepoBuildInputs, getRepositoryIdentity);
|
|
422
|
+
const buildStepContextDirectories = [...buildStepContextDirectoriesDeclared()].sort(
|
|
423
|
+
(left, right) =>
|
|
424
|
+
compareStrings(
|
|
425
|
+
`${left.repo}\u0000${left.path}`,
|
|
426
|
+
`${right.repo}\u0000${right.path}`,
|
|
427
|
+
),
|
|
428
|
+
);
|
|
429
|
+
for (const directory of buildStepContextDirectories) {
|
|
430
|
+
if (!sourceRepos.has(directory.repo)) {
|
|
431
|
+
throw new Error(
|
|
432
|
+
`build-step context directory "${directory.path}" names repository ` +
|
|
433
|
+
`"${directory.repo}", which is not an image source repository`,
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
for (const input of crossRepoBuildInputs) {
|
|
438
|
+
for (const repo of [input.consumerRepo, input.sourceRepo]) {
|
|
439
|
+
if (!sourceRepos.has(repo)) {
|
|
440
|
+
throw new Error(
|
|
441
|
+
`cross-repo build input for "${input.consumerService}" names repository ` +
|
|
442
|
+
`"${repo}", which is not an image source repository`,
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return {
|
|
448
|
+
schemaVersion: 1,
|
|
449
|
+
contractAuthority,
|
|
450
|
+
supplementalSubjects,
|
|
451
|
+
crossRepoBuildInputs,
|
|
452
|
+
buildStepContextDirectories,
|
|
453
|
+
repositoryPins,
|
|
454
|
+
platformServiceBindings: [...platformServiceRuntimeBindings()].sort(
|
|
455
|
+
(left, right) =>
|
|
456
|
+
compareStrings(left.platformService, right.platformService),
|
|
457
|
+
),
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function compareSubject(left, right) {
|
|
462
|
+
return (
|
|
463
|
+
compareStrings(left.kind, right.kind) || compareStrings(left.id, right.id)
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
export async function validateRuntimeImageSourceIndex(
|
|
468
|
+
index,
|
|
469
|
+
schemas,
|
|
470
|
+
{
|
|
471
|
+
// Whether `repositoryPins` must already cover every image source repository.
|
|
472
|
+
// TRUE by default, and true for everyone who PRODUCES this index — that is
|
|
473
|
+
// this module's own contract and the assertion below is where it lives.
|
|
474
|
+
//
|
|
475
|
+
// The kernel-authority repin passes false, because it produces nothing here:
|
|
476
|
+
// it rewrites ONE field, `contractAuthority`, on an index somebody else
|
|
477
|
+
// built. Requiring coverage of it makes onboarding a new source repository
|
|
478
|
+
// impossible by construction — the repin refuses the stale index, and the
|
|
479
|
+
// rebuild that would add the missing pin refuses the un-repinned authority.
|
|
480
|
+
// Measured 2026-08-26 when `repos/xema-connectors` was carved out: the
|
|
481
|
+
// documented six-step chain deadlocked at its own first step.
|
|
482
|
+
assertRepositoryCoverage = true,
|
|
483
|
+
// WHICH kernel checkout the committed authority is re-verified against.
|
|
484
|
+
// Undefined keeps the default in-aggregator checkout; see `parseKernelRoot`
|
|
485
|
+
// for why a shared working tree must not decide this.
|
|
486
|
+
kernelRoot = undefined,
|
|
487
|
+
// The indexes this one is validated against: the committed ones by default,
|
|
488
|
+
// a develop snapshot's own when it injects them.
|
|
489
|
+
biomeIndex = readJson(biomeIndexPath()),
|
|
490
|
+
platformServiceIndex = readJson(
|
|
491
|
+
join(dataRoot(), 'platform-service-index.json'),
|
|
492
|
+
),
|
|
493
|
+
} = {},
|
|
494
|
+
) {
|
|
495
|
+
await validateKernelContractAuthorityFromGit({
|
|
496
|
+
authority: index.contractAuthority,
|
|
497
|
+
...(kernelRoot === undefined ? {} : { repositoryDirectory: kernelRoot }),
|
|
498
|
+
schemas,
|
|
499
|
+
onDrift: (drift) =>
|
|
500
|
+
console.log(`[runtime-image-source-index] ${describeKernelContractAuthorityDrift(drift)}`),
|
|
501
|
+
});
|
|
502
|
+
// Every platform service carries an exact source identity (the kernel lock
|
|
503
|
+
// schema requires it), so "is it in platform-service-index.json" no longer
|
|
504
|
+
// separates the two kinds — it is now true of all of them. The partition is
|
|
505
|
+
// the declared runtime binding, read from the one authority that owns it.
|
|
506
|
+
const expectedBindings = platformServiceRuntimeBindings()
|
|
507
|
+
.map((binding) => binding.platformService)
|
|
508
|
+
.sort(compareStrings);
|
|
509
|
+
const actualBindings = index.platformServiceBindings.map(
|
|
510
|
+
(binding) => binding.platformService,
|
|
511
|
+
);
|
|
512
|
+
if (serializeJson(actualBindings) !== serializeJson(expectedBindings)) {
|
|
513
|
+
throw new Error(
|
|
514
|
+
'runtime-image-source-index platform bindings do not exactly cover every declared platform service that another subject carries',
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
const supplemental = new Set(
|
|
518
|
+
index.supplementalSubjects.map(
|
|
519
|
+
(subject) => `${subject.kind}\0${subject.id}`,
|
|
520
|
+
),
|
|
521
|
+
);
|
|
522
|
+
const expectedRepositories = [
|
|
523
|
+
...new Set([
|
|
524
|
+
...biomeIndex.map((biome) => biome.repo),
|
|
525
|
+
...platformServiceIndex.services.map((service) => service.repo),
|
|
526
|
+
...index.supplementalSubjects.map((subject) => subject.repo),
|
|
527
|
+
]),
|
|
528
|
+
].sort(compareStrings);
|
|
529
|
+
const actualRepositories = index.repositoryPins.map(
|
|
530
|
+
(repository) => repository.repo,
|
|
531
|
+
);
|
|
532
|
+
if (
|
|
533
|
+
assertRepositoryCoverage &&
|
|
534
|
+
serializeJson(actualRepositories) !== serializeJson(expectedRepositories)
|
|
535
|
+
) {
|
|
536
|
+
throw new Error(
|
|
537
|
+
'runtime-image-source-index repository pins do not exactly cover every image source repository',
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
const repositoryPins = new Map(
|
|
541
|
+
index.repositoryPins.map((repository) => [repository.repo, repository]),
|
|
542
|
+
);
|
|
543
|
+
for (const repository of index.repositoryPins) {
|
|
544
|
+
if (
|
|
545
|
+
canonicalPublicHttpsRepositoryUrl(
|
|
546
|
+
repository.repositoryUrl,
|
|
547
|
+
`${repository.repo} repositoryUrl`,
|
|
548
|
+
) !== repository.repositoryUrl
|
|
549
|
+
) {
|
|
550
|
+
throw new Error(`${repository.repo} repositoryUrl is not canonical`);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
// Every subject a biome contributes to the image plane — BOTH the standing
|
|
554
|
+
// services and the runtime components. A runtime component is a full image
|
|
555
|
+
// subject (built, signed, mirrored) that simply is never scheduled, so a
|
|
556
|
+
// platform service may legitimately bind to one: `opencode-image-prepuller`
|
|
557
|
+
// is a standing service whose whole job is to keep the `opencode-worker`
|
|
558
|
+
// runtime-component image warm on every node.
|
|
559
|
+
const biomeImageSubjects = new Set(
|
|
560
|
+
biomeIndex.flatMap((biome) => [
|
|
561
|
+
// A worker running a sibling service's image is not a subject of its own.
|
|
562
|
+
...(biome.services ?? [])
|
|
563
|
+
.filter((service) => !Object.hasOwn(biome.workerImages ?? {}, service))
|
|
564
|
+
.map((service) => `service\0${service}`),
|
|
565
|
+
...(biome.runtimeComponents ?? []).map(
|
|
566
|
+
(component) => `runtime-component\0${component}`,
|
|
567
|
+
),
|
|
568
|
+
]),
|
|
569
|
+
);
|
|
570
|
+
for (const binding of index.platformServiceBindings) {
|
|
571
|
+
const key = `${binding.subjectKind}\0${binding.subjectId}`;
|
|
572
|
+
if (!supplemental.has(key) && !biomeImageSubjects.has(key)) {
|
|
573
|
+
throw new Error(
|
|
574
|
+
`platform service "${binding.platformService}" binds unknown runtime subject "${binding.subjectKind}:${binding.subjectId}"`,
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
for (const subject of index.supplementalSubjects) {
|
|
579
|
+
if (
|
|
580
|
+
canonicalPublicHttpsRepositoryUrl(
|
|
581
|
+
subject.repositoryUrl,
|
|
582
|
+
`${subject.kind}:${subject.id} repositoryUrl`,
|
|
583
|
+
) !== subject.repositoryUrl
|
|
584
|
+
) {
|
|
585
|
+
throw new Error(
|
|
586
|
+
`${subject.kind}:${subject.id} repositoryUrl is not canonical`,
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
if (
|
|
590
|
+
!subject.path.startsWith(`${subject.repo}/`) &&
|
|
591
|
+
subject.path !== subject.repo
|
|
592
|
+
) {
|
|
593
|
+
throw new Error(
|
|
594
|
+
`${subject.kind}:${subject.id} source path escapes ${subject.repo}`,
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
if (!subject.sourceManifestPath.startsWith(`${subject.repo}/`)) {
|
|
598
|
+
throw new Error(
|
|
599
|
+
`${subject.kind}:${subject.id} source manifest escapes ${subject.repo}`,
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
const repository = repositoryPins.get(subject.repo);
|
|
603
|
+
if (
|
|
604
|
+
!repository ||
|
|
605
|
+
repository.repositoryUrl !== subject.repositoryUrl ||
|
|
606
|
+
repository.sourceCommit !== subject.sourceCommit
|
|
607
|
+
) {
|
|
608
|
+
throw new Error(
|
|
609
|
+
`${subject.kind}:${subject.id} does not match its repository pin`,
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
return index;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/** The follow-ups a re-pin owes; printed on stderr, because the repin itself changes only one field. */
|
|
617
|
+
export const REPIN_CONTRACT_AUTHORITY_FOLLOW_UPS = Object.freeze([
|
|
618
|
+
'xema edition repin-evidence-trust (with the data-root and kernel flags): the evidence trust pin binds the authority; re-pin it.',
|
|
619
|
+
'Re-resolve every edition (xema edition resolve): each images.lock.json carries the contract authority, so its normalizedImageLockDigest changes.',
|
|
620
|
+
'Signed producer statements made under the old authority no longer verify: a statement binds the authority it was signed under, so they must be re-signed.',
|
|
621
|
+
]);
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Replace ONLY `contractAuthority` of the committed runtime image source index
|
|
625
|
+
* with the one trusted from the kernel checkout, and write it back.
|
|
626
|
+
*
|
|
627
|
+
* The document is read as raw strict JSON and the field replaced BEFORE it is
|
|
628
|
+
* parsed: the kernel's schema refuses an index whose authority `sourcePaths`
|
|
629
|
+
* differ from the bundled constant, so a stale index cannot be parsed first.
|
|
630
|
+
* Every repository pin and subject is kept byte for byte. Repository coverage
|
|
631
|
+
* is not asserted: this repin produces no pin, and requiring coverage would
|
|
632
|
+
* make a stale index unrepairable.
|
|
633
|
+
*/
|
|
634
|
+
export async function repinContractAuthority(schemas, kernelRoot) {
|
|
635
|
+
const path = runtimeImageSourceIndexPath();
|
|
636
|
+
if (!existsSync(path)) {
|
|
637
|
+
throw new Error(`${path} does not exist: there is no index whose contract authority could be re-pinned`);
|
|
638
|
+
}
|
|
639
|
+
const raw = readJson(path);
|
|
640
|
+
const contractAuthority = await buildTrustedKernelContractAuthority({
|
|
641
|
+
...(kernelRoot === undefined ? {} : { repositoryDirectory: kernelRoot }),
|
|
642
|
+
schemas,
|
|
643
|
+
});
|
|
644
|
+
const candidate = schemas.parseRuntimeImageSourceIndex({ ...raw, contractAuthority });
|
|
645
|
+
await validateRuntimeImageSourceIndex(candidate, schemas, {
|
|
646
|
+
assertRepositoryCoverage: false,
|
|
647
|
+
kernelRoot,
|
|
648
|
+
});
|
|
649
|
+
writeFileSync(path, serializeJson(candidate));
|
|
650
|
+
return { path, previous: raw.contractAuthority, current: contractAuthority };
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* `--kernel-root` names WHICH checkout of the kernel repository the committed
|
|
655
|
+
* contract authority is re-verified against. It relaxes NOTHING: the digest is
|
|
656
|
+
* still recomputed from `ls-tree` + `cat-file` blobs at the authority's own
|
|
657
|
+
* pinned commit and must match byte for byte.
|
|
658
|
+
*
|
|
659
|
+
* It exists for the reason this file already argues one function down — a
|
|
660
|
+
* development-state fact must not decide a release-composition question. The
|
|
661
|
+
* default checkout is SHARED, so it routinely sits on a stale or unrelated
|
|
662
|
+
* history, and the authority pin then fails an ancestry test that says nothing
|
|
663
|
+
* about the pin and everything about whose branch that directory is on.
|
|
664
|
+
* Measured 2026-09-14: the pin was refused as "not an ancestor of kernel HEAD"
|
|
665
|
+
* by a checkout 39 commits behind, while being a perfectly ordinary ancestor of
|
|
666
|
+
* the published history.
|
|
667
|
+
*/
|
|
668
|
+
function parseRepoScope(argv) {
|
|
669
|
+
const index = argv.indexOf('--repo');
|
|
670
|
+
if (index === -1) {return { repoScope: undefined, remaining: argv };}
|
|
671
|
+
const value = argv[index + 1];
|
|
672
|
+
if (!value || value.startsWith('--')) {
|
|
673
|
+
throw new Error('--repo requires a repository path, e.g. repos/<name>');
|
|
674
|
+
}
|
|
675
|
+
return {
|
|
676
|
+
repoScope: value,
|
|
677
|
+
remaining: [...argv.slice(0, index), ...argv.slice(index + 2)],
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* `--at <sha>` — pin the `--repo` repository at an EXACT commit instead of at
|
|
683
|
+
* whatever the live resolver judges newest-built. Only meaningful with `--repo`,
|
|
684
|
+
* and refused without it: "pin everything at this one commit" is not a thing.
|
|
685
|
+
*/
|
|
686
|
+
function parseExactCommit(argv) {
|
|
687
|
+
const index = argv.indexOf('--at');
|
|
688
|
+
if (index === -1) {return { exactCommit: undefined, remaining: argv };}
|
|
689
|
+
const value = argv[index + 1];
|
|
690
|
+
if (!value || value.startsWith('--')) {
|
|
691
|
+
throw new Error('--at requires a full 40-character commit sha');
|
|
692
|
+
}
|
|
693
|
+
return {
|
|
694
|
+
exactCommit: value,
|
|
695
|
+
remaining: [...argv.slice(0, index), ...argv.slice(index + 2)],
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
function parseKernelRoot(argv) {
|
|
700
|
+
const index = argv.indexOf('--kernel-root');
|
|
701
|
+
if (index === -1) {return { kernelRoot: undefined, remaining: argv };}
|
|
702
|
+
const value = argv[index + 1];
|
|
703
|
+
if (!value) {throw new Error('--kernel-root requires a directory');}
|
|
704
|
+
if (!isAbsolute(value)) {
|
|
705
|
+
throw new Error('--kernel-root requires an ABSOLUTE directory');
|
|
706
|
+
}
|
|
707
|
+
return {
|
|
708
|
+
kernelRoot: realpathSync(value),
|
|
709
|
+
remaining: [...argv.slice(0, index), ...argv.slice(index + 2)],
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
const REPIN_FLAG = '--repin-contract-authority';
|
|
714
|
+
|
|
715
|
+
async function main() {
|
|
716
|
+
const { kernelRoot, remaining: afterKernelRoot } = parseKernelRoot(
|
|
717
|
+
process.argv.slice(2),
|
|
718
|
+
);
|
|
719
|
+
const { repoScope, remaining: afterRepoScope } = parseRepoScope(afterKernelRoot);
|
|
720
|
+
const { exactCommit, remaining: argv } = parseExactCommit(afterRepoScope);
|
|
721
|
+
const { mode, remaining } = parseSourceModeArgs(argv);
|
|
722
|
+
if (exactCommit !== undefined && repoScope === undefined) {
|
|
723
|
+
throw new Error('--at requires --repo: it pins ONE repository at an exact commit');
|
|
724
|
+
}
|
|
725
|
+
const [flag] = remaining;
|
|
726
|
+
if (
|
|
727
|
+
remaining.length !== 1 ||
|
|
728
|
+
!['--check', '--check-sources', '--trusted-write', REPIN_FLAG].includes(flag)
|
|
729
|
+
) {
|
|
730
|
+
throw new Error(
|
|
731
|
+
'usage: build-runtime-image-source-index.mjs (--check | --check-sources | --trusted-write | --repin-contract-authority) ' +
|
|
732
|
+
'[--source published|workspace] [--kernel-root <absolute-checkout>] ' +
|
|
733
|
+
'[--repo <repository> [--at <40-hex-commit>]]',
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
const schemas = await loadKernelDistributionSchemas();
|
|
737
|
+
if (flag === REPIN_FLAG) {
|
|
738
|
+
if (repoScope !== undefined || exactCommit !== undefined) {
|
|
739
|
+
throw new Error(`${REPIN_FLAG} replaces the contract authority only: it takes no --repo or --at`);
|
|
740
|
+
}
|
|
741
|
+
const { path, previous, current } = await repinContractAuthority(schemas, kernelRoot);
|
|
742
|
+
console.log(
|
|
743
|
+
`[runtime-image-source-index] wrote ${path} (contract authority ${previous?.sourceCommit ?? 'absent'} -> ${current.sourceCommit}; every repository pin and subject preserved)`,
|
|
744
|
+
);
|
|
745
|
+
for (const followUp of REPIN_CONTRACT_AUTHORITY_FOLLOW_UPS) {
|
|
746
|
+
console.error(`[runtime-image-source-index] owed after the re-pin: ${followUp}`);
|
|
747
|
+
}
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
if (
|
|
751
|
+
!existsSync(runtimeImageSourceIndexPath()) &&
|
|
752
|
+
flag !== '--trusted-write'
|
|
753
|
+
) {
|
|
754
|
+
throw new Error(`${runtimeImageSourceIndexPath()} does not exist`);
|
|
755
|
+
}
|
|
756
|
+
if (flag === '--check') {
|
|
757
|
+
const parsed = schemas.parseRuntimeImageSourceIndex(
|
|
758
|
+
readJson(runtimeImageSourceIndexPath()),
|
|
759
|
+
);
|
|
760
|
+
await validateRuntimeImageSourceIndex(parsed, schemas, { kernelRoot });
|
|
761
|
+
console.log(
|
|
762
|
+
`[runtime-image-source-index] offline check passed for ${parsed.supplementalSubjects.length} supplemental subjects and ${parsed.platformServiceBindings.length} bindings`,
|
|
763
|
+
);
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
const candidate = schemas.parseRuntimeImageSourceIndex(
|
|
767
|
+
await buildTrustedIndex(
|
|
768
|
+
schemas,
|
|
769
|
+
exactCommit === undefined
|
|
770
|
+
? createSourceAuthority({
|
|
771
|
+
mode,
|
|
772
|
+
knownRepositories: declaredSourceRepositories(),
|
|
773
|
+
})
|
|
774
|
+
: createExactCommitSourceAuthority({
|
|
775
|
+
delegate: createSourceAuthority({
|
|
776
|
+
mode,
|
|
777
|
+
knownRepositories: declaredSourceRepositories(),
|
|
778
|
+
}),
|
|
779
|
+
repo: repoScope,
|
|
780
|
+
commit: exactCommit,
|
|
781
|
+
}),
|
|
782
|
+
kernelRoot,
|
|
783
|
+
repoScope,
|
|
784
|
+
{
|
|
785
|
+
biomeIndex: readJson(biomeIndexPath()),
|
|
786
|
+
platformServiceIndex: readJson(join(dataRoot(), 'platform-service-index.json')),
|
|
787
|
+
},
|
|
788
|
+
),
|
|
789
|
+
);
|
|
790
|
+
await validateRuntimeImageSourceIndex(candidate, schemas, { kernelRoot });
|
|
791
|
+
if (flag === '--check-sources') {
|
|
792
|
+
if (
|
|
793
|
+
readFileSync(runtimeImageSourceIndexPath(), 'utf8') !==
|
|
794
|
+
serializeJson(candidate)
|
|
795
|
+
) {
|
|
796
|
+
throw new Error(
|
|
797
|
+
'runtime-image-source-index.json does not match exact trusted sources',
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
console.log('[runtime-image-source-index] trusted source check passed');
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
writeFileSync(runtimeImageSourceIndexPath(), serializeJson(candidate));
|
|
804
|
+
console.log(
|
|
805
|
+
`[runtime-image-source-index] wrote ${runtimeImageSourceIndexPath()}` +
|
|
806
|
+
(repoScope === undefined
|
|
807
|
+
? ' (all repository pins re-resolved)'
|
|
808
|
+
: ` (${
|
|
809
|
+
exactCommit === undefined
|
|
810
|
+
? `re-resolved ${repoScope}`
|
|
811
|
+
: `pinned ${repoScope} at the exact commit ${exactCommit}`
|
|
812
|
+
}; ${candidate.repositoryPins.length - 1} other pin(s) preserved)`),
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
if (isEntryModule(import.meta.url)) {
|
|
817
|
+
main().catch((error) => {
|
|
818
|
+
console.error(`[runtime-image-source-index] ${error.message}`);
|
|
819
|
+
process.exitCode = 1;
|
|
820
|
+
});
|
|
821
|
+
}
|