@colophon-claims/verify 0.1.0 → 0.2.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/README.md +23 -9
- package/dist/admission/contracts.d.ts +483 -0
- package/dist/admission/contracts.js +285 -0
- package/dist/admission/index.d.ts +2 -0
- package/dist/admission/index.js +2 -0
- package/dist/admission/prompted-commitment.d.ts +19 -0
- package/dist/admission/prompted-commitment.js +52 -0
- package/dist/admission/prompted-selection.d.ts +24 -0
- package/dist/admission/prompted-selection.js +85 -0
- package/dist/admission/verification.d.ts +17 -3
- package/dist/admission/verification.js +230 -49
- package/dist/assets.d.ts +21 -1
- package/dist/assets.js +60 -3
- package/dist/binding/beacon-binding.d.ts +230 -0
- package/dist/binding/beacon-binding.js +325 -0
- package/dist/binding/report-face.d.ts +45 -0
- package/dist/binding/report-face.js +153 -0
- package/dist/cli.js +74 -12
- package/dist/index.d.ts +14 -3
- package/dist/index.js +16 -3
- package/dist/manifest.d.ts +30 -4
- package/dist/manifest.js +30 -0
- package/dist/materialize.d.ts +7 -0
- package/dist/materialize.js +7 -0
- package/dist/outcome.d.ts +31 -0
- package/dist/outcome.js +45 -0
- package/dist/profile/binary-qualification.js +6 -0
- package/dist/profile/claim-consistency.d.ts +8 -1
- package/dist/profile/claim-consistency.js +4 -4
- package/dist/profile/claim.d.ts +203 -1
- package/dist/profile/claim.js +192 -43
- package/dist/profile/disclosure.d.ts +273 -0
- package/dist/profile/disclosure.js +240 -0
- package/dist/profile/pinning-evidence.d.ts +1 -1
- package/dist/profile/run-results.d.ts +8 -2
- package/dist/profile/run-results.js +9 -3
- package/dist/profile/task-selection.d.ts +69 -0
- package/dist/profile/task-selection.js +140 -0
- package/dist/reader-instructions.d.ts +38 -1
- package/dist/reader-instructions.js +42 -2
- package/dist/schema.d.ts +13 -2
- package/dist/schema.js +39 -8
- package/dist/signers.d.ts +48 -0
- package/dist/signers.js +77 -0
- package/dist/verify.d.ts +20 -6
- package/dist/verify.js +156 -31
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +14 -14
|
@@ -3,9 +3,11 @@ import { z } from "zod";
|
|
|
3
3
|
import { VERDICT_DSSE_PAYLOAD_TYPE, canonicalJsonBytes, compareCodeUnitStrings, parseBinaryJudgmentAnalysisContext, parseBinaryJudgmentInstrument, parseBinaryJudgmentLabelResolution, parseBinaryJudgmentPayload, recordDigest, sealBinaryJudgmentInstrument, } from "@jinn-network/task-execution-profiles";
|
|
4
4
|
import { parseExactDsseEnvelope } from "@jinn-network/trust-core";
|
|
5
5
|
import { BINARY_JUDGMENT_HUMAN_REVIEW_EVALUATION_SPEC_SEALED, HUMAN_REVIEW_FORM_SEALED, } from "./application.js";
|
|
6
|
-
import { BinaryJudgmentAdmissionManifestSchema, HUMAN_REVIEW_OPERATOR_ASSERTION_MEDIA_TYPE, HUMAN_REVIEW_PACKET_MEDIA_TYPE, HUMAN_REVIEW_RESPONSE_MEDIA_TYPE, HUMAN_REVIEW_REVEAL_RECEIPT_MEDIA_TYPE, HUMAN_REVIEW_ROSTER_MEDIA_TYPE, HUMAN_REVIEW_VISIBILITY_RECEIPT_MEDIA_TYPE, SCREENING_REVEAL_RECEIPT_MEDIA_TYPE, SCREENING_TABLE_MEDIA_TYPE, HumanReviewOperatorAssertionSchema, HumanReviewPacketSchema, HumanReviewReplacementLedgerSchema, HumanReviewResponseSchema, HumanReviewRevealReceiptSchema, HumanReviewRosterSchema, HumanReviewVisibilityReceiptSchema, ScreeningRevealReceiptSchema, ScreeningTableSchema, } from "./contracts.js";
|
|
6
|
+
import { BinaryJudgmentAdmissionManifestSchema, HUMAN_REVIEW_OPERATOR_ASSERTION_MEDIA_TYPE, HUMAN_REVIEW_PACKET_MEDIA_TYPE, HUMAN_REVIEW_RESPONSE_MEDIA_TYPE, HUMAN_REVIEW_REVEAL_RECEIPT_MEDIA_TYPE, HUMAN_REVIEW_ROSTER_MEDIA_TYPE, HUMAN_REVIEW_VISIBILITY_RECEIPT_MEDIA_TYPE, SCREENING_REVEAL_RECEIPT_MEDIA_TYPE, SCREENING_TABLE_MEDIA_TYPE, SCREENING_TABLE_V2_MEDIA_TYPE, HumanReviewOperatorAssertionSchema, HumanReviewPacketSchema, HumanReviewReplacementLedgerSchema, HumanReviewResponseSchema, HumanReviewRevealReceiptSchema, HumanReviewRosterSchema, HumanReviewVisibilityReceiptSchema, ScreeningRevealReceiptSchema, ScreeningTableSchema, ScreeningTableV2Schema, PromptedScreeningProcedureV1Schema, ScreeningPoolSchema, SCREENING_POOL_V2_PROTOCOL, PROMPTED_SCREENING_LIMITATIONS, PROMPTED_SCREENING_PROFILE, } from "./contracts.js";
|
|
7
|
+
import { selectPromptedScreeningPool, PromptedScreeningSelectionError } from "./prompted-selection.js";
|
|
8
|
+
import { parseScreeningSampleCommitmentBytes, promptedScreeningCommitmentView, } from "./prompted-commitment.js";
|
|
7
9
|
import { readOrderedAdmissionVerdictMeasurements, readAdmissionVerdictEnvelope } from "./result-evaluation.js";
|
|
8
|
-
import { computeScreeningSample, ScreeningSampleError } from "./screening-sample.js";
|
|
10
|
+
import { computeScreeningPoolDigest, computeScreeningSample, ScreeningSampleError } from "./screening-sample.js";
|
|
9
11
|
// Verbatim duplicate of `schema.ts`'s `BUNDLE_V4_ADMISSION_EVIDENCE_ROLES`, hand-kept in sync
|
|
10
12
|
// (spec §6.8a Group C).
|
|
11
13
|
export const BINARY_JUDGMENT_ADMISSION_RECORD_ROLES = [
|
|
@@ -21,7 +23,17 @@ export const BINARY_JUDGMENT_ADMISSION_RECORD_ROLES = [
|
|
|
21
23
|
"screening-instrument",
|
|
22
24
|
"screening-sampling-script",
|
|
23
25
|
"screening-raw-outputs",
|
|
26
|
+
"screening-prompt",
|
|
27
|
+
"screening-procedure",
|
|
28
|
+
"screening-pool",
|
|
29
|
+
"screening-sample-commitment",
|
|
30
|
+
"screening-transcript",
|
|
24
31
|
];
|
|
32
|
+
export const PROMPTED_SCREENING_CAPABILITY_BOUNDARY = {
|
|
33
|
+
status: "not-machine-verified",
|
|
34
|
+
machineVerified: ["sealed-pool", "sealed-sample", "signed-table", "operator-decisions", "artifact-integrity"],
|
|
35
|
+
notMachineVerified: PROMPTED_SCREENING_LIMITATIONS,
|
|
36
|
+
};
|
|
25
37
|
export class BinaryJudgmentAdmissionClosureError extends Error {
|
|
26
38
|
path;
|
|
27
39
|
constructor(path, detail, options) {
|
|
@@ -244,14 +256,15 @@ function authorityPayload(state, options) {
|
|
|
244
256
|
catch (cause) {
|
|
245
257
|
fail(path, "authority evidence is not an exact compact DSSE envelope", cause);
|
|
246
258
|
}
|
|
247
|
-
|
|
259
|
+
const allowedMediaTypes = typeof mediaType === "string" ? [mediaType] : mediaType;
|
|
260
|
+
if (!allowedMediaTypes.includes(envelope.payloadType) || envelope.signatures.length !== 1)
|
|
248
261
|
fail(path, "authority evidence has the wrong payload type or signature count");
|
|
249
262
|
const signature = envelope.signatures[0];
|
|
250
263
|
const signatureKeyId = signature.keyid;
|
|
251
264
|
if (typeof signatureKeyId !== "string" || !state.ports.verifyAuthoritySignature({ envelopeBytes, keyId: signatureKeyId, role })) {
|
|
252
265
|
fail(path, `authority signature is invalid for role ${role}`);
|
|
253
266
|
}
|
|
254
|
-
return { value: exactCanonical(schema, envelope.payloadBytes, `${path}.payload`), keyId: signatureKeyId };
|
|
267
|
+
return { value: exactCanonical(schema, envelope.payloadBytes, `${path}.payload`), keyId: signatureKeyId, mediaType: envelope.payloadType };
|
|
255
268
|
}
|
|
256
269
|
function verifyHumanEvidence(state, refs, expectedItemSha256, expectedDraftId, admittedAt, path) {
|
|
257
270
|
const reviews = refs.reviewVerdictSha256s.map((digest, index) => verifyReview(state, digest, expectedItemSha256, `${path}.reviews.${index}`));
|
|
@@ -314,27 +327,29 @@ function expectedExclusionReason(reviews) {
|
|
|
314
327
|
return "review-disagreement";
|
|
315
328
|
return undefined;
|
|
316
329
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
330
|
+
function screeningRowAgreed(row) {
|
|
331
|
+
return row.screeningVerdict !== "UNSURE"
|
|
332
|
+
&& row.screeningVerdict !== "indeterminate"
|
|
333
|
+
&& row.screeningVerdict === row.intendedLabel;
|
|
334
|
+
}
|
|
335
|
+
function screeningRowChecked(row) {
|
|
336
|
+
return "ritsuDecision" in row ? row.ritsuDecision.checked : row.handChecked;
|
|
337
|
+
}
|
|
338
|
+
function screeningRowHandVerdict(row) {
|
|
339
|
+
if ("ritsuDecision" in row)
|
|
340
|
+
return row.ritsuDecision.checked ? row.ritsuDecision.verdict : undefined;
|
|
341
|
+
return row.handVerdict;
|
|
342
|
+
}
|
|
343
|
+
function screeningRowAdmitted(row) {
|
|
344
|
+
return screeningRowChecked(row) ? screeningRowHandVerdict(row) === "confirm" : screeningRowAgreed(row);
|
|
345
|
+
}
|
|
331
346
|
function expectedScreeningExclusionReason(row) {
|
|
332
|
-
const agreed = row
|
|
333
|
-
const admitted = row
|
|
347
|
+
const agreed = screeningRowAgreed(row);
|
|
348
|
+
const admitted = screeningRowAdmitted(row);
|
|
334
349
|
if (admitted)
|
|
335
350
|
return undefined;
|
|
336
351
|
if (!agreed)
|
|
337
|
-
return row.screeningVerdict === "indeterminate" ? "screening-indeterminate" : "screening-disagreement";
|
|
352
|
+
return row.screeningVerdict === "indeterminate" || row.screeningVerdict === "UNSURE" ? "screening-indeterminate" : "screening-disagreement";
|
|
338
353
|
return "screening-hand-excluded";
|
|
339
354
|
}
|
|
340
355
|
/** The reviewer Result Evaluation verifier shared by admission construction and closure replay. */
|
|
@@ -365,6 +380,10 @@ export function verifyBinaryJudgmentAdmissionClosure(input, ports) {
|
|
|
365
380
|
let screeningSample = new Set();
|
|
366
381
|
let screeningSampleAgreementRate;
|
|
367
382
|
let screeningInstrumentSha256;
|
|
383
|
+
let promptedScreeningProcedureSha256;
|
|
384
|
+
let promptedScreeningPool;
|
|
385
|
+
let promptedCommittedSample;
|
|
386
|
+
let screeningTableAuthorityKeyId;
|
|
368
387
|
if (manifest.truthAdmission === "screened-operator-sampled") {
|
|
369
388
|
if (manifest.screeningTableSha256 === undefined) {
|
|
370
389
|
fail("admissionManifest.screeningTableSha256", "screened admission manifest carries no screening table reference");
|
|
@@ -380,28 +399,100 @@ export function verifyBinaryJudgmentAdmissionClosure(input, ports) {
|
|
|
380
399
|
// branch's sole legal authority set is `["truth-reveal-attestor"]` alone (§6.8a Group C), so
|
|
381
400
|
// that is the one key that signs both the bank-scoped table and the bank-scoped reveal
|
|
382
401
|
// receipt: "signed once per bank" as D1 describes.
|
|
383
|
-
|
|
402
|
+
const tableEvidence = authorityPayload(state, {
|
|
384
403
|
digest: manifest.screeningTableSha256,
|
|
385
|
-
mediaType: SCREENING_TABLE_MEDIA_TYPE,
|
|
404
|
+
mediaType: [SCREENING_TABLE_MEDIA_TYPE, SCREENING_TABLE_V2_MEDIA_TYPE],
|
|
386
405
|
role: "truth-reveal-attestor",
|
|
387
406
|
evidenceRole: "screening-table",
|
|
388
|
-
schema: ScreeningTableSchema,
|
|
407
|
+
schema: z.union([ScreeningTableSchema, ScreeningTableV2Schema]),
|
|
389
408
|
path: "admissionManifest.screeningTableSha256",
|
|
390
|
-
})
|
|
409
|
+
});
|
|
410
|
+
screeningTable = tableEvidence.value;
|
|
411
|
+
screeningTableAuthorityKeyId = tableEvidence.keyId;
|
|
391
412
|
if (screeningTable.draftId !== input.expectedDraftId || screeningTable.sealedAt !== manifest.admittedAt) {
|
|
392
413
|
fail("screeningTable", "table draft/time does not bind the manifest");
|
|
393
414
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
415
|
+
let sampleSeed;
|
|
416
|
+
let sampleSize;
|
|
417
|
+
let sampleIdentities;
|
|
418
|
+
if (screeningTable.protocol === "https://spec.jinn.network/binary-judgment/screening-table/v1") {
|
|
419
|
+
if (tableEvidence.mediaType !== SCREENING_TABLE_MEDIA_TYPE)
|
|
420
|
+
fail("screeningTable", "v1 table protocol must use the v1 media type");
|
|
421
|
+
const screeningInstrument = parseProfileRecord(state, screeningTable.screeningInstrumentSha256, "screeningTable.screeningInstrumentSha256", parseBinaryJudgmentInstrument, "screening-instrument");
|
|
422
|
+
const resealedScreeningInstrument = sealBinaryJudgmentInstrument(screeningInstrument.value);
|
|
423
|
+
if (resealedScreeningInstrument.digest !== screeningTable.screeningInstrumentSha256 || !bytesEqual(resealedScreeningInstrument.bytes, screeningInstrument.bytes)) {
|
|
424
|
+
fail("screeningTable.screeningInstrumentSha256", "instrument does not reseal to its table commitment");
|
|
425
|
+
}
|
|
426
|
+
screeningInstrumentSha256 = screeningTable.screeningInstrumentSha256;
|
|
427
|
+
resolve(state, screeningTable.samplingScriptSha256, "screeningTable.samplingScriptSha256", "screening-sampling-script");
|
|
428
|
+
resolve(state, screeningTable.rawOutputsSha256, "screeningTable.rawOutputsSha256", "screening-raw-outputs");
|
|
429
|
+
sampleSeed = screeningTable.sampleSeed;
|
|
430
|
+
sampleSize = screeningTable.sampleSize;
|
|
431
|
+
sampleIdentities = screeningTable.rows.map((row) => row.itemSha256);
|
|
432
|
+
}
|
|
433
|
+
else {
|
|
434
|
+
if (tableEvidence.mediaType !== SCREENING_TABLE_V2_MEDIA_TYPE)
|
|
435
|
+
fail("screeningTable", "v2 table protocol must use the v2 media type");
|
|
436
|
+
const procedure = exactCanonical(PromptedScreeningProcedureV1Schema, resolve(state, screeningTable.procedureSha256, "screeningTable.procedureSha256", "screening-procedure"), "screeningProcedure");
|
|
437
|
+
const pool = exactCanonical(ScreeningPoolSchema, resolve(state, screeningTable.poolSha256, "screeningTable.poolSha256", "screening-pool"), "screeningPool");
|
|
438
|
+
let commitment;
|
|
439
|
+
try {
|
|
440
|
+
commitment = parseScreeningSampleCommitmentBytes(resolve(state, screeningTable.sampleCommitmentSha256, "screeningTable.sampleCommitmentSha256", "screening-sample-commitment"));
|
|
441
|
+
}
|
|
442
|
+
catch (cause) {
|
|
443
|
+
fail("screeningSampleCommitment", cause instanceof Error ? cause.message : "screening sample commitment is invalid", cause);
|
|
444
|
+
}
|
|
445
|
+
const commitmentView = promptedScreeningCommitmentView(commitment);
|
|
446
|
+
resolve(state, screeningTable.coordinatorPromptSha256, "screeningTable.coordinatorPromptSha256", "screening-prompt");
|
|
447
|
+
resolve(state, screeningTable.samplingScriptSha256, "screeningTable.samplingScriptSha256", "screening-sampling-script");
|
|
448
|
+
resolve(state, screeningTable.transcriptSha256, "screeningTable.transcriptSha256", "screening-transcript");
|
|
449
|
+
if (procedure.coordinatorPromptSha256 !== screeningTable.coordinatorPromptSha256
|
|
450
|
+
|| procedure.transcriptSha256 !== screeningTable.transcriptSha256
|
|
451
|
+
|| pool.draftId !== input.expectedDraftId
|
|
452
|
+
|| (commitmentView.draftId !== undefined && commitmentView.draftId !== input.expectedDraftId)
|
|
453
|
+
|| (commitmentView.poolSha256 !== undefined && commitmentView.poolSha256 !== screeningTable.poolSha256)
|
|
454
|
+
|| commitmentView.poolIdentityCommitmentSha256 !== pool.identityCommitmentSha256
|
|
455
|
+
|| (commitmentView.samplingScriptSha256 !== undefined && commitmentView.samplingScriptSha256 !== screeningTable.samplingScriptSha256)
|
|
456
|
+
|| Date.parse(procedure.sealedAt) > Date.parse(screeningTable.sealedAt)
|
|
457
|
+
|| Date.parse(pool.sealedAt) > Date.parse(screeningTable.sealedAt)
|
|
458
|
+
|| (commitmentView.poolSha256 !== undefined && Date.parse(pool.sealedAt) > Date.parse(commitmentView.committedAt))
|
|
459
|
+
|| Date.parse(commitmentView.committedAt) > Date.parse(screeningTable.sealedAt))
|
|
460
|
+
fail("screeningTable", "prompted procedure, prompt, transcript, pool, commitment, draft, or timestamps do not join");
|
|
461
|
+
const poolIdentities = pool.protocol === SCREENING_POOL_V2_PROTOCOL
|
|
462
|
+
? pool.items.map((item) => item.screeningIdentitySha256)
|
|
463
|
+
: pool.items.map((item) => item.itemSha256);
|
|
464
|
+
if (commitmentView.registeredIdentitySha256s !== undefined
|
|
465
|
+
&& !sameStrings([...poolIdentities].sort(compareCodeUnitStrings), commitmentView.registeredIdentitySha256s))
|
|
466
|
+
fail("screeningSampleCommitment.candidateItemDigests", "registered identities do not exactly cover the later pool bridge");
|
|
467
|
+
if (computeScreeningPoolDigest(poolIdentities) !== pool.identityCommitmentSha256)
|
|
468
|
+
fail("screeningPool.identityCommitmentSha256", "identity commitment does not match the sorted unique pool identities");
|
|
469
|
+
const itemIds = new Set();
|
|
470
|
+
for (const [index, poolItem] of pool.items.entries()) {
|
|
471
|
+
const item = parseProfileRecord(state, poolItem.itemSha256, `screeningPool.items.${index}.itemSha256`, (bytes) => parseBinaryJudgmentPayload(bytes), "source-item").value;
|
|
472
|
+
if (itemIds.has(item.itemId))
|
|
473
|
+
fail("screeningPool.items", `duplicate source itemId ${item.itemId}`);
|
|
474
|
+
itemIds.add(item.itemId);
|
|
475
|
+
}
|
|
476
|
+
const rowIdentities = screeningTable.rows.map((row) => row.itemSha256);
|
|
477
|
+
const sortedPoolItemSha256s = pool.items.map((item) => item.itemSha256).sort(compareCodeUnitStrings);
|
|
478
|
+
if (!sameStrings(rowIdentities, sortedPoolItemSha256s))
|
|
479
|
+
fail("screeningTable.rows", "v2 table must cover all 664 pool item digests in sorted order");
|
|
480
|
+
const poolByItem = new Map(pool.items.map((item) => [item.itemSha256, item]));
|
|
481
|
+
for (const row of screeningTable.rows) {
|
|
482
|
+
const poolItem = poolByItem.get(row.itemSha256);
|
|
483
|
+
if (row.intendedLabel !== poolItem.intendedLabel)
|
|
484
|
+
fail("screeningTable.rows", `row ${row.itemSha256} intended label differs from the sealed pool`);
|
|
485
|
+
if (row.ritsuDecision.checked && (Date.parse(row.ritsuDecision.decidedAt) < Date.parse(commitmentView.committedAt)
|
|
486
|
+
|| Date.parse(row.ritsuDecision.decidedAt) > Date.parse(screeningTable.sealedAt)))
|
|
487
|
+
fail("screeningTable.rows", `row ${row.itemSha256} has a Ritsu decision outside the committed-sample-to-table interval`);
|
|
488
|
+
}
|
|
489
|
+
sampleSeed = commitmentView.sampleSeed;
|
|
490
|
+
sampleSize = commitmentView.sampleSize;
|
|
491
|
+
sampleIdentities = poolIdentities;
|
|
492
|
+
promptedScreeningProcedureSha256 = screeningTable.procedureSha256;
|
|
493
|
+
promptedScreeningPool = pool;
|
|
494
|
+
promptedCommittedSample = commitmentView.sampleItemSha256s;
|
|
399
495
|
}
|
|
400
|
-
screeningInstrumentSha256 = screeningTable.screeningInstrumentSha256;
|
|
401
|
-
// These two sidecars are intentionally opaque. Resolving them is sufficient to prove exact
|
|
402
|
-
// digest-bound reachability; screening-sample/1 remains the independent sampling authority.
|
|
403
|
-
resolve(state, screeningTable.samplingScriptSha256, "screeningTable.samplingScriptSha256", "screening-sampling-script");
|
|
404
|
-
resolve(state, screeningTable.rawOutputsSha256, "screeningTable.rawOutputsSha256", "screening-raw-outputs");
|
|
405
496
|
screeningRowsByItem = new Map(screeningTable.rows.map((row) => [row.itemSha256, row]));
|
|
406
497
|
// (1) Sample membership: recomputed by screening-sample/1, never by executing the sealed
|
|
407
498
|
// script (R-4). The recomputation is authoritative for checks (2) and (3) below -- there is
|
|
@@ -409,9 +500,9 @@ export function verifyBinaryJudgmentAdmissionClosure(input, ports) {
|
|
|
409
500
|
let sampleResult;
|
|
410
501
|
try {
|
|
411
502
|
sampleResult = computeScreeningSample({
|
|
412
|
-
itemSha256s:
|
|
413
|
-
sampleSeed
|
|
414
|
-
sampleSize
|
|
503
|
+
itemSha256s: sampleIdentities,
|
|
504
|
+
sampleSeed,
|
|
505
|
+
sampleSize,
|
|
415
506
|
});
|
|
416
507
|
}
|
|
417
508
|
catch (cause) {
|
|
@@ -420,21 +511,28 @@ export function verifyBinaryJudgmentAdmissionClosure(input, ports) {
|
|
|
420
511
|
: "sample recomputation (screening-sample/1) failed", cause);
|
|
421
512
|
}
|
|
422
513
|
screeningSample = new Set(sampleResult.sample);
|
|
514
|
+
if (promptedCommittedSample !== undefined && !sameStrings([...sampleResult.sample].sort(compareCodeUnitStrings), promptedCommittedSample))
|
|
515
|
+
fail("screeningSampleCommitment.sampleItemSha256s", "public sample membership differs from screening-sample/1 recomputation");
|
|
516
|
+
const sampledItemSha256s = promptedScreeningPool?.protocol === SCREENING_POOL_V2_PROTOCOL
|
|
517
|
+
? new Set(promptedScreeningPool.items
|
|
518
|
+
.filter((item) => screeningSample.has(item.screeningIdentitySha256))
|
|
519
|
+
.map((item) => item.itemSha256))
|
|
520
|
+
: screeningSample;
|
|
423
521
|
// (2) Required hand checks: flagged := rows where NOT agreed. Every row in flagged ∪ sample
|
|
424
522
|
// must carry handChecked === true.
|
|
425
523
|
for (const row of screeningTable.rows) {
|
|
426
|
-
const flagged = row
|
|
427
|
-
if ((flagged ||
|
|
524
|
+
const flagged = !screeningRowAgreed(row);
|
|
525
|
+
if ((flagged || sampledItemSha256s.has(row.itemSha256)) && !screeningRowChecked(row)) {
|
|
428
526
|
fail("screeningTable.rows", `row ${row.itemSha256} is flagged or sampled but was never hand-checked`);
|
|
429
527
|
}
|
|
430
528
|
}
|
|
431
529
|
// (3) Sample agreement rate -- the one definition (spec §6.5(3)), symmetric on purpose. Every
|
|
432
530
|
// row in `screeningSample` is hand-checked by check (2) above, so `handVerdict` is defined.
|
|
433
|
-
const sampleMatches = [...
|
|
531
|
+
const sampleMatches = [...sampledItemSha256s].filter((sampledItemSha256) => {
|
|
434
532
|
const row = screeningRowsByItem.get(sampledItemSha256);
|
|
435
|
-
return (row
|
|
533
|
+
return screeningRowAgreed(row) === (screeningRowHandVerdict(row) === "confirm");
|
|
436
534
|
}).length;
|
|
437
|
-
screeningSampleAgreementRate = sampleMatches /
|
|
535
|
+
screeningSampleAgreementRate = sampleMatches / sampledItemSha256s.size;
|
|
438
536
|
}
|
|
439
537
|
const resolutions = manifest.labelResolutionSha256s.map((digest, index) => ({
|
|
440
538
|
digest,
|
|
@@ -528,6 +626,7 @@ export function verifyBinaryJudgmentAdmissionClosure(input, ports) {
|
|
|
528
626
|
const reveal = revealEvidence.value;
|
|
529
627
|
if (reveal.attestorRole !== "truth-reveal-attestor"
|
|
530
628
|
|| reveal.attestorKeyId !== revealEvidence.keyId
|
|
629
|
+
|| revealEvidence.keyId !== screeningTableAuthorityKeyId
|
|
531
630
|
|| reveal.draftId !== input.expectedDraftId
|
|
532
631
|
|| reveal.screeningTableSha256 !== resolution.screeningTableSha256
|
|
533
632
|
|| reveal.judgeExecutionState !== "not-started"
|
|
@@ -539,12 +638,13 @@ export function verifyBinaryJudgmentAdmissionClosure(input, ports) {
|
|
|
539
638
|
const row = screeningRowsByItem.get(resolution.itemSha256);
|
|
540
639
|
if (row === undefined)
|
|
541
640
|
fail(path, "accepted screened item has no covering screening-table row");
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
if (!admitted)
|
|
545
|
-
fail(path, "admitted screened item's table row is not itself admitted per §6.4's admission rule");
|
|
641
|
+
if (!screeningRowAdmitted(row))
|
|
642
|
+
fail(path, "admitted screened item's table row is not itself admitted per the sealed admission rule");
|
|
546
643
|
if (row.intendedLabel !== resolution.truthLabel)
|
|
547
644
|
fail(path, "resolution truth label differs from the screening table's intended label");
|
|
645
|
+
const poolItem = promptedScreeningPool?.items.find((item) => item.itemSha256 === resolution.itemSha256);
|
|
646
|
+
if (poolItem !== undefined && (poolItem.candidateClass !== resolution.candidateClass || poolItem.stratum !== resolution.stratum))
|
|
647
|
+
fail(path, "resolution class or stratum differs from the prompted screening pool");
|
|
548
648
|
break;
|
|
549
649
|
}
|
|
550
650
|
default: {
|
|
@@ -566,6 +666,36 @@ export function verifyBinaryJudgmentAdmissionClosure(input, ports) {
|
|
|
566
666
|
if (contextByResolution.size !== resolutions.length || contexts.some((entry) => !manifest.labelResolutionSha256s.includes(entry.value.labelResolutionSha256))) {
|
|
567
667
|
fail("admissionManifest.analysisContextSha256s", "analysis-context coverage has an orphan or omission");
|
|
568
668
|
}
|
|
669
|
+
const promptedExpectedReplacements = new Map();
|
|
670
|
+
if (promptedScreeningPool !== undefined) {
|
|
671
|
+
let selection;
|
|
672
|
+
try {
|
|
673
|
+
selection = selectPromptedScreeningPool(promptedScreeningPool, screeningRowsByItem);
|
|
674
|
+
}
|
|
675
|
+
catch (cause) {
|
|
676
|
+
fail("screeningPool.items", cause instanceof PromptedScreeningSelectionError ? cause.message : "prompted reserve selection failed", cause);
|
|
677
|
+
}
|
|
678
|
+
const expectedAccepted = new Set(selection.winners.map((item) => item.itemSha256));
|
|
679
|
+
for (const replacement of selection.replacements) {
|
|
680
|
+
promptedExpectedReplacements.set(replacement.excludedMain.itemSha256, {
|
|
681
|
+
replacementItemSha256: replacement.replacement.itemSha256,
|
|
682
|
+
receivingSlotId: replacement.receivingSlotId,
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
const actualAccepted = [...acceptedItems].sort(compareCodeUnitStrings);
|
|
686
|
+
if (!sameStrings(actualAccepted, [...expectedAccepted].sort(compareCodeUnitStrings)))
|
|
687
|
+
fail("admissionManifest.labelResolutionSha256s", "accepted items are not the first admissible candidate in every sealed slot");
|
|
688
|
+
if (accepted.length !== 240)
|
|
689
|
+
fail("admissionManifest.labelResolutionSha256s", "prompted screening must admit exactly 240 items");
|
|
690
|
+
for (const candidateClass of ["correct", "specific-wrong", "vague-topical-wrong"]) {
|
|
691
|
+
if (accepted.filter((item) => item.candidateClass === candidateClass).length !== 80)
|
|
692
|
+
fail("admissionManifest.labelResolutionSha256s", `final bank must contain exactly 80 ${candidateClass} items`);
|
|
693
|
+
for (const stratum of ["category-1", "category-2", "category-3", "category-4"]) {
|
|
694
|
+
if (accepted.filter((item) => item.candidateClass === candidateClass && item.stratum === stratum).length !== 20)
|
|
695
|
+
fail("admissionManifest.labelResolutionSha256s", `final bank must contain exactly 20 ${candidateClass}/${stratum} items`);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
569
699
|
const excludedDigests = ledger.entries.map((entry) => entry.excludedItemSha256).sort(compareCodeUnitStrings);
|
|
570
700
|
if (!sameStrings(excludedDigests, manifest.excludedItemSha256s))
|
|
571
701
|
fail("replacementLedger.entries", "ledger exclusion coverage differs from manifest");
|
|
@@ -577,7 +707,14 @@ export function verifyBinaryJudgmentAdmissionClosure(input, ports) {
|
|
|
577
707
|
let previousPosition = 0;
|
|
578
708
|
for (const [index, entry] of ledger.entries.entries()) {
|
|
579
709
|
const path = `replacementLedger.entries.${index}`;
|
|
580
|
-
|
|
710
|
+
const sharedPool = promptedScreeningPool?.protocol === SCREENING_POOL_V2_PROTOCOL;
|
|
711
|
+
if (sharedPool) {
|
|
712
|
+
const previousSlotId = index === 0 ? undefined : ledger.entries[index - 1].receivingSlotId;
|
|
713
|
+
if (entry.receivingSlotId === undefined || (previousSlotId !== undefined && compareCodeUnitStrings(previousSlotId, entry.receivingSlotId) >= 0)) {
|
|
714
|
+
fail(path, "shared-reserve ledger entries are not in deterministic receiving-slot order");
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
else if (entry.excludedPoolPosition <= previousPosition)
|
|
581
718
|
fail(path, "ledger entries are not in deterministic pool order");
|
|
582
719
|
previousPosition = entry.excludedPoolPosition;
|
|
583
720
|
if (excludedItems.has(entry.excludedItemSha256) || acceptedItems.has(entry.excludedItemSha256))
|
|
@@ -593,6 +730,35 @@ export function verifyBinaryJudgmentAdmissionClosure(input, ports) {
|
|
|
593
730
|
|| replacement.candidateClass !== entry.candidateClass
|
|
594
731
|
|| replacement.stratum !== entry.stratum)
|
|
595
732
|
fail(path, "replacement is not a later admitted reserve in the same class and stratum");
|
|
733
|
+
if (promptedScreeningPool !== undefined) {
|
|
734
|
+
const excludedPoolItem = promptedScreeningPool.items.find((item) => item.itemSha256 === entry.excludedItemSha256);
|
|
735
|
+
const replacementPoolItem = promptedScreeningPool.items.find((item) => item.itemSha256 === entry.replacementItemSha256);
|
|
736
|
+
const expected = promptedExpectedReplacements.get(entry.excludedItemSha256);
|
|
737
|
+
let sealedPoolItemsMatch = false;
|
|
738
|
+
let actualReceivingSlotId;
|
|
739
|
+
if (promptedScreeningPool.protocol === SCREENING_POOL_V2_PROTOCOL) {
|
|
740
|
+
if (excludedPoolItem?.poolKind === "main" && replacementPoolItem?.poolKind === "reserve") {
|
|
741
|
+
actualReceivingSlotId = entry.receivingSlotId;
|
|
742
|
+
sealedPoolItemsMatch = excludedPoolItem.candidateClass === replacementPoolItem.candidateClass
|
|
743
|
+
&& excludedPoolItem.stratum === replacementPoolItem.stratum
|
|
744
|
+
&& entry.receivingSlotId === excludedPoolItem.slotId;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
else if (excludedPoolItem?.poolKind === "main"
|
|
748
|
+
&& replacementPoolItem?.poolKind === "reserve"
|
|
749
|
+
&& "slotId" in excludedPoolItem
|
|
750
|
+
&& "slotId" in replacementPoolItem) {
|
|
751
|
+
actualReceivingSlotId = excludedPoolItem.slotId;
|
|
752
|
+
sealedPoolItemsMatch = excludedPoolItem.slotId === replacementPoolItem.slotId
|
|
753
|
+
&& entry.receivingSlotId === undefined;
|
|
754
|
+
}
|
|
755
|
+
if (!sealedPoolItemsMatch
|
|
756
|
+
|| entry.excludedPoolPosition !== excludedPoolItem?.poolPosition
|
|
757
|
+
|| entry.replacementPoolPosition !== replacementPoolItem?.poolPosition
|
|
758
|
+
|| expected?.replacementItemSha256 !== entry.replacementItemSha256
|
|
759
|
+
|| expected.receivingSlotId !== actualReceivingSlotId)
|
|
760
|
+
fail(path, "ledger does not name the deterministic first-admissible reserve and its receiving slot at the exact sealed positions");
|
|
761
|
+
}
|
|
596
762
|
// Branches on admission mode (spec §6.4, §6.5 check (4); ratified resolution to G-1): a
|
|
597
763
|
// screened exclusion is verified against the screening table's own per-row admission rule
|
|
598
764
|
// rather than against two signed reviews. §6.9 deliberately DROPS the four per-item two-human
|
|
@@ -650,8 +816,15 @@ export function verifyBinaryJudgmentAdmissionClosure(input, ports) {
|
|
|
650
816
|
stratum: entry.stratum,
|
|
651
817
|
reason: entry.reason,
|
|
652
818
|
replacementItemSha256: prefixed(entry.replacementItemSha256),
|
|
819
|
+
...(entry.receivingSlotId === undefined ? {} : { receivingSlotId: entry.receivingSlotId }),
|
|
653
820
|
});
|
|
654
821
|
}
|
|
822
|
+
if (promptedScreeningPool !== undefined) {
|
|
823
|
+
const ledgerReplacements = new Map(ledger.entries.map((entry) => [entry.excludedItemSha256, entry.replacementItemSha256]));
|
|
824
|
+
if (ledgerReplacements.size !== promptedExpectedReplacements.size || [...promptedExpectedReplacements].some(([excludedItem, expected]) => ledgerReplacements.get(excludedItem) !== expected.replacementItemSha256)) {
|
|
825
|
+
fail("replacementLedger.entries", "ledger does not exactly cover every prompted main-slot replacement");
|
|
826
|
+
}
|
|
827
|
+
}
|
|
655
828
|
// (0) Coverage, the other half (spec §6.5 check (0)): the table's rows must cover the frozen
|
|
656
829
|
// bank's candidate pool EXACTLY. The pool itself is not a separate input to this function --
|
|
657
830
|
// it is reconstructed as accepted ∪ excluded, the closure's own admission decision, which by
|
|
@@ -659,7 +832,7 @@ export function verifyBinaryJudgmentAdmissionClosure(input, ports) {
|
|
|
659
832
|
// with a same-class same-stratum replacement, and every replacement is itself an admitted pool
|
|
660
833
|
// member). `sampleSize <= rows.length` (check (0)'s other clause) is enforced transitively:
|
|
661
834
|
// `computeScreeningSample` above refuses a `sampleSize` exceeding the pool size on its own.
|
|
662
|
-
if (manifest.truthAdmission === "screened-operator-sampled") {
|
|
835
|
+
if (manifest.truthAdmission === "screened-operator-sampled" && promptedScreeningPool === undefined) {
|
|
663
836
|
if (screeningTable === undefined) {
|
|
664
837
|
// Unreachable today: the screened arm resolves the table before any row is admitted and fails
|
|
665
838
|
// closed when it is absent. Written as a refusal anyway, because the `&& screeningTable !==
|
|
@@ -705,5 +878,13 @@ export function verifyBinaryJudgmentAdmissionClosure(input, ports) {
|
|
|
705
878
|
instrumentSha256: screeningInstrumentSha256,
|
|
706
879
|
},
|
|
707
880
|
}),
|
|
881
|
+
...(screeningSampleAgreementRate === undefined || promptedScreeningProcedureSha256 === undefined ? {} : {
|
|
882
|
+
promptedScreening: {
|
|
883
|
+
sampleAgreementRate: screeningSampleAgreementRate,
|
|
884
|
+
procedureSha256: promptedScreeningProcedureSha256,
|
|
885
|
+
profile: PROMPTED_SCREENING_PROFILE,
|
|
886
|
+
capabilityBoundary: PROMPTED_SCREENING_CAPABILITY_BOUNDARY,
|
|
887
|
+
},
|
|
888
|
+
}),
|
|
708
889
|
};
|
|
709
890
|
}
|
package/dist/assets.d.ts
CHANGED
|
@@ -10,7 +10,12 @@ export interface PublicAssetInput {
|
|
|
10
10
|
/** Canonically sorted identities for every authenticated `records/<sha>.bin` closure member. */
|
|
11
11
|
readonly recordSha256s: readonly string[];
|
|
12
12
|
readonly dissentCellKeys: readonly string[];
|
|
13
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* Verifier-derived, authenticated human projection. Absent only for the
|
|
15
|
+
* qualification-projecting profile (`benchmark-product-public-bundle/4`, `/7`, and `/8`), which
|
|
16
|
+
* carries `binaryQualification` instead. There is no third, comparison-free profile: a
|
|
17
|
+
* bundle rendered with neither field is refused at verification (issue #2984).
|
|
18
|
+
*/
|
|
14
19
|
readonly comparison?: PublicComparisonView;
|
|
15
20
|
/** Producer-verified binary admission/instrument facts. Required for claim-package/2 only. */
|
|
16
21
|
readonly binaryQualification?: {
|
|
@@ -26,6 +31,21 @@ export interface PublicAssetInput {
|
|
|
26
31
|
}[];
|
|
27
32
|
};
|
|
28
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* The six-variable disclosure table (issue #2839, ruling Q4: the bundle's own `index.html` and
|
|
36
|
+
* `README.md` are S2's; the site template is packet R1's).
|
|
37
|
+
*
|
|
38
|
+
* Rendered from `claim.disclosure` — a verified fact the claim already carries — so this hangs off
|
|
39
|
+
* the REPORT/claim facts rather than off a format test, and every bundle without a declaration
|
|
40
|
+
* renders byte-identically to what it rendered before this section existed.
|
|
41
|
+
*
|
|
42
|
+
* `measured-here` and `disclosed-by-publisher` are kept visually distinct and are never merged into
|
|
43
|
+
* one list: the whole point of the record is that a reader can tell at a glance which variables this
|
|
44
|
+
* venue proved and which it only carries. Nothing here counts, scores, or ranks the statuses (design
|
|
45
|
+
* §8), and there is no explanatory caption — the status token and the statement are the content.
|
|
46
|
+
*/
|
|
47
|
+
export declare function disclosureSpecificationHtml(input: PublicAssetInput): string;
|
|
48
|
+
export declare function disclosureSpecificationMarkdown(input: PublicAssetInput): string;
|
|
29
49
|
export declare function binaryAdmissionHtml(input: PublicAssetInput): string;
|
|
30
50
|
export declare function binaryAdmissionMarkdown(input: PublicAssetInput): string;
|
|
31
51
|
/** Fixed, deterministic public-bundle/2 presentation bytes. The builder only projects already
|
package/dist/assets.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BENCHMARKING_METHOD_IDS } from "@jinn-network/benchmarking-records";
|
|
1
|
+
import { BENCHMARKING_METHOD_IDS, DISCLOSURE_VARIABLE_KEYS } from "@jinn-network/benchmarking-records";
|
|
2
2
|
import { validateBinaryInstrumentQualificationProjection } from "@jinn-network/benchmarking-aggregate";
|
|
3
3
|
import { canonicalJsonBytes } from "@jinn-network/trust-core";
|
|
4
4
|
import { readFileSync } from "node:fs";
|
|
@@ -450,6 +450,57 @@ function publicationGradeWording(truthAdmission) {
|
|
|
450
450
|
}
|
|
451
451
|
}
|
|
452
452
|
}
|
|
453
|
+
/**
|
|
454
|
+
* The six-variable disclosure table (issue #2839, ruling Q4: the bundle's own `index.html` and
|
|
455
|
+
* `README.md` are S2's; the site template is packet R1's).
|
|
456
|
+
*
|
|
457
|
+
* Rendered from `claim.disclosure` — a verified fact the claim already carries — so this hangs off
|
|
458
|
+
* the REPORT/claim facts rather than off a format test, and every bundle without a declaration
|
|
459
|
+
* renders byte-identically to what it rendered before this section existed.
|
|
460
|
+
*
|
|
461
|
+
* `measured-here` and `disclosed-by-publisher` are kept visually distinct and are never merged into
|
|
462
|
+
* one list: the whole point of the record is that a reader can tell at a glance which variables this
|
|
463
|
+
* venue proved and which it only carries. Nothing here counts, scores, or ranks the statuses (design
|
|
464
|
+
* §8), and there is no explanatory caption — the status token and the statement are the content.
|
|
465
|
+
*/
|
|
466
|
+
export function disclosureSpecificationHtml(input) {
|
|
467
|
+
const disclosure = input.claim.disclosure;
|
|
468
|
+
if (disclosure === undefined)
|
|
469
|
+
return "";
|
|
470
|
+
const rows = DISCLOSURE_VARIABLE_KEYS.map((key) => {
|
|
471
|
+
const entry = disclosure.variables[key];
|
|
472
|
+
const detail = entry.status === "undisclosed"
|
|
473
|
+
? `<p class="disclosure-reason">${escapeMarkup(entry.reason)}</p>`
|
|
474
|
+
: `<p class="disclosure-statement">${escapeMarkup(entry.statement)}</p>`;
|
|
475
|
+
const sources = entry.status === "disclosed-by-publisher" && entry.sources !== undefined
|
|
476
|
+
? `<ul class="disclosure-sources">${entry.sources.map((source) => `<li>${escapeMarkup(source.uri)}</li>`).join("")}</ul>`
|
|
477
|
+
: "";
|
|
478
|
+
const evidence = entry.status === "measured-here"
|
|
479
|
+
? `<ul class="disclosure-evidence">${entry.evidence.map((citation) => `<li><span class="role">${escapeMarkup(citation.role)}</span> <span class="digest">${escapeMarkup(citation.digest.sha256)}</span></li>`).join("")}</ul>`
|
|
480
|
+
: "";
|
|
481
|
+
return `<div class="disclosure-variable disclosure-${escapeMarkup(entry.status)}"><dt>${escapeMarkup(key)}</dt><dd><span class="disclosure-status">${escapeMarkup(entry.status)}</span>${detail}${sources}${evidence}</dd></div>`;
|
|
482
|
+
}).join("");
|
|
483
|
+
return `<section id="disclosure-specification" aria-labelledby="disclosure-specification-heading"><h2 id="disclosure-specification-heading">Six-variable disclosure</h2><dl class="facts disclosure">${rows}</dl><p class="digest">${escapeMarkup(disclosure.specification)} · record ${escapeMarkup(disclosure.recordSha256)} · subject ${escapeMarkup(disclosure.subjectSha256)}</p></section>`;
|
|
484
|
+
}
|
|
485
|
+
export function disclosureSpecificationMarkdown(input) {
|
|
486
|
+
const disclosure = input.claim.disclosure;
|
|
487
|
+
if (disclosure === undefined)
|
|
488
|
+
return "";
|
|
489
|
+
const rows = DISCLOSURE_VARIABLE_KEYS.map((key) => {
|
|
490
|
+
const entry = disclosure.variables[key];
|
|
491
|
+
const detail = entry.status === "undisclosed"
|
|
492
|
+
? `\n - ${escapeMarkdown(entry.reason)}`
|
|
493
|
+
: `\n - ${escapeMarkdown(entry.statement)}`;
|
|
494
|
+
const sources = entry.status === "disclosed-by-publisher" && entry.sources !== undefined
|
|
495
|
+
? entry.sources.map((source) => `\n - source: ${escapeMarkdown(source.uri)}`).join("")
|
|
496
|
+
: "";
|
|
497
|
+
const evidence = entry.status === "measured-here"
|
|
498
|
+
? entry.evidence.map((citation) => `\n - ${escapeMarkdown(citation.role)}: ${escapeMarkdown(citation.digest.sha256)}`).join("")
|
|
499
|
+
: "";
|
|
500
|
+
return `- ${escapeMarkdown(key)}: ${escapeMarkdown(entry.status)}${detail}${sources}${evidence}`;
|
|
501
|
+
}).join("\n");
|
|
502
|
+
return `## Six-variable disclosure\n\n${rows}\n\n- ${escapeMarkdown(disclosure.specification)}\n- record: ${escapeMarkdown(disclosure.recordSha256)}\n- subject: ${escapeMarkdown(disclosure.subjectSha256)}\n\n`;
|
|
503
|
+
}
|
|
453
504
|
export function binaryAdmissionHtml(input) {
|
|
454
505
|
const admission = input.binaryQualification;
|
|
455
506
|
if (admission === undefined)
|
|
@@ -493,6 +544,12 @@ function neutralClaimHtml(facts) {
|
|
|
493
544
|
}
|
|
494
545
|
return '<p class="neutral">Verified binary-instrument qualification. Facts are presented per instrument without comparative conclusions.</p>';
|
|
495
546
|
}
|
|
547
|
+
// Task-selection provenance (issue #2980) is sealed into the Run and verified under
|
|
548
|
+
// `claim-consistency`, but deliberately renders NOTHING here, and no asset below projects it.
|
|
549
|
+
// The classic and anchored allocations pin `@colophon-claims/verify@0.1.0`, whose
|
|
550
|
+
// `verifyPublicBundleSnapshot` byte-compares every presentation asset against its own rebuild, so
|
|
551
|
+
// a bundle that rendered the sentence would carry an instruction to run a verifier that refuses
|
|
552
|
+
// it. Restoring the render is issue #3416, once the reader line that derives it is re-pinned.
|
|
496
553
|
function buildIndex(input, reportFacts, claimFacts) {
|
|
497
554
|
const outcome = input.matrix.completeness.runOutcome;
|
|
498
555
|
const status = reportFacts.kind === "binary" ? qualificationOutcomeLabel(outcome) : outcomeLabel(outcome);
|
|
@@ -543,7 +600,7 @@ ${neutralClaimHtml(reportFacts)}
|
|
|
543
600
|
</header>
|
|
544
601
|
<main>
|
|
545
602
|
<section class="adverse" aria-labelledby="adverse-heading"><h2 id="adverse-heading">Prominent adverse facts</h2>${list(adverse, "No adverse facts stated.")}</section>${input.comparison === undefined ? "" : `\n${comparisonSectionHtml(input.comparison)}`}
|
|
546
|
-
<section aria-labelledby="scope-heading"><h2 id="scope-heading">Benchmark and configuration scope</h2><dl class="facts"><div><dt>Benchmark digest</dt><dd class="digest">${input.claim.scope.benchmarkSha256}</dd></div><div><dt>Tasks</dt><dd>${input.claim.scope.taskCount}</dd></div><div><dt>Replicates</dt><dd>${input.claim.scope.replicates}</dd></div><div><dt>Venue</dt><dd>${escapeMarkup(input.claim.scope.venue)}</dd></div></dl><h3>Arms and pinned configuration</h3><ul>${arms}</ul></section>${reportFacts.kind === "binary" ? binaryAdmissionHtml(input) : ""}
|
|
603
|
+
<section aria-labelledby="scope-heading"><h2 id="scope-heading">Benchmark and configuration scope</h2><dl class="facts"><div><dt>Benchmark digest</dt><dd class="digest">${input.claim.scope.benchmarkSha256}</dd></div><div><dt>Tasks</dt><dd>${input.claim.scope.taskCount}</dd></div><div><dt>Replicates</dt><dd>${input.claim.scope.replicates}</dd></div><div><dt>Venue</dt><dd>${escapeMarkup(input.claim.scope.venue)}</dd></div></dl><h3>Arms and pinned configuration</h3><ul>${arms}</ul></section>${reportFacts.kind === "binary" ? binaryAdmissionHtml(input) : ""}${disclosureSpecificationHtml(input)}
|
|
547
604
|
<section aria-labelledby="matrix-heading"><h2 id="matrix-heading">Sealed Matrix accounting</h2><p class="source-label">Source: authenticated <a href="matrix.json">matrix.json</a>; values below are copied without reconciliation.</p><pre>${escapeMarkup(canonicalText({ completeness: input.matrix.completeness, attrition: input.matrix.attrition }))}</pre><h3>Completeness and attrition</h3><dl class="facts"><div><dt>Matrix run outcome</dt><dd>${escapeMarkup(outcome)}</dd></div><div><dt>Matrix expected</dt><dd>${input.matrix.completeness.expected}</dd></div><div><dt>Matrix judged</dt><dd>${input.matrix.completeness.judged}</dd></div><div><dt>Matrix floor</dt><dd>${escapeMarkup(input.matrix.completeness.floor)}</dd></div></dl><div class="table-scroll" tabindex="0" role="region" aria-label="Per-arm Matrix attrition"><table><caption>Exact per-arm attrition stored in the Matrix</caption><thead><tr><th scope="col">Arm</th><th scope="col">Expected</th><th scope="col">Judged</th><th scope="col">Unjudged</th><th scope="col">Unscorable</th><th scope="col">Expired</th><th scope="col">Invalidated</th><th scope="col">Excluded</th><th scope="col">Replacements</th></tr></thead><tbody>${attritionRows(input)}</tbody></table></div><h3>Matrix asymmetry flags</h3>${list(input.matrix.attrition.asymmetryFlags, "None recorded in the Matrix.")}</section>
|
|
548
605
|
<section aria-labelledby="report-heading"><h2 id="report-heading">Sealed Report facts</h2><p class="source-label">Source: authenticated <a href="report.json">report.json</a>; values below are copied without reconciliation.</p><h3>${factsHeading(reportFacts, "report")}</h3>${armResultsHtml(reportFacts, "Exact wilson@1 values from the sealed Report")}<h3>Method and assurance facts stored in the Report</h3><dl class="facts"><div><dt>Report method</dt><dd>${escapeMarkup(input.report.method.id)} @ ${escapeMarkup(input.report.method.version)}</dd></div><div><dt>Report preregistered</dt><dd>${input.report.preregistered === true ? "Yes" : "No"}</dd></div></dl><h3>Report parameters</h3><pre>${escapeMarkup(canonicalText(input.report.method.parameters))}</pre><h3>Report conflicts</h3><pre>${escapeMarkup(canonicalText(reportFacts.conflicted))}</pre><h3>Report disclosures</h3><pre>${escapeMarkup(canonicalText(input.report.disclosures))}</pre></section>
|
|
549
606
|
<section aria-labelledby="claim-heading"><h2 id="claim-heading">Stored Claim facts</h2><p class="source-label">Source: authenticated <a href="claim-package.json">claim-package.json</a>; values below are copied without reconciliation.</p><h3>${factsHeading(claimFacts, "claim")}</h3>${armResultsHtml(claimFacts, "Exact arm values stored in the Claim package")}<h3>Claim method and preregistration</h3><dl class="facts"><div><dt>Claim method</dt><dd>${escapeMarkup(input.claim.method.id)} @ ${escapeMarkup(input.claim.method.version)}</dd></div><div><dt>Claim preregistered</dt><dd>${input.claim.method.preregistered ? "Yes" : "No"}</dd></div><div><dt>Assurance preset</dt><dd>${escapeMarkup(input.claim.assurance.preset)}</dd></div></dl><h3>Claim parameters</h3><pre>${escapeMarkup(canonicalText(input.claim.method.parameters))}</pre><h3>Claim completeness</h3><pre>${escapeMarkup(canonicalText(input.claim.completeness))}</pre><h3>Claim attrition</h3><pre>${escapeMarkup(canonicalText(input.claim.attrition))}</pre><h3>Claim conflicts</h3><pre>${escapeMarkup(canonicalText(input.claim.conflicted))}</pre><h3>Claim disclosures</h3><h4>Unverifiable axes, integrity tiers, and per-subject disclosures</h4><pre>${escapeMarkup(canonicalText(input.claim.disclosures))}</pre><h3>Resolved assurance primitives</h3><pre>${escapeMarkup(canonicalText(input.claim.assurance.resolved))}</pre><p>${escapeMarkup(input.claim.assurance.disclosure)}</p><h3>Rehearsal disclosure</h3>${rehearsalHtml}</section>
|
|
@@ -749,7 +806,7 @@ ${adverse.map((value) => `- ${escapeMarkdown(value)}`).join("\n")}
|
|
|
749
806
|
|
|
750
807
|
## Configurations
|
|
751
808
|
|
|
752
|
-
${arms}${reportFacts.kind === "binary" ? `\n\n${binaryAdmissionMarkdown(input).trimEnd()}` : ""}
|
|
809
|
+
${arms}${reportFacts.kind === "binary" ? `\n\n${binaryAdmissionMarkdown(input).trimEnd()}` : ""}${input.claim.disclosure === undefined ? "" : `\n\n${disclosureSpecificationMarkdown(input).trimEnd()}`}
|
|
753
810
|
|
|
754
811
|
## Sealed Matrix accounting
|
|
755
812
|
|