@peasant-labs/schema 0.1.0-rc6 → 0.1.0-rc8

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.
@@ -28,6 +28,32 @@ export const canonicalTimelineFixtures = {
28
28
  "sessionIds": [
29
29
  "session-a",
30
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
+ }
31
57
  ]
32
58
  },
33
59
  {
@@ -36,6 +62,20 @@ export const canonicalTimelineFixtures = {
36
62
  "hasSession": true,
37
63
  "sessionIds": [
38
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
+ }
39
79
  ]
40
80
  }
41
81
  ]
@@ -68,7 +108,8 @@ export const canonicalTimelineFixtures = {
68
108
  "hash": "commit-empty",
69
109
  "subject": "manual change",
70
110
  "hasSession": false,
71
- "sessionIds": []
111
+ "sessionIds": [],
112
+ "associations": []
72
113
  }
73
114
  ]
74
115
  },
@@ -99,7 +140,8 @@ export const canonicalTimelineFixtures = {
99
140
  "hash": "commit-recent",
100
141
  "subject": "recent manual change",
101
142
  "hasSession": false,
102
- "sessionIds": []
143
+ "sessionIds": [],
144
+ "associations": []
103
145
  }
104
146
  ]
105
147
  },
@@ -113,6 +155,259 @@ export const canonicalTimelineFixtures = {
113
155
  "description": "keeps binding truth when the associated commit is outside the returned window"
114
156
  }
115
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
+ },
116
411
  {
117
412
  "family": "non-default-branch-binding",
118
413
  "name": "binding_on_non_default_branch",
@@ -295,6 +590,20 @@ export const canonicalTimelineFixtures = {
295
590
  "hasSession": false,
296
591
  "sessionIds": [
297
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
+ }
298
607
  ]
299
608
  }
300
609
  ]
@@ -330,6 +639,20 @@ export const canonicalTimelineFixtures = {
330
639
  "hasSession": true,
331
640
  "sessionIds": [
332
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
+ }
333
656
  ]
334
657
  }
335
658
  ]
@@ -358,6 +681,20 @@ export const canonicalTimelineFixtures = {
358
681
  "hasSession": true,
359
682
  "sessionIds": [
360
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
+ }
361
698
  ]
362
699
  }
363
700
  ]
@@ -426,6 +763,32 @@ export const canonicalTimelineFixtures = {
426
763
  "sessionIds": [
427
764
  "session-a",
428
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
+ }
429
792
  ]
430
793
  }
431
794
  ]
@@ -452,6 +815,7 @@ export const canonicalTimelineFixtures = {
452
815
  "hash": "commit-a",
453
816
  "subject": "work",
454
817
  "hasSession": false,
818
+ "associations": [],
455
819
  "sessionIds": null
456
820
  }
457
821
  ]
@@ -522,6 +886,32 @@ export const canonicalTimelineFixtures = {
522
886
  "sessionIds": [
523
887
  "session-b",
524
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
+ }
525
915
  ]
526
916
  }
527
917
  ]
@@ -537,6 +927,628 @@ export const canonicalTimelineFixtures = {
537
927
  "mutation": {
538
928
  "description": "reverses two valid bindings relative to the canonical session order"
539
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
+ }
540
1552
  }
541
1553
  ]
542
1554
  };