@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
package/dist/schema.js ADDED
@@ -0,0 +1,498 @@
1
+ import { z } from "zod";
2
+ import { compareCodeUnitStrings } from "@jinn-network/benchmarking-records";
3
+ import { TASK_EXECUTION_ERROR_CATEGORIES } from "@jinn-network/task-execution-protocol";
4
+ const Sha256HexSchema = z.string().regex(/^[a-f0-9]{64}$/);
5
+ const Rfc3339Schema = z.string().datetime({ offset: true });
6
+ export const BUNDLE_TRUST_FORMAT = "benchmark-product-public-trust/2";
7
+ export const BUNDLE_EVIDENCE_FORMAT = "benchmark-product-evidence-catalog/2";
8
+ export const BUNDLE_VERDICTS_FORMAT = "benchmark-product-verdict-catalog/2";
9
+ export const BUNDLE_ASSEMBLY_FORMAT = "benchmark-product-assembly/2";
10
+ export const BUNDLE_V4_TRUST_FORMAT = "benchmark-product-public-trust/4";
11
+ export const BUNDLE_V4_EVIDENCE_FORMAT = "benchmark-product-evidence-catalog/4";
12
+ export const BUNDLE_QUALIFICATION_FORMAT = "benchmark-product-binary-qualification/1";
13
+ export const BUNDLE_V4_EVIDENCE_ROLES = [
14
+ "task",
15
+ "runtime-selection",
16
+ "evaluation-spec",
17
+ "admission-receipt",
18
+ "solve-submission",
19
+ "run-pinning-evidence",
20
+ "evaluation-submission",
21
+ "solve-delivery",
22
+ "solve-output",
23
+ "evaluation-task",
24
+ "evaluation-delivery",
25
+ "verdict",
26
+ "item-bank",
27
+ "source-manifest",
28
+ "admission-index",
29
+ "admission-manifest",
30
+ "replacement-ledger",
31
+ "source-item",
32
+ "judge-instrument",
33
+ "analysis-context",
34
+ "label-resolution",
35
+ "human-review-evaluation-spec",
36
+ "human-review-form",
37
+ "human-review-packet",
38
+ "human-review-response",
39
+ "human-review-verdict",
40
+ "reviewer-roster",
41
+ "review-visibility-receipt",
42
+ "review-reveal-receipt",
43
+ "operator-assertion",
44
+ // Appended at the very end (spec §1.6 site 9 / §1.5 rule 5): this array's index order is the
45
+ // frozen role-ordering map used by both the catalog schema's ordering refinement and the bundle
46
+ // writer, so inserting anywhere else would move existing bundles' bytes.
47
+ "snapshot-probe",
48
+ // Appended after snapshot-probe (spec §6.8a Group C, first bullet; packet P6): the
49
+ // screened-operator-sampled admission branch's two new sealed records.
50
+ "screening-table",
51
+ "screening-reveal-receipt",
52
+ "screening-instrument",
53
+ "screening-sampling-script",
54
+ "screening-raw-outputs",
55
+ "screening-prompt",
56
+ "screening-procedure",
57
+ "screening-pool",
58
+ "screening-sample-commitment",
59
+ "screening-transcript",
60
+ ];
61
+ export const BUNDLE_V4_ADMISSION_EVIDENCE_ROLES = [
62
+ "admission-manifest", "replacement-ledger", "source-item", "label-resolution",
63
+ "analysis-context", "human-review-evaluation-spec", "human-review-form",
64
+ "human-review-packet", "human-review-response", "human-review-verdict",
65
+ "reviewer-roster", "review-visibility-receipt", "review-reveal-receipt",
66
+ "operator-assertion",
67
+ // Appended after operator-assertion (spec §6.8a Group C, first bullet; packet P6), matching the
68
+ // full list above. This subset's relative order intentionally differs from the full list
69
+ // elsewhere (no judge-instrument, analysis-context/label-resolution swapped) — that is frozen
70
+ // and is not to be normalized.
71
+ "screening-table",
72
+ "screening-reveal-receipt",
73
+ "screening-instrument",
74
+ "screening-sampling-script",
75
+ "screening-raw-outputs",
76
+ "screening-prompt",
77
+ "screening-procedure",
78
+ "screening-pool",
79
+ "screening-sample-commitment",
80
+ "screening-transcript",
81
+ ];
82
+ const PublicKeySchema = z.object({
83
+ keyId: z.string().min(1),
84
+ algorithm: z.literal("ed25519"),
85
+ spkiDerBase64: z.string().min(1),
86
+ });
87
+ export const BundleTrustSchema = z.object({
88
+ format: z.literal(BUNDLE_TRUST_FORMAT),
89
+ selfRun: z.object({
90
+ custody: z.literal("workspace-minted"),
91
+ evaluatorDistinctness: z.literal("agent-distinctness-only"),
92
+ partyIndependence: z.literal("not-established"),
93
+ }),
94
+ report: PublicKeySchema.extend({
95
+ author: z.string().min(1),
96
+ didKey: z.string().min(1),
97
+ validFrom: Rfc3339Schema,
98
+ }),
99
+ evaluators: z.array(PublicKeySchema.extend({ evaluator: z.string().min(1) })),
100
+ });
101
+ const BundleV4PublicKeySchema = z.strictObject({
102
+ keyId: z.string().min(1),
103
+ algorithm: z.literal("ed25519"),
104
+ spkiDerBase64: z.string().min(1),
105
+ });
106
+ export const BundleV4TrustSchema = z.strictObject({
107
+ format: z.literal(BUNDLE_V4_TRUST_FORMAT),
108
+ selfRun: z.strictObject({
109
+ custody: z.literal("workspace-minted"),
110
+ evaluatorDistinctness: z.literal("agent-distinctness-only"),
111
+ partyIndependence: z.literal("not-established"),
112
+ }),
113
+ report: BundleV4PublicKeySchema.extend({
114
+ author: z.string().min(1),
115
+ didKey: z.string().min(1),
116
+ validFrom: Rfc3339Schema,
117
+ }),
118
+ evaluators: z.array(BundleV4PublicKeySchema.extend({ evaluator: z.string().min(1) })),
119
+ admission: z.strictObject({
120
+ reviewers: z.array(z.strictObject({
121
+ evaluator: z.string().min(1),
122
+ keyId: z.string().min(1),
123
+ })),
124
+ authorities: z.array(z.strictObject({
125
+ role: z.enum(["roster-attestor", "truth-reveal-attestor", "operator-truth-attestor"]),
126
+ keyId: z.string().min(1),
127
+ })),
128
+ }),
129
+ }).superRefine((trust, ctx) => {
130
+ for (let index = 1; index < trust.evaluators.length; index += 1) {
131
+ if (compareCodeUnitStrings(trust.evaluators[index - 1].evaluator, trust.evaluators[index].evaluator) >= 0) {
132
+ ctx.addIssue({ code: "custom", path: ["evaluators", index], message: "v4 evaluators must be code-unit sorted and unique" });
133
+ }
134
+ }
135
+ if (new Set(trust.evaluators.map((entry) => entry.keyId)).size !== trust.evaluators.length) {
136
+ ctx.addIssue({ code: "custom", path: ["evaluators"], message: "v4 evaluator key IDs must be unique" });
137
+ }
138
+ const evaluatorKeys = new Map(trust.evaluators.map((entry) => [entry.evaluator, entry.keyId]));
139
+ const reviewers = trust.admission.reviewers;
140
+ if (reviewers.length === 1)
141
+ ctx.addIssue({ code: "custom", path: ["admission", "reviewers"], message: "admission reviewers must be empty or a registry of at least two reviewers" });
142
+ for (const [index, reviewer] of reviewers.entries()) {
143
+ if (index > 0 && compareCodeUnitStrings(reviewers[index - 1].evaluator, reviewer.evaluator) >= 0)
144
+ ctx.addIssue({ code: "custom", path: ["admission", "reviewers"], message: "reviewer bindings must be sorted and unique" });
145
+ if (evaluatorKeys.get(reviewer.evaluator) !== reviewer.keyId)
146
+ ctx.addIssue({ code: "custom", path: ["admission", "reviewers", index], message: "reviewer binding does not resolve to one exact carried public key" });
147
+ }
148
+ if (new Set(reviewers.map((entry) => entry.keyId)).size !== reviewers.length)
149
+ ctx.addIssue({ code: "custom", path: ["admission", "reviewers"], message: "reviewer keys must be distinct" });
150
+ const authorityRoles = trust.admission.authorities.map((entry) => entry.role);
151
+ const humanRoles = ["roster-attestor", "truth-reveal-attestor"];
152
+ const operatorRoles = ["operator-truth-attestor"];
153
+ // Third legal authority set (spec §6.8a Group C, third bullet; packet P6): the screened
154
+ // branch's ordering receipt (§6.6) presents `truth-reveal-attestor` alone -- neither of the two
155
+ // existing sets. `AdmissionAuthorityRole` itself stays unchanged (§6.6 deliberately reuses the
156
+ // role rather than minting one).
157
+ const screenedRoles = ["truth-reveal-attestor"];
158
+ if (JSON.stringify(authorityRoles) !== JSON.stringify(humanRoles)
159
+ && JSON.stringify(authorityRoles) !== JSON.stringify(operatorRoles)
160
+ && JSON.stringify(authorityRoles) !== JSON.stringify(screenedRoles))
161
+ ctx.addIssue({ code: "custom", path: ["admission", "authorities"], message: "authority bindings must be exactly the human pair, the operator-only role, or the screened role alone" });
162
+ if (trust.admission.authorities.some((entry) => entry.keyId !== trust.report.keyId))
163
+ ctx.addIssue({ code: "custom", path: ["admission", "authorities"], message: "admission authority must resolve to the carried report-authority public key" });
164
+ });
165
+ // The /2 catalog's original twelve roles, plus the three binary-family records
166
+ // pairwise-disagreement@1 / paired-majority-delta@1 recompute from (packet P5).
167
+ // Existing /2 bundles do not declare the three; adding them is a compatible
168
+ // widening. Snapshot-probe and the admission-only V4 roles stay V4-only.
169
+ export const BUNDLE_EVIDENCE_ROLES = [
170
+ "task",
171
+ "runtime-selection",
172
+ "evaluation-spec",
173
+ "admission-receipt",
174
+ "solve-submission",
175
+ "run-pinning-evidence",
176
+ "evaluation-submission",
177
+ "solve-delivery",
178
+ "solve-output",
179
+ "evaluation-task",
180
+ "evaluation-delivery",
181
+ "verdict",
182
+ "judge-instrument",
183
+ "analysis-context",
184
+ "label-resolution",
185
+ ];
186
+ export const BundleEvidenceCatalogSchema = z.object({
187
+ format: z.literal(BUNDLE_EVIDENCE_FORMAT),
188
+ records: z.array(z.object({
189
+ sha256: Sha256HexSchema,
190
+ roles: z.array(z.enum(BUNDLE_EVIDENCE_ROLES)).min(1),
191
+ })),
192
+ });
193
+ export const BundleV4EvidenceCatalogSchema = z.strictObject({
194
+ format: z.literal(BUNDLE_V4_EVIDENCE_FORMAT),
195
+ records: z.array(z.strictObject({
196
+ sha256: Sha256HexSchema,
197
+ roles: z.array(z.enum(BUNDLE_V4_EVIDENCE_ROLES)).min(1),
198
+ })),
199
+ }).superRefine((catalog, ctx) => {
200
+ for (let index = 0; index < catalog.records.length; index += 1) {
201
+ const record = catalog.records[index];
202
+ if (index > 0 && compareCodeUnitStrings(catalog.records[index - 1].sha256, record.sha256) >= 0)
203
+ ctx.addIssue({ code: "custom", path: ["records", index], message: "records must be sorted and unique" });
204
+ const order = new Map(BUNDLE_V4_EVIDENCE_ROLES.map((role, roleIndex) => [role, roleIndex]));
205
+ for (let roleIndex = 1; roleIndex < record.roles.length; roleIndex += 1) {
206
+ if (order.get(record.roles[roleIndex - 1]) >= order.get(record.roles[roleIndex]))
207
+ ctx.addIssue({ code: "custom", path: ["records", index, "roles"], message: "roles must be in frozen order and unique" });
208
+ }
209
+ }
210
+ });
211
+ const PrefixedSha256Schema = z.string().regex(/^sha256:[a-f0-9]{64}$/u);
212
+ // §3.1 rule 1: the same identifier dialect used for stratum names elsewhere (e.g.
213
+ // `admission/contracts.ts`'s `BinaryJudgmentStratumSchema`). `candidateClasses` below deliberately
214
+ // stays ungrammared: tightening it is outside P4's contract and could refuse bundles written today.
215
+ const IDENTIFIER_NAME = /^[A-Za-z][A-Za-z0-9._-]{0,63}$/u;
216
+ export const BundleQualificationSchema = z.strictObject({
217
+ format: z.literal(BUNDLE_QUALIFICATION_FORMAT),
218
+ claimSchema: z.literal("benchmark-product.claim-package/2"),
219
+ sourceManifestSha256: PrefixedSha256Schema,
220
+ admissionManifestSha256: PrefixedSha256Schema,
221
+ publicationGrade: z.boolean(),
222
+ // Widened spec §6.8 (packet P6): a third admission mode, screened by a pinned model and
223
+ // sampled/hand-checked by the operator. BUNDLE_QUALIFICATION_FORMAT does not move (§0.4): every
224
+ // existing document still validates and seals byte-identically. The publicationGrade coupling
225
+ // for this third branch, and the admission-record/authority-set closure that makes a screened
226
+ // document constructible at all, are out of this packet's scope (spec §6.8a Group A/C).
227
+ truthAdmission: z.enum(["two-human-unanimous", "operator-only", "screened-operator-sampled"]),
228
+ candidateClasses: z.array(z.string().min(1)),
229
+ // Sorted-unique, grammar-conforming, non-empty (spec §3.1 rule 6). BUNDLE_QUALIFICATION_FORMAT
230
+ // stays at its current version under §0.4: every ["core","stress"] bundle ever written still
231
+ // validates, byte-identically.
232
+ strata: z.array(z.string().regex(IDENTIFIER_NAME)).min(1),
233
+ // .min(2), not a literal count (spec §1.6 rule 3). BUNDLE_QUALIFICATION_FORMAT stays at its
234
+ // current version under §0.4: every four-arm bundle ever written still validates,
235
+ // byte-identically.
236
+ arms: z.array(z.strictObject({
237
+ armId: z.string().min(1),
238
+ instrumentSha256: PrefixedSha256Schema,
239
+ })).min(2),
240
+ items: z.array(z.strictObject({
241
+ taskSha256: PrefixedSha256Schema,
242
+ itemSha256: PrefixedSha256Schema,
243
+ labelResolutionSha256: PrefixedSha256Schema,
244
+ analysisContextSha256: PrefixedSha256Schema,
245
+ })),
246
+ exclusions: z.array(z.strictObject({
247
+ itemSha256: PrefixedSha256Schema,
248
+ replacementItemSha256: PrefixedSha256Schema,
249
+ // Widened spec §6.4 (packet P6), second copy of the replacement-ledger reason vocabulary
250
+ // (the first is `admission/contracts.ts`'s `HumanReviewReplacementLedgerEntrySchema.reason`).
251
+ // The existing three values are byte-unchanged; the three new values are derived from §6.4's
252
+ // admission rule (screen disagreed, screen indeterminate, or the hand check excluded it).
253
+ reason: z.enum([
254
+ "review-disagreement", "review-indeterminate", "review-incomplete",
255
+ "screening-disagreement", "screening-indeterminate", "screening-hand-excluded",
256
+ ]),
257
+ })),
258
+ admissionRecords: z.array(z.strictObject({
259
+ sha256: PrefixedSha256Schema,
260
+ roles: z.array(z.enum(BUNDLE_V4_ADMISSION_EVIDENCE_ROLES)).min(1),
261
+ })),
262
+ reachableSha256s: z.array(PrefixedSha256Schema),
263
+ }).superRefine((qualification, ctx) => {
264
+ const sortedUnique = (values, path) => {
265
+ for (let index = 1; index < values.length; index += 1) {
266
+ if (compareCodeUnitStrings(values[index - 1], values[index]) >= 0)
267
+ ctx.addIssue({ code: "custom", path, message: "values must be code-unit sorted and unique" });
268
+ }
269
+ };
270
+ sortedUnique(qualification.candidateClasses, ["candidateClasses"]);
271
+ sortedUnique(qualification.strata, ["strata"]);
272
+ sortedUnique(qualification.arms.map((entry) => entry.armId), ["arms"]);
273
+ sortedUnique(qualification.items.map((entry) => entry.taskSha256), ["items"]);
274
+ for (const field of ["itemSha256", "labelResolutionSha256", "analysisContextSha256"]) {
275
+ if (new Set(qualification.items.map((entry) => entry[field])).size !== qualification.items.length)
276
+ ctx.addIssue({ code: "custom", path: ["items"], message: `${field} edges must be unique` });
277
+ }
278
+ sortedUnique(qualification.exclusions.map((entry) => entry.itemSha256), ["exclusions"]);
279
+ sortedUnique(qualification.admissionRecords.map((entry) => entry.sha256), ["admissionRecords"]);
280
+ const admissionRoleOrder = new Map(BUNDLE_V4_ADMISSION_EVIDENCE_ROLES.map((role, index) => [role, index]));
281
+ for (const [recordIndex, record] of qualification.admissionRecords.entries()) {
282
+ for (let roleIndex = 1; roleIndex < record.roles.length; roleIndex += 1) {
283
+ if (admissionRoleOrder.get(record.roles[roleIndex - 1]) >= admissionRoleOrder.get(record.roles[roleIndex]))
284
+ ctx.addIssue({ code: "custom", path: ["admissionRecords", recordIndex, "roles"], message: "admission roles must be in frozen order and unique" });
285
+ }
286
+ }
287
+ sortedUnique(qualification.reachableSha256s, ["reachableSha256s"]);
288
+ // Counts against arms.length, not the literal 4 (spec §1.6 rule 3, site 9).
289
+ if (new Set(qualification.arms.map((entry) => entry.instrumentSha256)).size !== qualification.arms.length)
290
+ ctx.addIssue({ code: "custom", path: ["arms"], message: "arms must bind one distinct instrument each" });
291
+ for (const [index, item] of qualification.items.entries()) {
292
+ for (const digest of [item.itemSha256, item.labelResolutionSha256, item.analysisContextSha256]) {
293
+ if (!qualification.reachableSha256s.includes(digest))
294
+ ctx.addIssue({ code: "custom", path: ["items", index], message: "item graph edge is absent from the authenticated admission closure" });
295
+ }
296
+ }
297
+ for (const [index, exclusion] of qualification.exclusions.entries()) {
298
+ if (!qualification.reachableSha256s.includes(exclusion.itemSha256) || !qualification.reachableSha256s.includes(exclusion.replacementItemSha256))
299
+ ctx.addIssue({ code: "custom", path: ["exclusions", index], message: "exclusion graph edge is absent from the authenticated admission closure" });
300
+ }
301
+ if (!qualification.reachableSha256s.includes(qualification.admissionManifestSha256))
302
+ ctx.addIssue({ code: "custom", path: ["admissionManifestSha256"], message: "admission manifest is absent from reachable closure" });
303
+ const rolesByDigest = new Map(qualification.admissionRecords.map((entry) => [entry.sha256, new Set(entry.roles)]));
304
+ const requireRole = (digest, role, path) => {
305
+ if (!rolesByDigest.get(digest)?.has(role))
306
+ ctx.addIssue({ code: "custom", path, message: `${digest} is not bound to semantic role ${role}` });
307
+ };
308
+ requireRole(qualification.admissionManifestSha256, "admission-manifest", ["admissionManifestSha256"]);
309
+ for (const [index, item] of qualification.items.entries()) {
310
+ requireRole(item.itemSha256, "source-item", ["items", index, "itemSha256"]);
311
+ requireRole(item.labelResolutionSha256, "label-resolution", ["items", index, "labelResolutionSha256"]);
312
+ requireRole(item.analysisContextSha256, "analysis-context", ["items", index, "analysisContextSha256"]);
313
+ }
314
+ for (const [index, exclusion] of qualification.exclusions.entries()) {
315
+ requireRole(exclusion.itemSha256, "source-item", ["exclusions", index, "itemSha256"]);
316
+ requireRole(exclusion.replacementItemSha256, "source-item", ["exclusions", index, "replacementItemSha256"]);
317
+ }
318
+ const roleCounts = new Map(BUNDLE_V4_ADMISSION_EVIDENCE_ROLES.map((role) => [
319
+ role,
320
+ qualification.admissionRecords.filter((entry) => entry.roles.includes(role)).length,
321
+ ]));
322
+ if (roleCounts.get("admission-manifest") !== 1 || roleCounts.get("replacement-ledger") !== 1
323
+ || roleCounts.get("human-review-evaluation-spec") !== 1 || roleCounts.get("human-review-form") !== 1) {
324
+ ctx.addIssue({ code: "custom", path: ["admissionRecords"], message: "admission closure must bind one manifest, ledger, frozen review spec, and frozen review form" });
325
+ }
326
+ const humanEvidenceRoles = [
327
+ "human-review-packet", "human-review-response", "human-review-verdict", "reviewer-roster",
328
+ "review-visibility-receipt", "review-reveal-receipt",
329
+ ];
330
+ const commonScreeningEvidenceRoles = ["screening-table", "screening-reveal-receipt", "screening-sampling-script"];
331
+ const legacyScreeningEvidenceRoles = ["screening-instrument", "screening-raw-outputs"];
332
+ const promptedScreeningEvidenceRoles = [
333
+ "screening-prompt", "screening-procedure", "screening-pool",
334
+ "screening-sample-commitment", "screening-transcript",
335
+ ];
336
+ if (qualification.truthAdmission === "two-human-unanimous") {
337
+ if (humanEvidenceRoles.some((role) => (roleCounts.get(role) ?? 0) === 0)
338
+ || roleCounts.get("operator-assertion") !== 0) {
339
+ ctx.addIssue({ code: "custom", path: ["admissionRecords"], message: "two-human admission must carry the frozen human-review closure and no operator assertion" });
340
+ }
341
+ }
342
+ else if (qualification.truthAdmission === "operator-only") {
343
+ if (humanEvidenceRoles.some((role) => (roleCounts.get(role) ?? 0) !== 0) || (roleCounts.get("operator-assertion") ?? 0) === 0) {
344
+ ctx.addIssue({ code: "custom", path: ["admissionRecords"], message: "operator-only admission must carry operator assertions and no human-review evidence" });
345
+ }
346
+ // Spec §6.8a Group C, second bullet. Named explicitly rather than left as a bare `else`: this
347
+ // was the family's last catch-all router, and Group B's doctrine is that a mode-dispatching
348
+ // branch names the mode it handles, so a fourth admission mode lands in no arm instead of
349
+ // silently inheriting the screened one's evidence rules.
350
+ }
351
+ else if (qualification.truthAdmission === "screened-operator-sampled") {
352
+ const legacyRoleSet = legacyScreeningEvidenceRoles.every((role) => roleCounts.get(role) === 1)
353
+ && promptedScreeningEvidenceRoles.every((role) => roleCounts.get(role) === 0);
354
+ const promptedRoleSet = promptedScreeningEvidenceRoles.every((role) => roleCounts.get(role) === 1)
355
+ && legacyScreeningEvidenceRoles.every((role) => roleCounts.get(role) === 0);
356
+ if (commonScreeningEvidenceRoles.some((role) => (roleCounts.get(role) ?? 0) !== 1)
357
+ || (!legacyRoleSet && !promptedRoleSet)
358
+ || humanEvidenceRoles.some((role) => (roleCounts.get(role) ?? 0) !== 0)
359
+ || (roleCounts.get("operator-assertion") ?? 0) !== 0) {
360
+ ctx.addIssue({ code: "custom", path: ["admissionRecords"], message: "screened admission must carry exactly one complete legacy-v1 or prompted-v2 screening record set and no human-review evidence or operator assertion" });
361
+ }
362
+ const screeningInstrument = qualification.admissionRecords.find((entry) => (entry.roles.includes("screening-instrument")));
363
+ if (screeningInstrument !== undefined
364
+ && qualification.arms.some((arm) => arm.instrumentSha256 === screeningInstrument.sha256)) {
365
+ ctx.addIssue({ code: "custom", path: ["arms"], message: "the screening instrument cannot also be a run judge arm" });
366
+ }
367
+ }
368
+ if (qualification.admissionRecords.length !== qualification.reachableSha256s.length
369
+ || qualification.admissionRecords.some((entry, index) => entry.sha256 !== qualification.reachableSha256s[index]))
370
+ ctx.addIssue({ code: "custom", path: ["admissionRecords"], message: "semantic admission roles must exactly cover reachableSha256s" });
371
+ if (qualification.truthAdmission === "two-human-unanimous" && !qualification.publicationGrade)
372
+ ctx.addIssue({ code: "custom", path: ["publicationGrade"], message: "two-human unanimous truth is publication grade" });
373
+ if (qualification.truthAdmission === "operator-only" && qualification.publicationGrade)
374
+ ctx.addIssue({ code: "custom", path: ["publicationGrade"], message: "operator-only truth is not publication grade" });
375
+ // §6.8: the screened branch is publication-grade -- without this branch, §6.8a Group A's fix to
376
+ // the two publicationGrade derivations (verification.ts, human-review.ts) would have nothing to
377
+ // enforce against.
378
+ if (qualification.truthAdmission === "screened-operator-sampled" && !qualification.publicationGrade)
379
+ ctx.addIssue({ code: "custom", path: ["publicationGrade"], message: "screened-operator-sampled truth is publication grade" });
380
+ });
381
+ export const BundleVerdictCatalogSchema = z.object({
382
+ format: z.literal(BUNDLE_VERDICTS_FORMAT),
383
+ verdicts: z.array(z.object({
384
+ sha256: Sha256HexSchema,
385
+ evaluator: z.string().min(1),
386
+ keyId: z.string().min(1),
387
+ cellKeys: z.array(z.string().min(1)).min(1),
388
+ })),
389
+ });
390
+ const AssemblyVerdictSchema = z.object({
391
+ sha256: Sha256HexSchema,
392
+ relationship: z.enum(["same-execution-scorer", "separate-log-verifier"]).optional(),
393
+ evalTaskSha256: Sha256HexSchema.optional(),
394
+ evalSubmissionSha256: Sha256HexSchema.optional(),
395
+ evalDeliverySha256: Sha256HexSchema.optional(),
396
+ evalAttempt: z.string().min(1).optional(),
397
+ evalIndex: z.number().int().positive(),
398
+ evaluator: z.string().min(1),
399
+ verdict: z.enum(["pass", "fail", "inconclusive"]),
400
+ evaluationSpecSha256: Sha256HexSchema,
401
+ measurements: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])),
402
+ });
403
+ export const BundleAssemblyHeaderSchema = z.object({
404
+ format: z.literal(BUNDLE_ASSEMBLY_FORMAT),
405
+ kind: z.literal("run"),
406
+ runCancelled: z.boolean(),
407
+ draftId: z.string().min(1),
408
+ assurancePreset: z.string().min(1),
409
+ rehearsal: z.object({
410
+ previewCount: z.number().int().positive(),
411
+ timestamps: z.array(Rfc3339Schema).min(1),
412
+ }).optional(),
413
+ graph: z.object({
414
+ admissions: z.array(z.object({ taskSha256: Sha256HexSchema, receiptSha256: Sha256HexSchema })),
415
+ solveSubmissions: z.array(z.object({
416
+ cellKey: z.string().min(1),
417
+ dispatch: z.number().int().positive(),
418
+ sha256: Sha256HexSchema,
419
+ pinningEvidenceSha256: Sha256HexSchema.optional(),
420
+ })),
421
+ evaluationSubmissions: z.array(z.object({
422
+ cellKey: z.string().min(1),
423
+ dispatch: z.number().int().positive(),
424
+ evalIndex: z.number().int().positive(),
425
+ evaluationAttempt: z.number().int().positive().optional(),
426
+ evaluator: z.string().min(1),
427
+ evalTaskSha256: Sha256HexSchema,
428
+ sha256: Sha256HexSchema,
429
+ })),
430
+ solveDeliveries: z.array(z.object({
431
+ cellKey: z.string().min(1),
432
+ dispatch: z.number().int().positive(),
433
+ attempt: z.string().min(1),
434
+ sha256: Sha256HexSchema,
435
+ outputs: z.array(z.object({ name: z.string(), sha256: Sha256HexSchema })),
436
+ })),
437
+ evaluations: z.array(z.object({
438
+ cellKey: z.string().min(1),
439
+ evalIndex: z.number().int().positive(),
440
+ evaluationAttempt: z.number().int().positive().optional(),
441
+ relationship: z.enum(["same-execution-scorer", "separate-log-verifier"]).optional(),
442
+ evaluator: z.string().optional(),
443
+ evalTaskSha256: Sha256HexSchema.optional(),
444
+ evalSubmissionSha256: Sha256HexSchema.optional(),
445
+ evalAttempt: z.string().optional(),
446
+ evalDeliverySha256: Sha256HexSchema.optional(),
447
+ verdictSha256: Sha256HexSchema.optional(),
448
+ evaluationTerminal: z.literal("could-not-grade").optional(),
449
+ /** The operational category that terminalized this leg as could-not-grade. Present only
450
+ * on a could-not-grade terminal whose journal entry recorded one, which today is the
451
+ * infrastructure failure that exhausted the sealed retry budget. Without it the accounted
452
+ * ungradeable cell reaches the bundle as an uncategorized absence: `evaluationRetries`
453
+ * carries the category of every failure that WAS retried, and the terminal one is the
454
+ * only failure in the leg that has no retry row to carry it. Additive and optional, so
455
+ * every bundle that carried no such terminal seals to identical bytes. */
456
+ failureCategory: z.enum(TASK_EXECUTION_ERROR_CATEGORIES).optional(),
457
+ })),
458
+ evaluationRetries: z.array(z.object({
459
+ cellKey: z.string().min(1),
460
+ dispatch: z.number().int().positive(),
461
+ evalIndex: z.number().int().positive(),
462
+ evaluationAttempt: z.number().int().positive(),
463
+ evaluator: z.string().min(1),
464
+ evalTaskSha256: Sha256HexSchema.optional(),
465
+ evalSubmissionSha256: Sha256HexSchema.optional(),
466
+ evalAttempt: z.string().min(1).optional(),
467
+ failureCategory: z.enum(TASK_EXECUTION_ERROR_CATEGORIES),
468
+ recoveryAdvice: z.literal("new-attempt-required"),
469
+ detail: z.string().min(1),
470
+ })).optional(),
471
+ }),
472
+ });
473
+ export const BundleAssemblyCellSchema = z.object({
474
+ kind: z.literal("cell"),
475
+ cellKey: z.string().min(1),
476
+ armId: z.string().min(1),
477
+ replicate: z.number().int().positive(),
478
+ taskDigest: Sha256HexSchema,
479
+ dispatches: z.number().int().nonnegative(),
480
+ accounted: z.number().int().positive().optional(),
481
+ submissionSha256: Sha256HexSchema.optional(),
482
+ pinningEvidenceSha256: Sha256HexSchema.optional(),
483
+ attempt: z.string().min(1).optional(),
484
+ deliverySha256: Sha256HexSchema.optional(),
485
+ solveOutputs: z.array(z.object({ name: z.string(), sha256: Sha256HexSchema })).optional(),
486
+ evaluationSpecSha256: Sha256HexSchema.optional(),
487
+ evaluationTerminal: z.literal("could-not-grade").optional(),
488
+ admission: z.object({
489
+ zeroReplayVariance: z.boolean(),
490
+ externalCapabilities: z.boolean(),
491
+ }).optional(),
492
+ admissionReceiptSha256: Sha256HexSchema.optional(),
493
+ verdicts: z.array(AssemblyVerdictSchema),
494
+ });
495
+ export const BundleCancelMarkerSchema = z.object({
496
+ requestedAt: Rfc3339Schema,
497
+ principal: z.string().min(1),
498
+ });
@@ -0,0 +1,72 @@
1
+ import { type EvidenceNativePortableBundleVerification } from "@jinn-network/benchmarking-evidence";
2
+ import { type InspectRuntimeMethodDisclosure } from "./profile/inspect-disclosure.js";
3
+ import { type PublicComparisonView } from "./comparison.js";
4
+ import { type VerifiedBundleSnapshot, type VerifyBundleSnapshotDeps } from "./manifest.js";
5
+ import { type IntegrityAnchorsReport, type PublicBundleAnchorTrustMaterial } from "./anchor/check.js";
6
+ import { type BinaryItemBankEntry, type BinarySourceManifestEntry } from "./admission/intake.js";
7
+ export type PublicBundleVerificationCheck = "manifest" | "evidence-closure" | "trust" | "matrix-rederivation" | "report-verification" | "claim-consistency"
8
+ /** Always present for `benchmark-product-public-bundle/6`, never for any earlier closure
9
+ * (anchor-evidence design §8, §12). */
10
+ | "integrity-anchors";
11
+ export interface LegacyPublicBundleVerificationResult {
12
+ readonly format: "benchmark-product-public-bundle/2" | "benchmark-product-public-bundle/4" | "benchmark-product-public-bundle/6";
13
+ readonly identity: string;
14
+ readonly checks: readonly PublicBundleVerificationCheck[];
15
+ readonly benchmarkSha256: string;
16
+ readonly runSha256: string;
17
+ readonly matrixSha256: string;
18
+ readonly reportSha256: string;
19
+ readonly reportEnvelopeSha256: string;
20
+ readonly runtimeMethod?: InspectRuntimeMethodDisclosure;
21
+ /** Present exactly for the anchored closure: every carried anchor's own outcome plus each
22
+ * subject's context outcome (anchor-evidence design §8). Statuses are disclosed facts, not a
23
+ * summary — nothing here is folded into a single verified badge. */
24
+ readonly anchors?: IntegrityAnchorsReport;
25
+ readonly qualification?: {
26
+ readonly publicationGrade: boolean;
27
+ readonly truthAdmission: "two-human-unanimous" | "operator-only" | "screened-operator-sampled";
28
+ readonly candidateClasses: readonly string[];
29
+ readonly strata: readonly string[];
30
+ readonly armCount: number;
31
+ readonly itemCount: number;
32
+ readonly exclusionCount: number;
33
+ };
34
+ }
35
+ export type PublicBundleVerificationResult = LegacyPublicBundleVerificationResult | EvidenceNativePortableBundleVerification;
36
+ export interface VerifyPublicBundleDeps extends VerifyBundleSnapshotDeps {
37
+ /**
38
+ * Trust material for the `integrity-anchors` check (anchor-evidence design §8 step 3): timestamp
39
+ * authority roots, Bitcoin block headers. **Strictly the verifier operator's own configuration.**
40
+ * This package ships none, so the default outcome for a well-formed proof is `present`, not
41
+ * `verified`; a chain validated solely against roots a bundle carried would re-import the
42
+ * self-run problem with extra ceremony.
43
+ */
44
+ readonly anchorTrust?: PublicBundleAnchorTrustMaterial;
45
+ }
46
+ /**
47
+ * One semantically verified result bound to the exact authenticated bytes used
48
+ * to derive it. Consumers such as the local reader must serve these bytes
49
+ * rather than reopening bundle paths after verification.
50
+ */
51
+ export interface VerifiedPublicBundleSnapshot {
52
+ readonly verification: PublicBundleVerificationResult;
53
+ readonly comparison?: PublicComparisonView;
54
+ readonly snapshot: VerifiedBundleSnapshot;
55
+ }
56
+ /**
57
+ * The cold verifier's item-bank/source-manifest closure, over already-parsed canonical rows.
58
+ *
59
+ * Exported so the closure can be exercised directly. It has to be: a bundle whose cluster key is
60
+ * decoupled from its declared sources cannot be produced by this repository's own import path,
61
+ * because the importer's code-unit-least rule is strictly stronger than the membership rule below.
62
+ * This check exists for bundles produced by other implementations, so its test has to construct
63
+ * the rows rather than round-trip them through the importer.
64
+ */
65
+ export declare function checkItemBankSourceClosure(itemRows: readonly BinaryItemBankEntry[], sourceRows: readonly BinarySourceManifestEntry[]): {
66
+ readonly itemDigests: ReadonlySet<string>;
67
+ };
68
+ /** Verifies a copied public bundle using one authenticated byte snapshot and only bundle-carried
69
+ * public keys. No pathname is reopened after manifest authentication. */
70
+ export declare function verifyPublicBundleSnapshot(bundleDir: string, deps?: VerifyPublicBundleDeps): Promise<VerifiedPublicBundleSnapshot>;
71
+ /** Verifies a bundle without exposing its authenticated byte snapshot. */
72
+ export declare function verifyPublicBundle(bundleDir: string, deps?: VerifyPublicBundleDeps): Promise<PublicBundleVerificationResult>;