@colophon-claims/verify 0.0.0 → 0.2.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.
Files changed (96) hide show
  1. package/README.md +39 -0
  2. package/dist/admission/application.d.ts +18 -0
  3. package/dist/admission/application.js +53 -0
  4. package/dist/admission/contracts.d.ts +506 -0
  5. package/dist/admission/contracts.js +459 -0
  6. package/dist/admission/index.d.ts +5 -0
  7. package/dist/admission/index.js +5 -0
  8. package/dist/admission/intake.d.ts +75 -0
  9. package/dist/admission/intake.js +70 -0
  10. package/dist/admission/result-evaluation.d.ts +18 -0
  11. package/dist/admission/result-evaluation.js +71 -0
  12. package/dist/admission/screening-sample.d.ts +56 -0
  13. package/dist/admission/screening-sample.js +126 -0
  14. package/dist/admission/verification.d.ts +102 -0
  15. package/dist/admission/verification.js +844 -0
  16. package/dist/anchor/check.d.ts +95 -0
  17. package/dist/anchor/check.js +146 -0
  18. package/dist/anchor/ports.d.ts +6 -0
  19. package/dist/anchor/ports.js +354 -0
  20. package/dist/assets.d.ts +33 -0
  21. package/dist/assets.js +885 -0
  22. package/dist/bin.d.ts +2 -0
  23. package/dist/bin.js +20 -0
  24. package/dist/cli.d.ts +14 -0
  25. package/dist/cli.js +205 -0
  26. package/dist/comparison.d.ts +64 -0
  27. package/dist/comparison.js +235 -0
  28. package/dist/index.d.ts +23 -0
  29. package/dist/index.js +23 -0
  30. package/dist/manifest.d.ts +54 -0
  31. package/dist/manifest.js +240 -0
  32. package/dist/materialize.d.ts +4 -0
  33. package/dist/materialize.js +13 -0
  34. package/dist/profile/admission-receipts.d.ts +11 -0
  35. package/dist/profile/admission-receipts.js +24 -0
  36. package/dist/profile/anchor-claims.d.ts +157 -0
  37. package/dist/profile/anchor-claims.js +330 -0
  38. package/dist/profile/artifacts.d.ts +261 -0
  39. package/dist/profile/artifacts.js +139 -0
  40. package/dist/profile/assembly-ports.d.ts +19 -0
  41. package/dist/profile/assembly-ports.js +42 -0
  42. package/dist/profile/binary-judge-manifest.d.ts +121 -0
  43. package/dist/profile/binary-judge-manifest.js +106 -0
  44. package/dist/profile/binary-qualification.d.ts +9 -0
  45. package/dist/profile/binary-qualification.js +58 -0
  46. package/dist/profile/branding-assets.d.ts +5 -0
  47. package/dist/profile/branding-assets.js +5 -0
  48. package/dist/profile/branding.d.ts +24 -0
  49. package/dist/profile/branding.js +14 -0
  50. package/dist/profile/claim-consistency.d.ts +26 -0
  51. package/dist/profile/claim-consistency.js +91 -0
  52. package/dist/profile/claim.d.ts +327 -0
  53. package/dist/profile/claim.js +742 -0
  54. package/dist/profile/errors.d.ts +49 -0
  55. package/dist/profile/errors.js +78 -0
  56. package/dist/profile/inspect-assurance.d.ts +16 -0
  57. package/dist/profile/inspect-assurance.js +30 -0
  58. package/dist/profile/inspect-disclosure.d.ts +28 -0
  59. package/dist/profile/inspect-disclosure.js +54 -0
  60. package/dist/profile/inspect-manifest.d.ts +706 -0
  61. package/dist/profile/inspect-manifest.js +397 -0
  62. package/dist/profile/isolation.d.ts +21 -0
  63. package/dist/profile/isolation.js +31 -0
  64. package/dist/profile/pinning-evidence.d.ts +41 -0
  65. package/dist/profile/pinning-evidence.js +43 -0
  66. package/dist/profile/ports.d.ts +3 -0
  67. package/dist/profile/ports.js +11 -0
  68. package/dist/profile/preview-log.d.ts +4 -0
  69. package/dist/profile/preview-log.js +3 -0
  70. package/dist/profile/run-results.d.ts +17 -0
  71. package/dist/profile/run-results.js +23 -0
  72. package/dist/profile/signing.d.ts +10 -0
  73. package/dist/profile/signing.js +46 -0
  74. package/dist/profile/trust.d.ts +13 -0
  75. package/dist/profile/trust.js +35 -0
  76. package/dist/profile/venue.d.ts +2 -0
  77. package/dist/profile/venue.js +2 -0
  78. package/dist/profile/verdict.d.ts +28 -0
  79. package/dist/profile/verdict.js +51 -0
  80. package/dist/reader-instructions.d.ts +41 -0
  81. package/dist/reader-instructions.js +52 -0
  82. package/dist/schema.d.ts +380 -0
  83. package/dist/schema.js +498 -0
  84. package/dist/verify.d.ts +72 -0
  85. package/dist/verify.js +1573 -0
  86. package/dist/version.d.ts +2 -0
  87. package/dist/version.js +2 -0
  88. package/package.json +63 -3
  89. package/schemas/assembly-row.schema.json +194 -0
  90. package/schemas/bundle-manifest.schema.json +23 -0
  91. package/schemas/claim-package.schema.json +279 -0
  92. package/schemas/dsse-envelope.schema.json +25 -0
  93. package/schemas/evidence-catalog.schema.json +43 -0
  94. package/schemas/public-trust.schema.json +44 -0
  95. package/schemas/verdict-catalog.schema.json +27 -0
  96. package/scripts/external-verify.py +238 -0
@@ -0,0 +1,844 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ import { z } from "zod";
3
+ import { VERDICT_DSSE_PAYLOAD_TYPE, canonicalJsonBytes, compareCodeUnitStrings, parseBinaryJudgmentAnalysisContext, parseBinaryJudgmentInstrument, parseBinaryJudgmentLabelResolution, parseBinaryJudgmentPayload, recordDigest, sealBinaryJudgmentInstrument, } from "@jinn-network/task-execution-profiles";
4
+ import { parseExactDsseEnvelope } from "@jinn-network/trust-core";
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, SCREENING_TABLE_V2_MEDIA_TYPE, HumanReviewOperatorAssertionSchema, HumanReviewPacketSchema, HumanReviewReplacementLedgerSchema, HumanReviewResponseSchema, HumanReviewRevealReceiptSchema, HumanReviewRosterSchema, HumanReviewVisibilityReceiptSchema, ScreeningRevealReceiptSchema, ScreeningTableSchema, ScreeningTableV2Schema, PromptedScreeningProcedureV1Schema, ScreeningPoolV1Schema, ScreeningSampleCommitmentV1Schema, PROMPTED_SCREENING_LIMITATIONS, PROMPTED_SCREENING_PROFILE, } from "./contracts.js";
7
+ import { readOrderedAdmissionVerdictMeasurements, readAdmissionVerdictEnvelope } from "./result-evaluation.js";
8
+ import { computeScreeningPoolDigest, computeScreeningSample, ScreeningSampleError } from "./screening-sample.js";
9
+ // Verbatim duplicate of `schema.ts`'s `BUNDLE_V4_ADMISSION_EVIDENCE_ROLES`, hand-kept in sync
10
+ // (spec §6.8a Group C).
11
+ export const BINARY_JUDGMENT_ADMISSION_RECORD_ROLES = [
12
+ "admission-manifest", "replacement-ledger", "source-item", "label-resolution",
13
+ "analysis-context", "human-review-evaluation-spec", "human-review-form",
14
+ "human-review-packet", "human-review-response", "human-review-verdict",
15
+ "reviewer-roster", "review-visibility-receipt", "review-reveal-receipt",
16
+ "operator-assertion",
17
+ // Appended after operator-assertion (spec §6.8a Group C, first bullet; packet P6).
18
+ "screening-table",
19
+ "screening-reveal-receipt",
20
+ // Appended only: these exact nested records are authenticated by the signed screening table.
21
+ "screening-instrument",
22
+ "screening-sampling-script",
23
+ "screening-raw-outputs",
24
+ "screening-prompt",
25
+ "screening-procedure",
26
+ "screening-pool",
27
+ "screening-sample-commitment",
28
+ "screening-transcript",
29
+ ];
30
+ export const PROMPTED_SCREENING_CAPABILITY_BOUNDARY = {
31
+ status: "not-machine-verified",
32
+ machineVerified: ["sealed-pool", "sealed-sample", "signed-table", "operator-decisions", "artifact-integrity"],
33
+ notMachineVerified: PROMPTED_SCREENING_LIMITATIONS,
34
+ };
35
+ export class BinaryJudgmentAdmissionClosureError extends Error {
36
+ path;
37
+ constructor(path, detail, options) {
38
+ super(`${path}: ${detail}`, options);
39
+ this.name = "BinaryJudgmentAdmissionClosureError";
40
+ this.path = path;
41
+ }
42
+ }
43
+ function fail(path, detail, cause) {
44
+ throw new BinaryJudgmentAdmissionClosureError(path, detail, cause === undefined ? undefined : { cause });
45
+ }
46
+ function bytesEqual(left, right) {
47
+ return left.byteLength === right.byteLength && left.every((byte, index) => byte === right[index]);
48
+ }
49
+ function exactCanonical(schema, bytes, path) {
50
+ let json;
51
+ try {
52
+ json = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
53
+ }
54
+ catch (cause) {
55
+ fail(path, "record is not UTF-8 JSON", cause);
56
+ }
57
+ const parsed = schema.safeParse(json);
58
+ if (!parsed.success)
59
+ fail(path, parsed.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`).join("; "));
60
+ if (!bytesEqual(bytes, canonicalJsonBytes(parsed.data)))
61
+ fail(path, "record is not in exact canonical JSON encoding");
62
+ return parsed.data;
63
+ }
64
+ function prefixed(value) {
65
+ return (value.startsWith("sha256:") ? value : `sha256:${value}`);
66
+ }
67
+ function bare(value) {
68
+ return value.startsWith("sha256:") ? value.slice("sha256:".length) : value;
69
+ }
70
+ function sameStrings(left, right) {
71
+ return left.length === right.length && left.every((value, index) => value === right[index]);
72
+ }
73
+ function resolve(state, digest, path, role) {
74
+ if (!/^sha256:[0-9a-f]{64}$/u.test(digest))
75
+ fail(path, "record digest is not canonical sha256");
76
+ const exactDigest = digest;
77
+ let bytes;
78
+ try {
79
+ bytes = state.ports.resolveExactRecord(exactDigest);
80
+ }
81
+ catch (cause) {
82
+ fail(path, `cannot resolve ${digest}`, cause);
83
+ }
84
+ if (recordDigest(bytes) !== digest)
85
+ fail(path, `resolved bytes do not hash to ${digest}`);
86
+ state.reachable.add(exactDigest);
87
+ const roles = state.roles.get(exactDigest) ?? new Set();
88
+ roles.add(role);
89
+ state.roles.set(exactDigest, roles);
90
+ return bytes;
91
+ }
92
+ function parseProfileRecord(state, digest, path, parse, role) {
93
+ const bytes = resolve(state, digest, path, role);
94
+ let value;
95
+ try {
96
+ value = parse(bytes);
97
+ }
98
+ catch (cause) {
99
+ fail(path, "record does not satisfy its registered schema", cause);
100
+ }
101
+ if (!bytesEqual(bytes, canonicalJsonBytes(value)))
102
+ fail(path, "record is not in exact canonical JSON encoding");
103
+ return { value, bytes };
104
+ }
105
+ function verifyFrozenHumanReviewSpec(state, path) {
106
+ const specBytes = resolve(state, BINARY_JUDGMENT_HUMAN_REVIEW_EVALUATION_SPEC_SEALED.digest, `${path}.evaluationSpecification`, "human-review-evaluation-spec");
107
+ if (!bytesEqual(specBytes, BINARY_JUDGMENT_HUMAN_REVIEW_EVALUATION_SPEC_SEALED.bytes)) {
108
+ fail(path, "frozen human-review EvaluationSpec bytes do not match the registered contract");
109
+ }
110
+ const formBytes = resolve(state, HUMAN_REVIEW_FORM_SEALED.digest, `${path}.reviewForm`, "human-review-form");
111
+ if (!bytesEqual(formBytes, HUMAN_REVIEW_FORM_SEALED.bytes)) {
112
+ fail(path, "frozen human-review form bytes do not match the registered contract");
113
+ }
114
+ }
115
+ function verifyReview(state, verdictSha256, expectedItemSha256, path) {
116
+ const envelopeBytes = resolve(state, verdictSha256, path, "human-review-verdict");
117
+ let envelope;
118
+ try {
119
+ envelope = parseExactDsseEnvelope(envelopeBytes);
120
+ }
121
+ catch (cause) {
122
+ fail(path, "review verdict is not an exact compact DSSE envelope", cause);
123
+ }
124
+ if (envelope.payloadType !== VERDICT_DSSE_PAYLOAD_TYPE || envelope.signatures.length !== 1) {
125
+ fail(path, "review verdict must be one Result Evaluation envelope with one signature");
126
+ }
127
+ let statement;
128
+ try {
129
+ statement = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(envelope.payloadBytes));
130
+ }
131
+ catch (cause) {
132
+ fail(path, "Result Evaluation payload is not UTF-8 JSON", cause);
133
+ }
134
+ if (!bytesEqual(canonicalJsonBytes(statement), envelope.payloadBytes))
135
+ fail(path, "Result Evaluation payload is not exact canonical JSON");
136
+ let view;
137
+ let ordered;
138
+ try {
139
+ view = readAdmissionVerdictEnvelope(envelopeBytes);
140
+ ordered = readOrderedAdmissionVerdictMeasurements(envelopeBytes);
141
+ }
142
+ catch (cause) {
143
+ fail(path, "Result Evaluation payload is outside the closed verdict grammar", cause);
144
+ }
145
+ const signature = envelope.signatures[0];
146
+ const signatureKeyId = signature.keyid;
147
+ if (typeof signatureKeyId !== "string" || !state.ports.verifyReviewerSignature({ envelopeBytes, evaluatorId: view.evaluatorId, keyId: signatureKeyId })) {
148
+ fail(path, "reviewer signature or evaluator/key binding is invalid");
149
+ }
150
+ if (view.evaluationSpecificationSha256 !== bare(BINARY_JUDGMENT_HUMAN_REVIEW_EVALUATION_SPEC_SEALED.digest)) {
151
+ fail(path, "review names a different human-review EvaluationSpec");
152
+ }
153
+ verifyFrozenHumanReviewSpec(state, path);
154
+ const expectedNames = ["truthLabel", "reviewComplete", "reviewPacketSha256", "visibilityReceiptSha256", "responseSha256"];
155
+ if (ordered.length !== expectedNames.length || ordered.some((entry, index) => entry.name !== expectedNames[index])) {
156
+ fail(path, "review measurements do not match the frozen human-review EvaluationSpec");
157
+ }
158
+ const [labelRaw, completeRaw, packetRaw, visibilityRaw, responseRaw] = ordered.map((entry) => entry.value);
159
+ const labelResult = z.enum(["CORRECT", "WRONG", "indeterminate"]).safeParse(labelRaw);
160
+ if (!labelResult.success || typeof completeRaw !== "boolean" || typeof packetRaw !== "string" || typeof visibilityRaw !== "string" || typeof responseRaw !== "string") {
161
+ fail(path, "review measurement values are invalid");
162
+ }
163
+ const packetSha256 = prefixed(packetRaw);
164
+ const visibilityReceiptSha256 = prefixed(visibilityRaw);
165
+ const responseSha256 = prefixed(responseRaw);
166
+ const packet = exactCanonical(HumanReviewPacketSchema, resolve(state, packetSha256, `${path}.packet`, "human-review-packet"), `${path}.packet`);
167
+ const visibility = exactCanonical(HumanReviewVisibilityReceiptSchema, resolve(state, visibilityReceiptSha256, `${path}.visibility`, "review-visibility-receipt"), `${path}.visibility`);
168
+ const response = exactCanonical(HumanReviewResponseSchema, resolve(state, responseSha256, `${path}.response`, "human-review-response"), `${path}.response`);
169
+ const itemBytes = resolve(state, expectedItemSha256, `${path}.item`, "source-item");
170
+ const item = parseProfileRecord(state, expectedItemSha256, `${path}.item`, (bytes) => parseBinaryJudgmentPayload(bytes), "source-item").value;
171
+ if (!bytesEqual(itemBytes, canonicalJsonBytes(packet.item)) || packet.item.itemId !== item.itemId) {
172
+ fail(path, "packet embedded item does not equal the exact referenced item record");
173
+ }
174
+ const subjects = statement.subject;
175
+ const predicate = statement.predicate;
176
+ if (!Array.isArray(subjects) || subjects.length !== 2 || typeof predicate !== "object" || predicate === null || Array.isArray(predicate)) {
177
+ fail(path, "Result Evaluation subjects or predicate are malformed");
178
+ }
179
+ const subjectView = subjects;
180
+ const predicateView = predicate;
181
+ const expectedVerdict = !completeRaw || labelResult.data === "indeterminate"
182
+ ? "inconclusive"
183
+ : labelResult.data === "CORRECT" ? "pass" : "fail";
184
+ const expectedEvidence = [
185
+ ["human-review-packet", bare(packetSha256), HUMAN_REVIEW_PACKET_MEDIA_TYPE],
186
+ ["visibility-receipt", bare(visibilityReceiptSha256), HUMAN_REVIEW_VISIBILITY_RECEIPT_MEDIA_TYPE],
187
+ ["human-review-response", bare(responseSha256), HUMAN_REVIEW_RESPONSE_MEDIA_TYPE],
188
+ ];
189
+ const evidenceMatches = Array.isArray(predicateView.evidence)
190
+ && predicateView.evidence.length === expectedEvidence.length
191
+ && predicateView.evidence.every((raw, index) => {
192
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw))
193
+ return false;
194
+ const entry = raw;
195
+ const expected = expectedEvidence[index];
196
+ return entry.name === expected[0]
197
+ && entry.digest?.sha256 === expected[1]
198
+ && entry.mediaType === expected[2]
199
+ && Object.keys(entry).every((key) => ["name", "digest", "mediaType"].includes(key));
200
+ });
201
+ if (packet.itemSha256 !== expectedItemSha256
202
+ || recordDigest(canonicalJsonBytes(packet.item)) !== expectedItemSha256
203
+ || packet.evaluationSpecSha256 !== BINARY_JUDGMENT_HUMAN_REVIEW_EVALUATION_SPEC_SEALED.digest
204
+ || packet.reviewerId !== view.evaluatorId
205
+ || visibility.packetSha256 !== packetSha256
206
+ || visibility.itemSha256 !== expectedItemSha256
207
+ || visibility.reviewerId !== view.evaluatorId
208
+ || response.packetSha256 !== packetSha256
209
+ || response.visibilityReceiptSha256 !== visibilityReceiptSha256
210
+ || response.itemSha256 !== expectedItemSha256
211
+ || response.evaluatorId !== view.evaluatorId
212
+ || response.label !== labelResult.data
213
+ || response.complete !== completeRaw
214
+ || response.completedAt !== view.evaluatedAt
215
+ || Date.parse(response.completedAt) < Date.parse(visibility.issuedAt)
216
+ || subjectView[0]?.name !== "binary-judgment-item"
217
+ || subjectView[0]?.digest?.sha256 !== bare(expectedItemSha256)
218
+ || Object.keys(subjectView[0] ?? {}).some((key) => !["name", "digest"].includes(key))
219
+ || subjectView[1]?.name !== "human-review-response"
220
+ || subjectView[1]?.digest?.sha256 !== bare(responseSha256)
221
+ || subjectView[1]?.mediaType !== HUMAN_REVIEW_RESPONSE_MEDIA_TYPE
222
+ || Object.keys(subjectView[1] ?? {}).some((key) => !["name", "digest", "mediaType"].includes(key))
223
+ || predicateView.taskSubject !== "binary-judgment-item"
224
+ || !Array.isArray(predicateView.resultSubjects)
225
+ || predicateView.resultSubjects.length !== 1
226
+ || predicateView.resultSubjects[0] !== "human-review-response"
227
+ || predicateView.evaluator?.id !== view.evaluatorId
228
+ || predicateView.evaluator?.["network.jinn.reviewer.keyid"] !== signatureKeyId
229
+ || predicateView.verdict !== expectedVerdict
230
+ || predicateView.evaluatedAt !== response.completedAt
231
+ || !evidenceMatches
232
+ || !Array.isArray(predicateView.limitations)
233
+ || !sameStrings(predicateView.limitations, ["reviewer-person-distinctness-is-roster-attested"])
234
+ || view.evidence?.length !== 3)
235
+ fail(path, "Result Evaluation packet/response/visibility/item/evaluator joins do not close");
236
+ return {
237
+ evaluatorId: view.evaluatorId,
238
+ keyId: signatureKeyId,
239
+ label: labelResult.data,
240
+ complete: completeRaw,
241
+ completedAt: response.completedAt,
242
+ itemId: item.itemId,
243
+ packetSha256,
244
+ visibilityReceiptSha256,
245
+ };
246
+ }
247
+ function authorityPayload(state, options) {
248
+ const { digest, mediaType, role, evidenceRole, schema, path } = options;
249
+ const envelopeBytes = resolve(state, digest, path, evidenceRole);
250
+ let envelope;
251
+ try {
252
+ envelope = parseExactDsseEnvelope(envelopeBytes);
253
+ }
254
+ catch (cause) {
255
+ fail(path, "authority evidence is not an exact compact DSSE envelope", cause);
256
+ }
257
+ const allowedMediaTypes = typeof mediaType === "string" ? [mediaType] : mediaType;
258
+ if (!allowedMediaTypes.includes(envelope.payloadType) || envelope.signatures.length !== 1)
259
+ fail(path, "authority evidence has the wrong payload type or signature count");
260
+ const signature = envelope.signatures[0];
261
+ const signatureKeyId = signature.keyid;
262
+ if (typeof signatureKeyId !== "string" || !state.ports.verifyAuthoritySignature({ envelopeBytes, keyId: signatureKeyId, role })) {
263
+ fail(path, `authority signature is invalid for role ${role}`);
264
+ }
265
+ return { value: exactCanonical(schema, envelope.payloadBytes, `${path}.payload`), keyId: signatureKeyId, mediaType: envelope.payloadType };
266
+ }
267
+ function verifyHumanEvidence(state, refs, expectedItemSha256, expectedDraftId, admittedAt, path) {
268
+ const reviews = refs.reviewVerdictSha256s.map((digest, index) => verifyReview(state, digest, expectedItemSha256, `${path}.reviews.${index}`));
269
+ if (reviews[0].evaluatorId === reviews[1].evaluatorId || reviews[0].keyId === reviews[1].keyId) {
270
+ fail(path, "reviewers must have distinct evaluator identities and keys");
271
+ }
272
+ if (reviews[0].itemId !== reviews[1].itemId)
273
+ fail(path, "review packets disagree on itemId");
274
+ const visibilityDigests = reviews.map((review) => review.visibilityReceiptSha256).sort(compareCodeUnitStrings);
275
+ if (!sameStrings(visibilityDigests, refs.visibilityReceiptSha256s))
276
+ fail(path, "declared visibility receipt coverage differs from signed reviews");
277
+ const rosterEvidence = authorityPayload(state, {
278
+ digest: refs.reviewerRosterSha256,
279
+ mediaType: HUMAN_REVIEW_ROSTER_MEDIA_TYPE,
280
+ role: "roster-attestor",
281
+ evidenceRole: "reviewer-roster",
282
+ schema: HumanReviewRosterSchema,
283
+ path: `${path}.roster`,
284
+ });
285
+ const roster = rosterEvidence.value;
286
+ if (roster.attestorRole !== "roster-attestor" || roster.attestorKeyId !== rosterEvidence.keyId || roster.itemSha256 !== expectedItemSha256) {
287
+ fail(path, "roster authority role/key/item binding is invalid");
288
+ }
289
+ const declarations = [...roster.reviewers];
290
+ if (compareCodeUnitStrings(declarations[0].evaluatorId, declarations[1].evaluatorId) >= 0)
291
+ fail(path, "roster reviewers are not sorted and unique");
292
+ const reviewByEvaluator = new Map(reviews.map((review) => [review.evaluatorId, review]));
293
+ if (declarations.some((declaration) => reviewByEvaluator.get(declaration.evaluatorId)?.keyId !== declaration.keyId)) {
294
+ fail(path, "roster does not bind the exact reviewer evaluator/key pairs");
295
+ }
296
+ if (declarations[0].personId === declarations[1].personId || declarations.some((declaration) => declaration.conflicts.length > 0)) {
297
+ fail(path, "roster does not attest distinct conflict-free people");
298
+ }
299
+ const revealEvidence = authorityPayload(state, {
300
+ digest: refs.revealReceiptSha256,
301
+ mediaType: HUMAN_REVIEW_REVEAL_RECEIPT_MEDIA_TYPE,
302
+ role: "truth-reveal-attestor",
303
+ evidenceRole: "review-reveal-receipt",
304
+ schema: HumanReviewRevealReceiptSchema,
305
+ path: `${path}.reveal`,
306
+ });
307
+ const reveal = revealEvidence.value;
308
+ if (reveal.attestorRole !== "truth-reveal-attestor"
309
+ || reveal.attestorKeyId !== revealEvidence.keyId
310
+ || revealEvidence.keyId !== rosterEvidence.keyId
311
+ || reveal.draftId !== expectedDraftId
312
+ || reveal.itemSha256 !== expectedItemSha256
313
+ || reveal.judgeExecutionState !== "not-started"
314
+ || reveal.truthFrozenAt !== admittedAt
315
+ || reviews.some((review) => Date.parse(review.completedAt) > Date.parse(reveal.truthFrozenAt)))
316
+ fail(path, "signed reveal does not follow both completed reviews under the same authority");
317
+ return { reviews, itemId: reviews[0].itemId };
318
+ }
319
+ function expectedExclusionReason(reviews) {
320
+ if (reviews.some((review) => !review.complete))
321
+ return "review-incomplete";
322
+ if (reviews.some((review) => review.label === "indeterminate"))
323
+ return "review-indeterminate";
324
+ if (reviews[0].label !== reviews[1].label)
325
+ return "review-disagreement";
326
+ return undefined;
327
+ }
328
+ function screeningRowAgreed(row) {
329
+ return row.screeningVerdict !== "UNSURE"
330
+ && row.screeningVerdict !== "indeterminate"
331
+ && row.screeningVerdict === row.intendedLabel;
332
+ }
333
+ function screeningRowChecked(row) {
334
+ return "ritsuDecision" in row ? row.ritsuDecision.checked : row.handChecked;
335
+ }
336
+ function screeningRowHandVerdict(row) {
337
+ if ("ritsuDecision" in row)
338
+ return row.ritsuDecision.checked ? row.ritsuDecision.verdict : undefined;
339
+ return row.handVerdict;
340
+ }
341
+ function screeningRowAdmitted(row) {
342
+ return screeningRowChecked(row) ? screeningRowHandVerdict(row) === "confirm" : screeningRowAgreed(row);
343
+ }
344
+ function expectedScreeningExclusionReason(row) {
345
+ const agreed = screeningRowAgreed(row);
346
+ const admitted = screeningRowAdmitted(row);
347
+ if (admitted)
348
+ return undefined;
349
+ if (!agreed)
350
+ return row.screeningVerdict === "indeterminate" || row.screeningVerdict === "UNSURE" ? "screening-indeterminate" : "screening-disagreement";
351
+ return "screening-hand-excluded";
352
+ }
353
+ /** The reviewer Result Evaluation verifier shared by admission construction and closure replay. */
354
+ export function verifyBinaryJudgmentReviewerResult(input, ports) {
355
+ return verifyReview({ ports, reachable: new Set(), roles: new Map() }, input.verdictSha256, input.expectedItemSha256, "reviewVerdictSha256");
356
+ }
357
+ /**
358
+ * Portable, fail-closed replay of the entire binary-judgment admission graph. The caller supplies
359
+ * immutable record resolution and role-separated trust; no candidate truth is caller-authored.
360
+ */
361
+ export function verifyBinaryJudgmentAdmissionClosure(input, ports) {
362
+ const state = { ports, reachable: new Set(), roles: new Map() };
363
+ const manifest = exactCanonical(BinaryJudgmentAdmissionManifestSchema, resolve(state, input.admissionManifestSha256, "admissionManifestSha256", "admission-manifest"), "admissionManifest");
364
+ if (manifest.draftId !== input.expectedDraftId)
365
+ fail("admissionManifest.draftId", "manifest belongs to a different draft");
366
+ const ledger = exactCanonical(HumanReviewReplacementLedgerSchema, resolve(state, manifest.replacementLedgerSha256, "admissionManifest.replacementLedgerSha256", "replacement-ledger"), "replacementLedger");
367
+ if (ledger.draftId !== input.expectedDraftId || ledger.sealedAt !== manifest.admittedAt)
368
+ fail("replacementLedger", "ledger draft/time does not bind the manifest");
369
+ // Screened-operator-sampled admission (spec §6.4, §6.5; packet P6 item A): the table and S2's
370
+ // `screening-sample/1` recomputation are resolved and checked ONCE per bank, ahead of the
371
+ // resolutions and ledger loops below, which read `screeningRowsByItem` per item. Checks (1)-(3)
372
+ // run here, over `rows` alone; check (0)'s coverage half runs after both loops, once
373
+ // `acceptedItems`/`excludedItems` (the closure's own reconstruction of the frozen bank's
374
+ // candidate pool -- see the coverage check below) are fully known; check (4) runs per item in
375
+ // both loops.
376
+ let screeningTable;
377
+ let screeningRowsByItem = new Map();
378
+ let screeningSample = new Set();
379
+ let screeningSampleAgreementRate;
380
+ let screeningInstrumentSha256;
381
+ let promptedScreeningProcedureSha256;
382
+ let promptedScreeningPool;
383
+ let promptedCommittedSample;
384
+ let screeningTableAuthorityKeyId;
385
+ if (manifest.truthAdmission === "screened-operator-sampled") {
386
+ if (manifest.screeningTableSha256 === undefined) {
387
+ fail("admissionManifest.screeningTableSha256", "screened admission manifest carries no screening table reference");
388
+ }
389
+ // The table is signed ONCE, DSSE-sealed on the same path as the other admission records
390
+ // (spec §6.3, final sentence, verbatim) -- this is what makes §6.9's dropping of 240 per-item
391
+ // signatures sound ("one key signing 240 rows separately proves nothing beyond one signature
392
+ // on the whole table"). The table schema carries no attestorRole field (unlike the roster and
393
+ // reveal receipt), but `sealRoleEvidence` (core/src/operations/human-review.ts:218-233) DSSE-
394
+ // wraps arbitrary bytes under any payload type regardless of the payload's own shape, so
395
+ // `screeningTableSha256` names the DSSE ENVELOPE digest, exactly like `reviewerRosterSha256`
396
+ // and `revealReceiptSha256` already do -- not the bare canonical table bytes. The screened
397
+ // branch's sole legal authority set is `["truth-reveal-attestor"]` alone (§6.8a Group C), so
398
+ // that is the one key that signs both the bank-scoped table and the bank-scoped reveal
399
+ // receipt: "signed once per bank" as D1 describes.
400
+ const tableEvidence = authorityPayload(state, {
401
+ digest: manifest.screeningTableSha256,
402
+ mediaType: [SCREENING_TABLE_MEDIA_TYPE, SCREENING_TABLE_V2_MEDIA_TYPE],
403
+ role: "truth-reveal-attestor",
404
+ evidenceRole: "screening-table",
405
+ schema: z.union([ScreeningTableSchema, ScreeningTableV2Schema]),
406
+ path: "admissionManifest.screeningTableSha256",
407
+ });
408
+ screeningTable = tableEvidence.value;
409
+ screeningTableAuthorityKeyId = tableEvidence.keyId;
410
+ if (screeningTable.draftId !== input.expectedDraftId || screeningTable.sealedAt !== manifest.admittedAt) {
411
+ fail("screeningTable", "table draft/time does not bind the manifest");
412
+ }
413
+ let sampleSeed;
414
+ let sampleSize;
415
+ let sampleIdentities;
416
+ if (screeningTable.protocol === "https://spec.jinn.network/binary-judgment/screening-table/v1") {
417
+ if (tableEvidence.mediaType !== SCREENING_TABLE_MEDIA_TYPE)
418
+ fail("screeningTable", "v1 table protocol must use the v1 media type");
419
+ const screeningInstrument = parseProfileRecord(state, screeningTable.screeningInstrumentSha256, "screeningTable.screeningInstrumentSha256", parseBinaryJudgmentInstrument, "screening-instrument");
420
+ const resealedScreeningInstrument = sealBinaryJudgmentInstrument(screeningInstrument.value);
421
+ if (resealedScreeningInstrument.digest !== screeningTable.screeningInstrumentSha256 || !bytesEqual(resealedScreeningInstrument.bytes, screeningInstrument.bytes)) {
422
+ fail("screeningTable.screeningInstrumentSha256", "instrument does not reseal to its table commitment");
423
+ }
424
+ screeningInstrumentSha256 = screeningTable.screeningInstrumentSha256;
425
+ resolve(state, screeningTable.samplingScriptSha256, "screeningTable.samplingScriptSha256", "screening-sampling-script");
426
+ resolve(state, screeningTable.rawOutputsSha256, "screeningTable.rawOutputsSha256", "screening-raw-outputs");
427
+ sampleSeed = screeningTable.sampleSeed;
428
+ sampleSize = screeningTable.sampleSize;
429
+ sampleIdentities = screeningTable.rows.map((row) => row.itemSha256);
430
+ }
431
+ else {
432
+ if (tableEvidence.mediaType !== SCREENING_TABLE_V2_MEDIA_TYPE)
433
+ fail("screeningTable", "v2 table protocol must use the v2 media type");
434
+ const procedure = exactCanonical(PromptedScreeningProcedureV1Schema, resolve(state, screeningTable.procedureSha256, "screeningTable.procedureSha256", "screening-procedure"), "screeningProcedure");
435
+ const pool = exactCanonical(ScreeningPoolV1Schema, resolve(state, screeningTable.poolSha256, "screeningTable.poolSha256", "screening-pool"), "screeningPool");
436
+ const commitment = exactCanonical(ScreeningSampleCommitmentV1Schema, resolve(state, screeningTable.sampleCommitmentSha256, "screeningTable.sampleCommitmentSha256", "screening-sample-commitment"), "screeningSampleCommitment");
437
+ resolve(state, screeningTable.coordinatorPromptSha256, "screeningTable.coordinatorPromptSha256", "screening-prompt");
438
+ resolve(state, screeningTable.samplingScriptSha256, "screeningTable.samplingScriptSha256", "screening-sampling-script");
439
+ resolve(state, screeningTable.transcriptSha256, "screeningTable.transcriptSha256", "screening-transcript");
440
+ if (procedure.coordinatorPromptSha256 !== screeningTable.coordinatorPromptSha256
441
+ || procedure.transcriptSha256 !== screeningTable.transcriptSha256
442
+ || pool.draftId !== input.expectedDraftId
443
+ || commitment.draftId !== input.expectedDraftId
444
+ || commitment.poolSha256 !== screeningTable.poolSha256
445
+ || commitment.poolIdentityCommitmentSha256 !== pool.identityCommitmentSha256
446
+ || Date.parse(procedure.sealedAt) > Date.parse(screeningTable.sealedAt)
447
+ || Date.parse(pool.sealedAt) > Date.parse(commitment.committedAt)
448
+ || Date.parse(commitment.committedAt) > Date.parse(screeningTable.sealedAt))
449
+ fail("screeningTable", "prompted procedure, prompt, transcript, pool, commitment, draft, or timestamps do not join");
450
+ const poolIdentities = pool.items.map((item) => item.itemSha256);
451
+ if (computeScreeningPoolDigest(poolIdentities) !== pool.identityCommitmentSha256)
452
+ fail("screeningPool.identityCommitmentSha256", "identity commitment does not match the sorted unique pool identities");
453
+ const itemIds = new Set();
454
+ for (const [index, poolItem] of pool.items.entries()) {
455
+ const item = parseProfileRecord(state, poolItem.itemSha256, `screeningPool.items.${index}.itemSha256`, (bytes) => parseBinaryJudgmentPayload(bytes), "source-item").value;
456
+ if (itemIds.has(item.itemId))
457
+ fail("screeningPool.items", `duplicate source itemId ${item.itemId}`);
458
+ itemIds.add(item.itemId);
459
+ }
460
+ const rowIdentities = screeningTable.rows.map((row) => row.itemSha256);
461
+ const sortedPoolIdentities = [...poolIdentities].sort(compareCodeUnitStrings);
462
+ if (!sameStrings(rowIdentities, sortedPoolIdentities))
463
+ fail("screeningTable.rows", "v2 table must cover all 664 pool identities in sorted order");
464
+ const poolByItem = new Map(pool.items.map((item) => [item.itemSha256, item]));
465
+ for (const row of screeningTable.rows) {
466
+ const poolItem = poolByItem.get(row.itemSha256);
467
+ if (row.intendedLabel !== poolItem.intendedLabel)
468
+ fail("screeningTable.rows", `row ${row.itemSha256} intended label differs from the sealed pool`);
469
+ if (row.ritsuDecision.checked && (Date.parse(row.ritsuDecision.decidedAt) < Date.parse(commitment.committedAt)
470
+ || Date.parse(row.ritsuDecision.decidedAt) > Date.parse(screeningTable.sealedAt)))
471
+ fail("screeningTable.rows", `row ${row.itemSha256} has a Ritsu decision outside the committed-sample-to-table interval`);
472
+ }
473
+ sampleSeed = commitment.sampleSeed;
474
+ sampleSize = commitment.sampleSize;
475
+ sampleIdentities = poolIdentities;
476
+ promptedScreeningProcedureSha256 = screeningTable.procedureSha256;
477
+ promptedScreeningPool = pool;
478
+ promptedCommittedSample = commitment.sampleItemSha256s;
479
+ }
480
+ screeningRowsByItem = new Map(screeningTable.rows.map((row) => [row.itemSha256, row]));
481
+ // (1) Sample membership: recomputed by screening-sample/1, never by executing the sealed
482
+ // script (R-4). The recomputation is authoritative for checks (2) and (3) below -- there is
483
+ // no separate "compare against the operator's script" step, because the script is never run.
484
+ let sampleResult;
485
+ try {
486
+ sampleResult = computeScreeningSample({
487
+ itemSha256s: sampleIdentities,
488
+ sampleSeed,
489
+ sampleSize,
490
+ });
491
+ }
492
+ catch (cause) {
493
+ fail("admissionManifest.screeningTableSha256", cause instanceof ScreeningSampleError
494
+ ? `sample recomputation refused its own input: ${cause.message}`
495
+ : "sample recomputation (screening-sample/1) failed", cause);
496
+ }
497
+ screeningSample = new Set(sampleResult.sample);
498
+ if (promptedCommittedSample !== undefined && !sameStrings([...sampleResult.sample].sort(compareCodeUnitStrings), promptedCommittedSample))
499
+ fail("screeningSampleCommitment.sampleItemSha256s", "public sample membership differs from screening-sample/1 recomputation");
500
+ // (2) Required hand checks: flagged := rows where NOT agreed. Every row in flagged ∪ sample
501
+ // must carry handChecked === true.
502
+ for (const row of screeningTable.rows) {
503
+ const flagged = !screeningRowAgreed(row);
504
+ if ((flagged || screeningSample.has(row.itemSha256)) && !screeningRowChecked(row)) {
505
+ fail("screeningTable.rows", `row ${row.itemSha256} is flagged or sampled but was never hand-checked`);
506
+ }
507
+ }
508
+ // (3) Sample agreement rate -- the one definition (spec §6.5(3)), symmetric on purpose. Every
509
+ // row in `screeningSample` is hand-checked by check (2) above, so `handVerdict` is defined.
510
+ const sampleMatches = [...screeningSample].filter((sampledItemSha256) => {
511
+ const row = screeningRowsByItem.get(sampledItemSha256);
512
+ return screeningRowAgreed(row) === (screeningRowHandVerdict(row) === "confirm");
513
+ }).length;
514
+ screeningSampleAgreementRate = sampleMatches / screeningSample.size;
515
+ }
516
+ const resolutions = manifest.labelResolutionSha256s.map((digest, index) => ({
517
+ digest,
518
+ ...parseProfileRecord(state, digest, `admissionManifest.labelResolutionSha256s.${index}`, (bytes) => parseBinaryJudgmentLabelResolution(bytes), "label-resolution"),
519
+ }));
520
+ const contexts = manifest.analysisContextSha256s.map((digest, index) => ({
521
+ digest,
522
+ ...parseProfileRecord(state, digest, `admissionManifest.analysisContextSha256s.${index}`, (bytes) => parseBinaryJudgmentAnalysisContext(bytes), "analysis-context"),
523
+ }));
524
+ if (resolutions.length !== contexts.length)
525
+ fail("admissionManifest", "resolution and analysis-context coverage differs");
526
+ const contextByResolution = new Map(contexts.map((entry) => [entry.value.labelResolutionSha256, entry]));
527
+ if (contextByResolution.size !== contexts.length)
528
+ fail("admissionManifest.analysisContextSha256s", "multiple contexts cover one resolution");
529
+ const acceptedItems = new Set();
530
+ const accepted = [];
531
+ for (const [index, entry] of resolutions.entries()) {
532
+ const resolution = entry.value;
533
+ const path = `resolutions.${index}`;
534
+ if (acceptedItems.has(resolution.itemSha256))
535
+ fail(path, "item is admitted more than once");
536
+ acceptedItems.add(resolution.itemSha256);
537
+ if (resolution.truthAdmission !== manifest.truthAdmission || resolution.resolvedAt !== manifest.admittedAt)
538
+ fail(path, "resolution admission kind/time differs from manifest");
539
+ // A screened resolution carries no humanReviewEvaluationSpecSha256 (spec §6.7: a row never
540
+ // hand-checked has no human-review evaluation), so this equality check only applies to the
541
+ // two branches that still carry the field. `verifyFrozenHumanReviewSpec` stays unconditional
542
+ // below regardless: the frozen spec+form are required admission-record roles for every mode
543
+ // (§6.8a's ratified G-5), not only the two that reference the spec by digest.
544
+ if (resolution.truthAdmission !== "screened-operator-sampled") {
545
+ if (resolution.humanReviewEvaluationSpecSha256 !== BINARY_JUDGMENT_HUMAN_REVIEW_EVALUATION_SPEC_SEALED.digest) {
546
+ fail(path, "resolution names a different human-review EvaluationSpec");
547
+ }
548
+ }
549
+ verifyFrozenHumanReviewSpec(state, path);
550
+ const item = parseProfileRecord(state, resolution.itemSha256, `${path}.itemSha256`, (bytes) => parseBinaryJudgmentPayload(bytes), "source-item").value;
551
+ if (item.itemId !== resolution.itemId)
552
+ fail(path, "resolution itemId differs from exact item record");
553
+ const contextEntry = contextByResolution.get(entry.digest);
554
+ if (contextEntry === undefined)
555
+ fail(path, "resolution has no analysis context");
556
+ const context = contextEntry.value;
557
+ if (context.itemSha256 !== resolution.itemSha256
558
+ || context.itemId !== resolution.itemId
559
+ || context.truthLabel !== resolution.truthLabel
560
+ || context.candidateClass !== resolution.candidateClass
561
+ || context.stratum !== resolution.stratum)
562
+ fail(path, "analysis context does not exactly project the resolution");
563
+ // Exhaustive switch over the three-member truthAdmission union, never-typed default (spec
564
+ // §6.8a Group B / this packet's G-3): an unrecognized future mode is a compile error here,
565
+ // not a silent reroute into the two-human or operator-only path.
566
+ switch (resolution.truthAdmission) {
567
+ case "two-human-unanimous": {
568
+ const human = verifyHumanEvidence(state, resolution, resolution.itemSha256, input.expectedDraftId, manifest.admittedAt, path);
569
+ if (human.itemId !== resolution.itemId
570
+ || human.reviews.some((review) => !review.complete || review.label === "indeterminate" || review.label !== resolution.truthLabel))
571
+ fail(path, "admitted human truth is not two complete unanimous reviews");
572
+ break;
573
+ }
574
+ case "operator-only": {
575
+ const assertionEvidence = authorityPayload(state, {
576
+ digest: resolution.operatorAssertionSha256,
577
+ mediaType: HUMAN_REVIEW_OPERATOR_ASSERTION_MEDIA_TYPE,
578
+ role: "operator-truth-attestor",
579
+ evidenceRole: "operator-assertion",
580
+ schema: HumanReviewOperatorAssertionSchema,
581
+ path: `${path}.operatorAssertion`,
582
+ });
583
+ const assertion = assertionEvidence.value;
584
+ if (assertion.attestorRole !== "operator-truth-attestor"
585
+ || assertion.attestorKeyId !== assertionEvidence.keyId
586
+ || assertion.itemSha256 !== resolution.itemSha256
587
+ || assertion.truthLabel !== resolution.truthLabel
588
+ || assertion.assertedAt !== resolution.resolvedAt
589
+ || assertion.limitation !== "operator-only-not-publication-grade")
590
+ fail(path, "operator assertion does not exactly bind the non-publication resolution");
591
+ break;
592
+ }
593
+ case "screened-operator-sampled": {
594
+ if (resolution.screeningTableSha256 !== manifest.screeningTableSha256) {
595
+ fail(path, "resolution names a different screening table than the manifest");
596
+ }
597
+ const revealEvidence = authorityPayload(state, {
598
+ digest: resolution.screeningRevealReceiptSha256,
599
+ mediaType: SCREENING_REVEAL_RECEIPT_MEDIA_TYPE,
600
+ role: "truth-reveal-attestor",
601
+ evidenceRole: "screening-reveal-receipt",
602
+ schema: ScreeningRevealReceiptSchema,
603
+ path: `${path}.screeningReveal`,
604
+ });
605
+ const reveal = revealEvidence.value;
606
+ if (reveal.attestorRole !== "truth-reveal-attestor"
607
+ || reveal.attestorKeyId !== revealEvidence.keyId
608
+ || revealEvidence.keyId !== screeningTableAuthorityKeyId
609
+ || reveal.draftId !== input.expectedDraftId
610
+ || reveal.screeningTableSha256 !== resolution.screeningTableSha256
611
+ || reveal.judgeExecutionState !== "not-started"
612
+ || reveal.truthFrozenAt !== manifest.admittedAt)
613
+ fail(path, "signed screening reveal does not bind the screening table under the same authority");
614
+ // (4) Per-row admission (spec §6.4): an admitted item's own table row must itself be
615
+ // admitted, and the resolution's truth label must be the row's intended label -- the
616
+ // screened branch never corrects a label, only confirms or excludes.
617
+ const row = screeningRowsByItem.get(resolution.itemSha256);
618
+ if (row === undefined)
619
+ fail(path, "accepted screened item has no covering screening-table row");
620
+ if (!screeningRowAdmitted(row))
621
+ fail(path, "admitted screened item's table row is not itself admitted per the sealed admission rule");
622
+ if (row.intendedLabel !== resolution.truthLabel)
623
+ fail(path, "resolution truth label differs from the screening table's intended label");
624
+ const poolItem = promptedScreeningPool?.items.find((item) => item.itemSha256 === resolution.itemSha256);
625
+ if (poolItem !== undefined && (poolItem.candidateClass !== resolution.candidateClass || poolItem.stratum !== resolution.stratum))
626
+ fail(path, "resolution class or stratum differs from the prompted screening pool");
627
+ break;
628
+ }
629
+ default: {
630
+ const exhaustive = resolution;
631
+ fail(path, `unsupported truthAdmission ${String(exhaustive.truthAdmission)}`);
632
+ }
633
+ }
634
+ accepted.push({
635
+ itemSha256: prefixed(resolution.itemSha256),
636
+ itemId: resolution.itemId,
637
+ truthLabel: resolution.truthLabel,
638
+ candidateClass: resolution.candidateClass,
639
+ stratum: resolution.stratum,
640
+ truthAdmission: resolution.truthAdmission,
641
+ labelResolutionSha256: prefixed(entry.digest),
642
+ analysisContextSha256: prefixed(contextEntry.digest),
643
+ });
644
+ }
645
+ if (contextByResolution.size !== resolutions.length || contexts.some((entry) => !manifest.labelResolutionSha256s.includes(entry.value.labelResolutionSha256))) {
646
+ fail("admissionManifest.analysisContextSha256s", "analysis-context coverage has an orphan or omission");
647
+ }
648
+ const promptedExpectedReplacements = new Map();
649
+ if (promptedScreeningPool !== undefined) {
650
+ const slotIds = [...new Set(promptedScreeningPool.items.map((item) => item.slotId))].sort(compareCodeUnitStrings);
651
+ const expectedAccepted = new Set();
652
+ for (const slotId of slotIds) {
653
+ const slotItems = promptedScreeningPool.items.filter((item) => item.slotId === slotId);
654
+ const main = slotItems.find((item) => item.poolKind === "main");
655
+ const reserves = slotItems
656
+ .filter((item) => item.poolKind === "reserve")
657
+ .sort((left, right) => left.reserveOrder - right.reserveOrder);
658
+ const winner = [main, ...reserves].find((item) => screeningRowAdmitted(screeningRowsByItem.get(item.itemSha256)));
659
+ if (winner === undefined)
660
+ fail("screeningPool.items", `${slotId} has no admissible main or reserve candidate`);
661
+ expectedAccepted.add(winner.itemSha256);
662
+ if (winner.itemSha256 !== main.itemSha256)
663
+ promptedExpectedReplacements.set(main.itemSha256, winner.itemSha256);
664
+ }
665
+ const actualAccepted = [...acceptedItems].sort(compareCodeUnitStrings);
666
+ if (!sameStrings(actualAccepted, [...expectedAccepted].sort(compareCodeUnitStrings)))
667
+ fail("admissionManifest.labelResolutionSha256s", "accepted items are not the first admissible candidate in every sealed slot");
668
+ if (accepted.length !== 240)
669
+ fail("admissionManifest.labelResolutionSha256s", "prompted screening must admit exactly 240 items");
670
+ for (const candidateClass of ["correct", "specific-wrong", "vague-topical-wrong"]) {
671
+ if (accepted.filter((item) => item.candidateClass === candidateClass).length !== 80)
672
+ fail("admissionManifest.labelResolutionSha256s", `final bank must contain exactly 80 ${candidateClass} items`);
673
+ for (const stratum of ["category-1", "category-2", "category-3", "category-4"]) {
674
+ if (accepted.filter((item) => item.candidateClass === candidateClass && item.stratum === stratum).length !== 20)
675
+ fail("admissionManifest.labelResolutionSha256s", `final bank must contain exactly 20 ${candidateClass}/${stratum} items`);
676
+ }
677
+ }
678
+ }
679
+ const excludedDigests = ledger.entries.map((entry) => entry.excludedItemSha256).sort(compareCodeUnitStrings);
680
+ if (!sameStrings(excludedDigests, manifest.excludedItemSha256s))
681
+ fail("replacementLedger.entries", "ledger exclusion coverage differs from manifest");
682
+ if (manifest.truthAdmission === "operator-only" && ledger.entries.length > 0)
683
+ fail("replacementLedger.entries", "operator-only admission cannot claim human-review exclusions");
684
+ const excludedItems = new Set();
685
+ const replacementItems = new Set();
686
+ const excluded = [];
687
+ let previousPosition = 0;
688
+ for (const [index, entry] of ledger.entries.entries()) {
689
+ const path = `replacementLedger.entries.${index}`;
690
+ if (entry.excludedPoolPosition <= previousPosition)
691
+ fail(path, "ledger entries are not in deterministic pool order");
692
+ previousPosition = entry.excludedPoolPosition;
693
+ if (excludedItems.has(entry.excludedItemSha256) || acceptedItems.has(entry.excludedItemSha256))
694
+ fail(path, "excluded item is duplicated or also admitted");
695
+ excludedItems.add(entry.excludedItemSha256);
696
+ if (replacementItems.has(entry.replacementItemSha256))
697
+ fail(path, "one admitted reserve is reused for multiple exclusions");
698
+ replacementItems.add(entry.replacementItemSha256);
699
+ const replacement = accepted.find((candidate) => candidate.itemSha256 === entry.replacementItemSha256);
700
+ if (replacement === undefined)
701
+ fail(path, "replacement is not in the admitted resolution set");
702
+ if (entry.replacementPoolPosition <= entry.excludedPoolPosition
703
+ || replacement.candidateClass !== entry.candidateClass
704
+ || replacement.stratum !== entry.stratum)
705
+ fail(path, "replacement is not a later admitted reserve in the same class and stratum");
706
+ if (promptedScreeningPool !== undefined) {
707
+ const excludedPoolItem = promptedScreeningPool.items.find((item) => item.itemSha256 === entry.excludedItemSha256);
708
+ const replacementPoolItem = promptedScreeningPool.items.find((item) => item.itemSha256 === entry.replacementItemSha256);
709
+ if (excludedPoolItem?.poolKind !== "main"
710
+ || replacementPoolItem?.poolKind !== "reserve"
711
+ || excludedPoolItem.slotId !== replacementPoolItem.slotId
712
+ || entry.excludedPoolPosition !== excludedPoolItem.poolPosition
713
+ || entry.replacementPoolPosition !== replacementPoolItem.poolPosition
714
+ || promptedExpectedReplacements.get(entry.excludedItemSha256) !== entry.replacementItemSha256)
715
+ fail(path, "ledger does not name the deterministic first-admissible reserve for the exact sealed slot positions");
716
+ }
717
+ // Branches on admission mode (spec §6.4, §6.5 check (4); ratified resolution to G-1): a
718
+ // screened exclusion is verified against the screening table's own per-row admission rule
719
+ // rather than against two signed reviews. §6.9 deliberately DROPS the four per-item two-human
720
+ // digests for the screened branch (one signature on the whole table, not 240 on individual
721
+ // rows), so those four fields are never read here for a screened entry. `operator-only` is
722
+ // unreachable in this loop body: any operator-only manifest with ledger.entries.length > 0
723
+ // already refused above, before this loop began.
724
+ let itemId;
725
+ let derivedReason;
726
+ switch (manifest.truthAdmission) {
727
+ case "screened-operator-sampled": {
728
+ const row = screeningRowsByItem.get(entry.excludedItemSha256);
729
+ if (row === undefined)
730
+ fail(path, "excluded item has no covering screening-table row");
731
+ derivedReason = expectedScreeningExclusionReason(row);
732
+ const item = parseProfileRecord(state, entry.excludedItemSha256, `${path}.item`, (bytes) => parseBinaryJudgmentPayload(bytes), "source-item").value;
733
+ itemId = item.itemId;
734
+ break;
735
+ }
736
+ case "two-human-unanimous": {
737
+ // TypeScript narrows each optional property read below, but not `entry`'s own declared
738
+ // (still-optional) structural type, so the narrowed fields are passed on explicitly
739
+ // rather than passing `entry` itself. Defensive: the schema's own present-iff refinement
740
+ // (checked at `exactCanonical` above) already guarantees these four digests are present
741
+ // for a two-human reason, so this guard should be unreachable, not load-bearing.
742
+ if (entry.reviewVerdictSha256s === undefined
743
+ || entry.visibilityReceiptSha256s === undefined
744
+ || entry.reviewerRosterSha256 === undefined
745
+ || entry.revealReceiptSha256 === undefined)
746
+ fail(path, "two-human ledger entry is missing its required review evidence digests");
747
+ const human = verifyHumanEvidence(state, {
748
+ reviewVerdictSha256s: entry.reviewVerdictSha256s,
749
+ visibilityReceiptSha256s: entry.visibilityReceiptSha256s,
750
+ reviewerRosterSha256: entry.reviewerRosterSha256,
751
+ revealReceiptSha256: entry.revealReceiptSha256,
752
+ }, entry.excludedItemSha256, input.expectedDraftId, manifest.admittedAt, path);
753
+ derivedReason = expectedExclusionReason(human.reviews);
754
+ itemId = human.itemId;
755
+ break;
756
+ }
757
+ case "operator-only":
758
+ fail(path, "operator-only admission cannot claim a replacement-ledger entry");
759
+ break;
760
+ default: {
761
+ const exhaustive = manifest.truthAdmission;
762
+ fail(path, `unsupported truthAdmission ${String(exhaustive)}`);
763
+ }
764
+ }
765
+ if (derivedReason === undefined || derivedReason !== entry.reason)
766
+ fail(path, "ledger reason does not derive from the admitted evidence");
767
+ excluded.push({
768
+ itemSha256: prefixed(entry.excludedItemSha256),
769
+ itemId,
770
+ candidateClass: entry.candidateClass,
771
+ stratum: entry.stratum,
772
+ reason: entry.reason,
773
+ replacementItemSha256: prefixed(entry.replacementItemSha256),
774
+ });
775
+ }
776
+ if (promptedScreeningPool !== undefined) {
777
+ const ledgerReplacements = new Map(ledger.entries.map((entry) => [entry.excludedItemSha256, entry.replacementItemSha256]));
778
+ if (ledgerReplacements.size !== promptedExpectedReplacements.size || [...promptedExpectedReplacements].some(([excludedItem, replacementItem]) => ledgerReplacements.get(excludedItem) !== replacementItem)) {
779
+ fail("replacementLedger.entries", "ledger does not exactly cover every prompted main-slot replacement");
780
+ }
781
+ }
782
+ // (0) Coverage, the other half (spec §6.5 check (0)): the table's rows must cover the frozen
783
+ // bank's candidate pool EXACTLY. The pool itself is not a separate input to this function --
784
+ // it is reconstructed as accepted ∪ excluded, the closure's own admission decision, which by
785
+ // construction is exactly the frozen bank (every candidate item is either admitted or excluded
786
+ // with a same-class same-stratum replacement, and every replacement is itself an admitted pool
787
+ // member). `sampleSize <= rows.length` (check (0)'s other clause) is enforced transitively:
788
+ // `computeScreeningSample` above refuses a `sampleSize` exceeding the pool size on its own.
789
+ if (manifest.truthAdmission === "screened-operator-sampled" && promptedScreeningPool === undefined) {
790
+ if (screeningTable === undefined) {
791
+ // Unreachable today: the screened arm resolves the table before any row is admitted and fails
792
+ // closed when it is absent. Written as a refusal anyway, because the `&& screeningTable !==
793
+ // undefined` conjunct this replaces was a type-narrowing device whose false branch silently
794
+ // SKIPPED the coverage check. A coverage check with a skip path is not a coverage check, and a
795
+ // future caller that reordered the resolution would have been passed rather than refused.
796
+ fail("screeningTable", "screened admission reached the coverage check with no resolved screening table");
797
+ }
798
+ const pool = [...acceptedItems, ...excludedItems].sort(compareCodeUnitStrings);
799
+ const rowIdentities = screeningTable.rows.map((row) => row.itemSha256);
800
+ if (!sameStrings(pool, rowIdentities)) {
801
+ fail("screeningTable.rows", "screening table rows do not cover the admitted-plus-excluded closure exactly");
802
+ }
803
+ }
804
+ const classes = [...new Set([...accepted.map((entry) => entry.candidateClass), ...excluded.map((entry) => entry.candidateClass)])].sort(compareCodeUnitStrings);
805
+ const strata = [...new Set([...accepted.map((entry) => entry.stratum), ...excluded.map((entry) => entry.stratum)])].sort(compareCodeUnitStrings);
806
+ return {
807
+ manifestSha256: input.admissionManifestSha256,
808
+ manifest,
809
+ replacementLedger: ledger,
810
+ // Derived by exclusion, not by equality (spec §6.8a Group A, first bullet; ratified fix):
811
+ // `=== "two-human-unanimous"` derives `false` for a screened manifest, which is
812
+ // publication-grade under §6.8, and would make schema.ts's third `superRefine` branch (which
813
+ // REQUIRES `true`) refuse every screened bundle. `operator-only` is the one mode that is
814
+ // never publication-grade.
815
+ publicationGrade: manifest.truthAdmission !== "operator-only",
816
+ classes,
817
+ strata,
818
+ accepted,
819
+ excluded,
820
+ reachableSha256s: [...state.reachable].sort(compareCodeUnitStrings),
821
+ reachableRecords: [...state.roles]
822
+ .sort(([left], [right]) => compareCodeUnitStrings(left, right))
823
+ .map(([sha256, roles]) => ({
824
+ sha256,
825
+ roles: BINARY_JUDGMENT_ADMISSION_RECORD_ROLES.filter((role) => roles.has(role)),
826
+ })),
827
+ // Present iff screened (spec §6.5 check (3)): the one definition of the screen-versus-hand
828
+ // agreement rate on the random sample, so no caller recomputes it independently.
829
+ ...(screeningSampleAgreementRate === undefined || screeningInstrumentSha256 === undefined ? {} : {
830
+ screening: {
831
+ sampleAgreementRate: screeningSampleAgreementRate,
832
+ instrumentSha256: screeningInstrumentSha256,
833
+ },
834
+ }),
835
+ ...(screeningSampleAgreementRate === undefined || promptedScreeningProcedureSha256 === undefined ? {} : {
836
+ promptedScreening: {
837
+ sampleAgreementRate: screeningSampleAgreementRate,
838
+ procedureSha256: promptedScreeningProcedureSha256,
839
+ profile: PROMPTED_SCREENING_PROFILE,
840
+ capabilityBoundary: PROMPTED_SCREENING_CAPABILITY_BOUNDARY,
841
+ },
842
+ }),
843
+ };
844
+ }