@colophon-claims/verify 0.2.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 (47) hide show
  1. package/README.md +18 -8
  2. package/dist/admission/contracts.d.ts +241 -0
  3. package/dist/admission/contracts.js +116 -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 +1 -0
  11. package/dist/admission/verification.js +88 -42
  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/claim-consistency.d.ts +8 -1
  28. package/dist/profile/claim-consistency.js +4 -4
  29. package/dist/profile/claim.d.ts +201 -2
  30. package/dist/profile/claim.js +178 -47
  31. package/dist/profile/disclosure.d.ts +273 -0
  32. package/dist/profile/disclosure.js +240 -0
  33. package/dist/profile/run-results.d.ts +8 -2
  34. package/dist/profile/run-results.js +9 -3
  35. package/dist/profile/task-selection.d.ts +69 -0
  36. package/dist/profile/task-selection.js +140 -0
  37. package/dist/reader-instructions.d.ts +38 -1
  38. package/dist/reader-instructions.js +42 -2
  39. package/dist/schema.d.ts +4 -3
  40. package/dist/schema.js +17 -0
  41. package/dist/signers.d.ts +48 -0
  42. package/dist/signers.js +77 -0
  43. package/dist/verify.d.ts +20 -6
  44. package/dist/verify.js +156 -31
  45. package/dist/version.d.ts +1 -1
  46. package/dist/version.js +1 -1
  47. package/package.json +14 -14
package/README.md CHANGED
@@ -8,17 +8,27 @@ npx @colophon-claims/verify@0.2 ./bundle
8
8
 
9
9
  Use `--json` for machine-readable output. The reader runs the checks declared by the
10
10
  bundle format, covering its manifest, evidence closure, calculations, report, and claim
11
- consistency. Exit status is `0` when every check passes, `1` for an invalid bundle, and
12
- `2` for usage or operational failures.
13
-
14
- This 0.2 reader supports public bundle formats v2, v4, v5, and v6. It intentionally
15
- rejects the unrelated accounting bundle v3. Formats v2 and v4 run six checks; the
16
- evidence-native v5 and anchored v6 formats run seven.
11
+ consistency. Exit status is `0` when the bundle is valid for the format and profile it
12
+ declares, `1` for an invalid bundle, and `2` for usage or operational failures. A check a
13
+ profile defers is reported as deferred, never as passed, and never as a failure.
14
+
15
+ This 0.2 reader supports public bundle formats v2, v4, v5, v6, v7, and v8. It
16
+ intentionally rejects the unrelated accounting bundle v3. Formats v2 and v4 run six
17
+ checks; the evidence-native v5 and the two anchored formats, v6 and v7, run seven; the
18
+ disclosed v8 runs eight. The v7 format is the anchored binary-qualification closure —
19
+ v4's members plus v6's anchors — and v8 is v7 plus a sealed six-variable
20
+ disclosure-specification record and the `disclosure-specification` check. Both exist
21
+ only from this 0.2.1 release, so their bundles pin `@0.2.1` rather than the `@0.1` line
22
+ every earlier closure stamps.
23
+ The evidence-native v5 has two declared profiles, full-evidence and metadata-first;
24
+ this reader supports both. A metadata-first bundle carries the artifact digests without
25
+ the artifact bodies, so `artifact-integrity` reports `not fetched` rather than passing,
26
+ and the verdict line counts it out of the passed total. Every other check is complete.
17
27
  Existing claim bundles retain their recorded verifier pins. Use this 0.2 reader
18
28
  for prompted-screening v2 support and the compatible `@0.2` line for this
19
29
  release.
20
- For this one-time prompted-screening v2 release, `@jinn-network/*` is pinned to
21
- the exact `0.1.0-canary.sha.e00b2fc47fc5635b007eb349fb1e41aa81bb3c50` receipt.
30
+ For this release, `@jinn-network/*` is pinned to the exact
31
+ `0.1.0-canary.sha.0533a224cf99f06d7facf0c23455f2781a5b9e62` receipt.
22
32
  It is not a floating `@canary` dependency and is not a stable stack release.
23
33
 
24
34
  Verification opens no network connection, reads no account or API credential, and uploads
@@ -10,7 +10,10 @@ export declare const BINARY_JUDGMENT_ADMISSION_MANIFEST_PROTOCOL: "https://spec.
10
10
  export declare const SCREENING_TABLE_PROTOCOL: "https://spec.jinn.network/binary-judgment/screening-table/v1";
11
11
  export declare const PROMPTED_SCREENING_PROCEDURE_PROTOCOL: "https://spec.jinn.network/binary-judgment/prompted-screening-procedure/v1";
12
12
  export declare const SCREENING_POOL_PROTOCOL: "https://spec.jinn.network/binary-judgment/screening-pool/v1";
13
+ export declare const SCREENING_POOL_V2_PROTOCOL: "https://spec.jinn.network/binary-judgment/screening-pool/v2";
14
+ export declare const SCREENING_POOL_V2_RESERVE_SELECTION_PROTOCOL: "shared-slice-first-admissible/1";
13
15
  export declare const SCREENING_SAMPLE_COMMITMENT_PROTOCOL: "https://spec.jinn.network/binary-judgment/screening-sample-commitment/v1";
16
+ export declare const REGISTERED_SCREENING_SAMPLE_COMMITMENT_V1_SCHEMA: "https://colophon-claims.github.io/locomo-judge-report/sampling-commitment/v1";
14
17
  export declare const SCREENING_TABLE_V2_PROTOCOL: "https://spec.jinn.network/binary-judgment/screening-table/v2";
15
18
  export declare const SCREENING_REVEAL_RECEIPT_PROTOCOL: "https://spec.jinn.network/binary-judgment/screening-reveal-receipt/v1";
16
19
  export declare const HUMAN_REVIEW_PACKET_MEDIA_TYPE: "application/vnd.jinn.binary-judgment.human-review-packet.v1+json";
@@ -337,6 +340,206 @@ export declare const ScreeningPoolV1Schema: z.ZodObject<{
337
340
  sealedAt: z.ZodString;
338
341
  }, z.core.$strict>;
339
342
  export type ScreeningPoolV1 = z.infer<typeof ScreeningPoolV1Schema>;
343
+ export declare const ScreeningPoolV2ItemSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
344
+ screeningIdentitySha256: z.ZodString;
345
+ itemSha256: z.ZodString;
346
+ intendedLabel: z.ZodEnum<{
347
+ CORRECT: "CORRECT";
348
+ WRONG: "WRONG";
349
+ }>;
350
+ candidateClass: z.ZodEnum<{
351
+ correct: "correct";
352
+ "specific-wrong": "specific-wrong";
353
+ "vague-topical-wrong": "vague-topical-wrong";
354
+ }>;
355
+ stratum: z.ZodEnum<{
356
+ "category-1": "category-1";
357
+ "category-2": "category-2";
358
+ "category-3": "category-3";
359
+ "category-4": "category-4";
360
+ }>;
361
+ poolPosition: z.ZodNumber;
362
+ sourceQuestionLineageId: z.ZodString;
363
+ poolKind: z.ZodLiteral<"main">;
364
+ slotId: z.ZodString;
365
+ }, z.core.$strict>, z.ZodObject<{
366
+ screeningIdentitySha256: z.ZodString;
367
+ itemSha256: z.ZodString;
368
+ intendedLabel: z.ZodEnum<{
369
+ CORRECT: "CORRECT";
370
+ WRONG: "WRONG";
371
+ }>;
372
+ candidateClass: z.ZodEnum<{
373
+ correct: "correct";
374
+ "specific-wrong": "specific-wrong";
375
+ "vague-topical-wrong": "vague-topical-wrong";
376
+ }>;
377
+ stratum: z.ZodEnum<{
378
+ "category-1": "category-1";
379
+ "category-2": "category-2";
380
+ "category-3": "category-3";
381
+ "category-4": "category-4";
382
+ }>;
383
+ poolPosition: z.ZodNumber;
384
+ sourceQuestionLineageId: z.ZodString;
385
+ poolKind: z.ZodLiteral<"reserve">;
386
+ reserveOrder: z.ZodNumber;
387
+ }, z.core.$strict>], "poolKind">;
388
+ export type ScreeningPoolV2Item = z.infer<typeof ScreeningPoolV2ItemSchema>;
389
+ /** A 240-main pool whose reserves are ordered shared slices within each class/stratum cell. */
390
+ export declare const ScreeningPoolV2Schema: z.ZodObject<{
391
+ protocol: z.ZodLiteral<"https://spec.jinn.network/binary-judgment/screening-pool/v2">;
392
+ reserveSelectionProtocol: z.ZodLiteral<"shared-slice-first-admissible/1">;
393
+ draftId: z.ZodString;
394
+ identityCommitmentSha256: z.ZodString;
395
+ items: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
396
+ screeningIdentitySha256: z.ZodString;
397
+ itemSha256: z.ZodString;
398
+ intendedLabel: z.ZodEnum<{
399
+ CORRECT: "CORRECT";
400
+ WRONG: "WRONG";
401
+ }>;
402
+ candidateClass: z.ZodEnum<{
403
+ correct: "correct";
404
+ "specific-wrong": "specific-wrong";
405
+ "vague-topical-wrong": "vague-topical-wrong";
406
+ }>;
407
+ stratum: z.ZodEnum<{
408
+ "category-1": "category-1";
409
+ "category-2": "category-2";
410
+ "category-3": "category-3";
411
+ "category-4": "category-4";
412
+ }>;
413
+ poolPosition: z.ZodNumber;
414
+ sourceQuestionLineageId: z.ZodString;
415
+ poolKind: z.ZodLiteral<"main">;
416
+ slotId: z.ZodString;
417
+ }, z.core.$strict>, z.ZodObject<{
418
+ screeningIdentitySha256: z.ZodString;
419
+ itemSha256: z.ZodString;
420
+ intendedLabel: z.ZodEnum<{
421
+ CORRECT: "CORRECT";
422
+ WRONG: "WRONG";
423
+ }>;
424
+ candidateClass: z.ZodEnum<{
425
+ correct: "correct";
426
+ "specific-wrong": "specific-wrong";
427
+ "vague-topical-wrong": "vague-topical-wrong";
428
+ }>;
429
+ stratum: z.ZodEnum<{
430
+ "category-1": "category-1";
431
+ "category-2": "category-2";
432
+ "category-3": "category-3";
433
+ "category-4": "category-4";
434
+ }>;
435
+ poolPosition: z.ZodNumber;
436
+ sourceQuestionLineageId: z.ZodString;
437
+ poolKind: z.ZodLiteral<"reserve">;
438
+ reserveOrder: z.ZodNumber;
439
+ }, z.core.$strict>], "poolKind">>;
440
+ sealedAt: z.ZodString;
441
+ }, z.core.$strict>;
442
+ export type ScreeningPoolV2 = z.infer<typeof ScreeningPoolV2Schema>;
443
+ export declare const ScreeningPoolSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
444
+ protocol: z.ZodLiteral<"https://spec.jinn.network/binary-judgment/screening-pool/v1">;
445
+ draftId: z.ZodString;
446
+ identityCommitmentSha256: z.ZodString;
447
+ items: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
448
+ itemSha256: z.ZodString;
449
+ intendedLabel: z.ZodEnum<{
450
+ CORRECT: "CORRECT";
451
+ WRONG: "WRONG";
452
+ }>;
453
+ candidateClass: z.ZodEnum<{
454
+ correct: "correct";
455
+ "specific-wrong": "specific-wrong";
456
+ "vague-topical-wrong": "vague-topical-wrong";
457
+ }>;
458
+ stratum: z.ZodEnum<{
459
+ "category-1": "category-1";
460
+ "category-2": "category-2";
461
+ "category-3": "category-3";
462
+ "category-4": "category-4";
463
+ }>;
464
+ poolPosition: z.ZodNumber;
465
+ slotId: z.ZodString;
466
+ poolKind: z.ZodLiteral<"main">;
467
+ }, z.core.$strict>, z.ZodObject<{
468
+ itemSha256: z.ZodString;
469
+ intendedLabel: z.ZodEnum<{
470
+ CORRECT: "CORRECT";
471
+ WRONG: "WRONG";
472
+ }>;
473
+ candidateClass: z.ZodEnum<{
474
+ correct: "correct";
475
+ "specific-wrong": "specific-wrong";
476
+ "vague-topical-wrong": "vague-topical-wrong";
477
+ }>;
478
+ stratum: z.ZodEnum<{
479
+ "category-1": "category-1";
480
+ "category-2": "category-2";
481
+ "category-3": "category-3";
482
+ "category-4": "category-4";
483
+ }>;
484
+ poolPosition: z.ZodNumber;
485
+ slotId: z.ZodString;
486
+ poolKind: z.ZodLiteral<"reserve">;
487
+ reserveOrder: z.ZodNumber;
488
+ }, z.core.$strict>], "poolKind">>;
489
+ sealedAt: z.ZodString;
490
+ }, z.core.$strict>, z.ZodObject<{
491
+ protocol: z.ZodLiteral<"https://spec.jinn.network/binary-judgment/screening-pool/v2">;
492
+ reserveSelectionProtocol: z.ZodLiteral<"shared-slice-first-admissible/1">;
493
+ draftId: z.ZodString;
494
+ identityCommitmentSha256: z.ZodString;
495
+ items: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
496
+ screeningIdentitySha256: z.ZodString;
497
+ itemSha256: z.ZodString;
498
+ intendedLabel: z.ZodEnum<{
499
+ CORRECT: "CORRECT";
500
+ WRONG: "WRONG";
501
+ }>;
502
+ candidateClass: z.ZodEnum<{
503
+ correct: "correct";
504
+ "specific-wrong": "specific-wrong";
505
+ "vague-topical-wrong": "vague-topical-wrong";
506
+ }>;
507
+ stratum: z.ZodEnum<{
508
+ "category-1": "category-1";
509
+ "category-2": "category-2";
510
+ "category-3": "category-3";
511
+ "category-4": "category-4";
512
+ }>;
513
+ poolPosition: z.ZodNumber;
514
+ sourceQuestionLineageId: z.ZodString;
515
+ poolKind: z.ZodLiteral<"main">;
516
+ slotId: z.ZodString;
517
+ }, z.core.$strict>, z.ZodObject<{
518
+ screeningIdentitySha256: z.ZodString;
519
+ itemSha256: z.ZodString;
520
+ intendedLabel: z.ZodEnum<{
521
+ CORRECT: "CORRECT";
522
+ WRONG: "WRONG";
523
+ }>;
524
+ candidateClass: z.ZodEnum<{
525
+ correct: "correct";
526
+ "specific-wrong": "specific-wrong";
527
+ "vague-topical-wrong": "vague-topical-wrong";
528
+ }>;
529
+ stratum: z.ZodEnum<{
530
+ "category-1": "category-1";
531
+ "category-2": "category-2";
532
+ "category-3": "category-3";
533
+ "category-4": "category-4";
534
+ }>;
535
+ poolPosition: z.ZodNumber;
536
+ sourceQuestionLineageId: z.ZodString;
537
+ poolKind: z.ZodLiteral<"reserve">;
538
+ reserveOrder: z.ZodNumber;
539
+ }, z.core.$strict>], "poolKind">>;
540
+ sealedAt: z.ZodString;
541
+ }, z.core.$strict>], "protocol">;
542
+ export type ScreeningPool = z.infer<typeof ScreeningPoolSchema>;
340
543
  export declare const ScreeningSampleCommitmentV1Schema: z.ZodObject<{
341
544
  protocol: z.ZodLiteral<"https://spec.jinn.network/binary-judgment/screening-sample-commitment/v1">;
342
545
  draftId: z.ZodString;
@@ -349,6 +552,42 @@ export declare const ScreeningSampleCommitmentV1Schema: z.ZodObject<{
349
552
  committedAt: z.ZodString;
350
553
  }, z.core.$strict>;
351
554
  export type ScreeningSampleCommitmentV1 = z.infer<typeof ScreeningSampleCommitmentV1Schema>;
555
+ /**
556
+ * Exact public identity-only commitments made by an external registration repository. Unlike the
557
+ * internal v1 envelope, this record deliberately does not claim that the later label-bearing pool
558
+ * bridge existed at registration time. Its sorted identities, product-defined pool digest, seed,
559
+ * sample size, script digest, and timestamp are sufficient to replay the committed sample.
560
+ */
561
+ export declare const RegisteredScreeningSampleCommitmentV1Schema: z.ZodObject<{
562
+ schema: z.ZodLiteral<"https://colophon-claims.github.io/locomo-judge-report/sampling-commitment/v1">;
563
+ candidateItemDigests: z.ZodArray<z.ZodString>;
564
+ committedAt: z.ZodString;
565
+ poolDigest: z.ZodString;
566
+ sampleSeed: z.ZodString;
567
+ sampleSize: z.ZodLiteral<72>;
568
+ samplingScriptSha256: z.ZodString;
569
+ }, z.core.$strict>;
570
+ export type RegisteredScreeningSampleCommitmentV1 = z.infer<typeof RegisteredScreeningSampleCommitmentV1Schema>;
571
+ export declare const ScreeningSampleCommitmentSchema: z.ZodUnion<readonly [z.ZodObject<{
572
+ protocol: z.ZodLiteral<"https://spec.jinn.network/binary-judgment/screening-sample-commitment/v1">;
573
+ draftId: z.ZodString;
574
+ poolSha256: z.ZodString;
575
+ poolIdentityCommitmentSha256: z.ZodString;
576
+ samplingProcedure: z.ZodLiteral<"screening-sample/1">;
577
+ sampleSeed: z.ZodString;
578
+ sampleSize: z.ZodLiteral<72>;
579
+ sampleItemSha256s: z.ZodArray<z.ZodString>;
580
+ committedAt: z.ZodString;
581
+ }, z.core.$strict>, z.ZodObject<{
582
+ schema: z.ZodLiteral<"https://colophon-claims.github.io/locomo-judge-report/sampling-commitment/v1">;
583
+ candidateItemDigests: z.ZodArray<z.ZodString>;
584
+ committedAt: z.ZodString;
585
+ poolDigest: z.ZodString;
586
+ sampleSeed: z.ZodString;
587
+ sampleSize: z.ZodLiteral<72>;
588
+ samplingScriptSha256: z.ZodString;
589
+ }, z.core.$strict>]>;
590
+ export type ScreeningSampleCommitment = z.infer<typeof ScreeningSampleCommitmentSchema>;
352
591
  export declare const PromptedScreeningRowV2Schema: z.ZodObject<{
353
592
  itemSha256: z.ZodString;
354
593
  intendedLabel: z.ZodEnum<{
@@ -443,6 +682,7 @@ export declare const HumanReviewReplacementLedgerEntrySchema: z.ZodObject<{
443
682
  "screening-indeterminate": "screening-indeterminate";
444
683
  "screening-hand-excluded": "screening-hand-excluded";
445
684
  }>;
685
+ receivingSlotId: z.ZodOptional<z.ZodString>;
446
686
  reviewVerdictSha256s: z.ZodOptional<z.ZodTuple<[z.ZodString, z.ZodString], null>>;
447
687
  visibilityReceiptSha256s: z.ZodOptional<z.ZodTuple<[z.ZodString, z.ZodString], null>>;
448
688
  reviewerRosterSha256: z.ZodOptional<z.ZodString>;
@@ -466,6 +706,7 @@ export declare const HumanReviewReplacementLedgerSchema: z.ZodObject<{
466
706
  "screening-indeterminate": "screening-indeterminate";
467
707
  "screening-hand-excluded": "screening-hand-excluded";
468
708
  }>;
709
+ receivingSlotId: z.ZodOptional<z.ZodString>;
469
710
  reviewVerdictSha256s: z.ZodOptional<z.ZodTuple<[z.ZodString, z.ZodString], null>>;
470
711
  visibilityReceiptSha256s: z.ZodOptional<z.ZodTuple<[z.ZodString, z.ZodString], null>>;
471
712
  reviewerRosterSha256: z.ZodOptional<z.ZodString>;
@@ -13,7 +13,10 @@ export const BINARY_JUDGMENT_ADMISSION_MANIFEST_PROTOCOL = "https://spec.jinn.ne
13
13
  export const SCREENING_TABLE_PROTOCOL = "https://spec.jinn.network/binary-judgment/screening-table/v1";
14
14
  export const PROMPTED_SCREENING_PROCEDURE_PROTOCOL = "https://spec.jinn.network/binary-judgment/prompted-screening-procedure/v1";
15
15
  export const SCREENING_POOL_PROTOCOL = "https://spec.jinn.network/binary-judgment/screening-pool/v1";
16
+ export const SCREENING_POOL_V2_PROTOCOL = "https://spec.jinn.network/binary-judgment/screening-pool/v2";
17
+ export const SCREENING_POOL_V2_RESERVE_SELECTION_PROTOCOL = "shared-slice-first-admissible/1";
16
18
  export const SCREENING_SAMPLE_COMMITMENT_PROTOCOL = "https://spec.jinn.network/binary-judgment/screening-sample-commitment/v1";
19
+ export const REGISTERED_SCREENING_SAMPLE_COMMITMENT_V1_SCHEMA = "https://colophon-claims.github.io/locomo-judge-report/sampling-commitment/v1";
17
20
  export const SCREENING_TABLE_V2_PROTOCOL = "https://spec.jinn.network/binary-judgment/screening-table/v2";
18
21
  export const SCREENING_REVEAL_RECEIPT_PROTOCOL = "https://spec.jinn.network/binary-judgment/screening-reveal-receipt/v1";
19
22
  export const HUMAN_REVIEW_PACKET_MEDIA_TYPE = "application/vnd.jinn.binary-judgment.human-review-packet.v1+json";
@@ -320,6 +323,90 @@ export const ScreeningPoolV1Schema = z.strictObject({
320
323
  ctx.addIssue({ code: "custom", path: ["items"], message: `main pool must contain 20 ${candidateClass}/${stratum} items` });
321
324
  }
322
325
  });
326
+ const ScreeningPoolV2CommonItemSchema = z.strictObject({
327
+ /** Privacy-preserving identity committed before screening outcomes were observed. */
328
+ screeningIdentitySha256: DigestSchema,
329
+ /** Digest of the exact later Colophon F0 item bytes. */
330
+ itemSha256: DigestSchema,
331
+ intendedLabel: BinaryJudgmentTruthLabelSchema,
332
+ candidateClass: z.enum(SCREENING_POOL_CANDIDATE_CLASSES),
333
+ stratum: z.enum(SCREENING_POOL_STRATA),
334
+ poolPosition: z.number().int().positive(),
335
+ sourceQuestionLineageId: IdentitySchema,
336
+ });
337
+ export const ScreeningPoolV2ItemSchema = z.discriminatedUnion("poolKind", [
338
+ ScreeningPoolV2CommonItemSchema.extend({
339
+ poolKind: z.literal("main"),
340
+ slotId: z.string().regex(/^slot-[0-9]{3}$/u),
341
+ }),
342
+ ScreeningPoolV2CommonItemSchema.extend({
343
+ poolKind: z.literal("reserve"),
344
+ reserveOrder: z.number().int().positive(),
345
+ }),
346
+ ]);
347
+ /** A 240-main pool whose reserves are ordered shared slices within each class/stratum cell. */
348
+ export const ScreeningPoolV2Schema = z.strictObject({
349
+ protocol: z.literal(SCREENING_POOL_V2_PROTOCOL),
350
+ reserveSelectionProtocol: z.literal(SCREENING_POOL_V2_RESERVE_SELECTION_PROTOCOL),
351
+ draftId: IdentitySchema,
352
+ identityCommitmentSha256: DigestSchema,
353
+ items: z.array(ScreeningPoolV2ItemSchema).length(664),
354
+ sealedAt: TimestampSchema,
355
+ }).superRefine((pool, ctx) => {
356
+ const identities = new Set();
357
+ const itemDigests = new Set();
358
+ const mains = pool.items.filter((item) => item.poolKind === "main");
359
+ const mainSlots = new Set();
360
+ const mainLineages = new Set();
361
+ for (const [index, item] of pool.items.entries()) {
362
+ if (item.poolPosition !== index + 1)
363
+ ctx.addIssue({ code: "custom", path: ["items", index, "poolPosition"], message: "poolPosition must be contiguous and match array order" });
364
+ if (identities.has(item.screeningIdentitySha256))
365
+ ctx.addIssue({ code: "custom", path: ["items", index, "screeningIdentitySha256"], message: "screeningIdentitySha256 must be unique" });
366
+ identities.add(item.screeningIdentitySha256);
367
+ if (itemDigests.has(item.itemSha256))
368
+ ctx.addIssue({ code: "custom", path: ["items", index, "itemSha256"], message: "itemSha256 must be unique" });
369
+ itemDigests.add(item.itemSha256);
370
+ const expectedLabel = item.candidateClass === "correct" ? "CORRECT" : "WRONG";
371
+ if (item.intendedLabel !== expectedLabel)
372
+ ctx.addIssue({ code: "custom", path: ["items", index, "intendedLabel"], message: "intendedLabel does not derive from candidateClass" });
373
+ if (item.poolKind === "main") {
374
+ if (mainSlots.has(item.slotId))
375
+ ctx.addIssue({ code: "custom", path: ["items", index, "slotId"], message: "main slotId must be unique" });
376
+ mainSlots.add(item.slotId);
377
+ if (mainLineages.has(item.sourceQuestionLineageId))
378
+ ctx.addIssue({ code: "custom", path: ["items", index, "sourceQuestionLineageId"], message: "main sourceQuestionLineageId must be unique" });
379
+ mainLineages.add(item.sourceQuestionLineageId);
380
+ }
381
+ }
382
+ if (mains.length !== 240)
383
+ ctx.addIssue({ code: "custom", path: ["items"], message: "pool must contain exactly 240 main items" });
384
+ const lastMainPosition = Math.max(...mains.map((item) => item.poolPosition));
385
+ for (const [index, item] of pool.items.entries()) {
386
+ if (item.poolKind === "reserve" && item.poolPosition <= lastMainPosition)
387
+ ctx.addIssue({ code: "custom", path: ["items", index, "poolPosition"], message: "all shared reserves must follow all main items in pool order" });
388
+ }
389
+ const expectedSlotIds = Array.from({ length: 240 }, (_, index) => `slot-${(index + 1).toString().padStart(3, "0")}`);
390
+ const actualSlotIds = [...mainSlots].sort(compareCodeUnitStrings);
391
+ if (actualSlotIds.length !== expectedSlotIds.length || actualSlotIds.some((value, index) => value !== expectedSlotIds[index]))
392
+ ctx.addIssue({ code: "custom", path: ["items"], message: "main slots must be exactly slot-001 through slot-240" });
393
+ for (const candidateClass of SCREENING_POOL_CANDIDATE_CLASSES)
394
+ for (const stratum of SCREENING_POOL_STRATA) {
395
+ if (mains.filter((item) => item.candidateClass === candidateClass && item.stratum === stratum).length !== 20)
396
+ ctx.addIssue({ code: "custom", path: ["items"], message: `main pool must contain 20 ${candidateClass}/${stratum} items` });
397
+ const reserves = pool.items
398
+ .filter((item) => item.poolKind === "reserve" && item.candidateClass === candidateClass && item.stratum === stratum)
399
+ .sort((left, right) => left.reserveOrder - right.reserveOrder);
400
+ for (const [index, reserve] of reserves.entries()) {
401
+ if (reserve.reserveOrder !== index + 1)
402
+ ctx.addIssue({ code: "custom", path: ["items"], message: `${candidateClass}/${stratum} reserveOrder must be contiguous and unique` });
403
+ }
404
+ }
405
+ });
406
+ export const ScreeningPoolSchema = z.discriminatedUnion("protocol", [
407
+ ScreeningPoolV1Schema,
408
+ ScreeningPoolV2Schema,
409
+ ]);
323
410
  export const ScreeningSampleCommitmentV1Schema = z.strictObject({
324
411
  protocol: z.literal(SCREENING_SAMPLE_COMMITMENT_PROTOCOL),
325
412
  draftId: IdentitySchema,
@@ -336,6 +423,32 @@ export const ScreeningSampleCommitmentV1Schema = z.strictObject({
336
423
  }),
337
424
  committedAt: TimestampSchema,
338
425
  });
426
+ /**
427
+ * Exact public identity-only commitments made by an external registration repository. Unlike the
428
+ * internal v1 envelope, this record deliberately does not claim that the later label-bearing pool
429
+ * bridge existed at registration time. Its sorted identities, product-defined pool digest, seed,
430
+ * sample size, script digest, and timestamp are sufficient to replay the committed sample.
431
+ */
432
+ export const RegisteredScreeningSampleCommitmentV1Schema = z.strictObject({
433
+ schema: z.literal(REGISTERED_SCREENING_SAMPLE_COMMITMENT_V1_SCHEMA),
434
+ candidateItemDigests: z.array(DigestSchema).length(664),
435
+ committedAt: TimestampSchema,
436
+ poolDigest: DigestSchema,
437
+ sampleSeed: z.string().min(1),
438
+ sampleSize: z.literal(72),
439
+ samplingScriptSha256: DigestSchema,
440
+ }).superRefine((commitment, ctx) => {
441
+ for (let index = 1; index < commitment.candidateItemDigests.length; index += 1) {
442
+ if (compareCodeUnitStrings(commitment.candidateItemDigests[index - 1], commitment.candidateItemDigests[index]) >= 0) {
443
+ ctx.addIssue({ code: "custom", path: ["candidateItemDigests"], message: "registered identities must be sorted and unique" });
444
+ return;
445
+ }
446
+ }
447
+ });
448
+ export const ScreeningSampleCommitmentSchema = z.union([
449
+ ScreeningSampleCommitmentV1Schema,
450
+ RegisteredScreeningSampleCommitmentV1Schema,
451
+ ]);
339
452
  const ScreeningDecisionSchema = z.enum(["CORRECT", "WRONG", "UNSURE"]);
340
453
  const RitsuScreeningDecisionSchema = z.discriminatedUnion("checked", [
341
454
  z.strictObject({ checked: z.literal(false) }),
@@ -396,6 +509,9 @@ export const HumanReviewReplacementLedgerEntrySchema = z.strictObject({
396
509
  excludedPoolPosition: z.number().int().positive(),
397
510
  replacementPoolPosition: z.number().int().positive(),
398
511
  reason: z.enum([...HUMAN_REVIEW_LEDGER_REASONS, ...SCREENING_LEDGER_REASONS]),
512
+ // Required by closure replay for shared-slice pool/v2 replacements. It names the main slot that
513
+ // receives the reserve; the reserve itself carries no static slot ownership.
514
+ receivingSlotId: z.string().regex(/^slot-[0-9]{3}$/u).optional(),
399
515
  // Per-item two-human apparatus (spec §6.9 deliberately drops it for the screened branch):
400
516
  // present if and only if `reason` is one of the three two-human reasons above.
401
517
  reviewVerdictSha256s: SortedUniqueDigestPairSchema.optional(),
@@ -1,5 +1,7 @@
1
1
  export * from "./application.js";
2
2
  export * from "./contracts.js";
3
3
  export * from "./intake.js";
4
+ export * from "./prompted-selection.js";
5
+ export * from "./prompted-commitment.js";
4
6
  export * from "./screening-sample.js";
5
7
  export * from "./verification.js";
@@ -1,5 +1,7 @@
1
1
  export * from "./application.js";
2
2
  export * from "./contracts.js";
3
3
  export * from "./intake.js";
4
+ export * from "./prompted-selection.js";
5
+ export * from "./prompted-commitment.js";
4
6
  export * from "./screening-sample.js";
5
7
  export * from "./verification.js";
@@ -0,0 +1,19 @@
1
+ import { type ScreeningSampleCommitment } from "./contracts.js";
2
+ export interface PromptedScreeningCommitmentView {
3
+ readonly committedAt: string;
4
+ readonly poolIdentityCommitmentSha256: string;
5
+ readonly sampleSeed: string;
6
+ readonly sampleSize: 72;
7
+ readonly registeredIdentitySha256s?: readonly string[];
8
+ readonly samplingScriptSha256?: string;
9
+ readonly sampleItemSha256s?: readonly string[];
10
+ readonly draftId?: string;
11
+ readonly poolSha256?: string;
12
+ }
13
+ /**
14
+ * Parse the two exact wire encodings. Internal Colophon envelopes use bare canonical JSON;
15
+ * external registration files use the canonical one-record JSONL form with exactly one final LF.
16
+ */
17
+ export declare function parseScreeningSampleCommitmentBytes(bytes: Uint8Array): ScreeningSampleCommitment;
18
+ /** Normalize the original internal envelope and exact external registration shape for replay. */
19
+ export declare function promptedScreeningCommitmentView(commitment: ScreeningSampleCommitment): PromptedScreeningCommitmentView;
@@ -0,0 +1,52 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ import { canonicalJsonBytes } from "@jinn-network/task-execution-profiles";
3
+ import { ScreeningSampleCommitmentSchema, } from "./contracts.js";
4
+ function sameBytes(left, right) {
5
+ return left.byteLength === right.byteLength && left.every((byte, index) => byte === right[index]);
6
+ }
7
+ /**
8
+ * Parse the two exact wire encodings. Internal Colophon envelopes use bare canonical JSON;
9
+ * external registration files use the canonical one-record JSONL form with exactly one final LF.
10
+ */
11
+ export function parseScreeningSampleCommitmentBytes(bytes) {
12
+ let input;
13
+ try {
14
+ input = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
15
+ }
16
+ catch {
17
+ throw new TypeError("screening sample commitment is not UTF-8 JSON");
18
+ }
19
+ const parsed = ScreeningSampleCommitmentSchema.safeParse(input);
20
+ if (!parsed.success)
21
+ throw new TypeError(`screening sample commitment failed its closed schema: ${parsed.error.message}`);
22
+ const canonical = canonicalJsonBytes(parsed.data);
23
+ const expected = "candidateItemDigests" in parsed.data
24
+ ? new Uint8Array([...canonical, 0x0a])
25
+ : canonical;
26
+ if (!sameBytes(bytes, expected)) {
27
+ throw new TypeError("screening sample commitment is not in its exact canonical wire encoding");
28
+ }
29
+ return parsed.data;
30
+ }
31
+ /** Normalize the original internal envelope and exact external registration shape for replay. */
32
+ export function promptedScreeningCommitmentView(commitment) {
33
+ if (!("candidateItemDigests" in commitment)) {
34
+ return {
35
+ committedAt: commitment.committedAt,
36
+ poolIdentityCommitmentSha256: commitment.poolIdentityCommitmentSha256,
37
+ sampleSeed: commitment.sampleSeed,
38
+ sampleSize: commitment.sampleSize,
39
+ sampleItemSha256s: commitment.sampleItemSha256s,
40
+ draftId: commitment.draftId,
41
+ poolSha256: commitment.poolSha256,
42
+ };
43
+ }
44
+ return {
45
+ committedAt: commitment.committedAt,
46
+ poolIdentityCommitmentSha256: commitment.poolDigest,
47
+ sampleSeed: commitment.sampleSeed,
48
+ sampleSize: commitment.sampleSize,
49
+ registeredIdentitySha256s: commitment.candidateItemDigests,
50
+ samplingScriptSha256: commitment.samplingScriptSha256,
51
+ };
52
+ }
@@ -0,0 +1,24 @@
1
+ import { type PromptedScreeningRowV2, type ScreeningPool } from "./contracts.js";
2
+ type PoolItem = ScreeningPool["items"][number];
3
+ type MainItem = Extract<PoolItem, {
4
+ poolKind: "main";
5
+ }>;
6
+ type ReserveItem = Extract<PoolItem, {
7
+ poolKind: "reserve";
8
+ }>;
9
+ export interface PromptedScreeningReplacement {
10
+ readonly excludedMain: MainItem;
11
+ readonly replacement: ReserveItem;
12
+ readonly receivingSlotId: string;
13
+ }
14
+ export interface PromptedScreeningSelection {
15
+ readonly winners: readonly PoolItem[];
16
+ readonly replacements: readonly PromptedScreeningReplacement[];
17
+ readonly participatingItemSha256s: ReadonlySet<string>;
18
+ }
19
+ export declare class PromptedScreeningSelectionError extends Error {
20
+ constructor(message: string);
21
+ }
22
+ /** Deterministic prompted-screening winner selection shared by the producer and portable replay. */
23
+ export declare function selectPromptedScreeningPool(pool: ScreeningPool, rows: ReadonlyMap<string, PromptedScreeningRowV2>): PromptedScreeningSelection;
24
+ export {};
@@ -0,0 +1,85 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ import { compareCodeUnitStrings } from "@jinn-network/task-execution-profiles";
3
+ import { SCREENING_POOL_V2_PROTOCOL, } from "./contracts.js";
4
+ export class PromptedScreeningSelectionError extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ this.name = "PromptedScreeningSelectionError";
8
+ }
9
+ }
10
+ function admitted(row) {
11
+ const agreed = row.screeningVerdict !== "UNSURE" && row.screeningVerdict === row.intendedLabel;
12
+ return row.ritsuDecision.checked ? row.ritsuDecision.verdict === "confirm" : agreed;
13
+ }
14
+ function v1Selection(pool, rows) {
15
+ const winners = [];
16
+ const replacements = [];
17
+ const participating = new Set();
18
+ const slotIds = [...new Set(pool.items.map((item) => item.slotId))].sort(compareCodeUnitStrings);
19
+ for (const slotId of slotIds) {
20
+ const slotItems = pool.items.filter((item) => item.slotId === slotId);
21
+ const main = slotItems.find((item) => item.poolKind === "main");
22
+ const reserves = slotItems
23
+ .filter((item) => item.poolKind === "reserve")
24
+ .sort((left, right) => left.reserveOrder - right.reserveOrder);
25
+ const winner = [main, ...reserves].find((item) => admitted(rows.get(item.itemSha256)));
26
+ if (winner === undefined)
27
+ throw new PromptedScreeningSelectionError(`${slotId} has no admissible main or reserve candidate`);
28
+ winners.push(winner);
29
+ participating.add(winner.itemSha256);
30
+ if (winner.poolKind === "reserve") {
31
+ participating.add(main.itemSha256);
32
+ replacements.push({ excludedMain: main, replacement: winner, receivingSlotId: slotId });
33
+ }
34
+ }
35
+ return { winners, replacements, participatingItemSha256s: participating };
36
+ }
37
+ function v2Selection(pool, rows) {
38
+ const mains = pool.items
39
+ .filter((item) => item.poolKind === "main")
40
+ .sort((left, right) => compareCodeUnitStrings(left.slotId, right.slotId));
41
+ const winners = [];
42
+ const replacements = [];
43
+ const participating = new Set();
44
+ const activeLineages = new Set();
45
+ for (const main of mains) {
46
+ if (!admitted(rows.get(main.itemSha256)))
47
+ continue;
48
+ if (activeLineages.has(main.sourceQuestionLineageId)) {
49
+ throw new PromptedScreeningSelectionError(`admitted mains duplicate source-question lineage ${main.sourceQuestionLineageId}`);
50
+ }
51
+ activeLineages.add(main.sourceQuestionLineageId);
52
+ winners.push(main);
53
+ participating.add(main.itemSha256);
54
+ }
55
+ for (const main of mains) {
56
+ if (admitted(rows.get(main.itemSha256)))
57
+ continue;
58
+ const reserves = pool.items
59
+ .filter((item) => item.poolKind === "reserve"
60
+ && item.candidateClass === main.candidateClass
61
+ && item.stratum === main.stratum)
62
+ .sort((left, right) => left.reserveOrder - right.reserveOrder);
63
+ const replacement = reserves.find((reserve) => admitted(rows.get(reserve.itemSha256))
64
+ && !activeLineages.has(reserve.sourceQuestionLineageId));
65
+ if (replacement === undefined) {
66
+ throw new PromptedScreeningSelectionError(`${main.slotId} has no admissible unused-lineage reserve in ${main.candidateClass}/${main.stratum}`);
67
+ }
68
+ activeLineages.add(replacement.sourceQuestionLineageId);
69
+ winners.push(replacement);
70
+ participating.add(main.itemSha256);
71
+ participating.add(replacement.itemSha256);
72
+ replacements.push({ excludedMain: main, replacement, receivingSlotId: main.slotId });
73
+ }
74
+ return {
75
+ winners: winners.sort((left, right) => left.poolPosition - right.poolPosition),
76
+ replacements,
77
+ participatingItemSha256s: participating,
78
+ };
79
+ }
80
+ /** Deterministic prompted-screening winner selection shared by the producer and portable replay. */
81
+ export function selectPromptedScreeningPool(pool, rows) {
82
+ return pool.protocol === SCREENING_POOL_V2_PROTOCOL
83
+ ? v2Selection(pool, rows)
84
+ : v1Selection(pool, rows);
85
+ }