@peasant-labs/schema 0.1.0-rc10

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 (37) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +66 -0
  3. package/dist/fixtures/quality.d.ts +81 -0
  4. package/dist/fixtures/quality.js +36 -0
  5. package/dist/fixtures/timeline.d.ts +53 -0
  6. package/dist/fixtures/timeline.js +2 -0
  7. package/dist/fixtures.d.ts +2 -0
  8. package/dist/fixtures.js +2 -0
  9. package/dist/index.d.ts +8 -0
  10. package/dist/index.js +36 -0
  11. package/dist/internal/generated/contract/zod.gen.d.ts +3441 -0
  12. package/dist/internal/generated/contract/zod.gen.js +1623 -0
  13. package/dist/internal/generated/enums.gen.d.ts +406 -0
  14. package/dist/internal/generated/enums.gen.js +445 -0
  15. package/dist/internal/generated/local-api.d.ts +1047 -0
  16. package/dist/internal/generated/local-api.js +1 -0
  17. package/dist/internal/generated/public-contract.gen.d.ts +2 -0
  18. package/dist/internal/generated/public-contract.gen.js +2 -0
  19. package/dist/internal/generated/quality-fixtures.gen.d.ts +2 -0
  20. package/dist/internal/generated/quality-fixtures.gen.js +367 -0
  21. package/dist/internal/generated/testcase.gen.d.ts +17 -0
  22. package/dist/internal/generated/testcase.gen.js +20 -0
  23. package/dist/internal/generated/timeline-fixtures.gen.d.ts +2 -0
  24. package/dist/internal/generated/timeline-fixtures.gen.js +1554 -0
  25. package/dist/internal/generated/versions.gen.d.ts +4 -0
  26. package/dist/internal/generated/versions.gen.js +5 -0
  27. package/dist/internal/generated/village-api.d.ts +652 -0
  28. package/dist/internal/generated/village-api.js +1 -0
  29. package/dist/local-api.d.ts +4 -0
  30. package/dist/local-api.js +1 -0
  31. package/dist/testcase.d.ts +32 -0
  32. package/dist/testcase.js +144 -0
  33. package/dist/types.d.ts +2 -0
  34. package/dist/types.js +2 -0
  35. package/dist/village-api.d.ts +4 -0
  36. package/dist/village-api.js +1 -0
  37. package/package.json +75 -0
@@ -0,0 +1,1554 @@
1
+ export const canonicalTimelineFixtures = {
2
+ "cases": [
3
+ {
4
+ "family": "many-to-many-bindings",
5
+ "name": "many_to_many_bindings",
6
+ "input": {
7
+ "sessions": [
8
+ {
9
+ "sessionId": "session-a",
10
+ "title": "first task",
11
+ "harness": "claude-code",
12
+ "startMs": 2000,
13
+ "hasCommitBinding": true
14
+ },
15
+ {
16
+ "sessionId": "session-b",
17
+ "title": "second task",
18
+ "harness": "codex",
19
+ "startMs": 1000,
20
+ "hasCommitBinding": true
21
+ }
22
+ ],
23
+ "commits": [
24
+ {
25
+ "hash": "commit-2",
26
+ "subject": "follow-up",
27
+ "hasSession": true,
28
+ "sessionIds": [
29
+ "session-a",
30
+ "session-b"
31
+ ],
32
+ "associations": [
33
+ {
34
+ "id": "assoc-commit-2-a",
35
+ "sessionId": "session-a",
36
+ "conclusion": "confirmed",
37
+ "confidence": "high",
38
+ "evidence": [
39
+ {
40
+ "kind": "recorded_commit",
41
+ "recordedCommitHash": "commit-2"
42
+ }
43
+ ]
44
+ },
45
+ {
46
+ "id": "assoc-commit-2-b",
47
+ "sessionId": "session-b",
48
+ "conclusion": "candidate",
49
+ "confidence": "medium",
50
+ "evidence": [
51
+ {
52
+ "kind": "touched_file",
53
+ "touchedFilePath": "src/main.go"
54
+ }
55
+ ]
56
+ }
57
+ ]
58
+ },
59
+ {
60
+ "hash": "commit-1",
61
+ "subject": "setup",
62
+ "hasSession": true,
63
+ "sessionIds": [
64
+ "session-a"
65
+ ],
66
+ "associations": [
67
+ {
68
+ "id": "assoc-commit-1-a",
69
+ "sessionId": "session-a",
70
+ "conclusion": "confirmed",
71
+ "confidence": "high",
72
+ "evidence": [
73
+ {
74
+ "kind": "recorded_commit",
75
+ "recordedCommitHash": "commit-1"
76
+ }
77
+ ]
78
+ }
79
+ ]
80
+ }
81
+ ]
82
+ },
83
+ "expected": {},
84
+ "classification": "must-pass",
85
+ "provenance": {
86
+ "source": "requirement",
87
+ "ref": "authoritative many-to-many commit bindings"
88
+ },
89
+ "mutation": {
90
+ "description": "binds two sessions to one commit and one session to two commits"
91
+ }
92
+ },
93
+ {
94
+ "family": "unattached-session",
95
+ "name": "session_not_linked_to_commit",
96
+ "input": {
97
+ "sessions": [
98
+ {
99
+ "sessionId": "session-unlinked",
100
+ "title": "exploration",
101
+ "harness": "opencode",
102
+ "startMs": 3000,
103
+ "hasCommitBinding": false
104
+ }
105
+ ],
106
+ "commits": [
107
+ {
108
+ "hash": "commit-empty",
109
+ "subject": "manual change",
110
+ "hasSession": false,
111
+ "sessionIds": [],
112
+ "associations": []
113
+ }
114
+ ]
115
+ },
116
+ "expected": {},
117
+ "classification": "must-pass",
118
+ "provenance": {
119
+ "source": "requirement",
120
+ "ref": "unattached sessions remain discoverable"
121
+ },
122
+ "mutation": {
123
+ "description": "includes a visible session with no commit association"
124
+ }
125
+ },
126
+ {
127
+ "family": "binding-outside-window",
128
+ "name": "binding_outside_default_branch_window",
129
+ "input": {
130
+ "sessions": [
131
+ {
132
+ "sessionId": "session-old",
133
+ "title": "older recorded work",
134
+ "harness": "claude-code",
135
+ "hasCommitBinding": true
136
+ }
137
+ ],
138
+ "commits": [
139
+ {
140
+ "hash": "commit-recent",
141
+ "subject": "recent manual change",
142
+ "hasSession": false,
143
+ "sessionIds": [],
144
+ "associations": []
145
+ }
146
+ ]
147
+ },
148
+ "expected": {},
149
+ "classification": "must-pass",
150
+ "provenance": {
151
+ "source": "requirement",
152
+ "ref": "complete binding truth exceeds bounded commit window"
153
+ },
154
+ "mutation": {
155
+ "description": "keeps binding truth when the associated commit is outside the returned window"
156
+ }
157
+ },
158
+ {
159
+ "family": "rewrite-ledger-bound-session",
160
+ "name": "rewrite_ledger_references_bound_session",
161
+ "input": {
162
+ "sessions": [
163
+ {
164
+ "sessionId": "session-rewrite-bound",
165
+ "title": "squash repair",
166
+ "harness": "claude-code",
167
+ "hasCommitBinding": true
168
+ }
169
+ ],
170
+ "commits": [
171
+ {
172
+ "hash": "successor-bound",
173
+ "subject": "squash successor",
174
+ "hasSession": false,
175
+ "sessionIds": [],
176
+ "associations": []
177
+ }
178
+ ],
179
+ "rewrittenCommits": [
180
+ {
181
+ "ghostHash": "ghost-bound",
182
+ "subject": "squash repair",
183
+ "sessionIds": [
184
+ "session-rewrite-bound"
185
+ ],
186
+ "associations": [
187
+ {
188
+ "id": "assoc-ghost-bound",
189
+ "sessionId": "session-rewrite-bound",
190
+ "conclusion": "confirmed",
191
+ "confidence": "high",
192
+ "evidence": [
193
+ {
194
+ "kind": "recorded_commit",
195
+ "recordedCommitHash": "ghost-bound"
196
+ }
197
+ ]
198
+ }
199
+ ],
200
+ "successorHash": "successor-bound",
201
+ "resolution": "rewritten",
202
+ "method": "patch_id",
203
+ "confidence": "high"
204
+ }
205
+ ]
206
+ },
207
+ "expected": {},
208
+ "classification": "must-pass",
209
+ "provenance": {
210
+ "source": "requirement",
211
+ "ref": "rewrite ledger references require authoritative binding truth"
212
+ },
213
+ "mutation": {
214
+ "description": "references a bound timeline session from the rewrite ledger and names a visible successor"
215
+ }
216
+ },
217
+ {
218
+ "family": "rewrite-ledger-binding-truth",
219
+ "name": "rewrite_ledger_reference_requires_binding_truth",
220
+ "input": {
221
+ "sessions": [
222
+ {
223
+ "sessionId": "session-rewrite-unbound",
224
+ "title": "stale ledger",
225
+ "harness": "codex",
226
+ "hasCommitBinding": false
227
+ }
228
+ ],
229
+ "commits": [
230
+ {
231
+ "hash": "successor-unbound",
232
+ "subject": "stale successor",
233
+ "hasSession": false,
234
+ "sessionIds": [],
235
+ "associations": []
236
+ }
237
+ ],
238
+ "rewrittenCommits": [
239
+ {
240
+ "ghostHash": "ghost-unbound",
241
+ "subject": "stale ledger",
242
+ "sessionIds": [
243
+ "session-rewrite-unbound"
244
+ ],
245
+ "associations": [
246
+ {
247
+ "id": "assoc-ghost-unbound",
248
+ "sessionId": "session-rewrite-unbound",
249
+ "conclusion": "confirmed",
250
+ "confidence": "high",
251
+ "evidence": [
252
+ {
253
+ "kind": "recorded_commit",
254
+ "recordedCommitHash": "ghost-unbound"
255
+ }
256
+ ]
257
+ }
258
+ ],
259
+ "successorHash": "successor-unbound",
260
+ "resolution": "rewritten",
261
+ "method": "patch_id",
262
+ "confidence": "high"
263
+ }
264
+ ]
265
+ },
266
+ "expected": {
267
+ "errorContains": "hasCommitBinding=false",
268
+ "repair": {
269
+ "kind": "set_session_binding_true",
270
+ "sessionId": "session-rewrite-unbound",
271
+ "postMutationValid": true
272
+ }
273
+ },
274
+ "classification": "must-fail",
275
+ "provenance": {
276
+ "source": "bug",
277
+ "ref": "ReviewListPayload rewrite ledger references must imply HasCommitBinding"
278
+ },
279
+ "mutation": {
280
+ "description": "points a rewrite ledger row at an existing timeline session whose binding truth is false"
281
+ }
282
+ },
283
+ {
284
+ "family": "rewrite-successor-association-identity",
285
+ "name": "rewrite_successor_preserves_association_identity",
286
+ "input": {
287
+ "sessions": [
288
+ {
289
+ "sessionId": "session-rewrite-preserved",
290
+ "title": "preserved relationship",
291
+ "harness": "claude-code",
292
+ "hasCommitBinding": true
293
+ }
294
+ ],
295
+ "commits": [
296
+ {
297
+ "hash": "successor-preserved",
298
+ "subject": "squash successor",
299
+ "hasSession": true,
300
+ "sessionIds": [
301
+ "session-rewrite-preserved"
302
+ ],
303
+ "associations": [
304
+ {
305
+ "id": "assoc-rewrite-preserved",
306
+ "sessionId": "session-rewrite-preserved",
307
+ "conclusion": "confirmed",
308
+ "confidence": "high",
309
+ "evidence": [
310
+ {
311
+ "kind": "recorded_commit",
312
+ "recordedCommitHash": "ghost-preserved"
313
+ }
314
+ ]
315
+ }
316
+ ]
317
+ }
318
+ ],
319
+ "rewrittenCommits": [
320
+ {
321
+ "ghostHash": "ghost-preserved",
322
+ "subject": "original recorded commit",
323
+ "sessionIds": [
324
+ "session-rewrite-preserved"
325
+ ],
326
+ "associations": [
327
+ {
328
+ "id": "assoc-rewrite-preserved",
329
+ "sessionId": "session-rewrite-preserved",
330
+ "conclusion": "confirmed",
331
+ "confidence": "high",
332
+ "evidence": [
333
+ {
334
+ "kind": "recorded_commit",
335
+ "recordedCommitHash": "ghost-preserved"
336
+ }
337
+ ]
338
+ }
339
+ ],
340
+ "successorHash": "successor-preserved",
341
+ "resolution": "rewritten",
342
+ "method": "patch_id",
343
+ "confidence": "high"
344
+ }
345
+ ]
346
+ },
347
+ "expected": {},
348
+ "classification": "must-pass",
349
+ "provenance": {
350
+ "source": "requirement",
351
+ "ref": "displayed rewrite successors preserve the first-class association object when they represent the binding"
352
+ },
353
+ "mutation": {
354
+ "description": "carries the same durable association ID, conclusion, confidence, and observation from the ledger to its displayed successor"
355
+ }
356
+ },
357
+ {
358
+ "family": "rewrite-ledger-missing-session",
359
+ "name": "rewrite_ledger_references_missing_session",
360
+ "input": {
361
+ "sessions": [],
362
+ "commits": [
363
+ {
364
+ "hash": "successor-missing-session",
365
+ "subject": "stale successor",
366
+ "hasSession": false,
367
+ "sessionIds": [],
368
+ "associations": []
369
+ }
370
+ ],
371
+ "rewrittenCommits": [
372
+ {
373
+ "ghostHash": "ghost-missing-session",
374
+ "subject": "stale ledger",
375
+ "sessionIds": [
376
+ "session-rewrite-missing"
377
+ ],
378
+ "associations": [
379
+ {
380
+ "id": "assoc-ghost-missing",
381
+ "sessionId": "session-rewrite-missing",
382
+ "conclusion": "confirmed",
383
+ "confidence": "high",
384
+ "evidence": [
385
+ {
386
+ "kind": "recorded_commit",
387
+ "recordedCommitHash": "ghost-missing-session"
388
+ }
389
+ ]
390
+ }
391
+ ],
392
+ "successorHash": "successor-missing-session",
393
+ "resolution": "rewritten",
394
+ "method": "patch_id",
395
+ "confidence": "high"
396
+ }
397
+ ]
398
+ },
399
+ "expected": {
400
+ "errorContains": "references sessionId \"session-rewrite-missing\" but that session is absent from sessions"
401
+ },
402
+ "classification": "must-fail",
403
+ "provenance": {
404
+ "source": "bug",
405
+ "ref": "rewrite ledger session references must name timeline sessions"
406
+ },
407
+ "mutation": {
408
+ "description": "points a rewrite ledger row at a session absent from the timeline session catalog"
409
+ }
410
+ },
411
+ {
412
+ "family": "non-default-branch-binding",
413
+ "name": "binding_on_non_default_branch",
414
+ "input": {
415
+ "sessions": [
416
+ {
417
+ "sessionId": "session-branch",
418
+ "title": "branch work",
419
+ "harness": "codex",
420
+ "hasCommitBinding": true
421
+ }
422
+ ],
423
+ "commits": []
424
+ },
425
+ "expected": {},
426
+ "classification": "must-pass",
427
+ "provenance": {
428
+ "source": "requirement",
429
+ "ref": "branch bindings are authoritative without inference"
430
+ },
431
+ "mutation": {
432
+ "description": "keeps binding truth when no default-branch commit is visible"
433
+ }
434
+ },
435
+ {
436
+ "family": "stable-session-order",
437
+ "name": "stable_equal_timestamp_and_missing_timestamp_order",
438
+ "input": {
439
+ "sessions": [
440
+ {
441
+ "sessionId": "session-a",
442
+ "title": "first tie",
443
+ "harness": "claude-code",
444
+ "startMs": 4000,
445
+ "hasCommitBinding": false
446
+ },
447
+ {
448
+ "sessionId": "session-b",
449
+ "title": "second tie",
450
+ "harness": "codex",
451
+ "startMs": 4000,
452
+ "hasCommitBinding": false
453
+ },
454
+ {
455
+ "sessionId": "session-c",
456
+ "title": "unknown time",
457
+ "harness": "opencode",
458
+ "hasCommitBinding": false
459
+ }
460
+ ],
461
+ "commits": []
462
+ },
463
+ "expected": {},
464
+ "classification": "must-pass",
465
+ "provenance": {
466
+ "source": "requirement",
467
+ "ref": "deterministic session timeline ordering"
468
+ },
469
+ "mutation": {
470
+ "description": "combines timestamp ties and a missing timestamp in canonical order"
471
+ }
472
+ },
473
+ {
474
+ "family": "descending-timestamp-order",
475
+ "name": "newer_timestamp_must_precede_older",
476
+ "input": {
477
+ "sessions": [
478
+ {
479
+ "sessionId": "session-old",
480
+ "title": "older",
481
+ "harness": "claude-code",
482
+ "startMs": 1000,
483
+ "hasCommitBinding": false
484
+ },
485
+ {
486
+ "sessionId": "session-new",
487
+ "title": "newer",
488
+ "harness": "codex",
489
+ "startMs": 2000,
490
+ "hasCommitBinding": false
491
+ }
492
+ ],
493
+ "commits": []
494
+ },
495
+ "expected": {
496
+ "errorContains": "violate canonical ordering"
497
+ },
498
+ "classification": "must-fail",
499
+ "provenance": {
500
+ "source": "boundary",
501
+ "ref": "descending known start timestamp order"
502
+ },
503
+ "mutation": {
504
+ "description": "places a newer timestamp after an older timestamp"
505
+ }
506
+ },
507
+ {
508
+ "family": "equal-timestamp-tiebreak",
509
+ "name": "equal_timestamp_uses_session_identity_tiebreak",
510
+ "input": {
511
+ "sessions": [
512
+ {
513
+ "sessionId": "session-b",
514
+ "title": "second",
515
+ "harness": "codex",
516
+ "startMs": 3000,
517
+ "hasCommitBinding": false
518
+ },
519
+ {
520
+ "sessionId": "session-a",
521
+ "title": "first",
522
+ "harness": "claude-code",
523
+ "startMs": 3000,
524
+ "hasCommitBinding": false
525
+ }
526
+ ],
527
+ "commits": []
528
+ },
529
+ "expected": {
530
+ "errorContains": "violate canonical ordering"
531
+ },
532
+ "classification": "must-fail",
533
+ "provenance": {
534
+ "source": "boundary",
535
+ "ref": "stable equal-timestamp tie break"
536
+ },
537
+ "mutation": {
538
+ "description": "reverses session identity order for equal timestamps"
539
+ }
540
+ },
541
+ {
542
+ "family": "missing-timestamp-order",
543
+ "name": "missing_timestamp_follows_known_timestamp",
544
+ "input": {
545
+ "sessions": [
546
+ {
547
+ "sessionId": "session-unknown",
548
+ "title": "unknown",
549
+ "harness": "opencode",
550
+ "hasCommitBinding": false
551
+ },
552
+ {
553
+ "sessionId": "session-known",
554
+ "title": "known",
555
+ "harness": "claude-code",
556
+ "startMs": 3000,
557
+ "hasCommitBinding": false
558
+ }
559
+ ],
560
+ "commits": []
561
+ },
562
+ "expected": {
563
+ "errorContains": "violate canonical ordering"
564
+ },
565
+ "classification": "must-fail",
566
+ "provenance": {
567
+ "source": "boundary",
568
+ "ref": "missing timestamps sort after known timestamps"
569
+ },
570
+ "mutation": {
571
+ "description": "places a missing timestamp before a known timestamp"
572
+ }
573
+ },
574
+ {
575
+ "family": "compatibility-boolean",
576
+ "name": "boolean_mirror_mismatch",
577
+ "input": {
578
+ "sessions": [
579
+ {
580
+ "sessionId": "session-a",
581
+ "title": "work",
582
+ "harness": "claude-code",
583
+ "hasCommitBinding": true
584
+ }
585
+ ],
586
+ "commits": [
587
+ {
588
+ "hash": "commit-a",
589
+ "subject": "work",
590
+ "hasSession": false,
591
+ "sessionIds": [
592
+ "session-a"
593
+ ],
594
+ "associations": [
595
+ {
596
+ "id": "assoc-boolean-a",
597
+ "sessionId": "session-a",
598
+ "conclusion": "confirmed",
599
+ "confidence": "high",
600
+ "evidence": [
601
+ {
602
+ "kind": "recorded_commit",
603
+ "recordedCommitHash": "commit-a"
604
+ }
605
+ ]
606
+ }
607
+ ]
608
+ }
609
+ ]
610
+ },
611
+ "expected": {
612
+ "errorContains": "hasSession must mirror"
613
+ },
614
+ "classification": "must-fail",
615
+ "provenance": {
616
+ "source": "requirement",
617
+ "ref": "compatibility boolean mirrors authoritative bindings"
618
+ },
619
+ "mutation": {
620
+ "description": "clears hasSession while retaining a session binding"
621
+ }
622
+ },
623
+ {
624
+ "family": "complete-binding-truth",
625
+ "name": "visible_reference_requires_complete_binding_truth",
626
+ "input": {
627
+ "sessions": [
628
+ {
629
+ "sessionId": "session-a",
630
+ "title": "work",
631
+ "harness": "claude-code",
632
+ "hasCommitBinding": false
633
+ }
634
+ ],
635
+ "commits": [
636
+ {
637
+ "hash": "commit-a",
638
+ "subject": "work",
639
+ "hasSession": true,
640
+ "sessionIds": [
641
+ "session-a"
642
+ ],
643
+ "associations": [
644
+ {
645
+ "id": "assoc-binding-a",
646
+ "sessionId": "session-a",
647
+ "conclusion": "confirmed",
648
+ "confidence": "high",
649
+ "evidence": [
650
+ {
651
+ "kind": "recorded_commit",
652
+ "recordedCommitHash": "commit-a"
653
+ }
654
+ ]
655
+ }
656
+ ]
657
+ }
658
+ ]
659
+ },
660
+ "expected": {
661
+ "errorContains": "hasCommitBinding=false"
662
+ },
663
+ "classification": "must-fail",
664
+ "provenance": {
665
+ "source": "requirement",
666
+ "ref": "visible binding proves complete session binding truth"
667
+ },
668
+ "mutation": {
669
+ "description": "marks a visibly bound session as having no authoritative binding"
670
+ }
671
+ },
672
+ {
673
+ "family": "known-session-reference",
674
+ "name": "unknown_session_reference",
675
+ "input": {
676
+ "sessions": [],
677
+ "commits": [
678
+ {
679
+ "hash": "commit-a",
680
+ "subject": "work",
681
+ "hasSession": true,
682
+ "sessionIds": [
683
+ "session-missing"
684
+ ],
685
+ "associations": [
686
+ {
687
+ "id": "assoc-missing",
688
+ "sessionId": "session-missing",
689
+ "conclusion": "candidate",
690
+ "confidence": "medium",
691
+ "evidence": [
692
+ {
693
+ "kind": "recorded_commit",
694
+ "recordedCommitHash": "commit-a"
695
+ }
696
+ ]
697
+ }
698
+ ]
699
+ }
700
+ ]
701
+ },
702
+ "expected": {
703
+ "errorContains": "references unknown sessionId"
704
+ },
705
+ "classification": "must-fail",
706
+ "provenance": {
707
+ "source": "requirement",
708
+ "ref": "commit bindings reference normalized session identities"
709
+ },
710
+ "mutation": {
711
+ "description": "points a commit binding at a session absent from the session catalog"
712
+ }
713
+ },
714
+ {
715
+ "family": "unique-session-identity",
716
+ "name": "duplicate_session_identity",
717
+ "input": {
718
+ "sessions": [
719
+ {
720
+ "sessionId": "session-a",
721
+ "title": "first",
722
+ "harness": "claude-code",
723
+ "hasCommitBinding": false
724
+ },
725
+ {
726
+ "sessionId": "session-a",
727
+ "title": "duplicate",
728
+ "harness": "claude-code",
729
+ "hasCommitBinding": false
730
+ }
731
+ ],
732
+ "commits": []
733
+ },
734
+ "expected": {
735
+ "errorContains": "duplicate timeline session"
736
+ },
737
+ "classification": "must-fail",
738
+ "provenance": {
739
+ "source": "boundary",
740
+ "ref": "normalized session identity uniqueness"
741
+ },
742
+ "mutation": {
743
+ "description": "repeats one session identity in the session catalog"
744
+ }
745
+ },
746
+ {
747
+ "family": "unique-commit-binding",
748
+ "name": "duplicate_binding",
749
+ "input": {
750
+ "sessions": [
751
+ {
752
+ "sessionId": "session-a",
753
+ "title": "work",
754
+ "harness": "claude-code",
755
+ "hasCommitBinding": true
756
+ }
757
+ ],
758
+ "commits": [
759
+ {
760
+ "hash": "commit-a",
761
+ "subject": "work",
762
+ "hasSession": true,
763
+ "sessionIds": [
764
+ "session-a",
765
+ "session-a"
766
+ ],
767
+ "associations": [
768
+ {
769
+ "id": "assoc-duplicate-a",
770
+ "sessionId": "session-a",
771
+ "conclusion": "confirmed",
772
+ "confidence": "high",
773
+ "evidence": [
774
+ {
775
+ "kind": "recorded_commit",
776
+ "recordedCommitHash": "commit-a"
777
+ }
778
+ ]
779
+ },
780
+ {
781
+ "id": "assoc-duplicate-b",
782
+ "sessionId": "session-a",
783
+ "conclusion": "confirmed",
784
+ "confidence": "high",
785
+ "evidence": [
786
+ {
787
+ "kind": "recorded_commit",
788
+ "recordedCommitHash": "commit-a"
789
+ }
790
+ ]
791
+ }
792
+ ]
793
+ }
794
+ ]
795
+ },
796
+ "expected": {
797
+ "errorContains": "repeats sessionId"
798
+ },
799
+ "classification": "must-fail",
800
+ "provenance": {
801
+ "source": "boundary",
802
+ "ref": "commit binding set uniqueness"
803
+ },
804
+ "mutation": {
805
+ "description": "repeats one session identity in a commit binding list"
806
+ }
807
+ },
808
+ {
809
+ "family": "non-null-commit-bindings",
810
+ "name": "null_commit_bindings",
811
+ "input": {
812
+ "sessions": [],
813
+ "commits": [
814
+ {
815
+ "hash": "commit-a",
816
+ "subject": "work",
817
+ "hasSession": false,
818
+ "associations": [],
819
+ "sessionIds": null
820
+ }
821
+ ]
822
+ },
823
+ "expected": {
824
+ "errorContains": "null sessionIds"
825
+ },
826
+ "classification": "must-fail",
827
+ "provenance": {
828
+ "source": "boundary",
829
+ "ref": "non-null commit binding arrays"
830
+ },
831
+ "mutation": {
832
+ "description": "omits the required sessionIds array from a commit"
833
+ }
834
+ },
835
+ {
836
+ "family": "known-harness",
837
+ "name": "unknown_timeline_harness",
838
+ "input": {
839
+ "sessions": [
840
+ {
841
+ "sessionId": "session-a",
842
+ "title": "work",
843
+ "harness": "unknown-harness",
844
+ "hasCommitBinding": false
845
+ }
846
+ ],
847
+ "commits": []
848
+ },
849
+ "expected": {
850
+ "errorContains": "unknown harness"
851
+ },
852
+ "classification": "must-fail",
853
+ "provenance": {
854
+ "source": "boundary",
855
+ "ref": "timeline sessions use the canonical harness closed set"
856
+ },
857
+ "mutation": {
858
+ "description": "replaces a known harness with an unknown harness token"
859
+ }
860
+ },
861
+ {
862
+ "family": "canonical-binding-order",
863
+ "name": "noncanonical_binding_order",
864
+ "input": {
865
+ "sessions": [
866
+ {
867
+ "sessionId": "session-a",
868
+ "title": "first",
869
+ "harness": "claude-code",
870
+ "startMs": 2000,
871
+ "hasCommitBinding": true
872
+ },
873
+ {
874
+ "sessionId": "session-b",
875
+ "title": "second",
876
+ "harness": "codex",
877
+ "startMs": 1000,
878
+ "hasCommitBinding": true
879
+ }
880
+ ],
881
+ "commits": [
882
+ {
883
+ "hash": "commit-a",
884
+ "subject": "work",
885
+ "hasSession": true,
886
+ "sessionIds": [
887
+ "session-b",
888
+ "session-a"
889
+ ],
890
+ "associations": [
891
+ {
892
+ "id": "assoc-order-b",
893
+ "sessionId": "session-b",
894
+ "conclusion": "confirmed",
895
+ "confidence": "high",
896
+ "evidence": [
897
+ {
898
+ "kind": "recorded_commit",
899
+ "recordedCommitHash": "commit-a"
900
+ }
901
+ ]
902
+ },
903
+ {
904
+ "id": "assoc-order-a",
905
+ "sessionId": "session-a",
906
+ "conclusion": "confirmed",
907
+ "confidence": "high",
908
+ "evidence": [
909
+ {
910
+ "kind": "recorded_commit",
911
+ "recordedCommitHash": "commit-a"
912
+ }
913
+ ]
914
+ }
915
+ ]
916
+ }
917
+ ]
918
+ },
919
+ "expected": {
920
+ "errorContains": "noncanonical sessionIds order"
921
+ },
922
+ "classification": "must-fail",
923
+ "provenance": {
924
+ "source": "boundary",
925
+ "ref": "commit bindings follow normalized timeline session rank"
926
+ },
927
+ "mutation": {
928
+ "description": "reverses two valid bindings relative to the canonical session order"
929
+ }
930
+ },
931
+ {
932
+ "family": "null-associations",
933
+ "name": "null_associations",
934
+ "input": {
935
+ "sessions": [
936
+ {
937
+ "sessionId": "session-a",
938
+ "title": "work",
939
+ "harness": "claude-code",
940
+ "hasCommitBinding": true
941
+ }
942
+ ],
943
+ "commits": [
944
+ {
945
+ "hash": "commit-a",
946
+ "subject": "work",
947
+ "hasSession": true,
948
+ "sessionIds": [
949
+ "session-a"
950
+ ],
951
+ "associations": null
952
+ }
953
+ ]
954
+ },
955
+ "expected": {
956
+ "errorContains": "null associations"
957
+ },
958
+ "classification": "must-fail",
959
+ "provenance": {
960
+ "source": "requirement",
961
+ "ref": "the non-nil Associations invariant"
962
+ },
963
+ "mutation": {
964
+ "description": "omits the required associations array from a commit that has sessionIds"
965
+ }
966
+ },
967
+ {
968
+ "family": "association-count-mismatch",
969
+ "name": "association_count_does_not_mirror_session_ids",
970
+ "input": {
971
+ "sessions": [
972
+ {
973
+ "sessionId": "session-a",
974
+ "title": "work",
975
+ "harness": "claude-code",
976
+ "hasCommitBinding": true
977
+ }
978
+ ],
979
+ "commits": [
980
+ {
981
+ "hash": "commit-a",
982
+ "subject": "work",
983
+ "hasSession": true,
984
+ "sessionIds": [
985
+ "session-a"
986
+ ],
987
+ "associations": []
988
+ }
989
+ ]
990
+ },
991
+ "expected": {
992
+ "errorContains": "Associations must mirror SessionIDs"
993
+ },
994
+ "classification": "must-fail",
995
+ "provenance": {
996
+ "source": "requirement",
997
+ "ref": "the Associations-mirrors-SessionIDs invariant"
998
+ },
999
+ "mutation": {
1000
+ "description": "leaves associations empty while sessionIds carries one binding"
1001
+ }
1002
+ },
1003
+ {
1004
+ "family": "association-rank-mismatch",
1005
+ "name": "association_rank_order_does_not_match_session_ids",
1006
+ "input": {
1007
+ "sessions": [
1008
+ {
1009
+ "sessionId": "session-a",
1010
+ "title": "first",
1011
+ "harness": "claude-code",
1012
+ "startMs": 2000,
1013
+ "hasCommitBinding": true
1014
+ },
1015
+ {
1016
+ "sessionId": "session-b",
1017
+ "title": "second",
1018
+ "harness": "codex",
1019
+ "startMs": 1000,
1020
+ "hasCommitBinding": true
1021
+ }
1022
+ ],
1023
+ "commits": [
1024
+ {
1025
+ "hash": "commit-a",
1026
+ "subject": "work",
1027
+ "hasSession": true,
1028
+ "sessionIds": [
1029
+ "session-a",
1030
+ "session-b"
1031
+ ],
1032
+ "associations": [
1033
+ {
1034
+ "id": "assoc-rank-b",
1035
+ "sessionId": "session-b",
1036
+ "conclusion": "confirmed",
1037
+ "confidence": "high",
1038
+ "evidence": [
1039
+ {
1040
+ "kind": "recorded_commit",
1041
+ "recordedCommitHash": "commit-a"
1042
+ }
1043
+ ]
1044
+ },
1045
+ {
1046
+ "id": "assoc-rank-a",
1047
+ "sessionId": "session-a",
1048
+ "conclusion": "confirmed",
1049
+ "confidence": "high",
1050
+ "evidence": [
1051
+ {
1052
+ "kind": "recorded_commit",
1053
+ "recordedCommitHash": "commit-a"
1054
+ }
1055
+ ]
1056
+ }
1057
+ ]
1058
+ }
1059
+ ]
1060
+ },
1061
+ "expected": {
1062
+ "errorContains": "Associations must equal SessionIDs in the same rank order"
1063
+ },
1064
+ "classification": "must-fail",
1065
+ "provenance": {
1066
+ "source": "requirement",
1067
+ "ref": "the Associations rank-order invariant"
1068
+ },
1069
+ "mutation": {
1070
+ "description": "swaps the two associations relative to the canonical sessionIds order"
1071
+ }
1072
+ },
1073
+ {
1074
+ "family": "association-invalid-conclusion",
1075
+ "name": "association_conclusion_not_in_closed_set",
1076
+ "input": {
1077
+ "sessions": [
1078
+ {
1079
+ "sessionId": "session-a",
1080
+ "title": "work",
1081
+ "harness": "claude-code",
1082
+ "hasCommitBinding": true
1083
+ }
1084
+ ],
1085
+ "commits": [
1086
+ {
1087
+ "hash": "commit-a",
1088
+ "subject": "work",
1089
+ "hasSession": true,
1090
+ "sessionIds": [
1091
+ "session-a"
1092
+ ],
1093
+ "associations": [
1094
+ {
1095
+ "id": "assoc-invalid-conclusion",
1096
+ "sessionId": "session-a",
1097
+ "conclusion": "unknown-conclusion",
1098
+ "confidence": "high",
1099
+ "evidence": [
1100
+ {
1101
+ "kind": "recorded_commit",
1102
+ "recordedCommitHash": "commit-a"
1103
+ }
1104
+ ]
1105
+ }
1106
+ ]
1107
+ }
1108
+ ]
1109
+ },
1110
+ "expected": {
1111
+ "errorContains": "association conclusion validation failed"
1112
+ },
1113
+ "classification": "must-fail",
1114
+ "provenance": {
1115
+ "source": "enum",
1116
+ "ref": "schema.AllAssociationConclusions"
1117
+ },
1118
+ "mutation": {
1119
+ "description": "replaces a known association conclusion with a value outside the closed set"
1120
+ }
1121
+ }
1122
+ ],
1123
+ "successorAssociationMirrorCases": [
1124
+ {
1125
+ "family": "rewrite-successor-association-id-mirror",
1126
+ "name": "rewrite_successor_association_id_drift_is_rejected",
1127
+ "input": {
1128
+ "sessions": [
1129
+ {
1130
+ "sessionId": "session-mirror-id",
1131
+ "title": "mirrored identifier",
1132
+ "harness": "claude-code",
1133
+ "hasCommitBinding": true
1134
+ }
1135
+ ],
1136
+ "commits": [
1137
+ {
1138
+ "hash": "successor-mirror-id",
1139
+ "subject": "squash successor",
1140
+ "hasSession": true,
1141
+ "sessionIds": [
1142
+ "session-mirror-id"
1143
+ ],
1144
+ "associations": [
1145
+ {
1146
+ "id": "assoc-mirror-id-successor",
1147
+ "sessionId": "session-mirror-id",
1148
+ "conclusion": "confirmed",
1149
+ "confidence": "high",
1150
+ "evidence": [
1151
+ {
1152
+ "kind": "recorded_commit",
1153
+ "recordedCommitHash": "ghost-mirror-id"
1154
+ }
1155
+ ]
1156
+ }
1157
+ ]
1158
+ }
1159
+ ],
1160
+ "rewrittenCommits": [
1161
+ {
1162
+ "ghostHash": "ghost-mirror-id",
1163
+ "subject": "original recorded commit",
1164
+ "sessionIds": [
1165
+ "session-mirror-id"
1166
+ ],
1167
+ "associations": [
1168
+ {
1169
+ "id": "assoc-mirror-id-ledger",
1170
+ "sessionId": "session-mirror-id",
1171
+ "conclusion": "confirmed",
1172
+ "confidence": "high",
1173
+ "evidence": [
1174
+ {
1175
+ "kind": "recorded_commit",
1176
+ "recordedCommitHash": "ghost-mirror-id"
1177
+ }
1178
+ ]
1179
+ }
1180
+ ],
1181
+ "successorHash": "successor-mirror-id",
1182
+ "resolution": "rewritten",
1183
+ "method": "patch_id",
1184
+ "confidence": "high"
1185
+ }
1186
+ ]
1187
+ },
1188
+ "expected": {
1189
+ "errorContains": "does not exactly mirror the rewrite-ledger association",
1190
+ "repair": {
1191
+ "kind": "replace_successor_association",
1192
+ "ghostHash": "ghost-mirror-id",
1193
+ "successorHash": "successor-mirror-id",
1194
+ "associationId": "assoc-mirror-id-ledger",
1195
+ "postMutationValid": true
1196
+ }
1197
+ },
1198
+ "classification": "must-fail",
1199
+ "provenance": {
1200
+ "source": "boundary",
1201
+ "ref": "displayed rewrite successors preserve durable association IDs"
1202
+ },
1203
+ "mutation": {
1204
+ "description": "changes only the successor association ID while retaining the same session binding"
1205
+ }
1206
+ },
1207
+ {
1208
+ "family": "rewrite-successor-association-session-mirror",
1209
+ "name": "rewrite_successor_association_session_drift_is_rejected",
1210
+ "input": {
1211
+ "sessions": [
1212
+ {
1213
+ "sessionId": "session-mirror-session-ledger",
1214
+ "title": "ledger session",
1215
+ "harness": "claude-code",
1216
+ "hasCommitBinding": true
1217
+ },
1218
+ {
1219
+ "sessionId": "session-mirror-session-successor",
1220
+ "title": "successor session",
1221
+ "harness": "codex",
1222
+ "hasCommitBinding": true
1223
+ }
1224
+ ],
1225
+ "commits": [
1226
+ {
1227
+ "hash": "successor-mirror-session",
1228
+ "subject": "squash successor",
1229
+ "hasSession": true,
1230
+ "sessionIds": [
1231
+ "session-mirror-session-successor"
1232
+ ],
1233
+ "associations": [
1234
+ {
1235
+ "id": "assoc-mirror-session",
1236
+ "sessionId": "session-mirror-session-successor",
1237
+ "conclusion": "confirmed",
1238
+ "confidence": "high",
1239
+ "evidence": [
1240
+ {
1241
+ "kind": "recorded_commit",
1242
+ "recordedCommitHash": "ghost-mirror-session"
1243
+ }
1244
+ ]
1245
+ }
1246
+ ]
1247
+ }
1248
+ ],
1249
+ "rewrittenCommits": [
1250
+ {
1251
+ "ghostHash": "ghost-mirror-session",
1252
+ "subject": "original recorded commit",
1253
+ "sessionIds": [
1254
+ "session-mirror-session-ledger"
1255
+ ],
1256
+ "associations": [
1257
+ {
1258
+ "id": "assoc-mirror-session",
1259
+ "sessionId": "session-mirror-session-ledger",
1260
+ "conclusion": "confirmed",
1261
+ "confidence": "high",
1262
+ "evidence": [
1263
+ {
1264
+ "kind": "recorded_commit",
1265
+ "recordedCommitHash": "ghost-mirror-session"
1266
+ }
1267
+ ]
1268
+ }
1269
+ ],
1270
+ "successorHash": "successor-mirror-session",
1271
+ "resolution": "rewritten",
1272
+ "method": "patch_id",
1273
+ "confidence": "high"
1274
+ }
1275
+ ]
1276
+ },
1277
+ "expected": {
1278
+ "errorContains": "does not exactly mirror the rewrite-ledger association",
1279
+ "repair": {
1280
+ "kind": "replace_successor_association",
1281
+ "ghostHash": "ghost-mirror-session",
1282
+ "successorHash": "successor-mirror-session",
1283
+ "associationId": "assoc-mirror-session",
1284
+ "postMutationValid": true
1285
+ }
1286
+ },
1287
+ "classification": "must-fail",
1288
+ "provenance": {
1289
+ "source": "boundary",
1290
+ "ref": "displayed rewrite successors preserve the ledger session binding for a durable association ID"
1291
+ },
1292
+ "mutation": {
1293
+ "description": "changes only the successor binding session while retaining the durable association ID"
1294
+ }
1295
+ },
1296
+ {
1297
+ "family": "rewrite-successor-association-conclusion-mirror",
1298
+ "name": "rewrite_successor_association_conclusion_drift_is_rejected",
1299
+ "input": {
1300
+ "sessions": [
1301
+ {
1302
+ "sessionId": "session-mirror-conclusion",
1303
+ "title": "mirrored conclusion",
1304
+ "harness": "claude-code",
1305
+ "hasCommitBinding": true
1306
+ }
1307
+ ],
1308
+ "commits": [
1309
+ {
1310
+ "hash": "successor-mirror-conclusion",
1311
+ "subject": "squash successor",
1312
+ "hasSession": true,
1313
+ "sessionIds": [
1314
+ "session-mirror-conclusion"
1315
+ ],
1316
+ "associations": [
1317
+ {
1318
+ "id": "assoc-mirror-conclusion",
1319
+ "sessionId": "session-mirror-conclusion",
1320
+ "conclusion": "candidate",
1321
+ "confidence": "high",
1322
+ "evidence": [
1323
+ {
1324
+ "kind": "recorded_commit",
1325
+ "recordedCommitHash": "ghost-mirror-conclusion"
1326
+ }
1327
+ ]
1328
+ }
1329
+ ]
1330
+ }
1331
+ ],
1332
+ "rewrittenCommits": [
1333
+ {
1334
+ "ghostHash": "ghost-mirror-conclusion",
1335
+ "subject": "original recorded commit",
1336
+ "sessionIds": [
1337
+ "session-mirror-conclusion"
1338
+ ],
1339
+ "associations": [
1340
+ {
1341
+ "id": "assoc-mirror-conclusion",
1342
+ "sessionId": "session-mirror-conclusion",
1343
+ "conclusion": "confirmed",
1344
+ "confidence": "high",
1345
+ "evidence": [
1346
+ {
1347
+ "kind": "recorded_commit",
1348
+ "recordedCommitHash": "ghost-mirror-conclusion"
1349
+ }
1350
+ ]
1351
+ }
1352
+ ],
1353
+ "successorHash": "successor-mirror-conclusion",
1354
+ "resolution": "rewritten",
1355
+ "method": "patch_id",
1356
+ "confidence": "high"
1357
+ }
1358
+ ]
1359
+ },
1360
+ "expected": {
1361
+ "errorContains": "does not exactly mirror the rewrite-ledger association",
1362
+ "repair": {
1363
+ "kind": "replace_successor_association",
1364
+ "ghostHash": "ghost-mirror-conclusion",
1365
+ "successorHash": "successor-mirror-conclusion",
1366
+ "associationId": "assoc-mirror-conclusion",
1367
+ "postMutationValid": true
1368
+ }
1369
+ },
1370
+ "classification": "must-fail",
1371
+ "provenance": {
1372
+ "source": "boundary",
1373
+ "ref": "displayed rewrite successors preserve the ledger conclusion"
1374
+ },
1375
+ "mutation": {
1376
+ "description": "changes only the successor association conclusion"
1377
+ }
1378
+ },
1379
+ {
1380
+ "family": "rewrite-successor-association-confidence-mirror",
1381
+ "name": "rewrite_successor_association_confidence_drift_is_rejected",
1382
+ "input": {
1383
+ "sessions": [
1384
+ {
1385
+ "sessionId": "session-mirror-confidence",
1386
+ "title": "mirrored confidence",
1387
+ "harness": "claude-code",
1388
+ "hasCommitBinding": true
1389
+ }
1390
+ ],
1391
+ "commits": [
1392
+ {
1393
+ "hash": "successor-mirror-confidence",
1394
+ "subject": "squash successor",
1395
+ "hasSession": true,
1396
+ "sessionIds": [
1397
+ "session-mirror-confidence"
1398
+ ],
1399
+ "associations": [
1400
+ {
1401
+ "id": "assoc-mirror-confidence",
1402
+ "sessionId": "session-mirror-confidence",
1403
+ "conclusion": "confirmed",
1404
+ "confidence": "low",
1405
+ "evidence": [
1406
+ {
1407
+ "kind": "recorded_commit",
1408
+ "recordedCommitHash": "ghost-mirror-confidence"
1409
+ }
1410
+ ]
1411
+ }
1412
+ ]
1413
+ }
1414
+ ],
1415
+ "rewrittenCommits": [
1416
+ {
1417
+ "ghostHash": "ghost-mirror-confidence",
1418
+ "subject": "original recorded commit",
1419
+ "sessionIds": [
1420
+ "session-mirror-confidence"
1421
+ ],
1422
+ "associations": [
1423
+ {
1424
+ "id": "assoc-mirror-confidence",
1425
+ "sessionId": "session-mirror-confidence",
1426
+ "conclusion": "confirmed",
1427
+ "confidence": "high",
1428
+ "evidence": [
1429
+ {
1430
+ "kind": "recorded_commit",
1431
+ "recordedCommitHash": "ghost-mirror-confidence"
1432
+ }
1433
+ ]
1434
+ }
1435
+ ],
1436
+ "successorHash": "successor-mirror-confidence",
1437
+ "resolution": "rewritten",
1438
+ "method": "patch_id",
1439
+ "confidence": "high"
1440
+ }
1441
+ ]
1442
+ },
1443
+ "expected": {
1444
+ "errorContains": "does not exactly mirror the rewrite-ledger association",
1445
+ "repair": {
1446
+ "kind": "replace_successor_association",
1447
+ "ghostHash": "ghost-mirror-confidence",
1448
+ "successorHash": "successor-mirror-confidence",
1449
+ "associationId": "assoc-mirror-confidence",
1450
+ "postMutationValid": true
1451
+ }
1452
+ },
1453
+ "classification": "must-fail",
1454
+ "provenance": {
1455
+ "source": "boundary",
1456
+ "ref": "displayed rewrite successors preserve the ledger confidence"
1457
+ },
1458
+ "mutation": {
1459
+ "description": "changes only the successor association confidence"
1460
+ }
1461
+ },
1462
+ {
1463
+ "family": "rewrite-successor-association-evidence-mirror",
1464
+ "name": "rewrite_successor_association_evidence_drift_is_rejected",
1465
+ "input": {
1466
+ "sessions": [
1467
+ {
1468
+ "sessionId": "session-mirror-evidence",
1469
+ "title": "mirrored evidence",
1470
+ "harness": "claude-code",
1471
+ "hasCommitBinding": true
1472
+ }
1473
+ ],
1474
+ "commits": [
1475
+ {
1476
+ "hash": "successor-mirror-evidence",
1477
+ "subject": "squash successor",
1478
+ "hasSession": true,
1479
+ "sessionIds": [
1480
+ "session-mirror-evidence"
1481
+ ],
1482
+ "associations": [
1483
+ {
1484
+ "id": "assoc-mirror-evidence",
1485
+ "sessionId": "session-mirror-evidence",
1486
+ "conclusion": "confirmed",
1487
+ "confidence": "high",
1488
+ "evidence": [
1489
+ {
1490
+ "kind": "recorded_commit",
1491
+ "recordedCommitHash": "successor-mirror-evidence"
1492
+ },
1493
+ {
1494
+ "kind": "touched_file",
1495
+ "touchedFilePath": "internal/api/review.go"
1496
+ }
1497
+ ]
1498
+ }
1499
+ ]
1500
+ }
1501
+ ],
1502
+ "rewrittenCommits": [
1503
+ {
1504
+ "ghostHash": "ghost-mirror-evidence",
1505
+ "subject": "original recorded commit",
1506
+ "sessionIds": [
1507
+ "session-mirror-evidence"
1508
+ ],
1509
+ "associations": [
1510
+ {
1511
+ "id": "assoc-mirror-evidence",
1512
+ "sessionId": "session-mirror-evidence",
1513
+ "conclusion": "confirmed",
1514
+ "confidence": "high",
1515
+ "evidence": [
1516
+ {
1517
+ "kind": "recorded_commit",
1518
+ "recordedCommitHash": "ghost-mirror-evidence"
1519
+ },
1520
+ {
1521
+ "kind": "touched_file",
1522
+ "touchedFilePath": "internal/api/review.go"
1523
+ }
1524
+ ]
1525
+ }
1526
+ ],
1527
+ "successorHash": "successor-mirror-evidence",
1528
+ "resolution": "rewritten",
1529
+ "method": "patch_id",
1530
+ "confidence": "high"
1531
+ }
1532
+ ]
1533
+ },
1534
+ "expected": {
1535
+ "errorContains": "does not exactly mirror the rewrite-ledger association",
1536
+ "repair": {
1537
+ "kind": "replace_successor_association",
1538
+ "ghostHash": "ghost-mirror-evidence",
1539
+ "successorHash": "successor-mirror-evidence",
1540
+ "associationId": "assoc-mirror-evidence",
1541
+ "postMutationValid": true
1542
+ }
1543
+ },
1544
+ "classification": "must-fail",
1545
+ "provenance": {
1546
+ "source": "boundary",
1547
+ "ref": "displayed rewrite successors preserve ordered atomic evidence"
1548
+ },
1549
+ "mutation": {
1550
+ "description": "changes only the recorded commit detail in otherwise canonical ordered successor evidence"
1551
+ }
1552
+ }
1553
+ ]
1554
+ };