@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.
Files changed (49) hide show
  1. package/README.md +23 -9
  2. package/dist/admission/contracts.d.ts +483 -0
  3. package/dist/admission/contracts.js +285 -0
  4. package/dist/admission/index.d.ts +2 -0
  5. package/dist/admission/index.js +2 -0
  6. package/dist/admission/prompted-commitment.d.ts +19 -0
  7. package/dist/admission/prompted-commitment.js +52 -0
  8. package/dist/admission/prompted-selection.d.ts +24 -0
  9. package/dist/admission/prompted-selection.js +85 -0
  10. package/dist/admission/verification.d.ts +17 -3
  11. package/dist/admission/verification.js +230 -49
  12. package/dist/assets.d.ts +21 -1
  13. package/dist/assets.js +60 -3
  14. package/dist/binding/beacon-binding.d.ts +230 -0
  15. package/dist/binding/beacon-binding.js +325 -0
  16. package/dist/binding/report-face.d.ts +45 -0
  17. package/dist/binding/report-face.js +153 -0
  18. package/dist/cli.js +74 -12
  19. package/dist/index.d.ts +14 -3
  20. package/dist/index.js +16 -3
  21. package/dist/manifest.d.ts +30 -4
  22. package/dist/manifest.js +30 -0
  23. package/dist/materialize.d.ts +7 -0
  24. package/dist/materialize.js +7 -0
  25. package/dist/outcome.d.ts +31 -0
  26. package/dist/outcome.js +45 -0
  27. package/dist/profile/binary-qualification.js +6 -0
  28. package/dist/profile/claim-consistency.d.ts +8 -1
  29. package/dist/profile/claim-consistency.js +4 -4
  30. package/dist/profile/claim.d.ts +203 -1
  31. package/dist/profile/claim.js +192 -43
  32. package/dist/profile/disclosure.d.ts +273 -0
  33. package/dist/profile/disclosure.js +240 -0
  34. package/dist/profile/pinning-evidence.d.ts +1 -1
  35. package/dist/profile/run-results.d.ts +8 -2
  36. package/dist/profile/run-results.js +9 -3
  37. package/dist/profile/task-selection.d.ts +69 -0
  38. package/dist/profile/task-selection.js +140 -0
  39. package/dist/reader-instructions.d.ts +38 -1
  40. package/dist/reader-instructions.js +42 -2
  41. package/dist/schema.d.ts +13 -2
  42. package/dist/schema.js +39 -8
  43. package/dist/signers.d.ts +48 -0
  44. package/dist/signers.js +77 -0
  45. package/dist/verify.d.ts +20 -6
  46. package/dist/verify.js +156 -31
  47. package/dist/version.d.ts +1 -1
  48. package/dist/version.js +1 -1
  49. package/package.json +14 -14
@@ -13,9 +13,9 @@ const PAIRED_ESTIMATE_LIMITATION = "This method estimates an effect; it does not
13
13
  const equal = (left, right) => left.length === right.length && left.every((byte, index) => byte === right[index]);
14
14
  /** The path of the first field that actually differs, or `undefined` when the two values are
15
15
  * equal — equal leaves must report NO difference, or the object branch returns on its first key
16
- * and every mismatch names the first key in sorted order instead of the edited field. Mirrors
17
- * `benchmark-product/core`'s own copy (`src/verification/claim-consistency.ts`). */
18
- function firstDifference(actual, expected, path = "claim") {
16
+ * and every mismatch names the first key in sorted order instead of the edited field. Single
17
+ * implementation: product core imports this helper rather than keeping a second copy. */
18
+ export function firstDifference(actual, expected, path = "claim") {
19
19
  // Never hand `undefined` to canonicalJsonBytes: it is deliberately not a JSON value, and a
20
20
  // field carried by one side alone is itself the first difference.
21
21
  if (actual === undefined || expected === undefined)
@@ -54,7 +54,7 @@ export function assertClaimConsistency(input) {
54
54
  // Presence, not emptiness: an empty section belongs to the anchored closure's declared-but-absent
55
55
  // bundle, and an omitted one to every unanchored bundle.
56
56
  const anchors = input.anchors;
57
- const expected = buildClaimPackage({ draftId: input.draftId, benchmarkSha256: identities.benchmarkSha256, runRecord, runSha256: identities.runSha256, matrixRecord, matrixSha256: identities.matrixSha256, reportRecord, reportSha256: identities.reportSha256, reportEnvelopeSha256: identities.reportEnvelopeSha256, venueHonesty: buildLocalVenueHonesty(matrixRecord.cells, runRecord, anchors ?? []), verificationCommandVerb: "bundle verify", assurance: { preset: input.assurancePreset, resolved: { independence: runRecord.policy.independence, minVerdicts, distinctEvaluator, verdictRule } }, ...(input.rehearsal === undefined ? {} : { previewDisclosure: input.rehearsal }), ...(anchors === undefined ? {} : { anchors }) });
57
+ const expected = buildClaimPackage({ draftId: input.draftId, benchmarkSha256: identities.benchmarkSha256, runRecord, runSha256: identities.runSha256, matrixRecord, matrixSha256: identities.matrixSha256, reportRecord, reportSha256: identities.reportSha256, reportEnvelopeSha256: identities.reportEnvelopeSha256, venueHonesty: buildLocalVenueHonesty(matrixRecord.cells, runRecord, anchors ?? []), verificationCommandVerb: "bundle verify", assurance: { preset: input.assurancePreset, resolved: { independence: runRecord.policy.independence, minVerdicts, distinctEvaluator, verdictRule } }, ...(input.rehearsal === undefined ? {} : { previewDisclosure: input.rehearsal }), ...(anchors === undefined ? {} : { anchors }), ...(input.disclosure === undefined ? {} : { disclosure: input.disclosure }) });
58
58
  if (!equal(canonicalJsonBytes(claim), canonicalJsonBytes(expected)))
59
59
  refuse("record-integrity", "claim-consistency", `claim package ${firstDifference(claim, expected) ?? "claim"} is not the exact projection of verified facts`);
60
60
  const rehearsalLine = input.rehearsal === undefined ? undefined : previewDisclosureSummaryLine(input.rehearsal);
@@ -27,9 +27,15 @@
27
27
  * disclosure. The one deliberate exception to "never cross-check here": `buildClaimPackage`
28
28
  * THROWS when the stated primitives disagree with what the sealed Run record's own policy
29
29
  * carries — a claim stating primitives the sealed Run does not carry would be dishonest.
30
+ *
31
+ * Issue #3205: the claim id is chosen on TWO independent axes — anchored or not, and
32
+ * qualification-projecting or not — which is four allocations, not three. `claim-package/5` is
33
+ * the fourth cell; before it existed an anchored binary-instrument run could not produce a
34
+ * claim at all, which made anchoring and binary-instrument benchmarking mutually exclusive.
30
35
  */
31
36
  import { z } from "zod";
32
37
  import type { MatrixRecord, ReportRecord, RunRecord } from "@jinn-network/benchmarking-records";
38
+ import type { ClaimDisclosureSection } from "./disclosure.js";
33
39
  import { SELF_RUN_TRUST_ROOT } from "./anchor-claims.js";
34
40
  import type { ClaimAnchor } from "./anchor-claims.js";
35
41
  type VenueHonesty = unknown;
@@ -42,13 +48,40 @@ export declare const BINARY_QUALIFICATION_CLAIM_PACKAGE_SCHEMA_ID = "benchmark-p
42
48
  * own later allocation.
43
49
  */
44
50
  export declare const ANCHORED_CLAIM_PACKAGE_SCHEMA_ID = "benchmark-product.claim-package/4";
51
+ /**
52
+ * The anchored binary-qualification claim package (issue #3205): claim-package/2's exact
53
+ * qualification projection plus claim-package/4's `anchors` section, carried by
54
+ * `benchmark-product-public-bundle/7`. This is the "later allocation" both earlier guards named:
55
+ * /2 has no anchors slot and /4 has no qualification slot, so before this number existed an
56
+ * anchored run of a binary-instrument benchmark could not produce a claim at all. The allocation is
57
+ * an ADDITION — /1, /2, /3, and /4 keep their meanings and their bytes.
58
+ */
59
+ export declare const ANCHORED_BINARY_QUALIFICATION_CLAIM_PACKAGE_SCHEMA_ID = "benchmark-product.claim-package/5";
60
+ /**
61
+ * The disclosed anchored binary-qualification claim package (issue #2839,
62
+ * disclosure-specification-record design §6.5/§6.6): claim-package/5 exactly, plus the
63
+ * `disclosure` section, carried by `benchmark-product-public-bundle/8`.
64
+ *
65
+ * The design reserved `/5` for its own disclosure allocation on the UNANCHORED qualified branch, on
66
+ * the premise (its §12.2) that anchoring and qualification could never combine. Issue #3205 both
67
+ * took `/5` and dissolved that premise, so per the design's own §6.5 rule — the implementation
68
+ * packet takes the then-next free numbers if a line has advanced — this is `/6`, and it stacks on
69
+ * the ANCHORED branch so the real flagship bundle can carry its disclosure record without giving up
70
+ * its anchor. The allocation is an ADDITION: /1, /2, /3, /4, and /5 keep their meanings and bytes.
71
+ */
72
+ export declare const DISCLOSED_CLAIM_PACKAGE_SCHEMA_ID = "benchmark-product.claim-package/6";
45
73
  export declare const BINARY_QUALIFICATION_VERIFICATION_COMMAND: "npx @colophon-claims/verify@0.1.0 <bundle-dir>";
46
74
  export declare const BINARY_QUALIFICATION_COMPATIBLE_VERIFICATION_COMMAND: "npx @colophon-claims/verify@0.1 <bundle-dir>";
75
+ /** Prompted-screening v2 claims require the verifier release that carries that admission surface. */
76
+ export declare const PROMPTED_BINARY_QUALIFICATION_VERIFICATION_COMMAND: "npx @colophon-claims/verify@0.2.1 <bundle-dir>";
77
+ /** Previously materialized prompted bundles remain valid under their immutable 0.2.0 claim. */
78
+ export declare const LEGACY_PROMPTED_BINARY_QUALIFICATION_VERIFICATION_COMMAND: "npx @colophon-claims/verify@0.2.0 <bundle-dir>";
79
+ export declare const PROMPTED_BINARY_QUALIFICATION_COMPATIBLE_VERIFICATION_COMMAND: "npx @colophon-claims/verify@0.2 <bundle-dir>";
47
80
  /** The fixed sentence every claim package's assurance block carries (BP-21, spec §6/§7.1): what
48
81
  * distinct evaluator identities on this venue actually prove. Never softened per-claim. */
49
82
  export declare const ASSURANCE_DISCLOSURE: string;
50
83
  export declare const ClaimPackageSchema: z.ZodPreprocess<z.ZodObject<{
51
- claimSchema: z.ZodUnion<readonly [z.ZodLiteral<"benchmark-product.claim-package/1">, z.ZodLiteral<"benchmark-product.claim-package/2">, z.ZodLiteral<"benchmark-product.claim-package/4">]>;
84
+ claimSchema: z.ZodUnion<readonly [z.ZodLiteral<"benchmark-product.claim-package/1">, z.ZodLiteral<"benchmark-product.claim-package/2">, z.ZodLiteral<"benchmark-product.claim-package/4">, z.ZodLiteral<"benchmark-product.claim-package/5">, z.ZodLiteral<"benchmark-product.claim-package/6">]>;
52
85
  scope: z.ZodObject<{
53
86
  draftId: z.ZodString;
54
87
  benchmarkSha256: z.ZodString;
@@ -269,6 +302,169 @@ export declare const ClaimPackageSchema: z.ZodPreprocess<z.ZodObject<{
269
302
  pending: z.ZodLiteral<true>;
270
303
  }, z.core.$strict>]>]>;
271
304
  }, z.core.$strict>>>;
305
+ disclosure: z.ZodOptional<z.ZodObject<{
306
+ recordSha256: z.ZodString;
307
+ specification: z.ZodLiteral<"https://spec.jinn.network/disclosure/six-variable/v1">;
308
+ subjectSha256: z.ZodString;
309
+ variables: z.ZodObject<{
310
+ "ingestion-model": z.ZodDiscriminatedUnion<[z.ZodObject<{
311
+ status: z.ZodLiteral<"measured-here">;
312
+ statement: z.ZodString;
313
+ evidence: z.ZodArray<z.ZodObject<{
314
+ role: z.ZodEnum<{
315
+ "pinned-configuration": "pinned-configuration";
316
+ "execution-observation": "execution-observation";
317
+ }>;
318
+ digest: z.ZodObject<{
319
+ sha256: z.ZodString;
320
+ }, z.core.$strict>;
321
+ }, z.core.$strict>>;
322
+ }, z.core.$strict>, z.ZodObject<{
323
+ status: z.ZodLiteral<"disclosed-by-publisher">;
324
+ statement: z.ZodString;
325
+ sources: z.ZodOptional<z.ZodArray<z.ZodObject<{
326
+ uri: z.ZodString;
327
+ }, z.core.$strict>>>;
328
+ }, z.core.$strict>, z.ZodObject<{
329
+ status: z.ZodLiteral<"undisclosed">;
330
+ reason: z.ZodEnum<{
331
+ "not-stated": "not-stated";
332
+ "stated-without-identifiers": "stated-without-identifiers";
333
+ "outside-this-experiment": "outside-this-experiment";
334
+ }>;
335
+ }, z.core.$strict>], "status">;
336
+ "retrieval-config": z.ZodDiscriminatedUnion<[z.ZodObject<{
337
+ status: z.ZodLiteral<"measured-here">;
338
+ statement: z.ZodString;
339
+ evidence: z.ZodArray<z.ZodObject<{
340
+ role: z.ZodEnum<{
341
+ "pinned-configuration": "pinned-configuration";
342
+ "execution-observation": "execution-observation";
343
+ }>;
344
+ digest: z.ZodObject<{
345
+ sha256: z.ZodString;
346
+ }, z.core.$strict>;
347
+ }, z.core.$strict>>;
348
+ }, z.core.$strict>, z.ZodObject<{
349
+ status: z.ZodLiteral<"disclosed-by-publisher">;
350
+ statement: z.ZodString;
351
+ sources: z.ZodOptional<z.ZodArray<z.ZodObject<{
352
+ uri: z.ZodString;
353
+ }, z.core.$strict>>>;
354
+ }, z.core.$strict>, z.ZodObject<{
355
+ status: z.ZodLiteral<"undisclosed">;
356
+ reason: z.ZodEnum<{
357
+ "not-stated": "not-stated";
358
+ "stated-without-identifiers": "stated-without-identifiers";
359
+ "outside-this-experiment": "outside-this-experiment";
360
+ }>;
361
+ }, z.core.$strict>], "status">;
362
+ "answer-model": z.ZodDiscriminatedUnion<[z.ZodObject<{
363
+ status: z.ZodLiteral<"measured-here">;
364
+ statement: z.ZodString;
365
+ evidence: z.ZodArray<z.ZodObject<{
366
+ role: z.ZodEnum<{
367
+ "pinned-configuration": "pinned-configuration";
368
+ "execution-observation": "execution-observation";
369
+ }>;
370
+ digest: z.ZodObject<{
371
+ sha256: z.ZodString;
372
+ }, z.core.$strict>;
373
+ }, z.core.$strict>>;
374
+ }, z.core.$strict>, z.ZodObject<{
375
+ status: z.ZodLiteral<"disclosed-by-publisher">;
376
+ statement: z.ZodString;
377
+ sources: z.ZodOptional<z.ZodArray<z.ZodObject<{
378
+ uri: z.ZodString;
379
+ }, z.core.$strict>>>;
380
+ }, z.core.$strict>, z.ZodObject<{
381
+ status: z.ZodLiteral<"undisclosed">;
382
+ reason: z.ZodEnum<{
383
+ "not-stated": "not-stated";
384
+ "stated-without-identifiers": "stated-without-identifiers";
385
+ "outside-this-experiment": "outside-this-experiment";
386
+ }>;
387
+ }, z.core.$strict>], "status">;
388
+ "answer-prompt": z.ZodDiscriminatedUnion<[z.ZodObject<{
389
+ status: z.ZodLiteral<"measured-here">;
390
+ statement: z.ZodString;
391
+ evidence: z.ZodArray<z.ZodObject<{
392
+ role: z.ZodEnum<{
393
+ "pinned-configuration": "pinned-configuration";
394
+ "execution-observation": "execution-observation";
395
+ }>;
396
+ digest: z.ZodObject<{
397
+ sha256: z.ZodString;
398
+ }, z.core.$strict>;
399
+ }, z.core.$strict>>;
400
+ }, z.core.$strict>, z.ZodObject<{
401
+ status: z.ZodLiteral<"disclosed-by-publisher">;
402
+ statement: z.ZodString;
403
+ sources: z.ZodOptional<z.ZodArray<z.ZodObject<{
404
+ uri: z.ZodString;
405
+ }, z.core.$strict>>>;
406
+ }, z.core.$strict>, z.ZodObject<{
407
+ status: z.ZodLiteral<"undisclosed">;
408
+ reason: z.ZodEnum<{
409
+ "not-stated": "not-stated";
410
+ "stated-without-identifiers": "stated-without-identifiers";
411
+ "outside-this-experiment": "outside-this-experiment";
412
+ }>;
413
+ }, z.core.$strict>], "status">;
414
+ "judge-model": z.ZodDiscriminatedUnion<[z.ZodObject<{
415
+ status: z.ZodLiteral<"measured-here">;
416
+ statement: z.ZodString;
417
+ evidence: z.ZodArray<z.ZodObject<{
418
+ role: z.ZodEnum<{
419
+ "pinned-configuration": "pinned-configuration";
420
+ "execution-observation": "execution-observation";
421
+ }>;
422
+ digest: z.ZodObject<{
423
+ sha256: z.ZodString;
424
+ }, z.core.$strict>;
425
+ }, z.core.$strict>>;
426
+ }, z.core.$strict>, z.ZodObject<{
427
+ status: z.ZodLiteral<"disclosed-by-publisher">;
428
+ statement: z.ZodString;
429
+ sources: z.ZodOptional<z.ZodArray<z.ZodObject<{
430
+ uri: z.ZodString;
431
+ }, z.core.$strict>>>;
432
+ }, z.core.$strict>, z.ZodObject<{
433
+ status: z.ZodLiteral<"undisclosed">;
434
+ reason: z.ZodEnum<{
435
+ "not-stated": "not-stated";
436
+ "stated-without-identifiers": "stated-without-identifiers";
437
+ "outside-this-experiment": "outside-this-experiment";
438
+ }>;
439
+ }, z.core.$strict>], "status">;
440
+ "judge-prompt": z.ZodDiscriminatedUnion<[z.ZodObject<{
441
+ status: z.ZodLiteral<"measured-here">;
442
+ statement: z.ZodString;
443
+ evidence: z.ZodArray<z.ZodObject<{
444
+ role: z.ZodEnum<{
445
+ "pinned-configuration": "pinned-configuration";
446
+ "execution-observation": "execution-observation";
447
+ }>;
448
+ digest: z.ZodObject<{
449
+ sha256: z.ZodString;
450
+ }, z.core.$strict>;
451
+ }, z.core.$strict>>;
452
+ }, z.core.$strict>, z.ZodObject<{
453
+ status: z.ZodLiteral<"disclosed-by-publisher">;
454
+ statement: z.ZodString;
455
+ sources: z.ZodOptional<z.ZodArray<z.ZodObject<{
456
+ uri: z.ZodString;
457
+ }, z.core.$strict>>>;
458
+ }, z.core.$strict>, z.ZodObject<{
459
+ status: z.ZodLiteral<"undisclosed">;
460
+ reason: z.ZodEnum<{
461
+ "not-stated": "not-stated";
462
+ "stated-without-identifiers": "stated-without-identifiers";
463
+ "outside-this-experiment": "outside-this-experiment";
464
+ }>;
465
+ }, z.core.$strict>], "status">;
466
+ }, z.core.$strict>;
467
+ }, z.core.$strict>>;
272
468
  }, z.core.$strip>>;
273
469
  export type ClaimPackage = z.infer<typeof ClaimPackageSchema>;
274
470
  export interface BuildClaimPackageInput {
@@ -310,10 +506,16 @@ export interface BuildClaimPackageInput {
310
506
  * here: this builder is pure, and the derivation needs the record bytes the caller already
311
507
  * authenticated. */
312
508
  readonly anchors?: readonly ClaimAnchor[];
509
+ /** disclosure-specification-record design §6.6: the projected disclosure section, already derived
510
+ * from the sealed record's exact bytes by the shared `deriveDisclosureSpecification`. Absent for
511
+ * every run with no declaration, which is what keeps every existing claim byte-identical. */
512
+ readonly disclosure?: ClaimDisclosureSection;
313
513
  }
314
514
  export declare const CLAIM_VERIFICATION_CHECKS: readonly string[];
315
515
  /** The anchored closure's list: the six frozen checks plus `integrity-anchors` (§8). */
316
516
  export declare const ANCHORED_CLAIM_VERIFICATION_CHECKS: readonly string[];
517
+ /** The disclosed closure's list: the anchored seven plus `disclosure-specification` (design §7). */
518
+ export declare const DISCLOSED_CLAIM_VERIFICATION_CHECKS: readonly string[];
317
519
  export declare const PUBLIC_BUNDLE_VERIFICATION_COMMAND: "npx @colophon-claims/verify@0.1.0 <bundle-dir>";
318
520
  /** The unconditional trust-root sentence, re-exported unchanged. It is DEFINED once beside its
319
521
  * anchored replacement in `anchor-claims.ts`: one sentence written out twice, in two mirrored
@@ -27,13 +27,20 @@
27
27
  * disclosure. The one deliberate exception to "never cross-check here": `buildClaimPackage`
28
28
  * THROWS when the stated primitives disagree with what the sealed Run record's own policy
29
29
  * carries — a claim stating primitives the sealed Run does not carry would be dishonest.
30
+ *
31
+ * Issue #3205: the claim id is chosen on TWO independent axes — anchored or not, and
32
+ * qualification-projecting or not — which is four allocations, not three. `claim-package/5` is
33
+ * the fourth cell; before it existed an anchored binary-instrument run could not produce a
34
+ * claim at all, which made anchoring and binary-instrument benchmarking mutually exclusive.
30
35
  */
31
36
  import { z } from "zod";
32
37
  import { Buffer } from "node:buffer";
33
38
  import { validateBinaryInstrumentQualificationProjection } from "@jinn-network/benchmarking-aggregate";
34
39
  import { BENCHMARKING_METHOD_IDS, BENCHMARKING_METHOD_VERSION } from "@jinn-network/benchmarking-records";
35
40
  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";
41
+ 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, PUBLIC_BUNDLE_V7_CHECKS as READER_ANCHORED_QUALIFICATION_VERIFICATION_CHECKS, PUBLIC_BUNDLE_V7_COMPATIBLE_VERIFICATION_COMMAND, PUBLIC_BUNDLE_V7_VERIFICATION_COMMAND, PUBLIC_BUNDLE_V8_CHECKS as READER_DISCLOSED_VERIFICATION_CHECKS, } from "../reader-instructions.js";
42
+ import { ClaimDisclosureSectionSchema } from "./disclosure.js";
43
+ import { PROMPTED_SCREENING_PROFILE } from "../admission/contracts.js";
37
44
  import { ClaimAnchorSchema, SELF_RUN_TRUST_ROOT, anchoredTrustRoot } from "./anchor-claims.js";
38
45
  export const CLAIM_PACKAGE_SCHEMA_ID = "benchmark-product.claim-package/1";
39
46
  export const BINARY_QUALIFICATION_CLAIM_PACKAGE_SCHEMA_ID = "benchmark-product.claim-package/2";
@@ -44,8 +51,35 @@ export const BINARY_QUALIFICATION_CLAIM_PACKAGE_SCHEMA_ID = "benchmark-product.c
44
51
  * own later allocation.
45
52
  */
46
53
  export const ANCHORED_CLAIM_PACKAGE_SCHEMA_ID = "benchmark-product.claim-package/4";
54
+ /**
55
+ * The anchored binary-qualification claim package (issue #3205): claim-package/2's exact
56
+ * qualification projection plus claim-package/4's `anchors` section, carried by
57
+ * `benchmark-product-public-bundle/7`. This is the "later allocation" both earlier guards named:
58
+ * /2 has no anchors slot and /4 has no qualification slot, so before this number existed an
59
+ * anchored run of a binary-instrument benchmark could not produce a claim at all. The allocation is
60
+ * an ADDITION — /1, /2, /3, and /4 keep their meanings and their bytes.
61
+ */
62
+ export const ANCHORED_BINARY_QUALIFICATION_CLAIM_PACKAGE_SCHEMA_ID = "benchmark-product.claim-package/5";
63
+ /**
64
+ * The disclosed anchored binary-qualification claim package (issue #2839,
65
+ * disclosure-specification-record design §6.5/§6.6): claim-package/5 exactly, plus the
66
+ * `disclosure` section, carried by `benchmark-product-public-bundle/8`.
67
+ *
68
+ * The design reserved `/5` for its own disclosure allocation on the UNANCHORED qualified branch, on
69
+ * the premise (its §12.2) that anchoring and qualification could never combine. Issue #3205 both
70
+ * took `/5` and dissolved that premise, so per the design's own §6.5 rule — the implementation
71
+ * packet takes the then-next free numbers if a line has advanced — this is `/6`, and it stacks on
72
+ * the ANCHORED branch so the real flagship bundle can carry its disclosure record without giving up
73
+ * its anchor. The allocation is an ADDITION: /1, /2, /3, /4, and /5 keep their meanings and bytes.
74
+ */
75
+ export const DISCLOSED_CLAIM_PACKAGE_SCHEMA_ID = "benchmark-product.claim-package/6";
47
76
  export const BINARY_QUALIFICATION_VERIFICATION_COMMAND = "npx @colophon-claims/verify@0.1.0 <bundle-dir>";
48
77
  export const BINARY_QUALIFICATION_COMPATIBLE_VERIFICATION_COMMAND = "npx @colophon-claims/verify@0.1 <bundle-dir>";
78
+ /** Prompted-screening v2 claims require the verifier release that carries that admission surface. */
79
+ export const PROMPTED_BINARY_QUALIFICATION_VERIFICATION_COMMAND = "npx @colophon-claims/verify@0.2.1 <bundle-dir>";
80
+ /** Previously materialized prompted bundles remain valid under their immutable 0.2.0 claim. */
81
+ export const LEGACY_PROMPTED_BINARY_QUALIFICATION_VERIFICATION_COMMAND = "npx @colophon-claims/verify@0.2.0 <bundle-dir>";
82
+ export const PROMPTED_BINARY_QUALIFICATION_COMPATIBLE_VERIFICATION_COMMAND = "npx @colophon-claims/verify@0.2 <bundle-dir>";
49
83
  const Sha256HexSchema = z.string().regex(/^[a-f0-9]{64}$/, "must be a lowercase sha256 hex digest");
50
84
  /** Mirrors `../run/preview-log.ts`'s own (unexported) `Rfc3339Schema` — restated here rather than
51
85
  * reached across the module boundary for one regex. */
@@ -185,6 +219,8 @@ const ClaimPackageWireSchema = z.object({
185
219
  z.literal(CLAIM_PACKAGE_SCHEMA_ID),
186
220
  z.literal(BINARY_QUALIFICATION_CLAIM_PACKAGE_SCHEMA_ID),
187
221
  z.literal(ANCHORED_CLAIM_PACKAGE_SCHEMA_ID),
222
+ z.literal(ANCHORED_BINARY_QUALIFICATION_CLAIM_PACKAGE_SCHEMA_ID),
223
+ z.literal(DISCLOSED_CLAIM_PACKAGE_SCHEMA_ID),
188
224
  ]),
189
225
  scope: z.object({
190
226
  draftId: z.string().min(1),
@@ -244,35 +280,70 @@ const ClaimPackageWireSchema = z.object({
244
280
  * headline, comparison, threshold, selection, or ranking projection. */
245
281
  qualification: z.unknown().optional(),
246
282
  /** anchor-evidence §7.4: one entry per AnchorEvidence record the bundle carries, in record-digest
247
- * order, each carrying only the facts embedded in the proof's own bytes. Present exactly on
248
- * claim-package/4 — the schema-level refine below refuses it on /1 and /2, so an unanchored claim
249
- * cannot grow an anchors section and an anchored one cannot be published without it. */
283
+ * order, each carrying only the facts embedded in the proof's own bytes. Present exactly on the
284
+ * two anchored allocations, claim-package/4 and claim-package/5 — the schema-level refine below
285
+ * refuses it on /1 and /2, so an unanchored claim cannot grow an anchors section and an anchored
286
+ * one cannot be published without it. */
250
287
  anchors: z.array(ClaimAnchorSchema).optional(),
288
+ /** disclosure-specification-record design §6.6: the sealed record's digest plus the facts embedded
289
+ * in its own bytes, each variable entry VERBATIM. Nothing here is summarized, counted, ranked, or
290
+ * reworded, and nothing here is derived from anything but the record. Present exactly on
291
+ * claim-package/6 — the refine below refuses it on every earlier allocation, so a claim cannot
292
+ * grow a disclosure section without moving to the closure whose check reads it. */
293
+ disclosure: ClaimDisclosureSectionSchema.optional(),
251
294
  }).superRefine((claim, ctx) => {
252
- if (claim.claimSchema !== ANCHORED_CLAIM_PACKAGE_SCHEMA_ID && claim.anchors !== undefined) {
295
+ // The two anchored allocations differ only in which method projection they carry: /4 takes the
296
+ // headline/comparison family, /5 (issue #3205) the binary qualification. Both carry the section.
297
+ // The disclosed allocation is the anchored binary one plus a section, so it inherits BOTH parents'
298
+ // rules by falling through every branch below that /5 falls through (issue #2839).
299
+ const disclosedClosure = claim.claimSchema === DISCLOSED_CLAIM_PACKAGE_SCHEMA_ID;
300
+ if (!disclosedClosure && claim.disclosure !== undefined) {
301
+ ctx.addIssue({
302
+ code: "custom",
303
+ message: `only ${DISCLOSED_CLAIM_PACKAGE_SCHEMA_ID} carries a disclosure section`,
304
+ path: ["disclosure"],
305
+ });
306
+ }
307
+ if (disclosedClosure && claim.disclosure === undefined) {
308
+ // Unlike `anchors`, this section has no legal EMPTY form: all six variables are structurally
309
+ // required, so a disclosed bundle with nothing to disclose is not a thing that exists. Omitting
310
+ // the section while claiming the closure would leave the check reading a record that no reader
311
+ // of the claim alone can see.
312
+ ctx.addIssue({
313
+ code: "custom",
314
+ message: `${DISCLOSED_CLAIM_PACKAGE_SCHEMA_ID} must carry its disclosure section`,
315
+ path: ["disclosure"],
316
+ });
317
+ }
318
+ const anchoredClosure = claim.claimSchema === ANCHORED_CLAIM_PACKAGE_SCHEMA_ID
319
+ || claim.claimSchema === ANCHORED_BINARY_QUALIFICATION_CLAIM_PACKAGE_SCHEMA_ID
320
+ // /6 is /5 plus a disclosure section, so it is anchored by inheritance: it carries the anchors
321
+ // section under the same presence rule, and an omitted one refuses identically.
322
+ || disclosedClosure;
323
+ if (!anchoredClosure && claim.anchors !== undefined) {
253
324
  ctx.addIssue({
254
325
  code: "custom",
255
- message: `only ${ANCHORED_CLAIM_PACKAGE_SCHEMA_ID} carries an anchors section`,
326
+ message: `only ${ANCHORED_CLAIM_PACKAGE_SCHEMA_ID} and ${ANCHORED_BINARY_QUALIFICATION_CLAIM_PACKAGE_SCHEMA_ID} carry an anchors section`,
327
+ path: ["anchors"],
328
+ });
329
+ }
330
+ if (anchoredClosure && claim.anchors === undefined) {
331
+ // Present, not necessarily non-empty. An empty section is legal on exactly one bundle: one
332
+ // whose sealed Run declared anchoring intent that no carried anchor satisfies (§7.3). That
333
+ // bundle must still be on the anchored closure so the check that reports the absence runs,
334
+ // and its claim must therefore still state the closure's seven checks. What no claim may do
335
+ // is omit the section while claiming the closure, or carry one while claiming an earlier one.
336
+ ctx.addIssue({
337
+ code: "custom",
338
+ message: `${claim.claimSchema} must carry its anchors section, even when the section is empty`,
256
339
  path: ["anchors"],
257
340
  });
258
341
  }
259
342
  if (claim.claimSchema === ANCHORED_CLAIM_PACKAGE_SCHEMA_ID) {
260
- if (claim.anchors === undefined) {
261
- // Present, not necessarily non-empty. An empty section is legal on exactly one bundle: one
262
- // whose sealed Run declared anchoring intent that no carried anchor satisfies (§7.3). That
263
- // bundle must still be on the anchored closure so the check that reports the absence runs,
264
- // and its claim must therefore still state the closure's seven checks. What no claim may do
265
- // is omit the section while claiming the closure, or carry one while claiming an earlier one.
266
- ctx.addIssue({
267
- code: "custom",
268
- message: `${ANCHORED_CLAIM_PACKAGE_SCHEMA_ID} must carry its anchors section, even when the section is empty`,
269
- path: ["anchors"],
270
- });
271
- }
272
343
  if (claim.qualification !== undefined) {
273
344
  ctx.addIssue({
274
345
  code: "custom",
275
- message: "the anchored binary-qualification closure is a later allocation, not claim-package/4",
346
+ message: `the anchored binary-qualification closure is ${ANCHORED_BINARY_QUALIFICATION_CLAIM_PACKAGE_SCHEMA_ID}, not claim-package/4`,
276
347
  path: ["qualification"],
277
348
  });
278
349
  }
@@ -361,9 +432,43 @@ const ClaimPackageWireSchema = z.object({
361
432
  if (!exactResult) {
362
433
  ctx.addIssue({ code: "custom", message: "qualification must exactly equal the Report's one F6 per-subject result", path: ["qualification"] });
363
434
  }
364
- if (claim.verification.command !== BINARY_QUALIFICATION_VERIFICATION_COMMAND
365
- || claim.verification.compatibleCommand !== BINARY_QUALIFICATION_COMPATIBLE_VERIFICATION_COMMAND) {
366
- ctx.addIssue({ code: "custom", message: "binary claim package must pin verifier 0.1.0/@0.1", path: ["verification"] });
435
+ if (claim.claimSchema === ANCHORED_BINARY_QUALIFICATION_CLAIM_PACKAGE_SCHEMA_ID
436
+ || disclosedClosure) {
437
+ // No released reader before 0.2.1 understands `benchmark-product-public-bundle/7` or `/8`, so
438
+ // both allocations pin that line unconditionally rather than inheriting /2's prompted/unprompted
439
+ // split — a claim naming an older reader would be an instruction to fail.
440
+ const expectedChecks = disclosedClosure
441
+ ? READER_DISCLOSED_VERIFICATION_CHECKS
442
+ : READER_ANCHORED_QUALIFICATION_VERIFICATION_CHECKS;
443
+ if (claim.verification.command !== PUBLIC_BUNDLE_V7_VERIFICATION_COMMAND
444
+ || claim.verification.compatibleCommand !== PUBLIC_BUNDLE_V7_COMPATIBLE_VERIFICATION_COMMAND) {
445
+ ctx.addIssue({ code: "custom", message: "anchored binary claim package must pin verifier 0.2.1/@0.2", path: ["verification"] });
446
+ }
447
+ if (claim.verification.checks.length !== expectedChecks.length
448
+ || claim.verification.checks.some((check, index) => check !== expectedChecks[index])) {
449
+ ctx.addIssue({
450
+ code: "custom",
451
+ message: disclosedClosure
452
+ ? "disclosed claim package must retain the seven anchored verification checks plus disclosure-specification, in order"
453
+ : "anchored binary claim package must retain the six frozen verification checks plus integrity-anchors, in order",
454
+ path: ["verification", "checks"],
455
+ });
456
+ }
457
+ return;
458
+ }
459
+ const prompted = claim.method.parameters["promptedScreeningProfile"] === PROMPTED_SCREENING_PROFILE;
460
+ const command = prompted
461
+ ? PROMPTED_BINARY_QUALIFICATION_VERIFICATION_COMMAND
462
+ : BINARY_QUALIFICATION_VERIFICATION_COMMAND;
463
+ const compatibleCommand = prompted
464
+ ? PROMPTED_BINARY_QUALIFICATION_COMPATIBLE_VERIFICATION_COMMAND
465
+ : BINARY_QUALIFICATION_COMPATIBLE_VERIFICATION_COMMAND;
466
+ const promptedCommandAccepted = prompted
467
+ && (claim.verification.command === PROMPTED_BINARY_QUALIFICATION_VERIFICATION_COMMAND
468
+ || claim.verification.command === LEGACY_PROMPTED_BINARY_QUALIFICATION_VERIFICATION_COMMAND);
469
+ if ((prompted ? !promptedCommandAccepted : claim.verification.command !== command)
470
+ || claim.verification.compatibleCommand !== compatibleCommand) {
471
+ ctx.addIssue({ code: "custom", message: `binary claim package must pin verifier ${prompted ? "0.2.1 (or historical 0.2.0)/@0.2" : "0.1.0/@0.1"}`, path: ["verification"] });
367
472
  }
368
473
  if (claim.verification.checks.length !== READER_VERIFICATION_CHECKS.length
369
474
  || claim.verification.checks.some((check, index) => check !== READER_VERIFICATION_CHECKS[index])) {
@@ -390,7 +495,7 @@ function exactBinaryClaimControls(input) {
390
495
  // control-shape failure. Neither judge field is ever set on an actual binary-instrument claim
391
496
  // (`methodProjection`'s dispatch is exclusive), so admitting them here is defense in depth, not
392
497
  // a widening any real claim exercises.
393
- return exactKeys(input, ["claimSchema", "scope", "records", "method", "results", "completeness", "attrition", "conflicted", "assurance", "disclosures", "limitations", "venueHonesty", "verification", "rehearsal", "qualification", "anchors", "pairwiseDisagreement", "pairedMajorityDelta"])
498
+ return exactKeys(input, ["claimSchema", "scope", "records", "method", "results", "completeness", "attrition", "conflicted", "assurance", "disclosures", "limitations", "venueHonesty", "verification", "rehearsal", "qualification", "anchors", "disclosure", "pairwiseDisagreement", "pairedMajorityDelta"])
394
499
  && exactKeys(scope, ["draftId", "benchmarkSha256", "taskCount", "arms", "replicates", "venue"])
395
500
  && Array.isArray(scope.arms)
396
501
  && (scope.arms).every((arm) => exactKeys(arm, ["armId", "pinning"]))
@@ -405,9 +510,17 @@ function exactBinaryClaimControls(input) {
405
510
  && exactKeys(verification, ["command", "compatibleCommand", "checks", "trustRoot"])
406
511
  && (input.rehearsal === undefined || exactKeys(input.rehearsal, ["previewCount", "timestamps"]));
407
512
  }
513
+ /** Both binary allocations pass through the same anti-conclusion control-shape gate: /5 is /2's
514
+ * projection plus an anchors section, so smuggling a ranking into it must fail identically. */
515
+ const BINARY_CLAIM_PACKAGE_SCHEMA_IDS = [
516
+ BINARY_QUALIFICATION_CLAIM_PACKAGE_SCHEMA_ID,
517
+ ANCHORED_BINARY_QUALIFICATION_CLAIM_PACKAGE_SCHEMA_ID,
518
+ // /6 is /5 plus a section, so a ranking smuggled into it must fail identically (issue #2839).
519
+ DISCLOSED_CLAIM_PACKAGE_SCHEMA_ID,
520
+ ];
408
521
  export const ClaimPackageSchema = z.preprocess((input) => {
409
522
  if (typeof input === "object" && input !== null && !Array.isArray(input)
410
- && input.claimSchema === BINARY_QUALIFICATION_CLAIM_PACKAGE_SCHEMA_ID
523
+ && BINARY_CLAIM_PACKAGE_SCHEMA_IDS.includes(input.claimSchema)
411
524
  && !exactBinaryClaimControls(input)) {
412
525
  return { claimSchema: "invalid-binary-claim-control-shape" };
413
526
  }
@@ -609,6 +722,8 @@ function summedPinningUnverifiableCounts(perSubject) {
609
722
  export const CLAIM_VERIFICATION_CHECKS = READER_VERIFICATION_CHECKS;
610
723
  /** The anchored closure's list: the six frozen checks plus `integrity-anchors` (§8). */
611
724
  export const ANCHORED_CLAIM_VERIFICATION_CHECKS = READER_ANCHORED_VERIFICATION_CHECKS;
725
+ /** The disclosed closure's list: the anchored seven plus `disclosure-specification` (design §7). */
726
+ export const DISCLOSED_CLAIM_VERIFICATION_CHECKS = READER_DISCLOSED_VERIFICATION_CHECKS;
612
727
  export const PUBLIC_BUNDLE_VERIFICATION_COMMAND = READER_VERIFICATION_COMMAND;
613
728
  /** The unconditional trust-root sentence, re-exported unchanged. It is DEFINED once beside its
614
729
  * anchored replacement in `anchor-claims.ts`: one sentence written out twice, in two mirrored
@@ -635,6 +750,10 @@ export function buildClaimPackage(input) {
635
750
  + " — a claim stating primitives the sealed Run does not carry would be dishonest");
636
751
  }
637
752
  const projection = methodProjection(reportRecord);
753
+ // Prompted screening is an authenticated Run-level admission fact. Every report/claim emitted
754
+ // from that Run uses the v2 reader, including its companion method claims; the binary claim's
755
+ // own schema separately binds the same exact profile in its method parameters.
756
+ const promptedScreening = input.runRecord.analysisPlan?.some((entry) => entry.parameters["promptedScreeningProfile"] === PROMPTED_SCREENING_PROFILE) === true;
638
757
  const disclosuresPerSubject = (reportRecord.disclosures?.perSubject ?? []);
639
758
  const taskCount = new Set(input.matrixRecord.cells.map((cell) => cell.taskDigest)).size;
640
759
  // anchor-evidence §7.4: the anchored closure is entered by the CALLER, by supplying the section
@@ -643,16 +762,32 @@ export function buildClaimPackage(input) {
643
762
  // this builder produced before the feature existed.
644
763
  const anchors = input.anchors ?? [];
645
764
  const anchored = input.anchors !== undefined;
646
- if (anchored && projection.qualification !== undefined) {
647
- throw new Error("claim package: the anchored binary-qualification closure is a later allocation"
648
- + " claim-package/4 has no qualification projection");
765
+ // Strictly opt-in, exactly like `anchors`: a run with no sealed disclosure declaration produces
766
+ // the claim this builder produced before the feature existed, byte for byte (issue #2839).
767
+ const disclosure = input.disclosure;
768
+ // Four allocations across two independent axes (anchored yes/no × qualification yes/no). The
769
+ // fourth cell, anchored+qualification, is claim-package/5 (issue #3205); before it existed this
770
+ // pairing threw, which made anchoring and binary-instrument benchmarking mutually exclusive.
771
+ const anchoredQualification = anchored && projection.qualification !== undefined;
772
+ // The disclosed allocation is the anchored QUALIFICATION cell plus a section, and there is no
773
+ // other disclosed cell (issue #2839). A run publishes one bundle per analysis, and its sibling
774
+ // headline/comparison analyses project no qualification, so they have nowhere to put the section.
775
+ // Refusing here rather than dropping it silently is what makes the caller state which entry the
776
+ // record belongs to instead of discovering later that one bundle quietly lost it.
777
+ if (disclosure !== undefined && !anchoredQualification) {
778
+ throw new Error("claim package: only the anchored binary-qualification closure carries a disclosure section"
779
+ + " — this projection has no qualification, and no other closure version expresses one");
649
780
  }
650
781
  return {
651
- claimSchema: anchored
652
- ? ANCHORED_CLAIM_PACKAGE_SCHEMA_ID
653
- : projection.qualification === undefined
654
- ? CLAIM_PACKAGE_SCHEMA_ID
655
- : BINARY_QUALIFICATION_CLAIM_PACKAGE_SCHEMA_ID,
782
+ claimSchema: anchoredQualification && disclosure !== undefined
783
+ ? DISCLOSED_CLAIM_PACKAGE_SCHEMA_ID
784
+ : anchoredQualification
785
+ ? ANCHORED_BINARY_QUALIFICATION_CLAIM_PACKAGE_SCHEMA_ID
786
+ : anchored
787
+ ? ANCHORED_CLAIM_PACKAGE_SCHEMA_ID
788
+ : projection.qualification === undefined
789
+ ? CLAIM_PACKAGE_SCHEMA_ID
790
+ : BINARY_QUALIFICATION_CLAIM_PACKAGE_SCHEMA_ID,
656
791
  scope: {
657
792
  draftId: input.draftId,
658
793
  benchmarkSha256: input.benchmarkSha256,
@@ -693,22 +828,36 @@ export function buildClaimPackage(input) {
693
828
  limitations: [...(reportRecord.limitations ?? [])],
694
829
  venueHonesty: input.venueHonesty,
695
830
  verification: {
696
- command: anchored
697
- ? PUBLIC_BUNDLE_V6_VERIFICATION_COMMAND
698
- : projection.qualification === undefined
699
- ? PUBLIC_BUNDLE_VERIFICATION_COMMAND
700
- : BINARY_QUALIFICATION_VERIFICATION_COMMAND,
701
- compatibleCommand: anchored
702
- ? PUBLIC_BUNDLE_V6_COMPATIBLE_VERIFICATION_COMMAND
703
- : projection.qualification === undefined
704
- ? PUBLIC_BUNDLE_COMPATIBLE_VERIFICATION_COMMAND
705
- : BINARY_QUALIFICATION_COMPATIBLE_VERIFICATION_COMMAND,
706
- checks: anchored ? [...ANCHORED_CLAIM_VERIFICATION_CHECKS] : [...CLAIM_VERIFICATION_CHECKS],
831
+ command: anchoredQualification
832
+ ? PUBLIC_BUNDLE_V7_VERIFICATION_COMMAND
833
+ : anchored
834
+ ? PUBLIC_BUNDLE_V6_VERIFICATION_COMMAND
835
+ : promptedScreening
836
+ ? PROMPTED_BINARY_QUALIFICATION_VERIFICATION_COMMAND
837
+ : projection.qualification === undefined
838
+ ? PUBLIC_BUNDLE_VERIFICATION_COMMAND
839
+ : BINARY_QUALIFICATION_VERIFICATION_COMMAND,
840
+ compatibleCommand: anchoredQualification
841
+ ? PUBLIC_BUNDLE_V7_COMPATIBLE_VERIFICATION_COMMAND
842
+ : anchored
843
+ ? PUBLIC_BUNDLE_V6_COMPATIBLE_VERIFICATION_COMMAND
844
+ : promptedScreening
845
+ ? PROMPTED_BINARY_QUALIFICATION_COMPATIBLE_VERIFICATION_COMMAND
846
+ : projection.qualification === undefined
847
+ ? PUBLIC_BUNDLE_COMPATIBLE_VERIFICATION_COMMAND
848
+ : BINARY_QUALIFICATION_COMPATIBLE_VERIFICATION_COMMAND,
849
+ checks: anchoredQualification && disclosure !== undefined
850
+ ? [...DISCLOSED_CLAIM_VERIFICATION_CHECKS]
851
+ : anchored ? [...ANCHORED_CLAIM_VERIFICATION_CHECKS] : [...CLAIM_VERIFICATION_CHECKS],
707
852
  // §9.2: the trust-root sentence is replaced only by a governing lock anchor. A bundle whose
708
853
  // only anchors are pending, or cover the Matrix alone, keeps the unconditional sentence.
709
854
  trustRoot: anchoredTrustRoot(anchors),
710
855
  },
711
856
  ...(anchored ? { anchors: anchors.map((anchor) => ({ ...anchor })) } : {}),
857
+ // Copied through, never rebuilt here: the section is `deriveDisclosureSpecification`'s output
858
+ // over the sealed record's exact bytes, and this builder is not entitled to a second opinion
859
+ // about what that record says (issue #2839, design §6.6).
860
+ ...(disclosure === undefined ? {} : { disclosure }),
712
861
  ...(input.previewDisclosure !== undefined
713
862
  ? {
714
863
  rehearsal: {