@colophon-claims/verify 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -9
- package/dist/admission/contracts.d.ts +483 -0
- package/dist/admission/contracts.js +285 -0
- package/dist/admission/index.d.ts +2 -0
- package/dist/admission/index.js +2 -0
- package/dist/admission/prompted-commitment.d.ts +19 -0
- package/dist/admission/prompted-commitment.js +52 -0
- package/dist/admission/prompted-selection.d.ts +24 -0
- package/dist/admission/prompted-selection.js +85 -0
- package/dist/admission/verification.d.ts +17 -3
- package/dist/admission/verification.js +230 -49
- package/dist/assets.d.ts +21 -1
- package/dist/assets.js +60 -3
- package/dist/binding/beacon-binding.d.ts +230 -0
- package/dist/binding/beacon-binding.js +325 -0
- package/dist/binding/report-face.d.ts +45 -0
- package/dist/binding/report-face.js +153 -0
- package/dist/cli.js +74 -12
- package/dist/index.d.ts +14 -3
- package/dist/index.js +16 -3
- package/dist/manifest.d.ts +30 -4
- package/dist/manifest.js +30 -0
- package/dist/materialize.d.ts +7 -0
- package/dist/materialize.js +7 -0
- package/dist/outcome.d.ts +31 -0
- package/dist/outcome.js +45 -0
- package/dist/profile/binary-qualification.js +6 -0
- package/dist/profile/claim-consistency.d.ts +8 -1
- package/dist/profile/claim-consistency.js +4 -4
- package/dist/profile/claim.d.ts +203 -1
- package/dist/profile/claim.js +192 -43
- package/dist/profile/disclosure.d.ts +273 -0
- package/dist/profile/disclosure.js +240 -0
- package/dist/profile/pinning-evidence.d.ts +1 -1
- package/dist/profile/run-results.d.ts +8 -2
- package/dist/profile/run-results.js +9 -3
- package/dist/profile/task-selection.d.ts +69 -0
- package/dist/profile/task-selection.js +140 -0
- package/dist/reader-instructions.d.ts +38 -1
- package/dist/reader-instructions.js +42 -2
- package/dist/schema.d.ts +13 -2
- package/dist/schema.js +39 -8
- package/dist/signers.d.ts +48 -0
- package/dist/signers.js +77 -0
- package/dist/verify.d.ts +20 -6
- package/dist/verify.js +156 -31
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +14 -14
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The disclosed closure's projection and check (disclosure-specification-record design §6.4, §6.6,
|
|
3
|
+
* §7; issue #2839).
|
|
4
|
+
*
|
|
5
|
+
* This module is **single-sourced, not mirrored**, exactly as `anchor-claims.ts` is: `@colophon-
|
|
6
|
+
* claims/core` already depends on this package, so the producer and the standalone verifier call
|
|
7
|
+
* ONE `deriveDisclosureSpecification`. The claim-consistency byte-compare then compares one
|
|
8
|
+
* function's output over two byte sets rather than two implementations' guesses.
|
|
9
|
+
*
|
|
10
|
+
* The one posture that matters more than any mechanism here (design R4):
|
|
11
|
+
*
|
|
12
|
+
* **The verifier AUTHENTICATES measurements and CARRIES assertions.**
|
|
13
|
+
*
|
|
14
|
+
* A `measured-here` variable must cite records the bundle actually carries, under a role admissible
|
|
15
|
+
* for the citation. A `disclosed-by-publisher` variable is checked for internal well-formedness and
|
|
16
|
+
* for nothing else — no lookup, no fetch, no cross-check against the Matrix or the Report, no status
|
|
17
|
+
* upgrade or downgrade. An assertion that turns out to be false is a false assertion in a valid
|
|
18
|
+
* record; that is the correct outcome and the honest one, and a verifier that refused it would be
|
|
19
|
+
* claiming a power it does not have.
|
|
20
|
+
*
|
|
21
|
+
* What this check deliberately does NOT do (design §8, each one a thing a well-meaning implementer
|
|
22
|
+
* would add):
|
|
23
|
+
*
|
|
24
|
+
* - It does not reconcile against `Report.disclosures.perSubject[].pinning`. Those counts answer
|
|
25
|
+
* "how well did each executed axis pin"; this record answers "which variables were executed at
|
|
26
|
+
* all". Two surfaces restating one fact are two surfaces that can disagree.
|
|
27
|
+
* - It does not infer a status from the bundle. A bundle that plainly executed a judge model does
|
|
28
|
+
* not license marking `judge-model` as `measured-here` if the record says otherwise.
|
|
29
|
+
* - It does not fetch anything, ever. A verifier that reached the network would make its own result
|
|
30
|
+
* depend on when it ran.
|
|
31
|
+
* - It does not rank, score, compare, or aggregate. There is no disclosure-completeness score:
|
|
32
|
+
* counting statuses would create a number publishers optimize against, and a six-of-six record
|
|
33
|
+
* with six vague assertions would outscore a two-of-six record with two proofs.
|
|
34
|
+
*/
|
|
35
|
+
import { z } from "zod";
|
|
36
|
+
import { SIX_VARIABLE_DISCLOSURE_SPECIFICATION, type DisclosureEvidenceRole, type DisclosureVariableEntry, type DisclosureVariableKey, type DisclosureVariableStatus } from "@jinn-network/benchmarking-records";
|
|
37
|
+
import type { BundleV4EvidenceRole } from "../schema.js";
|
|
38
|
+
/** The evidence-catalog role a disclosure-specification record carries, and the ONLY one it may
|
|
39
|
+
* carry: a record bearing this token together with any second role refuses (§7 step 1). */
|
|
40
|
+
export declare const DISCLOSURE_SPECIFICATION_BUNDLE_ROLE: "disclosure-specification";
|
|
41
|
+
/**
|
|
42
|
+
* §6.4's binding profile: which of this carrier's evidence roles satisfy which of the standard's two
|
|
43
|
+
* portable disclosure roles. Jinn-side mapping, deliberately NOT part of the portable record — the
|
|
44
|
+
* mapping from the standard's roles onto a specific carrier's record roles is the carrier's business.
|
|
45
|
+
*
|
|
46
|
+
* v1 deliberately does not narrow the admissible set per variable. Which record species fixes
|
|
47
|
+
* `judge-model` depends on the judge profile packet P0 is freezing, and encoding a guess here would
|
|
48
|
+
* put this module in P0's lane. Reserved as a v2 tightening once P0's record vocabulary merges.
|
|
49
|
+
*/
|
|
50
|
+
export declare const DISCLOSURE_ROLE_BINDING: Readonly<Record<DisclosureEvidenceRole, readonly BundleV4EvidenceRole[]>>;
|
|
51
|
+
/** The claim package's `disclosure` section (§6.6). Every variable entry is its record entry
|
|
52
|
+
* VERBATIM: nothing is summarized, counted, ranked, or reworded (R5). */
|
|
53
|
+
export interface ClaimDisclosureSection {
|
|
54
|
+
readonly recordSha256: string;
|
|
55
|
+
/** The literal standard identifier, not any string: this section names the standard the record
|
|
56
|
+
* claims compliance with, and a section naming a different one is not this record's projection. */
|
|
57
|
+
readonly specification: typeof SIX_VARIABLE_DISCLOSURE_SPECIFICATION;
|
|
58
|
+
readonly subjectSha256: string;
|
|
59
|
+
readonly variables: Readonly<Record<DisclosureVariableKey, DisclosureVariableEntry>>;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The claim section's own grammar, single-sourced from the record's schema so the section cannot
|
|
63
|
+
* describe a variable entry the record could not have carried. It is a second line of defense
|
|
64
|
+
* rather than the primary one: `assertClaimConsistency`'s whole-claim byte-compare against the
|
|
65
|
+
* rebuilt claim is what actually proves the section is this record's projection.
|
|
66
|
+
*/
|
|
67
|
+
export declare const ClaimDisclosureSectionSchema: z.ZodObject<{
|
|
68
|
+
recordSha256: z.ZodString;
|
|
69
|
+
specification: z.ZodLiteral<"https://spec.jinn.network/disclosure/six-variable/v1">;
|
|
70
|
+
subjectSha256: z.ZodString;
|
|
71
|
+
variables: z.ZodObject<{
|
|
72
|
+
"ingestion-model": z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
73
|
+
status: z.ZodLiteral<"measured-here">;
|
|
74
|
+
statement: z.ZodString;
|
|
75
|
+
evidence: z.ZodArray<z.ZodObject<{
|
|
76
|
+
role: z.ZodEnum<{
|
|
77
|
+
"pinned-configuration": "pinned-configuration";
|
|
78
|
+
"execution-observation": "execution-observation";
|
|
79
|
+
}>;
|
|
80
|
+
digest: z.ZodObject<{
|
|
81
|
+
sha256: z.ZodString;
|
|
82
|
+
}, z.core.$strict>;
|
|
83
|
+
}, z.core.$strict>>;
|
|
84
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
85
|
+
status: z.ZodLiteral<"disclosed-by-publisher">;
|
|
86
|
+
statement: z.ZodString;
|
|
87
|
+
sources: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
88
|
+
uri: z.ZodString;
|
|
89
|
+
}, z.core.$strict>>>;
|
|
90
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
91
|
+
status: z.ZodLiteral<"undisclosed">;
|
|
92
|
+
reason: z.ZodEnum<{
|
|
93
|
+
"not-stated": "not-stated";
|
|
94
|
+
"stated-without-identifiers": "stated-without-identifiers";
|
|
95
|
+
"outside-this-experiment": "outside-this-experiment";
|
|
96
|
+
}>;
|
|
97
|
+
}, z.core.$strict>], "status">;
|
|
98
|
+
"retrieval-config": z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
99
|
+
status: z.ZodLiteral<"measured-here">;
|
|
100
|
+
statement: z.ZodString;
|
|
101
|
+
evidence: z.ZodArray<z.ZodObject<{
|
|
102
|
+
role: z.ZodEnum<{
|
|
103
|
+
"pinned-configuration": "pinned-configuration";
|
|
104
|
+
"execution-observation": "execution-observation";
|
|
105
|
+
}>;
|
|
106
|
+
digest: z.ZodObject<{
|
|
107
|
+
sha256: z.ZodString;
|
|
108
|
+
}, z.core.$strict>;
|
|
109
|
+
}, z.core.$strict>>;
|
|
110
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
111
|
+
status: z.ZodLiteral<"disclosed-by-publisher">;
|
|
112
|
+
statement: z.ZodString;
|
|
113
|
+
sources: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
114
|
+
uri: z.ZodString;
|
|
115
|
+
}, z.core.$strict>>>;
|
|
116
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
117
|
+
status: z.ZodLiteral<"undisclosed">;
|
|
118
|
+
reason: z.ZodEnum<{
|
|
119
|
+
"not-stated": "not-stated";
|
|
120
|
+
"stated-without-identifiers": "stated-without-identifiers";
|
|
121
|
+
"outside-this-experiment": "outside-this-experiment";
|
|
122
|
+
}>;
|
|
123
|
+
}, z.core.$strict>], "status">;
|
|
124
|
+
"answer-model": z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
125
|
+
status: z.ZodLiteral<"measured-here">;
|
|
126
|
+
statement: z.ZodString;
|
|
127
|
+
evidence: z.ZodArray<z.ZodObject<{
|
|
128
|
+
role: z.ZodEnum<{
|
|
129
|
+
"pinned-configuration": "pinned-configuration";
|
|
130
|
+
"execution-observation": "execution-observation";
|
|
131
|
+
}>;
|
|
132
|
+
digest: z.ZodObject<{
|
|
133
|
+
sha256: z.ZodString;
|
|
134
|
+
}, z.core.$strict>;
|
|
135
|
+
}, z.core.$strict>>;
|
|
136
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
137
|
+
status: z.ZodLiteral<"disclosed-by-publisher">;
|
|
138
|
+
statement: z.ZodString;
|
|
139
|
+
sources: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
140
|
+
uri: z.ZodString;
|
|
141
|
+
}, z.core.$strict>>>;
|
|
142
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
143
|
+
status: z.ZodLiteral<"undisclosed">;
|
|
144
|
+
reason: z.ZodEnum<{
|
|
145
|
+
"not-stated": "not-stated";
|
|
146
|
+
"stated-without-identifiers": "stated-without-identifiers";
|
|
147
|
+
"outside-this-experiment": "outside-this-experiment";
|
|
148
|
+
}>;
|
|
149
|
+
}, z.core.$strict>], "status">;
|
|
150
|
+
"answer-prompt": z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
151
|
+
status: z.ZodLiteral<"measured-here">;
|
|
152
|
+
statement: z.ZodString;
|
|
153
|
+
evidence: z.ZodArray<z.ZodObject<{
|
|
154
|
+
role: z.ZodEnum<{
|
|
155
|
+
"pinned-configuration": "pinned-configuration";
|
|
156
|
+
"execution-observation": "execution-observation";
|
|
157
|
+
}>;
|
|
158
|
+
digest: z.ZodObject<{
|
|
159
|
+
sha256: z.ZodString;
|
|
160
|
+
}, z.core.$strict>;
|
|
161
|
+
}, z.core.$strict>>;
|
|
162
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
163
|
+
status: z.ZodLiteral<"disclosed-by-publisher">;
|
|
164
|
+
statement: z.ZodString;
|
|
165
|
+
sources: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
166
|
+
uri: z.ZodString;
|
|
167
|
+
}, z.core.$strict>>>;
|
|
168
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
169
|
+
status: z.ZodLiteral<"undisclosed">;
|
|
170
|
+
reason: z.ZodEnum<{
|
|
171
|
+
"not-stated": "not-stated";
|
|
172
|
+
"stated-without-identifiers": "stated-without-identifiers";
|
|
173
|
+
"outside-this-experiment": "outside-this-experiment";
|
|
174
|
+
}>;
|
|
175
|
+
}, z.core.$strict>], "status">;
|
|
176
|
+
"judge-model": z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
177
|
+
status: z.ZodLiteral<"measured-here">;
|
|
178
|
+
statement: z.ZodString;
|
|
179
|
+
evidence: z.ZodArray<z.ZodObject<{
|
|
180
|
+
role: z.ZodEnum<{
|
|
181
|
+
"pinned-configuration": "pinned-configuration";
|
|
182
|
+
"execution-observation": "execution-observation";
|
|
183
|
+
}>;
|
|
184
|
+
digest: z.ZodObject<{
|
|
185
|
+
sha256: z.ZodString;
|
|
186
|
+
}, z.core.$strict>;
|
|
187
|
+
}, z.core.$strict>>;
|
|
188
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
189
|
+
status: z.ZodLiteral<"disclosed-by-publisher">;
|
|
190
|
+
statement: z.ZodString;
|
|
191
|
+
sources: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
192
|
+
uri: z.ZodString;
|
|
193
|
+
}, z.core.$strict>>>;
|
|
194
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
195
|
+
status: z.ZodLiteral<"undisclosed">;
|
|
196
|
+
reason: z.ZodEnum<{
|
|
197
|
+
"not-stated": "not-stated";
|
|
198
|
+
"stated-without-identifiers": "stated-without-identifiers";
|
|
199
|
+
"outside-this-experiment": "outside-this-experiment";
|
|
200
|
+
}>;
|
|
201
|
+
}, z.core.$strict>], "status">;
|
|
202
|
+
"judge-prompt": z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
203
|
+
status: z.ZodLiteral<"measured-here">;
|
|
204
|
+
statement: z.ZodString;
|
|
205
|
+
evidence: z.ZodArray<z.ZodObject<{
|
|
206
|
+
role: z.ZodEnum<{
|
|
207
|
+
"pinned-configuration": "pinned-configuration";
|
|
208
|
+
"execution-observation": "execution-observation";
|
|
209
|
+
}>;
|
|
210
|
+
digest: z.ZodObject<{
|
|
211
|
+
sha256: z.ZodString;
|
|
212
|
+
}, z.core.$strict>;
|
|
213
|
+
}, z.core.$strict>>;
|
|
214
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
215
|
+
status: z.ZodLiteral<"disclosed-by-publisher">;
|
|
216
|
+
statement: z.ZodString;
|
|
217
|
+
sources: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
218
|
+
uri: z.ZodString;
|
|
219
|
+
}, z.core.$strict>>>;
|
|
220
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
221
|
+
status: z.ZodLiteral<"undisclosed">;
|
|
222
|
+
reason: z.ZodEnum<{
|
|
223
|
+
"not-stated": "not-stated";
|
|
224
|
+
"stated-without-identifiers": "stated-without-identifiers";
|
|
225
|
+
"outside-this-experiment": "outside-this-experiment";
|
|
226
|
+
}>;
|
|
227
|
+
}, z.core.$strict>], "status">;
|
|
228
|
+
}, z.core.$strict>;
|
|
229
|
+
}, z.core.$strict>;
|
|
230
|
+
export declare class DisclosureProjectionError extends Error {
|
|
231
|
+
constructor(message: string);
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* The one shared projection, called by BOTH the workspace producer and the standalone verifier.
|
|
235
|
+
*
|
|
236
|
+
* It carries only facts embedded in the record's own bytes, plus the record's digest — which is the
|
|
237
|
+
* digest of the very bytes handed in, never a separately supplied one. It exists at all so a reader
|
|
238
|
+
* of `claim-package.json` alone sees all six statuses without opening an evidence record, and so
|
|
239
|
+
* `assertClaimConsistency`'s existing whole-claim byte-compare covers the disclosure without a
|
|
240
|
+
* second bespoke comparison.
|
|
241
|
+
*/
|
|
242
|
+
export declare function deriveDisclosureSpecification(recordBytes: Uint8Array): ClaimDisclosureSection;
|
|
243
|
+
/** The disclosed statuses, surfaced on the verification result (§7 step 11). Disclosed facts, never
|
|
244
|
+
* folded into a single badge — the same posture `anchors` takes. */
|
|
245
|
+
export interface DisclosureSpecificationReport {
|
|
246
|
+
readonly recordSha256: string;
|
|
247
|
+
readonly specification: string;
|
|
248
|
+
readonly subjectSha256: string;
|
|
249
|
+
readonly statuses: Readonly<Record<DisclosureVariableKey, DisclosureVariableStatus>>;
|
|
250
|
+
}
|
|
251
|
+
export interface AssertDisclosureSpecificationInput {
|
|
252
|
+
/** The Report extension's descriptor digest — what the carrier says the record is. */
|
|
253
|
+
readonly extensionDigestSha256: string;
|
|
254
|
+
/** Every evidence-catalog record's declared roles, keyed by record digest. */
|
|
255
|
+
readonly catalogRoles: ReadonlyMap<string, ReadonlySet<string>>;
|
|
256
|
+
/** The exact carried bytes for every evidence record, keyed by digest. */
|
|
257
|
+
readonly recordBytes: ReadonlyMap<string, Uint8Array>;
|
|
258
|
+
/** The bundle's own Matrix digest — the record's one legal subject (R1). */
|
|
259
|
+
readonly matrixSha256: string;
|
|
260
|
+
/** The verified Report's author IRI. */
|
|
261
|
+
readonly reportAuthor: string;
|
|
262
|
+
/** Refuses with the verifier's own typed refusal, so this module raises no error class of its own
|
|
263
|
+
* at the call boundary and every path lands in the caller's issue shape. */
|
|
264
|
+
readonly refuse: (path: string, message: string) => never;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* §7 steps 1–8: authenticate what the Report extension names.
|
|
268
|
+
*
|
|
269
|
+
* Step 9 (claim-id pairing) and step 10 (projection equality) live in `verify.ts` and
|
|
270
|
+
* `assertClaimConsistency` respectively, because both compare against documents this function is
|
|
271
|
+
* deliberately not handed — keeping this function a statement about the RECORD and its evidence.
|
|
272
|
+
*/
|
|
273
|
+
export declare function assertDisclosureSpecification(input: AssertDisclosureSpecificationInput): DisclosureSpecificationReport;
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The disclosed closure's projection and check (disclosure-specification-record design §6.4, §6.6,
|
|
3
|
+
* §7; issue #2839).
|
|
4
|
+
*
|
|
5
|
+
* This module is **single-sourced, not mirrored**, exactly as `anchor-claims.ts` is: `@colophon-
|
|
6
|
+
* claims/core` already depends on this package, so the producer and the standalone verifier call
|
|
7
|
+
* ONE `deriveDisclosureSpecification`. The claim-consistency byte-compare then compares one
|
|
8
|
+
* function's output over two byte sets rather than two implementations' guesses.
|
|
9
|
+
*
|
|
10
|
+
* The one posture that matters more than any mechanism here (design R4):
|
|
11
|
+
*
|
|
12
|
+
* **The verifier AUTHENTICATES measurements and CARRIES assertions.**
|
|
13
|
+
*
|
|
14
|
+
* A `measured-here` variable must cite records the bundle actually carries, under a role admissible
|
|
15
|
+
* for the citation. A `disclosed-by-publisher` variable is checked for internal well-formedness and
|
|
16
|
+
* for nothing else — no lookup, no fetch, no cross-check against the Matrix or the Report, no status
|
|
17
|
+
* upgrade or downgrade. An assertion that turns out to be false is a false assertion in a valid
|
|
18
|
+
* record; that is the correct outcome and the honest one, and a verifier that refused it would be
|
|
19
|
+
* claiming a power it does not have.
|
|
20
|
+
*
|
|
21
|
+
* What this check deliberately does NOT do (design §8, each one a thing a well-meaning implementer
|
|
22
|
+
* would add):
|
|
23
|
+
*
|
|
24
|
+
* - It does not reconcile against `Report.disclosures.perSubject[].pinning`. Those counts answer
|
|
25
|
+
* "how well did each executed axis pin"; this record answers "which variables were executed at
|
|
26
|
+
* all". Two surfaces restating one fact are two surfaces that can disagree.
|
|
27
|
+
* - It does not infer a status from the bundle. A bundle that plainly executed a judge model does
|
|
28
|
+
* not license marking `judge-model` as `measured-here` if the record says otherwise.
|
|
29
|
+
* - It does not fetch anything, ever. A verifier that reached the network would make its own result
|
|
30
|
+
* depend on when it ran.
|
|
31
|
+
* - It does not rank, score, compare, or aggregate. There is no disclosure-completeness score:
|
|
32
|
+
* counting statuses would create a number publishers optimize against, and a six-of-six record
|
|
33
|
+
* with six vague assertions would outscore a two-of-six record with two proofs.
|
|
34
|
+
*/
|
|
35
|
+
import { createHash } from "node:crypto";
|
|
36
|
+
import { z } from "zod";
|
|
37
|
+
import { DISCLOSURE_VARIABLE_KEYS, DisclosureVariablesSchema, MATRIX_RECORD_KIND, SIX_VARIABLE_DISCLOSURE_SPECIFICATION, parseDisclosureSpecification, } from "@jinn-network/benchmarking-records";
|
|
38
|
+
/** The evidence-catalog role a disclosure-specification record carries, and the ONLY one it may
|
|
39
|
+
* carry: a record bearing this token together with any second role refuses (§7 step 1). */
|
|
40
|
+
export const DISCLOSURE_SPECIFICATION_BUNDLE_ROLE = "disclosure-specification";
|
|
41
|
+
/**
|
|
42
|
+
* §6.4's binding profile: which of this carrier's evidence roles satisfy which of the standard's two
|
|
43
|
+
* portable disclosure roles. Jinn-side mapping, deliberately NOT part of the portable record — the
|
|
44
|
+
* mapping from the standard's roles onto a specific carrier's record roles is the carrier's business.
|
|
45
|
+
*
|
|
46
|
+
* v1 deliberately does not narrow the admissible set per variable. Which record species fixes
|
|
47
|
+
* `judge-model` depends on the judge profile packet P0 is freezing, and encoding a guess here would
|
|
48
|
+
* put this module in P0's lane. Reserved as a v2 tightening once P0's record vocabulary merges.
|
|
49
|
+
*/
|
|
50
|
+
export const DISCLOSURE_ROLE_BINDING = {
|
|
51
|
+
"pinned-configuration": [
|
|
52
|
+
"task",
|
|
53
|
+
"item-bank",
|
|
54
|
+
"source-item",
|
|
55
|
+
"evaluation-spec",
|
|
56
|
+
"judge-instrument",
|
|
57
|
+
"runtime-selection",
|
|
58
|
+
"admission-manifest",
|
|
59
|
+
],
|
|
60
|
+
"execution-observation": [
|
|
61
|
+
"run-pinning-evidence",
|
|
62
|
+
"solve-submission",
|
|
63
|
+
"solve-delivery",
|
|
64
|
+
"solve-output",
|
|
65
|
+
"evaluation-submission",
|
|
66
|
+
"evaluation-delivery",
|
|
67
|
+
"verdict",
|
|
68
|
+
],
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* The claim section's own grammar, single-sourced from the record's schema so the section cannot
|
|
72
|
+
* describe a variable entry the record could not have carried. It is a second line of defense
|
|
73
|
+
* rather than the primary one: `assertClaimConsistency`'s whole-claim byte-compare against the
|
|
74
|
+
* rebuilt claim is what actually proves the section is this record's projection.
|
|
75
|
+
*/
|
|
76
|
+
export const ClaimDisclosureSectionSchema = z.strictObject({
|
|
77
|
+
recordSha256: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
78
|
+
specification: z.literal(SIX_VARIABLE_DISCLOSURE_SPECIFICATION),
|
|
79
|
+
subjectSha256: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
80
|
+
variables: DisclosureVariablesSchema,
|
|
81
|
+
});
|
|
82
|
+
export class DisclosureProjectionError extends Error {
|
|
83
|
+
constructor(message) {
|
|
84
|
+
super(message);
|
|
85
|
+
this.name = "DisclosureProjectionError";
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function sha256Hex(bytes) {
|
|
89
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The one shared projection, called by BOTH the workspace producer and the standalone verifier.
|
|
93
|
+
*
|
|
94
|
+
* It carries only facts embedded in the record's own bytes, plus the record's digest — which is the
|
|
95
|
+
* digest of the very bytes handed in, never a separately supplied one. It exists at all so a reader
|
|
96
|
+
* of `claim-package.json` alone sees all six statuses without opening an evidence record, and so
|
|
97
|
+
* `assertClaimConsistency`'s existing whole-claim byte-compare covers the disclosure without a
|
|
98
|
+
* second bespoke comparison.
|
|
99
|
+
*/
|
|
100
|
+
export function deriveDisclosureSpecification(recordBytes) {
|
|
101
|
+
let record;
|
|
102
|
+
try {
|
|
103
|
+
record = parseDisclosureSpecification(recordBytes);
|
|
104
|
+
}
|
|
105
|
+
catch (cause) {
|
|
106
|
+
throw new DisclosureProjectionError(`disclosure-specification record is not a valid sealed record: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
recordSha256: sha256Hex(recordBytes),
|
|
110
|
+
specification: record.specification,
|
|
111
|
+
subjectSha256: record.subject.digest.sha256,
|
|
112
|
+
// Verbatim, in the frozen key order. Building the object key by key rather than spreading
|
|
113
|
+
// `record.variables` is what makes the order a property of this function rather than of
|
|
114
|
+
// whatever order the record's own bytes happened to carry.
|
|
115
|
+
variables: Object.fromEntries(DISCLOSURE_VARIABLE_KEYS.map((key) => [key, record.variables[key]])),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* §7 steps 1–8: authenticate what the Report extension names.
|
|
120
|
+
*
|
|
121
|
+
* Step 9 (claim-id pairing) and step 10 (projection equality) live in `verify.ts` and
|
|
122
|
+
* `assertClaimConsistency` respectively, because both compare against documents this function is
|
|
123
|
+
* deliberately not handed — keeping this function a statement about the RECORD and its evidence.
|
|
124
|
+
*/
|
|
125
|
+
export function assertDisclosureSpecification(input) {
|
|
126
|
+
// Explicitly annotated so TypeScript's control-flow analysis treats every call below as
|
|
127
|
+
// terminating; a destructured member with an inferred type does not narrow through `never`.
|
|
128
|
+
const refuse = input.refuse;
|
|
129
|
+
// ── Step 1: carrier binding, in exact one-to-one correspondence ─────────────────────────────
|
|
130
|
+
//
|
|
131
|
+
// The extension's digest must resolve to EXACTLY ONE catalog record — none and two both refuse —
|
|
132
|
+
// and that record's declared roles must be exactly the one disclosure role. Conversely any catalog
|
|
133
|
+
// record bearing the role that the extension does not name refuses: without the second direction,
|
|
134
|
+
// a second disclosure record could ride along with nothing checking it.
|
|
135
|
+
const bearers = [...input.catalogRoles]
|
|
136
|
+
.filter(([, roles]) => roles.has(DISCLOSURE_SPECIFICATION_BUNDLE_ROLE))
|
|
137
|
+
.map(([digest]) => digest);
|
|
138
|
+
if (bearers.length !== 1 || bearers[0] !== input.extensionDigestSha256) {
|
|
139
|
+
refuse("disclosure-specification", bearers.length === 0
|
|
140
|
+
? "the Report names a disclosure-specification record that no evidence-catalog record carries"
|
|
141
|
+
: bearers.length > 1
|
|
142
|
+
? "the evidence catalog carries more than one disclosure-specification record; the Report names exactly one"
|
|
143
|
+
: "the evidence catalog's disclosure-specification record is not the one the Report extension names");
|
|
144
|
+
}
|
|
145
|
+
const declared = input.catalogRoles.get(input.extensionDigestSha256);
|
|
146
|
+
if (declared === undefined || declared.size !== 1) {
|
|
147
|
+
refuse("disclosure-specification", "the disclosure-specification record must declare exactly that one role and no second");
|
|
148
|
+
}
|
|
149
|
+
// ── Step 2: exact bytes ────────────────────────────────────────────────────────────────────
|
|
150
|
+
//
|
|
151
|
+
// The digest equality with the catalog entry is already established upstream (the bundle walk
|
|
152
|
+
// recomputes every `records/<sha256>.bin`), so what is proved here is that those bytes parse
|
|
153
|
+
// strictly AND are the one exact canonical encoding of what they parse to.
|
|
154
|
+
const bytes = input.recordBytes.get(input.extensionDigestSha256);
|
|
155
|
+
if (bytes === undefined) {
|
|
156
|
+
refuse("disclosure-specification", "the bundle does not carry the disclosure-specification record it names");
|
|
157
|
+
}
|
|
158
|
+
const record = (() => {
|
|
159
|
+
try {
|
|
160
|
+
return parseDisclosureSpecification(bytes);
|
|
161
|
+
}
|
|
162
|
+
catch (cause) {
|
|
163
|
+
refuse(`records/${input.extensionDigestSha256}.bin`, `disclosure-specification record is invalid: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
164
|
+
}
|
|
165
|
+
})();
|
|
166
|
+
// ── Step 3: subject binding (R1) ───────────────────────────────────────────────────────────
|
|
167
|
+
//
|
|
168
|
+
// The Matrix rather than the Report, because the Report cannot name its own digest inside its own
|
|
169
|
+
// signed payload, and because the record must be sealable before the Report is (§5).
|
|
170
|
+
if (record.subject.kind !== MATRIX_RECORD_KIND) {
|
|
171
|
+
refuse("disclosure-specification", `the record's subject kind must be ${MATRIX_RECORD_KIND}`);
|
|
172
|
+
}
|
|
173
|
+
if (record.subject.digest.sha256 !== input.matrixSha256) {
|
|
174
|
+
refuse("disclosure-specification", "the record's subject digest is not this bundle's Matrix digest");
|
|
175
|
+
}
|
|
176
|
+
if (record.specification !== SIX_VARIABLE_DISCLOSURE_SPECIFICATION) {
|
|
177
|
+
refuse("disclosure-specification", `the record must claim ${SIX_VARIABLE_DISCLOSURE_SPECIFICATION}`);
|
|
178
|
+
}
|
|
179
|
+
// ── Step 4: author binding ─────────────────────────────────────────────────────────────────
|
|
180
|
+
//
|
|
181
|
+
// A disclosure record asserting under one identity inside a bundle signed by another is a carrier
|
|
182
|
+
// mismatch, not an extra fact.
|
|
183
|
+
if (record.author !== input.reportAuthor) {
|
|
184
|
+
refuse("disclosure-specification", "the record's author is not the bundle's verified Report author");
|
|
185
|
+
}
|
|
186
|
+
// ── Step 5: vocabulary completeness (R2) ───────────────────────────────────────────────────
|
|
187
|
+
//
|
|
188
|
+
// Structural via the strict object; restated so the refusal names the KEY rather than a schema
|
|
189
|
+
// path a reader would have to decode.
|
|
190
|
+
for (const key of DISCLOSURE_VARIABLE_KEYS) {
|
|
191
|
+
if (record.variables[key] === undefined) {
|
|
192
|
+
refuse("disclosure-specification", `the record does not state the variable "${key}"`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
for (const key of Object.keys(record.variables)) {
|
|
196
|
+
if (!DISCLOSURE_VARIABLE_KEYS.includes(key)) {
|
|
197
|
+
refuse("disclosure-specification", `the record states an unknown variable "${key}"; the six-variable set is closed`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// ── Steps 6–8: authenticate measurements, carry assertions, and let undisclosed carry nothing ─
|
|
201
|
+
const statuses = {};
|
|
202
|
+
for (const key of DISCLOSURE_VARIABLE_KEYS) {
|
|
203
|
+
const entry = record.variables[key];
|
|
204
|
+
statuses[key] = entry.status;
|
|
205
|
+
if (entry.status === "measured-here") {
|
|
206
|
+
// Step 6. THE WHOLE SUBSTANCE of "variables the venue actually ran must match actual pinning
|
|
207
|
+
// evidence in the bundle": no measured-here variable may cite a record the bundle does not
|
|
208
|
+
// carry, and a citation resolving to bytes of the wrong species is as bad as a dangling one.
|
|
209
|
+
for (const citation of entry.evidence) {
|
|
210
|
+
const cited = input.catalogRoles.get(citation.digest.sha256);
|
|
211
|
+
if (cited === undefined) {
|
|
212
|
+
refuse("disclosure-specification", `"${key}" is measured here but cites ${citation.digest.sha256}, which this bundle does not carry`);
|
|
213
|
+
}
|
|
214
|
+
const citedRoles = cited;
|
|
215
|
+
const admissible = DISCLOSURE_ROLE_BINDING[citation.role];
|
|
216
|
+
if (!admissible.some((role) => citedRoles.has(role))) {
|
|
217
|
+
refuse("disclosure-specification", `"${key}" cites ${citation.digest.sha256} as ${citation.role}, but that record's bundle roles are`
|
|
218
|
+
+ ` outside the admissible set for it`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
if (entry.status === "disclosed-by-publisher") {
|
|
224
|
+
// Step 7. Internal consistency ONLY. The schema has already established the statement bound
|
|
225
|
+
// and the sorted, unique, absolute-IRI source list; there is nothing further to check here and
|
|
226
|
+
// — R4 — nothing further this verifier is entitled to check. Restated as a branch so a later
|
|
227
|
+
// reader sees the deliberate emptiness rather than an omission.
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
// Step 8. Structural via the union; restated so the refusal would name the variable. Reaching
|
|
231
|
+
// this branch at all means the entry is `undisclosed`, which by construction carries a reason
|
|
232
|
+
// token and nothing else.
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
recordSha256: input.extensionDigestSha256,
|
|
236
|
+
specification: record.specification,
|
|
237
|
+
subjectSha256: record.subject.digest.sha256,
|
|
238
|
+
statuses: statuses,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
@@ -16,8 +16,8 @@ export declare const RunPinningEvidenceSchema: z.ZodObject<{
|
|
|
16
16
|
}, z.core.$strict>>;
|
|
17
17
|
observations: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
18
18
|
axis: z.ZodEnum<{
|
|
19
|
-
harness: "harness";
|
|
20
19
|
model: "model";
|
|
20
|
+
harness: "harness";
|
|
21
21
|
loadout: "loadout";
|
|
22
22
|
isolation: "isolation";
|
|
23
23
|
}>;
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import type { MatrixCell, RunRecord } from "@jinn-network/benchmarking-records";
|
|
2
2
|
import { type ClaimAnchor } from "./anchor-claims.js";
|
|
3
|
+
import type { VerifiedRunBinding } from "../binding/beacon-binding.js";
|
|
3
4
|
export declare const LOCAL_VENUE_LIMITS: readonly string[];
|
|
4
5
|
export declare function localVenueLimitsForRun(run: Pick<RunRecord, "policy">): readonly string[];
|
|
5
6
|
/** `anchors` is the same derived section the producer used (anchor-evidence §7.4); the conditional
|
|
6
|
-
* copy is the same pure function on both sides, so claim-consistency stays an exact byte-compare.
|
|
7
|
-
|
|
7
|
+
* copy is the same pure function on both sides, so claim-consistency stays an exact byte-compare.
|
|
8
|
+
*
|
|
9
|
+
* `binding` is the run's verified `beacon-binding/1` record (issue #2976) and obeys the same rule:
|
|
10
|
+
* omitting it is the identity, so every run that carries no binding keeps its exact limits bytes.
|
|
11
|
+
* It is only ever passed by a caller that can show BOTH sides the same binding — today that means
|
|
12
|
+
* it is not passed, because the public bundle carries no binding record yet. */
|
|
13
|
+
export declare function buildLocalVenueHonesty(cells: readonly MatrixCell[], run: Pick<RunRecord, "policy">, anchors?: readonly ClaimAnchor[], binding?: VerifiedRunBinding): {
|
|
8
14
|
venue: "self-run";
|
|
9
15
|
preRegistration: "structural-and-append-order-only" | "structural-append-order-and-anchored-time";
|
|
10
16
|
limits: readonly string[];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { venueIsolationPostureForPolicy } from "./isolation.js";
|
|
2
2
|
import { anchoredPreRegistration, anchoredVenueLimits } from "./anchor-claims.js";
|
|
3
|
+
import { runBoundVenueLimits } from "../binding/report-face.js";
|
|
3
4
|
export const LOCAL_VENUE_LIMITS = [
|
|
4
5
|
"This is a local, self-run venue: the same operator controls task dispatch, execution, and evaluation.",
|
|
5
6
|
"Pre-registration here is a discipline enforced by this tool, not a proof against the run's own owner — nothing prevents the owner from having altered the record before publishing it.",
|
|
@@ -12,12 +13,17 @@ export function localVenueLimitsForRun(run) {
|
|
|
12
13
|
return venueIsolationPostureForPolicy(run.policy.submissionBaseline?.isolationPolicy).inventory.length === 1 ? LOCAL_VENUE_LIMITS : [LOCAL_VENUE_LIMITS[0], LOCAL_VENUE_LIMITS[1], MULTI, ...LOCAL_VENUE_LIMITS.slice(3)];
|
|
13
14
|
}
|
|
14
15
|
/** `anchors` is the same derived section the producer used (anchor-evidence §7.4); the conditional
|
|
15
|
-
* copy is the same pure function on both sides, so claim-consistency stays an exact byte-compare.
|
|
16
|
-
|
|
16
|
+
* copy is the same pure function on both sides, so claim-consistency stays an exact byte-compare.
|
|
17
|
+
*
|
|
18
|
+
* `binding` is the run's verified `beacon-binding/1` record (issue #2976) and obeys the same rule:
|
|
19
|
+
* omitting it is the identity, so every run that carries no binding keeps its exact limits bytes.
|
|
20
|
+
* It is only ever passed by a caller that can show BOTH sides the same binding — today that means
|
|
21
|
+
* it is not passed, because the public bundle carries no binding record yet. */
|
|
22
|
+
export function buildLocalVenueHonesty(cells, run, anchors = [], binding) {
|
|
17
23
|
const counts = { harness: 0, model: 0, loadout: 0, isolation: 0 };
|
|
18
24
|
for (const cell of cells)
|
|
19
25
|
for (const axis of Object.keys(counts))
|
|
20
26
|
if (cell.verification[axis] === "unverifiable")
|
|
21
27
|
counts[axis] += 1;
|
|
22
|
-
return { venue: "self-run", preRegistration: anchoredPreRegistration(anchors), limits: anchoredVenueLimits(localVenueLimitsForRun(run), anchors), unverifiableAxisCounts: counts };
|
|
28
|
+
return { venue: "self-run", preRegistration: anchoredPreRegistration(anchors), limits: runBoundVenueLimits(anchoredVenueLimits(localVenueLimitsForRun(run), anchors), binding), unverifiableAxisCounts: counts };
|
|
23
29
|
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task-selection provenance, verified against the records that carry it (issue #2980).
|
|
3
|
+
*
|
|
4
|
+
* The declaration is a closed-vocabulary value sealed into the Run record's
|
|
5
|
+
* `task-selection/v1` extension. Sealing makes it unforgeable after the lock; it does not make it
|
|
6
|
+
* true. What remains for a cold verifier is the part sealing cannot settle: whether the *other*
|
|
7
|
+
* sealed records are consistent with what the declaration asserts.
|
|
8
|
+
*
|
|
9
|
+
* In this record model the *selection is the Benchmark record itself*: the Run seals a Benchmark
|
|
10
|
+
* digest, and `expectedCellSet` is the full cartesian product `items x arms x replicates`, so the
|
|
11
|
+
* Matrix always covers every Benchmark item exactly. Comparing the Matrix's task digests against
|
|
12
|
+
* the Benchmark's items — the first thing this module tried — is therefore dead code. What is left
|
|
13
|
+
* is the Benchmark's own reveal policy and whether anyone declared the set at all.
|
|
14
|
+
*
|
|
15
|
+
* These checks are refusals, not endorsements, and the asymmetry is the point: each one names a
|
|
16
|
+
* declaration the records positively contradict. None of them can establish that a declaration is
|
|
17
|
+
* TRUE, because the bundle carries no independent witness of an upstream set. `assertTaskSelection-
|
|
18
|
+
* Consistency` below says exactly where that boundary falls and why the tempting rule on the other
|
|
19
|
+
* side of it is unsound.
|
|
20
|
+
*/
|
|
21
|
+
import { type BenchmarkRecord, type RunRecord, type TaskSelectionMode } from "@jinn-network/benchmarking-records";
|
|
22
|
+
export interface TaskSelectionConsistencyInput {
|
|
23
|
+
readonly benchmarkRecord: BenchmarkRecord;
|
|
24
|
+
readonly runRecord: RunRecord;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The declared mode, refusing rather than throwing raw on bytes the Run schema would not have
|
|
28
|
+
* sealed. Exported so any reader that resolves the mode does so through this refusal posture
|
|
29
|
+
* rather than re-deriving it with its own error handling. No presentation asset consumes it today
|
|
30
|
+
* -- the report face renders nothing for task selection until issue #3416 -- so its only callers
|
|
31
|
+
* are `taskSelectionContradiction` below and its own tests.
|
|
32
|
+
*/
|
|
33
|
+
export declare function declaredTaskSelectionMode(runRecord: RunRecord): TaskSelectionMode | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* The way the sealed records contradict a declared task-selection mode, or `undefined` when they
|
|
36
|
+
* do not. Pure, and shared deliberately: the producer calls it BEFORE the lock so a contradiction
|
|
37
|
+
* is a draft-validation refusal the claimant can still act on, and the cold verifier calls it after
|
|
38
|
+
* the fact through {@link assertTaskSelectionConsistency}. One rule, two postures — a second copy
|
|
39
|
+
* would be free to drift into refusing at publish what it accepted at lock, which is the worst
|
|
40
|
+
* possible place to disagree.
|
|
41
|
+
*
|
|
42
|
+
* Two contradictions, both provable from bundle bytes:
|
|
43
|
+
*
|
|
44
|
+
* 1. **`fixed-public-set` over an undeclared set.** A set nobody declared is not a publicly
|
|
45
|
+
* declared set, so the Benchmark record must at least name an `author`.
|
|
46
|
+
* 2. **Reveal policy that cannot coexist with the mode.** `fixed-public-set` is refused when the
|
|
47
|
+
* items were provably withheld at the lock; `drawn-post-lock` is refused when they were
|
|
48
|
+
* `immediate` — open the moment the record existed, so the Run sealed against a set the
|
|
49
|
+
* claimant could already read, and nothing was drawn afterwards.
|
|
50
|
+
*
|
|
51
|
+
* What this deliberately does NOT do is decide the stronger modes from `benchmark.author` versus
|
|
52
|
+
* `run.owner`. That rule looks decisive and is not: `author` is a self-declaration the design spec
|
|
53
|
+
* marks non-authoritative, and every task-set intake in this product re-authors the Benchmark under
|
|
54
|
+
* the workspace's own key (`intake/workspace-authored.ts`), which is also the Run's owner. Enforcing
|
|
55
|
+
* on it would refuse every bundle this product can produce, making two of the three vocabulary
|
|
56
|
+
* values dead letters.
|
|
57
|
+
*
|
|
58
|
+
* So the honest boundary: these checks catch declarations the records positively contradict, but
|
|
59
|
+
* none can prove a `fixed-public-set` claim TRUE — the bundle carries no independent witness of the
|
|
60
|
+
* upstream set. `PUBLIC-BUNDLE.md` says so in the same words, because a gap a reader can see is
|
|
61
|
+
* worth more than one a rule pretends to close.
|
|
62
|
+
*
|
|
63
|
+
* `claimant-chosen` is unconstrained on purpose. It asserts nothing about anyone but the claimant,
|
|
64
|
+
* so nothing can contradict it, and constraining it would only make the honest answer the
|
|
65
|
+
* expensive one.
|
|
66
|
+
*/
|
|
67
|
+
export declare function taskSelectionContradiction(input: TaskSelectionConsistencyInput): string | undefined;
|
|
68
|
+
/** The cold verifier's posture over {@link taskSelectionContradiction}: a typed record refusal. */
|
|
69
|
+
export declare function assertTaskSelectionConsistency(input: TaskSelectionConsistencyInput): void;
|