@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.
- package/README.md +39 -0
- package/dist/admission/application.d.ts +18 -0
- package/dist/admission/application.js +53 -0
- package/dist/admission/contracts.d.ts +506 -0
- package/dist/admission/contracts.js +459 -0
- package/dist/admission/index.d.ts +5 -0
- package/dist/admission/index.js +5 -0
- package/dist/admission/intake.d.ts +75 -0
- package/dist/admission/intake.js +70 -0
- package/dist/admission/result-evaluation.d.ts +18 -0
- package/dist/admission/result-evaluation.js +71 -0
- package/dist/admission/screening-sample.d.ts +56 -0
- package/dist/admission/screening-sample.js +126 -0
- package/dist/admission/verification.d.ts +102 -0
- package/dist/admission/verification.js +844 -0
- package/dist/anchor/check.d.ts +95 -0
- package/dist/anchor/check.js +146 -0
- package/dist/anchor/ports.d.ts +6 -0
- package/dist/anchor/ports.js +354 -0
- package/dist/assets.d.ts +33 -0
- package/dist/assets.js +885 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +20 -0
- package/dist/cli.d.ts +14 -0
- package/dist/cli.js +205 -0
- package/dist/comparison.d.ts +64 -0
- package/dist/comparison.js +235 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +23 -0
- package/dist/manifest.d.ts +54 -0
- package/dist/manifest.js +240 -0
- package/dist/materialize.d.ts +4 -0
- package/dist/materialize.js +13 -0
- package/dist/profile/admission-receipts.d.ts +11 -0
- package/dist/profile/admission-receipts.js +24 -0
- package/dist/profile/anchor-claims.d.ts +157 -0
- package/dist/profile/anchor-claims.js +330 -0
- package/dist/profile/artifacts.d.ts +261 -0
- package/dist/profile/artifacts.js +139 -0
- package/dist/profile/assembly-ports.d.ts +19 -0
- package/dist/profile/assembly-ports.js +42 -0
- package/dist/profile/binary-judge-manifest.d.ts +121 -0
- package/dist/profile/binary-judge-manifest.js +106 -0
- package/dist/profile/binary-qualification.d.ts +9 -0
- package/dist/profile/binary-qualification.js +58 -0
- package/dist/profile/branding-assets.d.ts +5 -0
- package/dist/profile/branding-assets.js +5 -0
- package/dist/profile/branding.d.ts +24 -0
- package/dist/profile/branding.js +14 -0
- package/dist/profile/claim-consistency.d.ts +26 -0
- package/dist/profile/claim-consistency.js +91 -0
- package/dist/profile/claim.d.ts +327 -0
- package/dist/profile/claim.js +742 -0
- package/dist/profile/errors.d.ts +49 -0
- package/dist/profile/errors.js +78 -0
- package/dist/profile/inspect-assurance.d.ts +16 -0
- package/dist/profile/inspect-assurance.js +30 -0
- package/dist/profile/inspect-disclosure.d.ts +28 -0
- package/dist/profile/inspect-disclosure.js +54 -0
- package/dist/profile/inspect-manifest.d.ts +706 -0
- package/dist/profile/inspect-manifest.js +397 -0
- package/dist/profile/isolation.d.ts +21 -0
- package/dist/profile/isolation.js +31 -0
- package/dist/profile/pinning-evidence.d.ts +41 -0
- package/dist/profile/pinning-evidence.js +43 -0
- package/dist/profile/ports.d.ts +3 -0
- package/dist/profile/ports.js +11 -0
- package/dist/profile/preview-log.d.ts +4 -0
- package/dist/profile/preview-log.js +3 -0
- package/dist/profile/run-results.d.ts +17 -0
- package/dist/profile/run-results.js +23 -0
- package/dist/profile/signing.d.ts +10 -0
- package/dist/profile/signing.js +46 -0
- package/dist/profile/trust.d.ts +13 -0
- package/dist/profile/trust.js +35 -0
- package/dist/profile/venue.d.ts +2 -0
- package/dist/profile/venue.js +2 -0
- package/dist/profile/verdict.d.ts +28 -0
- package/dist/profile/verdict.js +51 -0
- package/dist/reader-instructions.d.ts +41 -0
- package/dist/reader-instructions.js +52 -0
- package/dist/schema.d.ts +380 -0
- package/dist/schema.js +498 -0
- package/dist/verify.d.ts +72 -0
- package/dist/verify.js +1573 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +2 -0
- package/package.json +63 -3
- package/schemas/assembly-row.schema.json +194 -0
- package/schemas/bundle-manifest.schema.json +23 -0
- package/schemas/claim-package.schema.json +279 -0
- package/schemas/dsse-envelope.schema.json +25 -0
- package/schemas/evidence-catalog.schema.json +43 -0
- package/schemas/public-trust.schema.json +44 -0
- package/schemas/verdict-catalog.schema.json +27 -0
- package/scripts/external-verify.py +238 -0
|
@@ -0,0 +1,742 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The claim package (BP-13 deliverable 2, spec §8.2): a pure builder + a zod schema + a writer
|
|
3
|
+
* for the standalone, portable document a skeptic reads to check a benchmark result without
|
|
4
|
+
* re-running anything.
|
|
5
|
+
*
|
|
6
|
+
* `buildClaimPackage` is PURE — no filesystem, no digest recomputation, no re-derivation of the
|
|
7
|
+
* sealed Report's own judgment. Every headline-or-comparison number, disclosure, limitation, and
|
|
8
|
+
* attrition figure is EXTRACTED verbatim from the records the caller already sealed and verified
|
|
9
|
+
* elsewhere (`../operations/report.ts`), never recomputed here — a claim package that silently
|
|
10
|
+
* disagreed with its own cited report would be worse than one that failed to build. Nothing is
|
|
11
|
+
* dropped: `conflicted`, `completeness`, and `attrition` are always present, even when they carry
|
|
12
|
+
* unwelcome numbers (a conflicted cell, an incomplete run) — spec §8.2's whole point is that the
|
|
13
|
+
* claim package cannot be more flattering than the sealed records it cites.
|
|
14
|
+
*
|
|
15
|
+
* P4b Task 5: `buildClaimPackage` DISPATCHES on the produced Report's `method.id` — wilson@1's
|
|
16
|
+
* per-arm `headline` and paired-delta@1's `comparison` are mutually exclusive siblings, exactly
|
|
17
|
+
* one of which is present on any given claim (the schema refuses a claim carrying neither). Same
|
|
18
|
+
* "extracted, never recomputed" posture regardless of which method produced the Report.
|
|
19
|
+
*
|
|
20
|
+
* BP-20 (spec §7.2): the optional `rehearsal` block names any disclosed preview that preceded
|
|
21
|
+
* this run's lock, verbatim from the caller's `previewDisclosure` input — used previews are named
|
|
22
|
+
* in the official record; its absence means no preview preceded lock. Same "extracted, never
|
|
23
|
+
* recomputed" posture as everything else this builder emits.
|
|
24
|
+
*
|
|
25
|
+
* BP-21 (spec §6): the required `assurance` block states the draft's preset (a product-policy
|
|
26
|
+
* label) AND the platform primitives it resolved to, plus the fixed agent-distinctness
|
|
27
|
+
* disclosure. The one deliberate exception to "never cross-check here": `buildClaimPackage`
|
|
28
|
+
* THROWS when the stated primitives disagree with what the sealed Run record's own policy
|
|
29
|
+
* carries — a claim stating primitives the sealed Run does not carry would be dishonest.
|
|
30
|
+
*/
|
|
31
|
+
import { z } from "zod";
|
|
32
|
+
import { Buffer } from "node:buffer";
|
|
33
|
+
import { validateBinaryInstrumentQualificationProjection } from "@jinn-network/benchmarking-aggregate";
|
|
34
|
+
import { BENCHMARKING_METHOD_IDS, BENCHMARKING_METHOD_VERSION } from "@jinn-network/benchmarking-records";
|
|
35
|
+
import { canonicalJsonBytes } from "@jinn-network/trust-core";
|
|
36
|
+
import { PUBLIC_BUNDLE_COMPATIBLE_VERIFICATION_COMMAND, PUBLIC_BUNDLE_VERIFICATION_CHECKS as READER_VERIFICATION_CHECKS, PUBLIC_BUNDLE_VERIFICATION_COMMAND as READER_VERIFICATION_COMMAND, PUBLIC_BUNDLE_V6_CHECKS as READER_ANCHORED_VERIFICATION_CHECKS, PUBLIC_BUNDLE_V6_COMPATIBLE_VERIFICATION_COMMAND, PUBLIC_BUNDLE_V6_VERIFICATION_COMMAND, } from "../reader-instructions.js";
|
|
37
|
+
import { PROMPTED_SCREENING_PROFILE } from "../admission/contracts.js";
|
|
38
|
+
import { ClaimAnchorSchema, SELF_RUN_TRUST_ROOT, anchoredTrustRoot } from "./anchor-claims.js";
|
|
39
|
+
export const CLAIM_PACKAGE_SCHEMA_ID = "benchmark-product.claim-package/1";
|
|
40
|
+
export const BINARY_QUALIFICATION_CLAIM_PACKAGE_SCHEMA_ID = "benchmark-product.claim-package/2";
|
|
41
|
+
/**
|
|
42
|
+
* The anchored claim package (anchor-evidence design §7.4): claim-package/1 plus the `anchors`
|
|
43
|
+
* section, carried by `benchmark-product-public-bundle/6`. The allocation is the next free number
|
|
44
|
+
* in the classic path; the evidence-native claim-package/3 adopts the same anchor surface in its
|
|
45
|
+
* own later allocation.
|
|
46
|
+
*/
|
|
47
|
+
export const ANCHORED_CLAIM_PACKAGE_SCHEMA_ID = "benchmark-product.claim-package/4";
|
|
48
|
+
export const BINARY_QUALIFICATION_VERIFICATION_COMMAND = "npx @colophon-claims/verify@0.1.0 <bundle-dir>";
|
|
49
|
+
export const BINARY_QUALIFICATION_COMPATIBLE_VERIFICATION_COMMAND = "npx @colophon-claims/verify@0.1 <bundle-dir>";
|
|
50
|
+
/** Prompted-screening v2 claims require the verifier release that carries that admission surface. */
|
|
51
|
+
export const PROMPTED_BINARY_QUALIFICATION_VERIFICATION_COMMAND = "npx @colophon-claims/verify@0.2.0 <bundle-dir>";
|
|
52
|
+
export const PROMPTED_BINARY_QUALIFICATION_COMPATIBLE_VERIFICATION_COMMAND = "npx @colophon-claims/verify@0.2 <bundle-dir>";
|
|
53
|
+
const Sha256HexSchema = z.string().regex(/^[a-f0-9]{64}$/, "must be a lowercase sha256 hex digest");
|
|
54
|
+
/** Mirrors `../run/preview-log.ts`'s own (unexported) `Rfc3339Schema` — restated here rather than
|
|
55
|
+
* reached across the module boundary for one regex. */
|
|
56
|
+
const Rfc3339Schema = z.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/, "must be an RFC3339 timestamp");
|
|
57
|
+
const WilsonIntervalSchema = z.object({ low: z.string(), high: z.string() });
|
|
58
|
+
const HeadlineArmSchema = z.object({
|
|
59
|
+
n: z.number().int().nonnegative(),
|
|
60
|
+
passRate: z.string(),
|
|
61
|
+
wilsonInterval: WilsonIntervalSchema,
|
|
62
|
+
});
|
|
63
|
+
const ConflictedSchema = z.object({
|
|
64
|
+
count: z.number().int().nonnegative(),
|
|
65
|
+
cellKeys: z.array(z.string()),
|
|
66
|
+
});
|
|
67
|
+
const PairedIntervalSchema = z.object({ alpha: z.string(), low: z.string(), high: z.string() });
|
|
68
|
+
/**
|
|
69
|
+
* paired-delta@1's comparison block (P4b Task 5), sibling to `headline` (BP-13 method dispatch).
|
|
70
|
+
* Carried verbatim from the method's own compute() output (`benchmarking/aggregate/src/
|
|
71
|
+
* registry.ts`'s pairedDeltaMethod) — never recomputed, per this module's header. `baseline`,
|
|
72
|
+
* `candidate`, and `verdictRule` are deliberately NOT repeated here: they already live in the
|
|
73
|
+
* claim's `method.parameters` block, sealed verbatim by `run/compile.ts`'s buildAnalysisPlan.
|
|
74
|
+
* `pairing`, `clustering`, and `bootstrap` carry richer nested shapes than a claim reader needs
|
|
75
|
+
* pinned exactly, so — like this schema's own `results` field — they are typed loosely rather
|
|
76
|
+
* than re-specified field-by-field.
|
|
77
|
+
*/
|
|
78
|
+
const ComparisonSchema = z.object({
|
|
79
|
+
pairs: z.number().int().nonnegative(),
|
|
80
|
+
delta: z.string().nullable(),
|
|
81
|
+
interval: PairedIntervalSchema.nullable(),
|
|
82
|
+
reasons: z.array(z.string()),
|
|
83
|
+
pairing: z.object({ taskDigests: z.array(z.string()) }),
|
|
84
|
+
clustering: z.object({ basis: z.string(), clusters: z.number().int().nonnegative() }),
|
|
85
|
+
excluded: ConflictedSchema,
|
|
86
|
+
conflicted: ConflictedSchema,
|
|
87
|
+
bootstrap: z.unknown(),
|
|
88
|
+
});
|
|
89
|
+
/** `pairwise-disagreement@1` and `paired-majority-delta@1` (packet #2837, spec §7.1/§7.2a) both
|
|
90
|
+
* report intervals keyed `{lower, upper, alpha}` -- NOT `PairedIntervalSchema`'s `{low, high,
|
|
91
|
+
* alpha}`, which is `paired-delta@1`'s own key spelling. Reusing `PairedIntervalSchema` here would
|
|
92
|
+
* silently drop `lower`/`upper` at parse time (zod strips unrecognized keys by default) rather than
|
|
93
|
+
* refuse. Mirrors the identically-named schema in core's `report/claim.ts`. */
|
|
94
|
+
const JudgeIntervalSchema = z.object({ lower: z.string(), upper: z.string(), alpha: z.string() });
|
|
95
|
+
const ExclusionTripleSchema = z.object({
|
|
96
|
+
taskDigest: z.string().min(1),
|
|
97
|
+
armId: z.string().min(1),
|
|
98
|
+
reason: z.string().min(1),
|
|
99
|
+
});
|
|
100
|
+
/**
|
|
101
|
+
* `pairwise-disagreement@1`'s panel projection (spec §7.1), sibling to `comparison`/`headline`/
|
|
102
|
+
* `qualification` (BP-13 method dispatch). Carried verbatim from the method's own compute() output
|
|
103
|
+
* (`aggregate/src/pairwise-disagreement-method.ts`'s `computePairwiseDisagreement`) — never
|
|
104
|
+
* recomputed, per this module's header. No `baseline`/`candidate`: the method computes all
|
|
105
|
+
* unordered arm pairs in one pass and carries no chosen pair.
|
|
106
|
+
*/
|
|
107
|
+
const DisagreementCountSchema = z.object({
|
|
108
|
+
n: z.number().int().nonnegative(),
|
|
109
|
+
disagreements: z.number().int().nonnegative(),
|
|
110
|
+
rate: z.string().nullable(),
|
|
111
|
+
interval: JudgeIntervalSchema.nullable(),
|
|
112
|
+
});
|
|
113
|
+
const PairwiseDisagreementSchema = z.object({
|
|
114
|
+
pairs: z.array(DisagreementCountSchema.extend({
|
|
115
|
+
armA: z.string().min(1),
|
|
116
|
+
armB: z.string().min(1),
|
|
117
|
+
byCandidateClass: z.array(DisagreementCountSchema.extend({ candidateClass: z.string().min(1) })),
|
|
118
|
+
byStratum: z.array(DisagreementCountSchema.extend({ stratum: z.string().min(1) })),
|
|
119
|
+
exclusions: z.array(ExclusionTripleSchema),
|
|
120
|
+
})),
|
|
121
|
+
conflicted: ConflictedSchema,
|
|
122
|
+
});
|
|
123
|
+
/**
|
|
124
|
+
* `paired-majority-delta@1`'s evidence-contrast projection (spec §7.2a), sibling to `comparison`/
|
|
125
|
+
* `pairwiseDisagreement`. Carried verbatim from the method's own compute() output (`aggregate/src/
|
|
126
|
+
* paired-majority-delta-method.ts`'s `computePairedMajorityDelta`) — never recomputed. `baseline`
|
|
127
|
+
* is the evidence-declaring arm's evidence-free twin and `candidate` is the evidence-declaring arm
|
|
128
|
+
* itself (coordinator ruling, packet #2837): a positive `delta` means declaring evidence increased
|
|
129
|
+
* agreement.
|
|
130
|
+
*/
|
|
131
|
+
const DeltaProjectionSchema = z.object({
|
|
132
|
+
n: z.number().int().nonnegative(),
|
|
133
|
+
delta: z.string().nullable(),
|
|
134
|
+
interval: JudgeIntervalSchema.nullable(),
|
|
135
|
+
reasons: z.array(z.string()),
|
|
136
|
+
});
|
|
137
|
+
const PairedMajorityDeltaSchema = DeltaProjectionSchema.extend({
|
|
138
|
+
baseline: z.string().min(1),
|
|
139
|
+
candidate: z.string().min(1),
|
|
140
|
+
// `manifest` is optional (M4): the projection omits the key entirely when the method emitted
|
|
141
|
+
// none, rather than writing `manifest: undefined` into an object that is canonical-JSON encoded
|
|
142
|
+
// on both sides of the mirror. Present-and-absent must both be expressible for the two copies to
|
|
143
|
+
// agree byte-for-byte.
|
|
144
|
+
clusters: z.object({ count: z.number().int().nonnegative(), manifest: z.unknown().optional() }),
|
|
145
|
+
byCandidateClass: z.array(DeltaProjectionSchema.extend({ candidateClass: z.string().min(1) })),
|
|
146
|
+
byStratum: z.array(DeltaProjectionSchema.extend({ stratum: z.string().min(1) })),
|
|
147
|
+
exclusions: z.array(ExclusionTripleSchema),
|
|
148
|
+
conflicted: ConflictedSchema,
|
|
149
|
+
});
|
|
150
|
+
const IntegrityTierCountsSchema = z.object({
|
|
151
|
+
"re-derivable": z.number().int().nonnegative(),
|
|
152
|
+
"attested-only": z.number().int().nonnegative(),
|
|
153
|
+
});
|
|
154
|
+
const PinningUnverifiableCountsSchema = z.object({
|
|
155
|
+
harness: z.number().int().nonnegative(),
|
|
156
|
+
model: z.number().int().nonnegative(),
|
|
157
|
+
loadout: z.number().int().nonnegative(),
|
|
158
|
+
isolation: z.number().int().nonnegative(),
|
|
159
|
+
});
|
|
160
|
+
/** The report's own disclosures block, carried whole, plus the two convenience summaries this
|
|
161
|
+
* module adds (never a substitute for reading `perSubject` itself). */
|
|
162
|
+
const DisclosuresSchema = z.object({
|
|
163
|
+
perSubject: z.array(z.unknown()),
|
|
164
|
+
integrityTierCounts: IntegrityTierCountsSchema,
|
|
165
|
+
pinningUnverifiableCounts: PinningUnverifiableCountsSchema,
|
|
166
|
+
});
|
|
167
|
+
/** The fixed sentence every claim package's assurance block carries (BP-21, spec §6/§7.1): what
|
|
168
|
+
* distinct evaluator identities on this venue actually prove. Never softened per-claim. */
|
|
169
|
+
export const ASSURANCE_DISCLOSURE = "Distinct evaluator identities are workspace-minted keys; they prove agent-distinctness, "
|
|
170
|
+
+ "not party-independence, on this self-run venue.";
|
|
171
|
+
const ResolvedAssuranceSchema = z.object({
|
|
172
|
+
independence: z.enum(["gating", "disclosed"]),
|
|
173
|
+
minVerdicts: z.number().int().min(1),
|
|
174
|
+
distinctEvaluator: z.boolean(),
|
|
175
|
+
verdictRule: z.enum(["sole", "majority", "unanimous"]),
|
|
176
|
+
});
|
|
177
|
+
/** BP-21 (spec §6): the claim states the assurance preset AND the platform primitives it resolved
|
|
178
|
+
* to, never the label alone — preset names are product policy, the primitives are what the sealed
|
|
179
|
+
* Run actually carries (`buildClaimPackage` throws if the two disagree). */
|
|
180
|
+
const AssuranceBlockSchema = z.object({
|
|
181
|
+
/** Product preset label (product policy) — never a substitute for `resolved`. */
|
|
182
|
+
preset: z.string().min(1),
|
|
183
|
+
resolved: ResolvedAssuranceSchema,
|
|
184
|
+
/** The fixed agent-distinctness disclosure (`ASSURANCE_DISCLOSURE`). */
|
|
185
|
+
disclosure: z.string().min(1),
|
|
186
|
+
});
|
|
187
|
+
const ClaimPackageWireSchema = z.object({
|
|
188
|
+
claimSchema: z.union([
|
|
189
|
+
z.literal(CLAIM_PACKAGE_SCHEMA_ID),
|
|
190
|
+
z.literal(BINARY_QUALIFICATION_CLAIM_PACKAGE_SCHEMA_ID),
|
|
191
|
+
z.literal(ANCHORED_CLAIM_PACKAGE_SCHEMA_ID),
|
|
192
|
+
]),
|
|
193
|
+
scope: z.object({
|
|
194
|
+
draftId: z.string().min(1),
|
|
195
|
+
benchmarkSha256: Sha256HexSchema,
|
|
196
|
+
taskCount: z.number().int().nonnegative(),
|
|
197
|
+
arms: z.array(z.object({ armId: z.string().min(1), pinning: z.record(z.string(), z.unknown()) })),
|
|
198
|
+
replicates: z.number().int().positive(),
|
|
199
|
+
venue: z.literal("self-run"),
|
|
200
|
+
}),
|
|
201
|
+
records: z.object({
|
|
202
|
+
benchmarkSha256: Sha256HexSchema,
|
|
203
|
+
runSha256: Sha256HexSchema,
|
|
204
|
+
matrixSha256: Sha256HexSchema,
|
|
205
|
+
reportSha256: Sha256HexSchema,
|
|
206
|
+
reportEnvelopeSha256: Sha256HexSchema,
|
|
207
|
+
}),
|
|
208
|
+
method: z.object({
|
|
209
|
+
id: z.string().min(1),
|
|
210
|
+
version: z.string().min(1),
|
|
211
|
+
parameters: z.record(z.string(), z.unknown()),
|
|
212
|
+
preregistered: z.boolean(),
|
|
213
|
+
}),
|
|
214
|
+
results: z.unknown(),
|
|
215
|
+
/** wilson@1's per-arm headline. Optional and additive (P4b Task 5, mirrors the `rehearsal`
|
|
216
|
+
* precedent below): a paired claim carries `comparison` instead — see the schema-level refine
|
|
217
|
+
* at the bottom of this object, which refuses a claim carrying neither. */
|
|
218
|
+
headline: z.record(z.string(), HeadlineArmSchema).optional(),
|
|
219
|
+
completeness: z.unknown(),
|
|
220
|
+
attrition: z.unknown(),
|
|
221
|
+
conflicted: ConflictedSchema,
|
|
222
|
+
assurance: AssuranceBlockSchema,
|
|
223
|
+
disclosures: DisclosuresSchema,
|
|
224
|
+
limitations: z.array(z.string()),
|
|
225
|
+
venueHonesty: z.unknown(),
|
|
226
|
+
verification: z.object({
|
|
227
|
+
command: z.string().min(1),
|
|
228
|
+
compatibleCommand: z.string().min(1),
|
|
229
|
+
checks: z.array(z.string().min(1)).min(1),
|
|
230
|
+
trustRoot: z.string().min(1),
|
|
231
|
+
}),
|
|
232
|
+
/** BP-20 (spec §7.2): present only when a preview preceded this run's lock — see module
|
|
233
|
+
* header. Optional and additive; absent on every claim package built before this change. */
|
|
234
|
+
rehearsal: z.object({
|
|
235
|
+
previewCount: z.number().int().positive(),
|
|
236
|
+
timestamps: z.array(Rfc3339Schema).min(1),
|
|
237
|
+
}).optional(),
|
|
238
|
+
/** P4b Task 5: present only for a paired-delta@1 Report, sibling to `headline`. See
|
|
239
|
+
* `ComparisonSchema`'s own comment. */
|
|
240
|
+
comparison: ComparisonSchema.optional(),
|
|
241
|
+
/** packet #2837: present only for a pairwise-disagreement@1 Report, sibling to `comparison`. See
|
|
242
|
+
* `PairwiseDisagreementSchema`'s own comment. */
|
|
243
|
+
pairwiseDisagreement: PairwiseDisagreementSchema.optional(),
|
|
244
|
+
/** packet #2837: present only for a paired-majority-delta@1 Report, sibling to `comparison`. See
|
|
245
|
+
* `PairedMajorityDeltaSchema`'s own comment. */
|
|
246
|
+
pairedMajorityDelta: PairedMajorityDeltaSchema.optional(),
|
|
247
|
+
/** binary-instrument@1's complete per-subject F6 result. This is copied exactly; it is not a
|
|
248
|
+
* headline, comparison, threshold, selection, or ranking projection. */
|
|
249
|
+
qualification: z.unknown().optional(),
|
|
250
|
+
/** anchor-evidence §7.4: one entry per AnchorEvidence record the bundle carries, in record-digest
|
|
251
|
+
* order, each carrying only the facts embedded in the proof's own bytes. Present exactly on
|
|
252
|
+
* claim-package/4 — the schema-level refine below refuses it on /1 and /2, so an unanchored claim
|
|
253
|
+
* cannot grow an anchors section and an anchored one cannot be published without it. */
|
|
254
|
+
anchors: z.array(ClaimAnchorSchema).optional(),
|
|
255
|
+
}).superRefine((claim, ctx) => {
|
|
256
|
+
if (claim.claimSchema !== ANCHORED_CLAIM_PACKAGE_SCHEMA_ID && claim.anchors !== undefined) {
|
|
257
|
+
ctx.addIssue({
|
|
258
|
+
code: "custom",
|
|
259
|
+
message: `only ${ANCHORED_CLAIM_PACKAGE_SCHEMA_ID} carries an anchors section`,
|
|
260
|
+
path: ["anchors"],
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
if (claim.claimSchema === ANCHORED_CLAIM_PACKAGE_SCHEMA_ID) {
|
|
264
|
+
if (claim.anchors === undefined) {
|
|
265
|
+
// Present, not necessarily non-empty. An empty section is legal on exactly one bundle: one
|
|
266
|
+
// whose sealed Run declared anchoring intent that no carried anchor satisfies (§7.3). That
|
|
267
|
+
// bundle must still be on the anchored closure so the check that reports the absence runs,
|
|
268
|
+
// and its claim must therefore still state the closure's seven checks. What no claim may do
|
|
269
|
+
// is omit the section while claiming the closure, or carry one while claiming an earlier one.
|
|
270
|
+
ctx.addIssue({
|
|
271
|
+
code: "custom",
|
|
272
|
+
message: `${ANCHORED_CLAIM_PACKAGE_SCHEMA_ID} must carry its anchors section, even when the section is empty`,
|
|
273
|
+
path: ["anchors"],
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
if (claim.qualification !== undefined) {
|
|
277
|
+
ctx.addIssue({
|
|
278
|
+
code: "custom",
|
|
279
|
+
message: "the anchored binary-qualification closure is a later allocation, not claim-package/4",
|
|
280
|
+
path: ["qualification"],
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
if (claim.headline === undefined
|
|
284
|
+
&& claim.comparison === undefined
|
|
285
|
+
&& claim.pairwiseDisagreement === undefined
|
|
286
|
+
&& claim.pairedMajorityDelta === undefined) {
|
|
287
|
+
ctx.addIssue({
|
|
288
|
+
code: "custom",
|
|
289
|
+
message: "claim package must carry headline (wilson@1), comparison (paired-delta@1), "
|
|
290
|
+
+ "pairwiseDisagreement (pairwise-disagreement@1), or pairedMajorityDelta (paired-majority-delta@1)",
|
|
291
|
+
path: ["headline"],
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
if (claim.verification.command !== PUBLIC_BUNDLE_V6_VERIFICATION_COMMAND
|
|
295
|
+
|| claim.verification.compatibleCommand !== PUBLIC_BUNDLE_V6_COMPATIBLE_VERIFICATION_COMMAND) {
|
|
296
|
+
ctx.addIssue({ code: "custom", message: "anchored claim package must pin verifier 0.1.0/@0.1", path: ["verification"] });
|
|
297
|
+
}
|
|
298
|
+
if (claim.verification.checks.length !== READER_ANCHORED_VERIFICATION_CHECKS.length
|
|
299
|
+
|| claim.verification.checks.some((check, index) => check !== READER_ANCHORED_VERIFICATION_CHECKS[index])) {
|
|
300
|
+
ctx.addIssue({
|
|
301
|
+
code: "custom",
|
|
302
|
+
message: "anchored claim package must retain the six frozen verification checks plus integrity-anchors, in order",
|
|
303
|
+
path: ["verification", "checks"],
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
if (claim.claimSchema === CLAIM_PACKAGE_SCHEMA_ID) {
|
|
309
|
+
if (claim.headline === undefined
|
|
310
|
+
&& claim.comparison === undefined
|
|
311
|
+
&& claim.pairwiseDisagreement === undefined
|
|
312
|
+
&& claim.pairedMajorityDelta === undefined) {
|
|
313
|
+
ctx.addIssue({
|
|
314
|
+
code: "custom",
|
|
315
|
+
message: "claim package must carry headline (wilson@1), comparison (paired-delta@1), "
|
|
316
|
+
+ "pairwiseDisagreement (pairwise-disagreement@1), or pairedMajorityDelta (paired-majority-delta@1)",
|
|
317
|
+
path: ["headline"],
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
if (claim.method.id !== BENCHMARKING_METHOD_IDS.binaryInstrument
|
|
323
|
+
|| claim.method.version !== BENCHMARKING_METHOD_VERSION
|
|
324
|
+
|| claim.qualification === undefined
|
|
325
|
+
|| claim.headline !== undefined
|
|
326
|
+
|| claim.comparison !== undefined
|
|
327
|
+
|| claim.pairwiseDisagreement !== undefined
|
|
328
|
+
|| claim.pairedMajorityDelta !== undefined) {
|
|
329
|
+
ctx.addIssue({
|
|
330
|
+
code: "custom",
|
|
331
|
+
message: "binary claim package must carry only the exact binary-instrument qualification projection",
|
|
332
|
+
path: ["qualification"],
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
if (claim.qualification === undefined)
|
|
336
|
+
return;
|
|
337
|
+
const qualificationValidation = validateBinaryInstrumentQualificationProjection(claim.qualification);
|
|
338
|
+
if (!qualificationValidation.ok) {
|
|
339
|
+
ctx.addIssue({
|
|
340
|
+
code: "custom",
|
|
341
|
+
message: `binary qualification is outside the exact F6 schema: ${qualificationValidation.issues.join("; ")}`,
|
|
342
|
+
path: ["qualification"],
|
|
343
|
+
});
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
const reportResults = claim.results;
|
|
347
|
+
const subject = reportResults?.perSubject;
|
|
348
|
+
let exactResult = false;
|
|
349
|
+
const exactWrapper = (typeof reportResults === "object"
|
|
350
|
+
&& reportResults !== null
|
|
351
|
+
&& Object.keys(reportResults).join("\0") === "perSubject"
|
|
352
|
+
&& subject?.length === 1
|
|
353
|
+
&& typeof subject[0] === "object"
|
|
354
|
+
&& subject[0] !== null
|
|
355
|
+
&& Object.keys(subject[0]).sort().join("\0") === "results\0subjectSha256"
|
|
356
|
+
&& subject[0].subjectSha256 === claim.records.matrixSha256);
|
|
357
|
+
if (exactWrapper) {
|
|
358
|
+
try {
|
|
359
|
+
exactResult = Buffer.from(canonicalJsonBytes(subject[0].results)).equals(Buffer.from(canonicalJsonBytes(claim.qualification)));
|
|
360
|
+
}
|
|
361
|
+
catch {
|
|
362
|
+
exactResult = false;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
if (!exactResult) {
|
|
366
|
+
ctx.addIssue({ code: "custom", message: "qualification must exactly equal the Report's one F6 per-subject result", path: ["qualification"] });
|
|
367
|
+
}
|
|
368
|
+
const prompted = claim.method.parameters["promptedScreeningProfile"] === PROMPTED_SCREENING_PROFILE;
|
|
369
|
+
const command = prompted
|
|
370
|
+
? PROMPTED_BINARY_QUALIFICATION_VERIFICATION_COMMAND
|
|
371
|
+
: BINARY_QUALIFICATION_VERIFICATION_COMMAND;
|
|
372
|
+
const compatibleCommand = prompted
|
|
373
|
+
? PROMPTED_BINARY_QUALIFICATION_COMPATIBLE_VERIFICATION_COMMAND
|
|
374
|
+
: BINARY_QUALIFICATION_COMPATIBLE_VERIFICATION_COMMAND;
|
|
375
|
+
if (claim.verification.command !== command || claim.verification.compatibleCommand !== compatibleCommand) {
|
|
376
|
+
ctx.addIssue({ code: "custom", message: `binary claim package must pin verifier ${prompted ? "0.2.0/@0.2" : "0.1.0/@0.1"}`, path: ["verification"] });
|
|
377
|
+
}
|
|
378
|
+
if (claim.verification.checks.length !== READER_VERIFICATION_CHECKS.length
|
|
379
|
+
|| claim.verification.checks.some((check, index) => check !== READER_VERIFICATION_CHECKS[index])) {
|
|
380
|
+
ctx.addIssue({ code: "custom", message: "binary claim package must retain the six frozen verification checks in order", path: ["verification", "checks"] });
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
/** claim-package/1 predates `qualification`; its original non-strict object grammar accepted and
|
|
384
|
+
* stripped that unknown key. Remove it before the additive v1/v2 wire schema sees the document so
|
|
385
|
+
* v1 retains that exact behavior while claim-package/2 validates and preserves the field. */
|
|
386
|
+
function exactKeys(value, allowed) {
|
|
387
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
388
|
+
&& Object.keys(value).every((key) => allowed.includes(key));
|
|
389
|
+
}
|
|
390
|
+
function exactBinaryClaimControls(input) {
|
|
391
|
+
const scope = input.scope;
|
|
392
|
+
const records = input.records;
|
|
393
|
+
const method = input.method;
|
|
394
|
+
const assurance = input.assurance;
|
|
395
|
+
const disclosures = input.disclosures;
|
|
396
|
+
const verification = input.verification;
|
|
397
|
+
// `anchors`, `pairwiseDisagreement`, and `pairedMajorityDelta` are admitted here so a binary
|
|
398
|
+
// claim that grew one of them reaches the schema-level refine and is refused BY NAME (all three
|
|
399
|
+
// are checked there, alongside `headline`/`comparison`), rather than collapsing into the generic
|
|
400
|
+
// control-shape failure. Neither judge field is ever set on an actual binary-instrument claim
|
|
401
|
+
// (`methodProjection`'s dispatch is exclusive), so admitting them here is defense in depth, not
|
|
402
|
+
// a widening any real claim exercises.
|
|
403
|
+
return exactKeys(input, ["claimSchema", "scope", "records", "method", "results", "completeness", "attrition", "conflicted", "assurance", "disclosures", "limitations", "venueHonesty", "verification", "rehearsal", "qualification", "anchors", "pairwiseDisagreement", "pairedMajorityDelta"])
|
|
404
|
+
&& exactKeys(scope, ["draftId", "benchmarkSha256", "taskCount", "arms", "replicates", "venue"])
|
|
405
|
+
&& Array.isArray(scope.arms)
|
|
406
|
+
&& (scope.arms).every((arm) => exactKeys(arm, ["armId", "pinning"]))
|
|
407
|
+
&& exactKeys(records, ["benchmarkSha256", "runSha256", "matrixSha256", "reportSha256", "reportEnvelopeSha256"])
|
|
408
|
+
&& exactKeys(method, ["id", "version", "parameters", "preregistered"])
|
|
409
|
+
&& exactKeys(assurance, ["preset", "resolved", "disclosure"])
|
|
410
|
+
&& exactKeys(assurance?.resolved, ["independence", "minVerdicts", "distinctEvaluator", "verdictRule"])
|
|
411
|
+
&& exactKeys(disclosures, ["perSubject", "integrityTierCounts", "pinningUnverifiableCounts"])
|
|
412
|
+
&& exactKeys(disclosures?.integrityTierCounts, ["re-derivable", "attested-only"])
|
|
413
|
+
&& exactKeys(disclosures?.pinningUnverifiableCounts, ["harness", "model", "loadout", "isolation"])
|
|
414
|
+
&& exactKeys(input.conflicted, ["count", "cellKeys"])
|
|
415
|
+
&& exactKeys(verification, ["command", "compatibleCommand", "checks", "trustRoot"])
|
|
416
|
+
&& (input.rehearsal === undefined || exactKeys(input.rehearsal, ["previewCount", "timestamps"]));
|
|
417
|
+
}
|
|
418
|
+
export const ClaimPackageSchema = z.preprocess((input) => {
|
|
419
|
+
if (typeof input === "object" && input !== null && !Array.isArray(input)
|
|
420
|
+
&& input.claimSchema === BINARY_QUALIFICATION_CLAIM_PACKAGE_SCHEMA_ID
|
|
421
|
+
&& !exactBinaryClaimControls(input)) {
|
|
422
|
+
return { claimSchema: "invalid-binary-claim-control-shape" };
|
|
423
|
+
}
|
|
424
|
+
if (typeof input === "object" && input !== null && !Array.isArray(input)
|
|
425
|
+
&& input.claimSchema === CLAIM_PACKAGE_SCHEMA_ID) {
|
|
426
|
+
const { qualification: _legacyUnknown, ...legacy } = input;
|
|
427
|
+
return legacy;
|
|
428
|
+
}
|
|
429
|
+
return input;
|
|
430
|
+
}, ClaimPackageWireSchema);
|
|
431
|
+
/** Every method this product wires publishes its single Matrix subject's results at this fixed
|
|
432
|
+
* path — the wrapper itself is method-agnostic; only what's inside `results` differs by method. */
|
|
433
|
+
function singleSubjectResults(reportRecord) {
|
|
434
|
+
const results = reportRecord.results;
|
|
435
|
+
const perSubject = results?.perSubject;
|
|
436
|
+
if (!Array.isArray(perSubject) || perSubject.length !== 1) {
|
|
437
|
+
throw new Error("claim package: expected exactly one Report subject (this product's single-Matrix Report shape)");
|
|
438
|
+
}
|
|
439
|
+
return perSubject[0]?.results;
|
|
440
|
+
}
|
|
441
|
+
/** wilson@1's per-arm headline projection. A narrow, local shape check — deliberately not a
|
|
442
|
+
* general Method-results parser; a mismatch here means the sealed Report was not produced by
|
|
443
|
+
* wilson@1. */
|
|
444
|
+
function wilsonProjection(subjectResults) {
|
|
445
|
+
const shape = subjectResults;
|
|
446
|
+
if (typeof shape?.arms !== "object" || shape.arms === null
|
|
447
|
+
|| typeof shape.conflicted?.count !== "number"
|
|
448
|
+
|| !Array.isArray(shape.conflicted.cellKeys)) {
|
|
449
|
+
throw new Error("claim package: Report results do not carry wilson@1's arms/conflicted shape");
|
|
450
|
+
}
|
|
451
|
+
return {
|
|
452
|
+
headline: shape.arms,
|
|
453
|
+
conflicted: { count: shape.conflicted.count, cellKeys: shape.conflicted.cellKeys },
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
/** paired-delta@1's comparison projection (P4b Task 5), sibling to `wilsonProjection`. Carried
|
|
457
|
+
* verbatim from the method's own compute() output (`benchmarking/aggregate/src/registry.ts`'s
|
|
458
|
+
* pairedDeltaMethod) — never recomputed. Same narrow, local-shape-check posture as
|
|
459
|
+
* `wilsonProjection`: a mismatch here means the sealed Report was not produced by paired-delta@1. */
|
|
460
|
+
function comparisonProjection(subjectResults) {
|
|
461
|
+
const shape = subjectResults;
|
|
462
|
+
if (typeof shape?.pairs !== "number"
|
|
463
|
+
|| !(typeof shape.delta === "string" || shape.delta === null)
|
|
464
|
+
|| typeof shape.interval !== "object"
|
|
465
|
+
|| !Array.isArray(shape.reasons)
|
|
466
|
+
|| !Array.isArray(shape.pairing?.taskDigests)
|
|
467
|
+
|| typeof shape.clustering?.basis !== "string"
|
|
468
|
+
|| typeof shape.clustering.clusters !== "number"
|
|
469
|
+
|| typeof shape.excluded?.count !== "number"
|
|
470
|
+
|| !Array.isArray(shape.excluded.cellKeys)
|
|
471
|
+
|| typeof shape.conflicted?.count !== "number"
|
|
472
|
+
|| !Array.isArray(shape.conflicted.cellKeys)
|
|
473
|
+
|| shape.bootstrap === undefined) {
|
|
474
|
+
throw new Error("claim package: Report results do not carry paired-delta@1's comparison shape");
|
|
475
|
+
}
|
|
476
|
+
const conflicted = { count: shape.conflicted.count, cellKeys: shape.conflicted.cellKeys };
|
|
477
|
+
return {
|
|
478
|
+
comparison: {
|
|
479
|
+
pairs: shape.pairs,
|
|
480
|
+
delta: shape.delta,
|
|
481
|
+
interval: shape.interval,
|
|
482
|
+
reasons: [...shape.reasons],
|
|
483
|
+
pairing: { taskDigests: [...shape.pairing.taskDigests] },
|
|
484
|
+
clustering: { basis: shape.clustering.basis, clusters: shape.clustering.clusters },
|
|
485
|
+
excluded: { count: shape.excluded.count, cellKeys: [...shape.excluded.cellKeys] },
|
|
486
|
+
conflicted: { count: conflicted.count, cellKeys: [...conflicted.cellKeys] },
|
|
487
|
+
bootstrap: shape.bootstrap,
|
|
488
|
+
},
|
|
489
|
+
conflicted,
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
function binaryQualificationProjection(subjectResults) {
|
|
493
|
+
const shape = subjectResults;
|
|
494
|
+
if (typeof shape?.conflicted?.count !== "number" || !Array.isArray(shape.conflicted.cellKeys)) {
|
|
495
|
+
throw new Error("claim package: Report results do not carry binary-instrument@1's conflicted shape");
|
|
496
|
+
}
|
|
497
|
+
return {
|
|
498
|
+
qualification: subjectResults,
|
|
499
|
+
conflicted: { count: shape.conflicted.count, cellKeys: shape.conflicted.cellKeys },
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
/** `pairwise-disagreement@1`'s panel projection (packet #2837, spec §7.1), sibling to
|
|
503
|
+
* `comparisonProjection`. Carried verbatim from the method's own compute() output
|
|
504
|
+
* (`aggregate/src/pairwise-disagreement-method.ts`'s `computePairwiseDisagreement`) — never
|
|
505
|
+
* recomputed. Same narrow, local-shape-check posture as `wilsonProjection`/`comparisonProjection`:
|
|
506
|
+
* a mismatch here means the sealed Report was not produced by pairwise-disagreement@1. This
|
|
507
|
+
* method's own output already carries `pairs` and top-level `conflicted` and nothing else, so the
|
|
508
|
+
* whole object is carried through rather than field-by-field reassembled the way
|
|
509
|
+
* `comparisonProjection` does for its richer sibling shape. Mirrors the identically-named function
|
|
510
|
+
* in core's `report/claim.ts`. */
|
|
511
|
+
function pairwiseDisagreementProjection(subjectResults) {
|
|
512
|
+
const shape = subjectResults;
|
|
513
|
+
if (!Array.isArray(shape?.pairs)
|
|
514
|
+
|| typeof shape.conflicted?.count !== "number"
|
|
515
|
+
|| !Array.isArray(shape.conflicted.cellKeys)) {
|
|
516
|
+
throw new Error("claim package: Report results do not carry pairwise-disagreement@1's pairs/conflicted shape");
|
|
517
|
+
}
|
|
518
|
+
const conflicted = { count: shape.conflicted.count, cellKeys: shape.conflicted.cellKeys };
|
|
519
|
+
return {
|
|
520
|
+
pairwiseDisagreement: {
|
|
521
|
+
pairs: shape.pairs,
|
|
522
|
+
conflicted: { count: conflicted.count, cellKeys: [...conflicted.cellKeys] },
|
|
523
|
+
},
|
|
524
|
+
conflicted,
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
/** `paired-majority-delta@1`'s evidence-contrast projection (packet #2837, spec §7.2a), sibling to
|
|
528
|
+
* `comparisonProjection`/`pairwiseDisagreementProjection`. Carried verbatim from the method's own
|
|
529
|
+
* compute() output (`aggregate/src/paired-majority-delta-method.ts`'s
|
|
530
|
+
* `computePairedMajorityDelta`) — never recomputed. Same narrow, local-shape-check posture as its
|
|
531
|
+
* siblings. Mirrors the identically-named function in core's `report/claim.ts`. */
|
|
532
|
+
function pairedMajorityDeltaProjection(subjectResults) {
|
|
533
|
+
const shape = subjectResults;
|
|
534
|
+
if (typeof shape?.baseline !== "string"
|
|
535
|
+
|| typeof shape.candidate !== "string"
|
|
536
|
+
|| typeof shape.n !== "number"
|
|
537
|
+
|| !(typeof shape.delta === "string" || shape.delta === null)
|
|
538
|
+
|| typeof shape.interval !== "object"
|
|
539
|
+
|| !Array.isArray(shape.reasons)
|
|
540
|
+
|| typeof shape.clusters?.count !== "number"
|
|
541
|
+
|| !Array.isArray(shape.byCandidateClass)
|
|
542
|
+
|| !Array.isArray(shape.byStratum)
|
|
543
|
+
|| !Array.isArray(shape.exclusions)
|
|
544
|
+
|| typeof shape.conflicted?.count !== "number"
|
|
545
|
+
|| !Array.isArray(shape.conflicted.cellKeys)) {
|
|
546
|
+
throw new Error("claim package: Report results do not carry paired-majority-delta@1's baseline/candidate/delta shape");
|
|
547
|
+
}
|
|
548
|
+
const conflicted = { count: shape.conflicted.count, cellKeys: shape.conflicted.cellKeys };
|
|
549
|
+
return {
|
|
550
|
+
pairedMajorityDelta: {
|
|
551
|
+
baseline: shape.baseline,
|
|
552
|
+
candidate: shape.candidate,
|
|
553
|
+
n: shape.n,
|
|
554
|
+
delta: shape.delta,
|
|
555
|
+
interval: shape.interval,
|
|
556
|
+
reasons: [...shape.reasons],
|
|
557
|
+
// `manifest` is spread in only when present (M4): writing `manifest: undefined` puts an
|
|
558
|
+
// explicit undefined-valued key into an object that is canonical-JSON encoded on both sides
|
|
559
|
+
// of the mirror, and the two copies must agree byte-for-byte. An absent manifest stays
|
|
560
|
+
// absent rather than becoming a key with no value.
|
|
561
|
+
clusters: {
|
|
562
|
+
count: shape.clusters.count,
|
|
563
|
+
...(shape.clusters.manifest === undefined ? {} : { manifest: shape.clusters.manifest }),
|
|
564
|
+
},
|
|
565
|
+
byCandidateClass: shape.byCandidateClass,
|
|
566
|
+
byStratum: shape.byStratum,
|
|
567
|
+
exclusions: shape.exclusions,
|
|
568
|
+
conflicted: { count: conflicted.count, cellKeys: [...conflicted.cellKeys] },
|
|
569
|
+
},
|
|
570
|
+
conflicted,
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
/** Dispatches on the produced Report's method (P4b Task 5) — the claim package's headline/
|
|
574
|
+
* comparison projection is no longer a mandatory wilson@1 gate. Any method this product has not
|
|
575
|
+
* wired a projection for throws rather than silently building an incomplete claim. */
|
|
576
|
+
function methodProjection(reportRecord) {
|
|
577
|
+
const subjectResults = singleSubjectResults(reportRecord);
|
|
578
|
+
switch (reportRecord.method.id) {
|
|
579
|
+
case BENCHMARKING_METHOD_IDS.wilson:
|
|
580
|
+
return wilsonProjection(subjectResults);
|
|
581
|
+
case BENCHMARKING_METHOD_IDS.pairedDelta:
|
|
582
|
+
return comparisonProjection(subjectResults);
|
|
583
|
+
case BENCHMARKING_METHOD_IDS.binaryInstrument:
|
|
584
|
+
if (reportRecord.method.version !== BENCHMARKING_METHOD_VERSION) {
|
|
585
|
+
throw new Error(`claim package: binary-instrument version "${reportRecord.method.version}" is not supported`);
|
|
586
|
+
}
|
|
587
|
+
return binaryQualificationProjection(subjectResults);
|
|
588
|
+
case BENCHMARKING_METHOD_IDS.pairwiseDisagreement:
|
|
589
|
+
if (reportRecord.method.version !== BENCHMARKING_METHOD_VERSION) {
|
|
590
|
+
throw new Error(`claim package: pairwise-disagreement version "${reportRecord.method.version}" is not supported`);
|
|
591
|
+
}
|
|
592
|
+
return pairwiseDisagreementProjection(subjectResults);
|
|
593
|
+
case BENCHMARKING_METHOD_IDS.pairedMajorityDelta:
|
|
594
|
+
if (reportRecord.method.version !== BENCHMARKING_METHOD_VERSION) {
|
|
595
|
+
throw new Error(`claim package: paired-majority-delta version "${reportRecord.method.version}" is not supported`);
|
|
596
|
+
}
|
|
597
|
+
return pairedMajorityDeltaProjection(subjectResults);
|
|
598
|
+
default:
|
|
599
|
+
throw new Error(`claim package: method "${reportRecord.method.id}" has no claim-package projection`);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
function summedIntegrityTierCounts(perSubject) {
|
|
603
|
+
const counts = { "re-derivable": 0, "attested-only": 0 };
|
|
604
|
+
for (const subject of perSubject) {
|
|
605
|
+
counts["re-derivable"] += subject.integrityTiers["re-derivable"];
|
|
606
|
+
counts["attested-only"] += subject.integrityTiers["attested-only"];
|
|
607
|
+
}
|
|
608
|
+
return counts;
|
|
609
|
+
}
|
|
610
|
+
const PINNING_AXES = ["harness", "model", "loadout", "isolation"];
|
|
611
|
+
function summedPinningUnverifiableCounts(perSubject) {
|
|
612
|
+
const counts = { harness: 0, model: 0, loadout: 0, isolation: 0 };
|
|
613
|
+
for (const subject of perSubject) {
|
|
614
|
+
for (const axis of PINNING_AXES)
|
|
615
|
+
counts[axis] += subject.pinning[axis].unverifiable;
|
|
616
|
+
}
|
|
617
|
+
return counts;
|
|
618
|
+
}
|
|
619
|
+
export const CLAIM_VERIFICATION_CHECKS = READER_VERIFICATION_CHECKS;
|
|
620
|
+
/** The anchored closure's list: the six frozen checks plus `integrity-anchors` (§8). */
|
|
621
|
+
export const ANCHORED_CLAIM_VERIFICATION_CHECKS = READER_ANCHORED_VERIFICATION_CHECKS;
|
|
622
|
+
export const PUBLIC_BUNDLE_VERIFICATION_COMMAND = READER_VERIFICATION_COMMAND;
|
|
623
|
+
/** The unconditional trust-root sentence, re-exported unchanged. It is DEFINED once beside its
|
|
624
|
+
* anchored replacement in `anchor-claims.ts`: one sentence written out twice, in two mirrored
|
|
625
|
+
* files, is exactly the drift the conditional copy cannot afford. */
|
|
626
|
+
export { SELF_RUN_TRUST_ROOT };
|
|
627
|
+
/** Builds the claim package document (spec §8.2) from explicit, already-sealed record data. Pure
|
|
628
|
+
* — no filesystem access, no recomputation of anything the cited records already settled. */
|
|
629
|
+
export function buildClaimPackage(input) {
|
|
630
|
+
const { reportRecord } = input;
|
|
631
|
+
// BP-21: the assurance block may only state primitives the sealed Run record itself carries.
|
|
632
|
+
// `../run/compile.ts` seals the first three into `policy` at lock time and `verdictRule` into
|
|
633
|
+
// the analysisPlan entry matching the produced Report's method (BP-13 F2, P4b Task 5) — all
|
|
634
|
+
// four are checked against the sealed Run. The sealed plan may carry more than one entry (one
|
|
635
|
+
// per candidate method), so the entry is SELECTED by method match, not read at a fixed index.
|
|
636
|
+
const { resolved } = input.assurance;
|
|
637
|
+
const policy = input.runRecord.policy;
|
|
638
|
+
const matchingPlanEntry = input.runRecord.analysisPlan?.find((entry) => entry.method === reportRecord.method.id && entry.version === reportRecord.method.version);
|
|
639
|
+
const analysisParameters = matchingPlanEntry?.parameters;
|
|
640
|
+
if (policy.independence !== resolved.independence
|
|
641
|
+
|| policy.evaluation.minVerdicts !== resolved.minVerdicts
|
|
642
|
+
|| policy.evaluation.distinctEvaluator !== resolved.distinctEvaluator
|
|
643
|
+
|| analysisParameters?.verdictRule !== resolved.verdictRule) {
|
|
644
|
+
throw new Error("claim package: the stated assurance primitives do not match what the sealed Run record carries"
|
|
645
|
+
+ " — a claim stating primitives the sealed Run does not carry would be dishonest");
|
|
646
|
+
}
|
|
647
|
+
const projection = methodProjection(reportRecord);
|
|
648
|
+
// Prompted screening is an authenticated Run-level admission fact. Every report/claim emitted
|
|
649
|
+
// from that Run uses the v2 reader, including its companion method claims; the binary claim's
|
|
650
|
+
// own schema separately binds the same exact profile in its method parameters.
|
|
651
|
+
const promptedScreening = input.runRecord.analysisPlan?.some((entry) => entry.parameters["promptedScreeningProfile"] === PROMPTED_SCREENING_PROFILE) === true;
|
|
652
|
+
const disclosuresPerSubject = (reportRecord.disclosures?.perSubject ?? []);
|
|
653
|
+
const taskCount = new Set(input.matrixRecord.cells.map((cell) => cell.taskDigest)).size;
|
|
654
|
+
// anchor-evidence §7.4: the anchored closure is entered by the CALLER, by supplying the section
|
|
655
|
+
// at all -- a run that carries an anchor, or one whose sealed Run declared intent whose absence
|
|
656
|
+
// the bundle must still report (§7.3). A caller that supplies nothing produces exactly the bytes
|
|
657
|
+
// this builder produced before the feature existed.
|
|
658
|
+
const anchors = input.anchors ?? [];
|
|
659
|
+
const anchored = input.anchors !== undefined;
|
|
660
|
+
if (anchored && projection.qualification !== undefined) {
|
|
661
|
+
throw new Error("claim package: the anchored binary-qualification closure is a later allocation"
|
|
662
|
+
+ " — claim-package/4 has no qualification projection");
|
|
663
|
+
}
|
|
664
|
+
return {
|
|
665
|
+
claimSchema: anchored
|
|
666
|
+
? ANCHORED_CLAIM_PACKAGE_SCHEMA_ID
|
|
667
|
+
: projection.qualification === undefined
|
|
668
|
+
? CLAIM_PACKAGE_SCHEMA_ID
|
|
669
|
+
: BINARY_QUALIFICATION_CLAIM_PACKAGE_SCHEMA_ID,
|
|
670
|
+
scope: {
|
|
671
|
+
draftId: input.draftId,
|
|
672
|
+
benchmarkSha256: input.benchmarkSha256,
|
|
673
|
+
taskCount,
|
|
674
|
+
arms: input.runRecord.arms.map((arm) => ({ armId: arm.armId, pinning: arm.pinning })),
|
|
675
|
+
replicates: input.runRecord.replicates,
|
|
676
|
+
venue: "self-run",
|
|
677
|
+
},
|
|
678
|
+
records: {
|
|
679
|
+
benchmarkSha256: input.benchmarkSha256,
|
|
680
|
+
runSha256: input.runSha256,
|
|
681
|
+
matrixSha256: input.matrixSha256,
|
|
682
|
+
reportSha256: input.reportSha256,
|
|
683
|
+
reportEnvelopeSha256: input.reportEnvelopeSha256,
|
|
684
|
+
},
|
|
685
|
+
method: {
|
|
686
|
+
id: reportRecord.method.id,
|
|
687
|
+
version: reportRecord.method.version,
|
|
688
|
+
parameters: reportRecord.method.parameters,
|
|
689
|
+
preregistered: reportRecord.preregistered ?? false,
|
|
690
|
+
},
|
|
691
|
+
results: reportRecord.results,
|
|
692
|
+
...(projection.headline !== undefined ? { headline: projection.headline } : {}),
|
|
693
|
+
...(projection.qualification !== undefined ? { qualification: projection.qualification } : {}),
|
|
694
|
+
completeness: input.matrixRecord.completeness,
|
|
695
|
+
attrition: input.matrixRecord.attrition,
|
|
696
|
+
conflicted: { count: projection.conflicted.count, cellKeys: [...projection.conflicted.cellKeys] },
|
|
697
|
+
assurance: {
|
|
698
|
+
preset: input.assurance.preset,
|
|
699
|
+
resolved: { ...resolved },
|
|
700
|
+
disclosure: ASSURANCE_DISCLOSURE,
|
|
701
|
+
},
|
|
702
|
+
disclosures: {
|
|
703
|
+
perSubject: (reportRecord.disclosures?.perSubject ?? []),
|
|
704
|
+
integrityTierCounts: summedIntegrityTierCounts(disclosuresPerSubject),
|
|
705
|
+
pinningUnverifiableCounts: summedPinningUnverifiableCounts(disclosuresPerSubject),
|
|
706
|
+
},
|
|
707
|
+
limitations: [...(reportRecord.limitations ?? [])],
|
|
708
|
+
venueHonesty: input.venueHonesty,
|
|
709
|
+
verification: {
|
|
710
|
+
command: anchored
|
|
711
|
+
? PUBLIC_BUNDLE_V6_VERIFICATION_COMMAND
|
|
712
|
+
: promptedScreening
|
|
713
|
+
? PROMPTED_BINARY_QUALIFICATION_VERIFICATION_COMMAND
|
|
714
|
+
: projection.qualification === undefined
|
|
715
|
+
? PUBLIC_BUNDLE_VERIFICATION_COMMAND
|
|
716
|
+
: BINARY_QUALIFICATION_VERIFICATION_COMMAND,
|
|
717
|
+
compatibleCommand: anchored
|
|
718
|
+
? PUBLIC_BUNDLE_V6_COMPATIBLE_VERIFICATION_COMMAND
|
|
719
|
+
: promptedScreening
|
|
720
|
+
? PROMPTED_BINARY_QUALIFICATION_COMPATIBLE_VERIFICATION_COMMAND
|
|
721
|
+
: projection.qualification === undefined
|
|
722
|
+
? PUBLIC_BUNDLE_COMPATIBLE_VERIFICATION_COMMAND
|
|
723
|
+
: BINARY_QUALIFICATION_COMPATIBLE_VERIFICATION_COMMAND,
|
|
724
|
+
checks: anchored ? [...ANCHORED_CLAIM_VERIFICATION_CHECKS] : [...CLAIM_VERIFICATION_CHECKS],
|
|
725
|
+
// §9.2: the trust-root sentence is replaced only by a governing lock anchor. A bundle whose
|
|
726
|
+
// only anchors are pending, or cover the Matrix alone, keeps the unconditional sentence.
|
|
727
|
+
trustRoot: anchoredTrustRoot(anchors),
|
|
728
|
+
},
|
|
729
|
+
...(anchored ? { anchors: anchors.map((anchor) => ({ ...anchor })) } : {}),
|
|
730
|
+
...(input.previewDisclosure !== undefined
|
|
731
|
+
? {
|
|
732
|
+
rehearsal: {
|
|
733
|
+
previewCount: input.previewDisclosure.previewCount,
|
|
734
|
+
timestamps: [...input.previewDisclosure.timestamps],
|
|
735
|
+
},
|
|
736
|
+
}
|
|
737
|
+
: {}),
|
|
738
|
+
...(projection.comparison !== undefined ? { comparison: projection.comparison } : {}),
|
|
739
|
+
...(projection.pairwiseDisagreement !== undefined ? { pairwiseDisagreement: projection.pairwiseDisagreement } : {}),
|
|
740
|
+
...(projection.pairedMajorityDelta !== undefined ? { pairedMajorityDelta: projection.pairedMajorityDelta } : {}),
|
|
741
|
+
};
|
|
742
|
+
}
|