@xemahq/xema 0.33.1 → 0.33.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/distribution-assets.manifest.json +1 -17
- package/dist/edition/verify-image-evidence.bundle.mjs +9 -5
- package/dist/edition/verify-image-evidence.bundle.provenance.json +46 -40
- package/edition/checkout-indexed-source-repositories.mjs +89 -0
- package/edition/cross-repo-build-inputs.mjs +158 -0
- package/edition/git-json.mjs +54 -0
- package/edition/lib.mjs +3 -165
- package/edition/pin-source.mjs +10 -4
- package/edition/resolve-images.mjs +1 -3
- package/package.json +2 -2
- package/dist/assets/core/distribution.lock.json +0 -3947
- package/dist/assets/core/images.lock.json +0 -1422
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
|
|
4
|
+
import { parseStrictJson } from './strict-json.mjs';
|
|
5
|
+
|
|
6
|
+
const requireFromHere = createRequire(import.meta.url);
|
|
7
|
+
|
|
8
|
+
let loadedSourceHash;
|
|
9
|
+
|
|
10
|
+
/** Git-object primitives of `@xemahq/distribution-source-hash`; no kernel is consulted. */
|
|
11
|
+
function gitObjectReader() {
|
|
12
|
+
loadedSourceHash ??= requireFromHere('@xemahq/distribution-source-hash');
|
|
13
|
+
return loadedSourceHash;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Read + parse a JSON file out of an exact Git commit (never the work tree).
|
|
18
|
+
*
|
|
19
|
+
* This is the seam every authored source file crosses on its way into a lock
|
|
20
|
+
* or an index — `xema-biome.json`, a platform service's `package.json`,
|
|
21
|
+
* `cross-repo-build-inputs.json`, the committed generated biome index — so it
|
|
22
|
+
* parses STRICTLY: a duplicated object key is refused, naming the commit, the
|
|
23
|
+
* path and the JSON pointer of the object. `JSON.parse` would keep the last
|
|
24
|
+
* occurrence, which is how a `resources` declaration was dropped from a sealed
|
|
25
|
+
* lock with every reader agreeing (see `strict-json.mjs`).
|
|
26
|
+
*
|
|
27
|
+
* The commit check, the buffer ceiling and the exact-UTF-8 decode are the
|
|
28
|
+
* source-hash package's own (loaded WITHOUT the kernel check hashing needs, so
|
|
29
|
+
* the source checkout, which runs before any kernel is built, can read a record
|
|
30
|
+
* too); only the parse differs from its
|
|
31
|
+
* `readGitJsonAtCommit`, which is lenient and is therefore not delegated to.
|
|
32
|
+
*/
|
|
33
|
+
export function readGitJsonAtCommit(repoDir, repoRelativePath, commit) {
|
|
34
|
+
const sourceHash = gitObjectReader();
|
|
35
|
+
sourceHash.assertGitCommitExists(repoDir, commit);
|
|
36
|
+
let raw;
|
|
37
|
+
try {
|
|
38
|
+
raw = execFileSync(
|
|
39
|
+
'git',
|
|
40
|
+
['-C', repoDir, 'show', `${commit}:${repoRelativePath}`],
|
|
41
|
+
{
|
|
42
|
+
encoding: 'buffer',
|
|
43
|
+
maxBuffer: sourceHash.GIT_OBJECT_MAX_BUFFER_BYTES,
|
|
44
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
45
|
+
},
|
|
46
|
+
);
|
|
47
|
+
} catch {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`cannot read "${repoRelativePath}" from Git commit "${commit}"`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
const context = `Git object "${commit}:${repoRelativePath}"`;
|
|
53
|
+
return parseStrictJson(sourceHash.decodeExactUtf8(raw, context), context);
|
|
54
|
+
}
|
package/edition/lib.mjs
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
sourcePath,
|
|
31
31
|
} from './edition-workspace.mjs';
|
|
32
32
|
import { SHA256_HEX_PATTERN } from './evidence-grammars.mjs';
|
|
33
|
+
import { readGitJsonAtCommit } from './git-json.mjs';
|
|
33
34
|
import { isKernelCapabilityDomain } from './kernel-capability-domain.mjs';
|
|
34
35
|
import {
|
|
35
36
|
assertPrivilegesHavePrincipals,
|
|
@@ -42,7 +43,7 @@ import {
|
|
|
42
43
|
PUBLISHED_SOURCE_REF,
|
|
43
44
|
publishedSourceCommit,
|
|
44
45
|
} from './source-authority.mjs';
|
|
45
|
-
import {
|
|
46
|
+
import { readStrictJsonFile } from './strict-json.mjs';
|
|
46
47
|
import { compareStrings } from './string-order.mjs';
|
|
47
48
|
|
|
48
49
|
export {
|
|
@@ -51,6 +52,7 @@ export {
|
|
|
51
52
|
DistributionSourceMode,
|
|
52
53
|
PUBLISHED_SOURCE_REF,
|
|
53
54
|
publishedSourceCommit,
|
|
55
|
+
readGitJsonAtCommit,
|
|
54
56
|
};
|
|
55
57
|
|
|
56
58
|
|
|
@@ -3063,167 +3065,3 @@ export function hashImageBuildInputs(request, algorithm) {
|
|
|
3063
3065
|
return distributionSourceHash().hashImageBuildInputs(request, algorithm);
|
|
3064
3066
|
}
|
|
3065
3067
|
|
|
3066
|
-
/**
|
|
3067
|
-
* Read + parse a JSON file out of an exact Git commit (never the work tree).
|
|
3068
|
-
*
|
|
3069
|
-
* This is the seam every authored source file crosses on its way into a lock
|
|
3070
|
-
* or an index — `xema-biome.json`, a platform service's `package.json`,
|
|
3071
|
-
* `cross-repo-build-inputs.json`, the committed generated biome index — so it
|
|
3072
|
-
* parses STRICTLY: a duplicated object key is refused, naming the commit, the
|
|
3073
|
-
* path and the JSON pointer of the object. `JSON.parse` would keep the last
|
|
3074
|
-
* occurrence, which is how a `resources` declaration was dropped from a sealed
|
|
3075
|
-
* lock with every reader agreeing (see `strict-json.mjs`).
|
|
3076
|
-
*
|
|
3077
|
-
* The commit check, the buffer ceiling and the exact-UTF-8 decode are the
|
|
3078
|
-
* source-hash package's own; only the parse differs from its
|
|
3079
|
-
* `readGitJsonAtCommit`, which is lenient and is therefore not delegated to.
|
|
3080
|
-
*/
|
|
3081
|
-
export function readGitJsonAtCommit(repoDir, repoRelativePath, commit) {
|
|
3082
|
-
const sourceHash = distributionSourceHash();
|
|
3083
|
-
sourceHash.assertGitCommitExists(repoDir, commit);
|
|
3084
|
-
let raw;
|
|
3085
|
-
try {
|
|
3086
|
-
raw = execFileSync(
|
|
3087
|
-
'git',
|
|
3088
|
-
['-C', repoDir, 'show', `${commit}:${repoRelativePath}`],
|
|
3089
|
-
{
|
|
3090
|
-
encoding: 'buffer',
|
|
3091
|
-
maxBuffer: sourceHash.GIT_OBJECT_MAX_BUFFER_BYTES,
|
|
3092
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
3093
|
-
},
|
|
3094
|
-
);
|
|
3095
|
-
} catch {
|
|
3096
|
-
throw new Error(
|
|
3097
|
-
`cannot read "${repoRelativePath}" from Git commit "${commit}"`,
|
|
3098
|
-
);
|
|
3099
|
-
}
|
|
3100
|
-
const context = `Git object "${commit}:${repoRelativePath}"`;
|
|
3101
|
-
return parseStrictJson(sourceHash.decodeExactUtf8(raw, context), context);
|
|
3102
|
-
}
|
|
3103
|
-
|
|
3104
|
-
/**
|
|
3105
|
-
* The producing repository's OWN committed record of what it vendored.
|
|
3106
|
-
*
|
|
3107
|
-
* `vendor-cross-repo-inputs.mjs` reads exactly this file, at the commit being
|
|
3108
|
-
* built, to emit the `--vendored <dest>=<repoDir>@<commit>:<src>` arguments it
|
|
3109
|
-
* hands `compute-image-tag.mjs`. It is therefore the ONLY source of the vendored
|
|
3110
|
-
* commit that can agree with the tag CI pushes.
|
|
3111
|
-
*/
|
|
3112
|
-
export const CROSS_REPO_BUILD_INPUTS_PATH =
|
|
3113
|
-
'tooling/release/cross-repo-build-inputs.json';
|
|
3114
|
-
|
|
3115
|
-
/** Identity of one vendored input — the tuple that survives a pin bump. */
|
|
3116
|
-
export function crossRepoInputKey(input) {
|
|
3117
|
-
return [input.sourceRepo, input.sourcePath, input.destinationPath].join('\u0000');
|
|
3118
|
-
}
|
|
3119
|
-
|
|
3120
|
-
/**
|
|
3121
|
-
* Resolve the vendored inputs of ONE consumer repository at ONE commit.
|
|
3122
|
-
*
|
|
3123
|
-
* WHY THE COMMIT COMES FROM THE CONSUMER, NOT FROM THE INDEX
|
|
3124
|
-
*
|
|
3125
|
-
* The index's `crossRepoBuildInputs` carry no `sourceCommit`; they name only
|
|
3126
|
-
* WHICH content is vendored. Both this tooling and the parity gate used to
|
|
3127
|
-
* supply the missing commit from `runtimeSourceIndex.repositoryPins`, and CI
|
|
3128
|
-
* supplies it from the file above — so the two disagreed the moment a consumer
|
|
3129
|
-
* repository's mirror lagged the index by one pin, which is its NORMAL resting
|
|
3130
|
-
* state: the mirror is generated FROM the index and committed separately, so it
|
|
3131
|
-
* is stale by construction between the index moving and the consumer
|
|
3132
|
-
* regenerating.
|
|
3133
|
-
*
|
|
3134
|
-
* That is not a cosmetic disagreement. `crossRepo.sourceCommit` is framed into
|
|
3135
|
-
* the component KEY of the image-input hash, so a pin that differs by one
|
|
3136
|
-
* commit yields a different `contentHash` FOR BYTE-IDENTICAL VENDORED CONTENT —
|
|
3137
|
-
* a coordinate no CI run has ever pushed or ever will. Measured 2026-09-07 on
|
|
3138
|
-
* `origin/main`: five of the sixty-eight coordinates in the eight editions
|
|
3139
|
-
* (`backlog-api`, `channel-gateway-api`, `control-plane-api`,
|
|
3140
|
-
* `document-render-api` from repos/xema-community, and `biome-host-api` from
|
|
3141
|
-
* repos/xema-base) 404'd in the registry for exactly this reason, while the tags
|
|
3142
|
-
* their producing CI DID push were present and served on both platforms.
|
|
3143
|
-
*
|
|
3144
|
-
* Reading the index pin also imposed a requirement that cannot be satisfied.
|
|
3145
|
-
* `repos/xema-base` vendors from `repos/xema-community` and vice versa, so
|
|
3146
|
-
* "every mirror agrees with the index" is a mutual fixed point over a 2-cycle,
|
|
3147
|
-
* and bumping either side invalidates the other. No such pair exists: scanning
|
|
3148
|
-
* eighty commits of each on `origin/main` found ZERO mutually coherent pairs.
|
|
3149
|
-
* Reading the consumer's own record removes the fixed point entirely — an
|
|
3150
|
-
* image's identity depends only on the repository that builds it.
|
|
3151
|
-
*
|
|
3152
|
-
* It stays edition-INDEPENDENT, which is the property the previous design was
|
|
3153
|
-
* protecting: the record is a property of the consumer's COMMIT, not of any
|
|
3154
|
-
* edition's resolved commit map, so every edition shipping a subject still
|
|
3155
|
-
* derives the same `contentHash` for it.
|
|
3156
|
-
*/
|
|
3157
|
-
export function resolveCrossRepoVendoredInputs({
|
|
3158
|
-
indexInputs,
|
|
3159
|
-
consumerRepo,
|
|
3160
|
-
consumerRepoDir,
|
|
3161
|
-
consumerCommit,
|
|
3162
|
-
}) {
|
|
3163
|
-
const declared = indexInputs.filter(
|
|
3164
|
-
(input) => input.consumerRepo === consumerRepo,
|
|
3165
|
-
);
|
|
3166
|
-
if (declared.length === 0) {return [];}
|
|
3167
|
-
if (!consumerCommit) {
|
|
3168
|
-
throw new Error(
|
|
3169
|
-
`cross-repo build inputs are declared for "${consumerRepo}", but this ` +
|
|
3170
|
-
`distribution resolves no commit for it — the vendored content cannot ` +
|
|
3171
|
-
`be pinned, so the image cannot be addressed`,
|
|
3172
|
-
);
|
|
3173
|
-
}
|
|
3174
|
-
let record;
|
|
3175
|
-
try {
|
|
3176
|
-
record = readGitJsonAtCommit(
|
|
3177
|
-
consumerRepoDir,
|
|
3178
|
-
CROSS_REPO_BUILD_INPUTS_PATH,
|
|
3179
|
-
consumerCommit,
|
|
3180
|
-
);
|
|
3181
|
-
} catch (cause) {
|
|
3182
|
-
throw new Error(
|
|
3183
|
-
`"${consumerRepo}" declares cross-repo build inputs but ` +
|
|
3184
|
-
`${CROSS_REPO_BUILD_INPUTS_PATH} is unreadable at ${consumerCommit}. ` +
|
|
3185
|
-
`That file is what its CI feeds to compute-image-tag.mjs; without it ` +
|
|
3186
|
-
`no coordinate this tooling derives can match the tag CI pushes.`,
|
|
3187
|
-
{ cause },
|
|
3188
|
-
);
|
|
3189
|
-
}
|
|
3190
|
-
const mirrored = new Map();
|
|
3191
|
-
for (const input of record?.inputs ?? []) {
|
|
3192
|
-
mirrored.set(crossRepoInputKey(input), input.sourceCommit);
|
|
3193
|
-
}
|
|
3194
|
-
// Set equality in BOTH directions. A commit that LAGS is normal and is now
|
|
3195
|
-
// honoured; a differing input SET is a genuine declaration disagreement, and
|
|
3196
|
-
// it moves the hash just as surely — so it must fail rather than resolve to a
|
|
3197
|
-
// coordinate built from a different closure.
|
|
3198
|
-
const declaredKeys = new Set(declared.map((input) => crossRepoInputKey(input)));
|
|
3199
|
-
const resolved = declared.map((input) => {
|
|
3200
|
-
const sourceCommit = mirrored.get(crossRepoInputKey(input));
|
|
3201
|
-
if (!sourceCommit) {
|
|
3202
|
-
throw new Error(
|
|
3203
|
-
`the runtime image source index declares a cross-repo build input for ` +
|
|
3204
|
-
`"${consumerRepo}" vendoring "${input.sourceRepo}:${input.sourcePath}" ` +
|
|
3205
|
-
`to "${input.destinationPath}", but ${CROSS_REPO_BUILD_INPUTS_PATH} at ` +
|
|
3206
|
-
`${consumerCommit} declares no such input — its CI does not vendor it, ` +
|
|
3207
|
-
`so the coordinate this tooling would derive is one nothing builds`,
|
|
3208
|
-
);
|
|
3209
|
-
}
|
|
3210
|
-
return {
|
|
3211
|
-
consumerService: input.consumerService ?? null,
|
|
3212
|
-
destinationPath: input.destinationPath,
|
|
3213
|
-
sourceRepoDir: sourcePath(input.sourceRepo),
|
|
3214
|
-
sourcePath: input.sourcePath,
|
|
3215
|
-
sourceCommit,
|
|
3216
|
-
};
|
|
3217
|
-
});
|
|
3218
|
-
for (const key of mirrored.keys()) {
|
|
3219
|
-
if (declaredKeys.has(key)) {continue;}
|
|
3220
|
-
const [sourceRepo, sourcePath, destinationPath] = key.split('\0');
|
|
3221
|
-
throw new Error(
|
|
3222
|
-
`${CROSS_REPO_BUILD_INPUTS_PATH} at ${consumerCommit} vendors ` +
|
|
3223
|
-
`"${sourceRepo}:${sourcePath}" to "${destinationPath}" for ` +
|
|
3224
|
-
`"${consumerRepo}", which the runtime image source index does not ` +
|
|
3225
|
-
`declare — its CI bakes content this tooling does not hash`,
|
|
3226
|
-
);
|
|
3227
|
-
}
|
|
3228
|
-
return resolved;
|
|
3229
|
-
}
|
package/edition/pin-source.mjs
CHANGED
|
@@ -222,10 +222,16 @@ function applySnapshot(runtimeSourceIndex, pins) {
|
|
|
222
222
|
...runtimeSourceIndex,
|
|
223
223
|
repositoryPins: graph.map((pin) => ({
|
|
224
224
|
...pin,
|
|
225
|
-
//
|
|
226
|
-
//
|
|
227
|
-
//
|
|
228
|
-
//
|
|
225
|
+
// A develop snapshot pins every repository at its develop head, so that one
|
|
226
|
+
// SHA is both the built commit (image axis, `sourceCommit`) and the source
|
|
227
|
+
// tip (`vendoredSourceCommit`): the image pin lagging the source tip cannot
|
|
228
|
+
// arise, and the snapshot is "one frozen snapshot of pins and nothing else".
|
|
229
|
+
// Fusing both axes is the rule `effectiveRepositoryPins` and
|
|
230
|
+
// `createExactCommitSourceAuthority` already apply for an exact commit.
|
|
231
|
+
// The COMMITTED index keeps the axes apart (release channel): see
|
|
232
|
+
// `source-authority.mjs#publishedSourceTip` and
|
|
233
|
+
// `build-runtime-image-source-index.mjs#repositoryIdentity`.
|
|
234
|
+
vendoredSourceCommit: pins[pin.repo],
|
|
229
235
|
sourceCommit: pins[pin.repo],
|
|
230
236
|
})),
|
|
231
237
|
};
|
|
@@ -38,6 +38,7 @@ import { join, relative, sep } from 'node:path';
|
|
|
38
38
|
|
|
39
39
|
import { assertBuildLedgerComplete } from './build-ledger-completeness.mjs';
|
|
40
40
|
import { BUILD_LEDGER_FILE, readBuildLedger } from './build-ledger.mjs';
|
|
41
|
+
import { CROSS_REPO_BUILD_INPUTS_PATH, crossRepoInputKey, resolveCrossRepoVendoredInputs } from './cross-repo-build-inputs.mjs';
|
|
41
42
|
import { resolveDispatchedRuntimeComponents } from './dispatched-runtime-components.mjs';
|
|
42
43
|
import {
|
|
43
44
|
assertCanonicalDistributionId,
|
|
@@ -53,15 +54,12 @@ import {
|
|
|
53
54
|
} from './external-source-walk.mjs';
|
|
54
55
|
import { assertDeclaredSourceId, firstPartySourceId } from './first-party-source-ids.mjs';
|
|
55
56
|
import {
|
|
56
|
-
CROSS_REPO_BUILD_INPUTS_PATH,
|
|
57
57
|
compareStrings,
|
|
58
|
-
crossRepoInputKey,
|
|
59
58
|
hashImageBuildInputs,
|
|
60
59
|
imageInputSourceHashAlgorithm,
|
|
61
60
|
loadKernelDistributionSchemas,
|
|
62
61
|
parseResolvedDistributionLock,
|
|
63
62
|
readGitJsonAtCommit,
|
|
64
|
-
resolveCrossRepoVendoredInputs,
|
|
65
63
|
serializeJson,
|
|
66
64
|
sha256Hex,
|
|
67
65
|
validateImmutableDistributionLock,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xemahq/xema",
|
|
3
|
-
"version": "0.33.
|
|
3
|
+
"version": "0.33.3",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/xema-dev/xema-cli.git",
|
|
@@ -92,6 +92,6 @@
|
|
|
92
92
|
"typecheck": "tsc --noEmit"
|
|
93
93
|
},
|
|
94
94
|
"xemaRelease": {
|
|
95
|
-
"sourceDigest": "sha256:
|
|
95
|
+
"sourceDigest": "sha256:fddbe95c5e82a8ecc251e4d62f65718c96630117ec28d6fb9c8f0f05174432bc"
|
|
96
96
|
}
|
|
97
97
|
}
|