@xemahq/xema 0.44.1 → 0.45.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/edition/verify-image-evidence.bundle.mjs +926 -810
- package/dist/edition/verify-image-evidence.bundle.provenance.json +468 -456
- package/dist/runtime/infra/infra-catalog.js +1 -1
- package/edition/README.md +1 -1
- package/edition/develop-coverage-readers.mjs +5 -2
- package/edition/develop-coverage-run.mjs +2 -1
- package/edition/distribution-evidence-set.mjs +3 -0
- package/edition/edition-protocol.mjs +5 -1
- package/edition/oci-evidence-verbs.mjs +7 -1
- package/edition/oci-image-source-evidence.mjs +72 -9
- package/edition/plan-image-evidence-refresh.mjs +3 -0
- package/edition/producer-evidence-bundle.mjs +31 -0
- package/package.json +3 -3
|
@@ -189,7 +189,7 @@ exports.INFRA_CATALOG = Object.freeze({
|
|
|
189
189
|
exports.INFRA_COMPONENTS = Object.freeze(Object.values(schema_1.InfraComponent));
|
|
190
190
|
exports.OBJECT_STORE_BUCKETS = Object.freeze([
|
|
191
191
|
'session-session-attachments',
|
|
192
|
-
'session-
|
|
192
|
+
'session-session-snapshots',
|
|
193
193
|
'kb-knowledge-base-pages',
|
|
194
194
|
'plugin-bundles',
|
|
195
195
|
'artifact-store',
|
package/edition/README.md
CHANGED
|
@@ -142,7 +142,7 @@ The edition library builds, validates and verifies evidence statements; it never
|
|
|
142
142
|
|
|
143
143
|
## Kernel protocol and pins
|
|
144
144
|
|
|
145
|
-
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 v9 to
|
|
145
|
+
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 v9 to v16 and v19 to v20 (the versions the kernel declared and contracts published), each pinned to its digest and vendored (v10 to v20 grow the kernel source closure only; a data root whose pinned kernel speaks v8 stays on CLI 0.41.x, v5 on 0.33.x) 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.
|
|
146
146
|
|
|
147
147
|
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 `--kernel-sha`.
|
|
148
148
|
|
|
@@ -12,7 +12,7 @@ import { promisify } from 'node:util';
|
|
|
12
12
|
|
|
13
13
|
import { AttestationStatus, DEVELOP_EVIDENCE_CHANNEL } from './develop-coverage-verdict.mjs';
|
|
14
14
|
import { COSIGN_ENV, policyCandidates, sha256Digest, validatePinnedTrustedRoot, verifySigstoreEvidenceDescriptor } from './image-evidence.mjs';
|
|
15
|
-
import { resolveImageSourceEvidenceReferrer } from './oci-image-source-evidence.mjs';
|
|
15
|
+
import { resolveImageSourceEvidenceReferrer, writeForeignAuthorityReferrerReport } from './oci-image-source-evidence.mjs';
|
|
16
16
|
import { BUNDLE_MEDIA_TYPE } from './produce-image-source-evidence.mjs';
|
|
17
17
|
import { loadRegistryCredentialDeclarations, resolveRegistryCredential, splitImageRef } from './registry-credentials.mjs';
|
|
18
18
|
import { isNotPublishedYet } from './seal-image-digests.mjs';
|
|
@@ -81,13 +81,14 @@ function basicAuthParts(credential) {
|
|
|
81
81
|
* @param {string} request.policyRoot the directory the policy's trusted root is read from (the data root)
|
|
82
82
|
* @param {object} request.trustedCosign the digest-pinned cosign handle (prepareTrustedCosignBinary)
|
|
83
83
|
* @param {object} request.schemas kernel distribution schemas
|
|
84
|
+
* @param {object} request.expectedContractAuthority the kernel contract authority the data root pins
|
|
84
85
|
* @param {string} request.dataRoot declares how each registry is read
|
|
85
86
|
* @param {object} request.env environment the registry credentials are named in
|
|
86
87
|
* @param {Function} [request.runCosign] (path, args, env) => { code, stdout, stderr }; replaced by tests
|
|
87
88
|
* @param {Function} [request.fetchImpl] @param {Function} [request.run] oras runner, replaced by tests
|
|
88
89
|
* @param {Function} [request.clientFor] (authority) => registry client, replaced by tests
|
|
89
90
|
*/
|
|
90
|
-
export function createCoverageReaders({ policy, policyRoot, trustedCosign, schemas, dataRoot, env, runCosign = defaultRunCosign, fetchImpl, run, clientFor: injectedClientFor }) {
|
|
91
|
+
export function createCoverageReaders({ policy, policyRoot, trustedCosign, schemas, expectedContractAuthority, dataRoot, env, runCosign = defaultRunCosign, fetchImpl, run, clientFor: injectedClientFor }) {
|
|
91
92
|
const cosignPath = assertPreparedExecutable(trustedCosign, 'cosign');
|
|
92
93
|
const declarations = injectedClientFor === undefined ? loadRegistryCredentialDeclarations(dataRoot) : null;
|
|
93
94
|
const scratch = mkdtempSync(join(tmpdir(), 'xema-coverage-'));
|
|
@@ -141,6 +142,8 @@ export function createCoverageReaders({ policy, policyRoot, trustedCosign, schem
|
|
|
141
142
|
imageRepository: subject.imageRef,
|
|
142
143
|
subjectDigest: digest,
|
|
143
144
|
schemas,
|
|
145
|
+
expectedContractAuthority,
|
|
146
|
+
onForeignAuthorityReferrer: writeForeignAuthorityReferrerReport,
|
|
144
147
|
});
|
|
145
148
|
if (evidence === null) {return { status: AttestationStatus.None };}
|
|
146
149
|
if (rows.length === 0) {return { status: AttestationStatus.NoRow };}
|
|
@@ -8,6 +8,7 @@ import { collectCoverage, coverageExitCode, renderCoverageTable } from './develo
|
|
|
8
8
|
import { DEVELOP_EDITION_STEPS, resolveDevelopSnapshot } from './develop-edition-resolve.mjs';
|
|
9
9
|
import { loadDeclaredPinnedPolicy } from './evidence-trust-policy.mjs';
|
|
10
10
|
import { prepareTrustedCosignBinary } from './image-evidence.mjs';
|
|
11
|
+
import { readPinnedContractAuthority } from './produce-image-source-evidence.mjs';
|
|
11
12
|
|
|
12
13
|
const stderrProgress = (line) => process.stderr.write(`${line}\n`);
|
|
13
14
|
|
|
@@ -34,7 +35,7 @@ export async function runDevelopCoverage({
|
|
|
34
35
|
const { schemas, inputs, lock } = await resolveDevelopSnapshot({ id, pins, steps, phase: (name) => progress(`[coverage] ${name}`) });
|
|
35
36
|
const { imageLock } = await steps.resolveImages(id, { inputs, lock });
|
|
36
37
|
progress(`[coverage] image content hashes computed (${imageLock.subjects.length} subjects)`);
|
|
37
|
-
const readers = createCoverageReaders({ policy, policyRoot: dataRoot, trustedCosign, schemas, dataRoot, env, ...readerOptions });
|
|
38
|
+
const readers = createCoverageReaders({ policy, policyRoot: dataRoot, trustedCosign, schemas, expectedContractAuthority: readPinnedContractAuthority({ dataRoot, schemas }), dataRoot, env, ...readerOptions });
|
|
38
39
|
try {
|
|
39
40
|
const report = await collectCoverage({ distributionId: id, subjects: imageLock.subjects, policy, readers, progress });
|
|
40
41
|
write(json ? `${JSON.stringify(report, null, 2)}\n` : renderCoverageTable(report));
|
|
@@ -62,6 +62,7 @@ import { readBoundedNoFollowFile } from './no-follow-file.mjs';
|
|
|
62
62
|
import {
|
|
63
63
|
createRegistryClientResolver,
|
|
64
64
|
resolveImageSourceEvidenceReferrer,
|
|
65
|
+
writeForeignAuthorityReferrerReport,
|
|
65
66
|
} from './oci-image-source-evidence.mjs';
|
|
66
67
|
import { BUNDLE_MEDIA_TYPE, canonicalSignedBundleBytes, cosignVerifyAttestationArgs, validateProducedBundle } from './produce-image-source-evidence.mjs';
|
|
67
68
|
import { splitImageRef } from './registry-credentials.mjs';
|
|
@@ -499,6 +500,8 @@ export async function resolveProducerEvidence({
|
|
|
499
500
|
imageRepository: subject.imageRef,
|
|
500
501
|
subjectDigest: image.digest,
|
|
501
502
|
schemas,
|
|
503
|
+
expectedContractAuthority,
|
|
504
|
+
onForeignAuthorityReferrer: writeForeignAuthorityReferrerReport,
|
|
502
505
|
});
|
|
503
506
|
if (ociEvidence === null) {
|
|
504
507
|
throw new Error(
|
|
@@ -28,7 +28,7 @@ import { realpathSync } from 'node:fs';
|
|
|
28
28
|
|
|
29
29
|
import { readBoundedNoFollowFile } from './no-follow-file.mjs';
|
|
30
30
|
|
|
31
|
-
/** The edition protocols this library interprets: version -> content digest. v10..
|
|
31
|
+
/** The edition protocols this library interprets: version -> content digest. v10..v20 grow the kernel
|
|
32
32
|
* source closure only (entry subpaths and hashing unchanged), so one library speaks every row. */
|
|
33
33
|
export const SUPPORTED_EDITION_PROTOCOLS = Object.freeze([
|
|
34
34
|
Object.freeze({ version: 9, contentDigest: '7cd6d760e7e7caaec3634a08c739debbdeeeaac4f1bad6791246a12f4f248e37' }),
|
|
@@ -37,6 +37,10 @@ export const SUPPORTED_EDITION_PROTOCOLS = Object.freeze([
|
|
|
37
37
|
Object.freeze({ version: 12, contentDigest: 'cff51ea06b6ba7d95d02dcd6305b6a55061b428da48ae74dcbff8a5c8107ce41' }),
|
|
38
38
|
Object.freeze({ version: 13, contentDigest: '5f69fe73d1dea1650ef1757204a53ccd4030ade78bfa91959c2d01c937fc0e16' }),
|
|
39
39
|
Object.freeze({ version: 14, contentDigest: '403b6a491b5922df1a423ba8a2d6ae3d3900bb1caff4e8c0caa48de90a47eb9b' }),
|
|
40
|
+
Object.freeze({ version: 15, contentDigest: 'c66191703abfd9b532d8b6c15476bede2a5752af5bea5032e839fb9d6303a2d1' }),
|
|
41
|
+
Object.freeze({ version: 16, contentDigest: '2e65c280a399304ebb438e4e9829bcb4688f36a3c6a5e8b35b679cf69c689f55' }),
|
|
42
|
+
Object.freeze({ version: 19, contentDigest: '66dab3dcf18c3a587f073808cc91165cc513eabb237b35439ff6cfe2b312b127' }),
|
|
43
|
+
Object.freeze({ version: 20, contentDigest: '22c0e4ba6ee2771e91516ea78806117b5e3e60b76e085b9a9b98e993c1de79f8' }),
|
|
40
44
|
]);
|
|
41
45
|
|
|
42
46
|
/** Where a kernel package publishes its identity and its contract, package-relative. */
|
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
createRegistryClientResolver,
|
|
40
40
|
resolveImageSourceEvidenceReferrer,
|
|
41
41
|
validateMaterializedImageSourceEvidence,
|
|
42
|
+
writeForeignAuthorityReferrerReport,
|
|
42
43
|
} from './oci-image-source-evidence.mjs';
|
|
43
44
|
import { REFERRERS_DISTRIBUTION_SPEC } from './oras.mjs';
|
|
44
45
|
import { assertProducerInput, buildImageSourceStatements, cosignVerifyAttestationArgs, createCosignVerifier, readPinnedContractAuthority } from './produce-image-source-evidence.mjs';
|
|
@@ -216,6 +217,7 @@ export async function planOciEvidenceBackfill({
|
|
|
216
217
|
editionsRoot: editions,
|
|
217
218
|
producerRepositoryRoots,
|
|
218
219
|
schemas,
|
|
220
|
+
expectedContractAuthority,
|
|
219
221
|
registryClientFactory,
|
|
220
222
|
}) {
|
|
221
223
|
const gitIndex = indexCommittedProducerEvidence({
|
|
@@ -281,6 +283,8 @@ export async function planOciEvidenceBackfill({
|
|
|
281
283
|
imageRepository: item.imageRepository,
|
|
282
284
|
subjectDigest: item.subjectDigest,
|
|
283
285
|
schemas,
|
|
286
|
+
expectedContractAuthority,
|
|
287
|
+
onForeignAuthorityReferrer: writeForeignAuthorityReferrerReport,
|
|
284
288
|
});
|
|
285
289
|
const reported = {
|
|
286
290
|
subjectKind: item.subject.kind,
|
|
@@ -367,12 +371,14 @@ async function main() {
|
|
|
367
371
|
WorkspaceRoot.Kernel,
|
|
368
372
|
]);
|
|
369
373
|
const flags = parseEvidenceCliFlags(rest, { repeated: ['distribution', 'producer-repository-root'] });
|
|
374
|
+
const schemas = await loadKernelDistributionSchemas();
|
|
370
375
|
const plan = await planOciEvidenceBackfill({
|
|
371
376
|
distributionIds: flags.requireMany('distribution'),
|
|
372
377
|
dataRoot: dataRoot(),
|
|
373
378
|
editionsRoot: editionsRoot(),
|
|
374
379
|
producerRepositoryRoots: flags.requireMany('producer-repository-root').map((root) => resolve(root)),
|
|
375
|
-
schemas
|
|
380
|
+
schemas,
|
|
381
|
+
expectedContractAuthority: readPinnedContractAuthority({ dataRoot: dataRoot(), schemas }),
|
|
376
382
|
});
|
|
377
383
|
process.stdout.write(serializeJson(plan));
|
|
378
384
|
// 2: something is still to be produced or attached.
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
PRODUCER_STATEMENT_MEDIA_TYPE,
|
|
14
14
|
producerEvidenceLayerFiles,
|
|
15
15
|
readProducerEvidenceDirectory,
|
|
16
|
+
readRawStatementAuthorityCommits,
|
|
16
17
|
} from './producer-evidence-bundle.mjs';
|
|
17
18
|
import {
|
|
18
19
|
loadRegistryCredentialDeclarations,
|
|
@@ -156,9 +157,39 @@ export function validateMaterializedImageSourceEvidence({
|
|
|
156
157
|
return { ...evidence, created };
|
|
157
158
|
}
|
|
158
159
|
|
|
160
|
+
function assertExpectedAuthorityCommit(value) {
|
|
161
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
162
|
+
throw new Error('the expected kernel contract authority commit is required');
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** The artifact states a kernel contract authority other than the one evidence is read under. */
|
|
167
|
+
export class ForeignAuthorityReferrerError extends Error {
|
|
168
|
+
constructor({ artifactDigest, sourceCommit }) {
|
|
169
|
+
super(
|
|
170
|
+
`evidence referrer ${artifactDigest} is stated under kernel contract authority ${sourceCommit}`,
|
|
171
|
+
);
|
|
172
|
+
this.name = 'ForeignAuthorityReferrerError';
|
|
173
|
+
this.artifactDigest = artifactDigest;
|
|
174
|
+
this.sourceCommit = sourceCommit;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** The one stderr line that names a skipped foreign-authority referrer. */
|
|
179
|
+
export function writeForeignAuthorityReferrerReport(skipped) {
|
|
180
|
+
process.stderr.write(
|
|
181
|
+
`[evidence] skipped ${skipped.imageRepository}@${skipped.subjectDigest} referrer ${skipped.artifactDigest}: ` +
|
|
182
|
+
`stated under kernel contract authority ${skipped.sourceCommit}, ` +
|
|
183
|
+
`not ${skipped.expectedAuthorityCommit}\n`,
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
159
187
|
/**
|
|
160
188
|
* Fetch, content-address validate and decode one OCI evidence artifact.
|
|
161
189
|
* `getBlob` verifies every digest again, independently of the manifest.
|
|
190
|
+
* An artifact whose RAW statements state another authority commit than
|
|
191
|
+
* `expectedAuthorityCommit` is refused with `ForeignAuthorityReferrerError`
|
|
192
|
+
* before the strict parse, which is bound to the expected authority.
|
|
162
193
|
*/
|
|
163
194
|
export async function readImageSourceEvidenceArtifact({
|
|
164
195
|
client,
|
|
@@ -166,7 +197,9 @@ export async function readImageSourceEvidenceArtifact({
|
|
|
166
197
|
subjectDigest,
|
|
167
198
|
artifactDigest,
|
|
168
199
|
schemas,
|
|
200
|
+
expectedAuthorityCommit,
|
|
169
201
|
}) {
|
|
202
|
+
assertExpectedAuthorityCommit(expectedAuthorityCommit);
|
|
170
203
|
const fetched = await client.getManifestByDigest(repository, artifactDigest);
|
|
171
204
|
const { created, layers } = validateImageSourceEvidenceManifest({
|
|
172
205
|
manifest: fetched.document,
|
|
@@ -184,6 +217,11 @@ export async function readImageSourceEvidenceArtifact({
|
|
|
184
217
|
mkdirSync(dirname(path), { recursive: true });
|
|
185
218
|
writeFileSync(path, bytes, { flag: 'wx', mode: 0o600 });
|
|
186
219
|
}
|
|
220
|
+
for (const sourceCommit of readRawStatementAuthorityCommits({ directory })) {
|
|
221
|
+
if (sourceCommit !== expectedAuthorityCommit) {
|
|
222
|
+
throw new ForeignAuthorityReferrerError({ artifactDigest, sourceCommit });
|
|
223
|
+
}
|
|
224
|
+
}
|
|
187
225
|
const evidence = validateMaterializedImageSourceEvidence({
|
|
188
226
|
directory,
|
|
189
227
|
manifest: fetched.document,
|
|
@@ -196,13 +234,25 @@ export async function readImageSourceEvidenceArtifact({
|
|
|
196
234
|
}
|
|
197
235
|
}
|
|
198
236
|
|
|
199
|
-
/**
|
|
237
|
+
/**
|
|
238
|
+
* Resolve the newest valid evidence referrer deterministically. A referrer
|
|
239
|
+
* stated under a different kernel contract authority is not evidence under
|
|
240
|
+
* `expectedContractAuthority`: it is reported to `onForeignAuthorityReferrer`
|
|
241
|
+
* and skipped. Every other defect of a referrer still throws.
|
|
242
|
+
*/
|
|
200
243
|
export async function resolveImageSourceEvidenceReferrer({
|
|
201
244
|
client,
|
|
202
245
|
imageRepository,
|
|
203
246
|
subjectDigest,
|
|
204
247
|
schemas,
|
|
248
|
+
expectedContractAuthority,
|
|
249
|
+
onForeignAuthorityReferrer,
|
|
205
250
|
}) {
|
|
251
|
+
const expectedAuthorityCommit = expectedContractAuthority?.sourceCommit;
|
|
252
|
+
assertExpectedAuthorityCommit(expectedAuthorityCommit);
|
|
253
|
+
if (typeof onForeignAuthorityReferrer !== 'function') {
|
|
254
|
+
throw new Error('a foreign-authority referrer report handler is required');
|
|
255
|
+
}
|
|
206
256
|
const { authority, repository } = splitImageRef(imageRepository);
|
|
207
257
|
if (client.authority !== authority) {
|
|
208
258
|
throw new Error(
|
|
@@ -224,16 +274,29 @@ export async function resolveImageSourceEvidenceReferrer({
|
|
|
224
274
|
if (!SHA256_DIGEST_PATTERN.test(descriptor.digest ?? '')) {
|
|
225
275
|
throw new Error('registry returned a non-canonical evidence referrer digest');
|
|
226
276
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
277
|
+
try {
|
|
278
|
+
candidates.push(
|
|
279
|
+
await readImageSourceEvidenceArtifact({
|
|
280
|
+
client,
|
|
281
|
+
repository,
|
|
282
|
+
subjectDigest,
|
|
283
|
+
artifactDigest: descriptor.digest,
|
|
284
|
+
schemas,
|
|
285
|
+
expectedAuthorityCommit,
|
|
286
|
+
}),
|
|
287
|
+
);
|
|
288
|
+
} catch (error) {
|
|
289
|
+
if (!(error instanceof ForeignAuthorityReferrerError)) {throw error;}
|
|
290
|
+
onForeignAuthorityReferrer({
|
|
291
|
+
imageRepository,
|
|
231
292
|
subjectDigest,
|
|
232
|
-
artifactDigest:
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
293
|
+
artifactDigest: error.artifactDigest,
|
|
294
|
+
sourceCommit: error.sourceCommit,
|
|
295
|
+
expectedAuthorityCommit,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
236
298
|
}
|
|
299
|
+
if (candidates.length === 0) {return null;}
|
|
237
300
|
candidates.sort((left, right) => {
|
|
238
301
|
const byCreated = compareStrings(right.created, left.created);
|
|
239
302
|
return byCreated || compareStrings(left.artifactDigest, right.artifactDigest);
|
|
@@ -73,6 +73,7 @@ import { compareStrings, loadKernelDistributionSchemas } from './lib.mjs';
|
|
|
73
73
|
import {
|
|
74
74
|
createRegistryClientResolver,
|
|
75
75
|
resolveImageSourceEvidenceReferrer,
|
|
76
|
+
writeForeignAuthorityReferrerReport,
|
|
76
77
|
} from './oci-image-source-evidence.mjs';
|
|
77
78
|
import {
|
|
78
79
|
loadProducerKernelSchemas,
|
|
@@ -193,6 +194,8 @@ export async function planImageEvidenceRefresh({
|
|
|
193
194
|
imageRepository: subject.imageRef,
|
|
194
195
|
subjectDigest: image.digest,
|
|
195
196
|
schemas,
|
|
197
|
+
expectedContractAuthority,
|
|
198
|
+
onForeignAuthorityReferrer: writeForeignAuthorityReferrerReport,
|
|
196
199
|
});
|
|
197
200
|
if (artifact !== null) {
|
|
198
201
|
matches = artifact.records.filter(
|
|
@@ -62,6 +62,37 @@ export function producerStatementMatchesInput(statement, expectedStatement) {
|
|
|
62
62
|
);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
* The RAW `predicate.contractAuthority.sourceCommit` of every statement a
|
|
67
|
+
* producer evidence directory carries, read BEFORE any schema parse: the parse
|
|
68
|
+
* is bound to one kernel authority, so deciding whose authority a statement
|
|
69
|
+
* speaks under cannot go through it. A statement that states no string commit
|
|
70
|
+
* contributes nothing here; the strict parse refuses it later.
|
|
71
|
+
*/
|
|
72
|
+
export function readRawStatementAuthorityCommits({ directory }) {
|
|
73
|
+
const index = JSON.parse(
|
|
74
|
+
decodeExactUtf8(
|
|
75
|
+
readBounded(directory, PRODUCER_EVIDENCE_INDEX_FILE, 'producer evidence index'),
|
|
76
|
+
'producer evidence index',
|
|
77
|
+
),
|
|
78
|
+
);
|
|
79
|
+
if (!Array.isArray(index?.producerEvidence)) {
|
|
80
|
+
throw new Error('producer evidence index is not non-empty schemaVersion 1');
|
|
81
|
+
}
|
|
82
|
+
const commits = new Set();
|
|
83
|
+
for (const entry of index.producerEvidence) {
|
|
84
|
+
const statement = JSON.parse(
|
|
85
|
+
decodeExactUtf8(
|
|
86
|
+
readBounded(directory, entry?.evidence?.statementPath, 'producer statement'),
|
|
87
|
+
'producer statement',
|
|
88
|
+
),
|
|
89
|
+
);
|
|
90
|
+
const commit = statement?.predicate?.contractAuthority?.sourceCommit;
|
|
91
|
+
if (typeof commit === 'string') {commits.add(commit);}
|
|
92
|
+
}
|
|
93
|
+
return commits;
|
|
94
|
+
}
|
|
95
|
+
|
|
65
96
|
/** Read and structurally validate one producer evidence directory. */
|
|
66
97
|
export function readProducerEvidenceDirectory({ directory, schemas }) {
|
|
67
98
|
if (!isAbsolute(directory)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xemahq/xema",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.45.1",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/xema-dev/xema-cli.git",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"@xemahq/distribution-source-hash": "^0.2.2",
|
|
55
55
|
"@xemahq/dsl": "^0.24.0",
|
|
56
56
|
"@xemahq/identity-client": "^0.16.0",
|
|
57
|
-
"@xemahq/contracts": "^0.
|
|
57
|
+
"@xemahq/contracts": "^0.102.0",
|
|
58
58
|
"@xemahq/kernel-state-runtime": "^0.8.0",
|
|
59
59
|
"@xemahq/license-verifier": "^0.2.0",
|
|
60
60
|
"@xemahq/service-registry-nest": "^0.12.2",
|
|
@@ -92,6 +92,6 @@
|
|
|
92
92
|
"typecheck": "tsc --noEmit"
|
|
93
93
|
},
|
|
94
94
|
"xemaRelease": {
|
|
95
|
-
"sourceDigest": "sha256:
|
|
95
|
+
"sourceDigest": "sha256:04abcd3c081c6cd50d0133c543310f75801357071b18834ec4b15af587760461"
|
|
96
96
|
}
|
|
97
97
|
}
|