@pepys/testing 0.0.0-stage → 0.1.0

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 (43) hide show
  1. package/README.md +35 -2
  2. package/package.json +31 -4
  3. package/src/consumer-entries/build-read-functions.ts +26 -0
  4. package/src/consumer-entries/run-entry.ts +46 -0
  5. package/src/fact-index/build-fact-index.ts +39 -0
  6. package/src/fact-index/collect-scope-facts.ts +42 -0
  7. package/src/fact-index/types.ts +15 -0
  8. package/src/fact-mode/apply-fact-operations.ts +71 -0
  9. package/src/fact-mode/read-row-functions.ts +34 -0
  10. package/src/index.ts +7 -0
  11. package/src/links/read-link-rows.ts +14 -0
  12. package/src/links/remove-link.ts +16 -0
  13. package/src/links/write-link.ts +24 -0
  14. package/src/memory-store-reader/create-memory-store-reader.ts +61 -0
  15. package/src/operations/parse-operations.ts +168 -0
  16. package/src/operations/types.ts +9 -0
  17. package/src/orders/apply-in-both-orders.ts +59 -0
  18. package/src/rows/build-key-filter.ts +19 -0
  19. package/src/rows/build-row-key.ts +8 -0
  20. package/src/rows/find-row-problem.ts +43 -0
  21. package/src/rows/read-table-rows.ts +35 -0
  22. package/src/rows/remove-row.ts +13 -0
  23. package/src/rows/to-row-value.ts +15 -0
  24. package/src/rows/to-sql-value.ts +18 -0
  25. package/src/rows/write-row.ts +21 -0
  26. package/src/scope-replace/build-link-key.ts +6 -0
  27. package/src/scope-replace/build-row-id.ts +4 -0
  28. package/src/scope-replace/collect-scope-result.ts +87 -0
  29. package/src/scope-replace/pick-scope-changes.ts +166 -0
  30. package/src/scope-replace/read-scope-state.ts +61 -0
  31. package/src/scope-replace/replace-scope.ts +42 -0
  32. package/src/scope-replace/scope-replace-cases.json +1745 -0
  33. package/src/scope-replace/types.ts +39 -0
  34. package/src/scope-replace/write-scope-changes.ts +50 -0
  35. package/src/store-database/create-store-database.ts +31 -0
  36. package/src/store-database/format-store-schema.ts +57 -0
  37. package/src/store-database/types.ts +12 -0
  38. package/src/test-store/create-test-store.ts +94 -0
  39. package/src/test-store/parse-applied-facts.ts +64 -0
  40. package/src/test-store/run-consumers.ts +117 -0
  41. package/src/test-store/types.ts +68 -0
  42. package/src/utils/apply-error.ts +13 -0
  43. package/src/utils/types.ts +47 -0
@@ -0,0 +1,1745 @@
1
+ {
2
+ "consumer": {
3
+ "name": "ledger_account",
4
+ "input": {
5
+ "mode": "scope",
6
+ "scopeKind": "ledger:account",
7
+ "streams": ["facts.ledger.account"]
8
+ },
9
+ "tables": {
10
+ "ledger_account": {
11
+ "columns": {
12
+ "id": {
13
+ "kind": "entityID",
14
+ "nullable": false
15
+ },
16
+ "name": {
17
+ "kind": "text",
18
+ "nullable": true
19
+ },
20
+ "balance": {
21
+ "kind": "integer",
22
+ "nullable": false
23
+ },
24
+ "is_closed": {
25
+ "kind": "boolean",
26
+ "nullable": false
27
+ },
28
+ "opened_at": {
29
+ "kind": "timestamp",
30
+ "nullable": true
31
+ },
32
+ "updated_at": {
33
+ "kind": "timestamp",
34
+ "nullable": false
35
+ }
36
+ },
37
+ "primaryKey": ["id"],
38
+ "indexes": []
39
+ },
40
+ "ledger_entry": {
41
+ "columns": {
42
+ "id": {
43
+ "kind": "entityID",
44
+ "nullable": false
45
+ },
46
+ "account_id": {
47
+ "kind": "entityID",
48
+ "nullable": false
49
+ },
50
+ "amount": {
51
+ "kind": "integer",
52
+ "nullable": false
53
+ },
54
+ "rate": {
55
+ "kind": "real",
56
+ "nullable": false
57
+ },
58
+ "memo": {
59
+ "kind": "text",
60
+ "nullable": false
61
+ },
62
+ "details": {
63
+ "kind": "json",
64
+ "nullable": false
65
+ },
66
+ "posted_at": {
67
+ "kind": "timestamp",
68
+ "nullable": false
69
+ }
70
+ },
71
+ "primaryKey": ["id"],
72
+ "indexes": [["account_id"]]
73
+ },
74
+ "ledger_rate": {
75
+ "columns": {
76
+ "rate": {
77
+ "kind": "real",
78
+ "nullable": false
79
+ },
80
+ "label": {
81
+ "kind": "text",
82
+ "nullable": false
83
+ }
84
+ },
85
+ "primaryKey": ["rate"],
86
+ "indexes": []
87
+ },
88
+ "ledger_tag": {
89
+ "columns": {
90
+ "tag": {
91
+ "kind": "json",
92
+ "nullable": false
93
+ },
94
+ "label": {
95
+ "kind": "text",
96
+ "nullable": false
97
+ }
98
+ },
99
+ "primaryKey": ["tag"],
100
+ "indexes": []
101
+ }
102
+ },
103
+ "links": [
104
+ {
105
+ "name": "entry_of",
106
+ "from": ["ledger:entry"],
107
+ "to": ["ledger:account"],
108
+ "hub": false
109
+ }
110
+ ]
111
+ },
112
+ "cases": [
113
+ {
114
+ "title": "a first result writes each row and link, with an insert entry for each",
115
+ "batches": [
116
+ {
117
+ "scopes": [
118
+ {
119
+ "scope": "ledger:account:a",
120
+ "output": "[{\"upsert\":{\"ledger_account\":{\"id\":\"ledger:account:a\",\"name\":\"Alpha\",\"balance\":5,\"is_closed\":false,\"opened_at\":\"2026-11-01T09:00:00.000Z\",\"updated_at\":\"2026-11-01T09:00:00.000Z\"}}},{\"upsert\":{\"ledger_entry\":{\"id\":\"ledger:entry:e1\",\"account_id\":\"ledger:account:a\",\"amount\":5,\"rate\":1.25,\"memo\":\"Rope\",\"details\":{\"till\":\"north\"},\"posted_at\":\"2026-11-01T09:00:00.000Z\"}}},{\"link\":{\"type\":\"entry_of\",\"from\":\"ledger:entry:e1\",\"to\":\"ledger:account:a\",\"confidence\":1,\"evidence\":\"event:ledger:e1\"}}]"
121
+ }
122
+ ],
123
+ "changes": [
124
+ {
125
+ "type": "ledger_account",
126
+ "key": "[\"ledger:account:a\"]",
127
+ "value": {
128
+ "id": "ledger:account:a",
129
+ "name": "Alpha",
130
+ "balance": 5,
131
+ "is_closed": false,
132
+ "opened_at": "2026-11-01T09:00:00.000Z",
133
+ "updated_at": "2026-11-01T09:00:00.000Z"
134
+ },
135
+ "headers": {
136
+ "operation": "insert"
137
+ }
138
+ },
139
+ {
140
+ "type": "ledger_entry",
141
+ "key": "[\"ledger:entry:e1\"]",
142
+ "value": {
143
+ "id": "ledger:entry:e1",
144
+ "account_id": "ledger:account:a",
145
+ "amount": 5,
146
+ "rate": 1.25,
147
+ "memo": "Rope",
148
+ "details": {
149
+ "till": "north"
150
+ },
151
+ "posted_at": "2026-11-01T09:00:00.000Z"
152
+ },
153
+ "headers": {
154
+ "operation": "insert"
155
+ }
156
+ },
157
+ {
158
+ "type": "link",
159
+ "key": "[\"entry_of\",\"ledger:entry:e1\",\"ledger:account:a\"]",
160
+ "value": {
161
+ "type": "entry_of",
162
+ "from": "ledger:entry:e1",
163
+ "to": "ledger:account:a",
164
+ "confidence": 1,
165
+ "evidence": "event:ledger:e1"
166
+ },
167
+ "headers": {
168
+ "operation": "insert"
169
+ }
170
+ }
171
+ ]
172
+ }
173
+ ],
174
+ "rows": {
175
+ "ledger_account": [
176
+ {
177
+ "id": "ledger:account:a",
178
+ "name": "Alpha",
179
+ "balance": 5,
180
+ "is_closed": false,
181
+ "opened_at": "2026-11-01T09:00:00.000Z",
182
+ "updated_at": "2026-11-01T09:00:00.000Z"
183
+ }
184
+ ],
185
+ "ledger_entry": [
186
+ {
187
+ "id": "ledger:entry:e1",
188
+ "account_id": "ledger:account:a",
189
+ "amount": 5,
190
+ "rate": 1.25,
191
+ "memo": "Rope",
192
+ "details": {
193
+ "till": "north"
194
+ },
195
+ "posted_at": "2026-11-01T09:00:00.000Z"
196
+ }
197
+ ],
198
+ "ledger_rate": [],
199
+ "ledger_tag": []
200
+ },
201
+ "links": [
202
+ {
203
+ "type": "entry_of",
204
+ "from_id": "ledger:entry:e1",
205
+ "to_id": "ledger:account:a",
206
+ "scope": "ledger:account:a",
207
+ "origin": "ledger_account",
208
+ "confidence": 1,
209
+ "evidence": "event:ledger:e1"
210
+ }
211
+ ]
212
+ },
213
+ {
214
+ "title": "the same result again changes nothing and gives no change entry",
215
+ "batches": [
216
+ {
217
+ "scopes": [
218
+ {
219
+ "scope": "ledger:account:a",
220
+ "output": "[{\"upsert\":{\"ledger_account\":{\"id\":\"ledger:account:a\",\"name\":\"Alpha\",\"balance\":5,\"is_closed\":false,\"opened_at\":\"2026-11-01T09:00:00.000Z\",\"updated_at\":\"2026-11-01T09:00:00.000Z\"}}},{\"upsert\":{\"ledger_entry\":{\"id\":\"ledger:entry:e1\",\"account_id\":\"ledger:account:a\",\"amount\":5,\"rate\":1.25,\"memo\":\"Rope\",\"details\":{\"till\":\"north\"},\"posted_at\":\"2026-11-01T09:00:00.000Z\"}}},{\"link\":{\"type\":\"entry_of\",\"from\":\"ledger:entry:e1\",\"to\":\"ledger:account:a\",\"confidence\":1,\"evidence\":\"event:ledger:e1\"}}]"
221
+ }
222
+ ],
223
+ "changes": [
224
+ {
225
+ "type": "ledger_account",
226
+ "key": "[\"ledger:account:a\"]",
227
+ "value": {
228
+ "id": "ledger:account:a",
229
+ "name": "Alpha",
230
+ "balance": 5,
231
+ "is_closed": false,
232
+ "opened_at": "2026-11-01T09:00:00.000Z",
233
+ "updated_at": "2026-11-01T09:00:00.000Z"
234
+ },
235
+ "headers": {
236
+ "operation": "insert"
237
+ }
238
+ },
239
+ {
240
+ "type": "ledger_entry",
241
+ "key": "[\"ledger:entry:e1\"]",
242
+ "value": {
243
+ "id": "ledger:entry:e1",
244
+ "account_id": "ledger:account:a",
245
+ "amount": 5,
246
+ "rate": 1.25,
247
+ "memo": "Rope",
248
+ "details": {
249
+ "till": "north"
250
+ },
251
+ "posted_at": "2026-11-01T09:00:00.000Z"
252
+ },
253
+ "headers": {
254
+ "operation": "insert"
255
+ }
256
+ },
257
+ {
258
+ "type": "link",
259
+ "key": "[\"entry_of\",\"ledger:entry:e1\",\"ledger:account:a\"]",
260
+ "value": {
261
+ "type": "entry_of",
262
+ "from": "ledger:entry:e1",
263
+ "to": "ledger:account:a",
264
+ "confidence": 1,
265
+ "evidence": "event:ledger:e1"
266
+ },
267
+ "headers": {
268
+ "operation": "insert"
269
+ }
270
+ }
271
+ ]
272
+ },
273
+ {
274
+ "scopes": [
275
+ {
276
+ "scope": "ledger:account:a",
277
+ "output": "[{\"upsert\":{\"ledger_account\":{\"id\":\"ledger:account:a\",\"name\":\"Alpha\",\"balance\":5,\"is_closed\":false,\"opened_at\":\"2026-11-01T09:00:00.000Z\",\"updated_at\":\"2026-11-01T09:00:00.000Z\"}}},{\"upsert\":{\"ledger_entry\":{\"id\":\"ledger:entry:e1\",\"account_id\":\"ledger:account:a\",\"amount\":5,\"rate\":1.25,\"memo\":\"Rope\",\"details\":{\"till\":\"north\"},\"posted_at\":\"2026-11-01T09:00:00.000Z\"}}},{\"link\":{\"type\":\"entry_of\",\"from\":\"ledger:entry:e1\",\"to\":\"ledger:account:a\",\"confidence\":1,\"evidence\":\"event:ledger:e1\"}}]"
278
+ }
279
+ ],
280
+ "changes": []
281
+ }
282
+ ],
283
+ "rows": {
284
+ "ledger_account": [
285
+ {
286
+ "id": "ledger:account:a",
287
+ "name": "Alpha",
288
+ "balance": 5,
289
+ "is_closed": false,
290
+ "opened_at": "2026-11-01T09:00:00.000Z",
291
+ "updated_at": "2026-11-01T09:00:00.000Z"
292
+ }
293
+ ],
294
+ "ledger_entry": [
295
+ {
296
+ "id": "ledger:entry:e1",
297
+ "account_id": "ledger:account:a",
298
+ "amount": 5,
299
+ "rate": 1.25,
300
+ "memo": "Rope",
301
+ "details": {
302
+ "till": "north"
303
+ },
304
+ "posted_at": "2026-11-01T09:00:00.000Z"
305
+ }
306
+ ],
307
+ "ledger_rate": [],
308
+ "ledger_tag": []
309
+ },
310
+ "links": [
311
+ {
312
+ "type": "entry_of",
313
+ "from_id": "ledger:entry:e1",
314
+ "to_id": "ledger:account:a",
315
+ "scope": "ledger:account:a",
316
+ "origin": "ledger_account",
317
+ "confidence": 1,
318
+ "evidence": "event:ledger:e1"
319
+ }
320
+ ]
321
+ },
322
+ {
323
+ "title": "a changed row is an update that holds the old value",
324
+ "batches": [
325
+ {
326
+ "scopes": [
327
+ {
328
+ "scope": "ledger:account:a",
329
+ "output": "[{\"upsert\":{\"ledger_account\":{\"id\":\"ledger:account:a\",\"name\":\"Alpha\",\"balance\":5,\"is_closed\":false,\"opened_at\":\"2026-11-01T09:00:00.000Z\",\"updated_at\":\"2026-11-01T09:00:00.000Z\"}}}]"
330
+ }
331
+ ],
332
+ "changes": [
333
+ {
334
+ "type": "ledger_account",
335
+ "key": "[\"ledger:account:a\"]",
336
+ "value": {
337
+ "id": "ledger:account:a",
338
+ "name": "Alpha",
339
+ "balance": 5,
340
+ "is_closed": false,
341
+ "opened_at": "2026-11-01T09:00:00.000Z",
342
+ "updated_at": "2026-11-01T09:00:00.000Z"
343
+ },
344
+ "headers": {
345
+ "operation": "insert"
346
+ }
347
+ }
348
+ ]
349
+ },
350
+ {
351
+ "scopes": [
352
+ {
353
+ "scope": "ledger:account:a",
354
+ "output": "[{\"upsert\":{\"ledger_account\":{\"id\":\"ledger:account:a\",\"name\":\"Alpha fund\",\"balance\":5,\"is_closed\":false,\"opened_at\":\"2026-11-01T09:00:00.000Z\",\"updated_at\":\"2026-11-01T09:00:00.000Z\"}}}]"
355
+ }
356
+ ],
357
+ "changes": [
358
+ {
359
+ "type": "ledger_account",
360
+ "key": "[\"ledger:account:a\"]",
361
+ "value": {
362
+ "id": "ledger:account:a",
363
+ "name": "Alpha fund",
364
+ "balance": 5,
365
+ "is_closed": false,
366
+ "opened_at": "2026-11-01T09:00:00.000Z",
367
+ "updated_at": "2026-11-01T09:00:00.000Z"
368
+ },
369
+ "old_value": {
370
+ "id": "ledger:account:a",
371
+ "name": "Alpha",
372
+ "balance": 5,
373
+ "is_closed": false,
374
+ "opened_at": "2026-11-01T09:00:00.000Z",
375
+ "updated_at": "2026-11-01T09:00:00.000Z"
376
+ },
377
+ "headers": {
378
+ "operation": "update"
379
+ }
380
+ }
381
+ ]
382
+ }
383
+ ],
384
+ "rows": {
385
+ "ledger_account": [
386
+ {
387
+ "id": "ledger:account:a",
388
+ "name": "Alpha fund",
389
+ "balance": 5,
390
+ "is_closed": false,
391
+ "opened_at": "2026-11-01T09:00:00.000Z",
392
+ "updated_at": "2026-11-01T09:00:00.000Z"
393
+ }
394
+ ],
395
+ "ledger_entry": [],
396
+ "ledger_rate": [],
397
+ "ledger_tag": []
398
+ },
399
+ "links": []
400
+ },
401
+ {
402
+ "title": "a row and a link that the scope no longer returns are deleted, and a deleted link has no old value",
403
+ "batches": [
404
+ {
405
+ "scopes": [
406
+ {
407
+ "scope": "ledger:account:a",
408
+ "output": "[{\"upsert\":{\"ledger_account\":{\"id\":\"ledger:account:a\",\"name\":\"Alpha\",\"balance\":5,\"is_closed\":false,\"opened_at\":\"2026-11-01T09:00:00.000Z\",\"updated_at\":\"2026-11-01T09:00:00.000Z\"}}},{\"upsert\":{\"ledger_entry\":{\"id\":\"ledger:entry:e1\",\"account_id\":\"ledger:account:a\",\"amount\":5,\"rate\":1.25,\"memo\":\"Rope\",\"details\":{\"till\":\"north\"},\"posted_at\":\"2026-11-01T09:00:00.000Z\"}}},{\"link\":{\"type\":\"entry_of\",\"from\":\"ledger:entry:e1\",\"to\":\"ledger:account:a\",\"confidence\":1,\"evidence\":\"event:ledger:e1\"}}]"
409
+ }
410
+ ],
411
+ "changes": [
412
+ {
413
+ "type": "ledger_account",
414
+ "key": "[\"ledger:account:a\"]",
415
+ "value": {
416
+ "id": "ledger:account:a",
417
+ "name": "Alpha",
418
+ "balance": 5,
419
+ "is_closed": false,
420
+ "opened_at": "2026-11-01T09:00:00.000Z",
421
+ "updated_at": "2026-11-01T09:00:00.000Z"
422
+ },
423
+ "headers": {
424
+ "operation": "insert"
425
+ }
426
+ },
427
+ {
428
+ "type": "ledger_entry",
429
+ "key": "[\"ledger:entry:e1\"]",
430
+ "value": {
431
+ "id": "ledger:entry:e1",
432
+ "account_id": "ledger:account:a",
433
+ "amount": 5,
434
+ "rate": 1.25,
435
+ "memo": "Rope",
436
+ "details": {
437
+ "till": "north"
438
+ },
439
+ "posted_at": "2026-11-01T09:00:00.000Z"
440
+ },
441
+ "headers": {
442
+ "operation": "insert"
443
+ }
444
+ },
445
+ {
446
+ "type": "link",
447
+ "key": "[\"entry_of\",\"ledger:entry:e1\",\"ledger:account:a\"]",
448
+ "value": {
449
+ "type": "entry_of",
450
+ "from": "ledger:entry:e1",
451
+ "to": "ledger:account:a",
452
+ "confidence": 1,
453
+ "evidence": "event:ledger:e1"
454
+ },
455
+ "headers": {
456
+ "operation": "insert"
457
+ }
458
+ }
459
+ ]
460
+ },
461
+ {
462
+ "scopes": [
463
+ {
464
+ "scope": "ledger:account:a",
465
+ "output": "[{\"upsert\":{\"ledger_account\":{\"id\":\"ledger:account:a\",\"name\":\"Alpha\",\"balance\":5,\"is_closed\":false,\"opened_at\":\"2026-11-01T09:00:00.000Z\",\"updated_at\":\"2026-11-01T09:00:00.000Z\"}}}]"
466
+ }
467
+ ],
468
+ "changes": [
469
+ {
470
+ "type": "ledger_entry",
471
+ "key": "[\"ledger:entry:e1\"]",
472
+ "old_value": {
473
+ "id": "ledger:entry:e1",
474
+ "account_id": "ledger:account:a",
475
+ "amount": 5,
476
+ "rate": 1.25,
477
+ "memo": "Rope",
478
+ "details": {
479
+ "till": "north"
480
+ },
481
+ "posted_at": "2026-11-01T09:00:00.000Z"
482
+ },
483
+ "headers": {
484
+ "operation": "delete"
485
+ }
486
+ },
487
+ {
488
+ "type": "link",
489
+ "key": "[\"entry_of\",\"ledger:entry:e1\",\"ledger:account:a\"]",
490
+ "headers": {
491
+ "operation": "delete"
492
+ }
493
+ }
494
+ ]
495
+ }
496
+ ],
497
+ "rows": {
498
+ "ledger_account": [
499
+ {
500
+ "id": "ledger:account:a",
501
+ "name": "Alpha",
502
+ "balance": 5,
503
+ "is_closed": false,
504
+ "opened_at": "2026-11-01T09:00:00.000Z",
505
+ "updated_at": "2026-11-01T09:00:00.000Z"
506
+ }
507
+ ],
508
+ "ledger_entry": [],
509
+ "ledger_rate": [],
510
+ "ledger_tag": []
511
+ },
512
+ "links": []
513
+ },
514
+ {
515
+ "title": "the change entries of a scope come as rows written, links written, rows deleted, links deleted, each in key order",
516
+ "batches": [
517
+ {
518
+ "scopes": [
519
+ {
520
+ "scope": "ledger:account:a",
521
+ "output": "[{\"upsert\":{\"ledger_account\":{\"id\":\"ledger:account:a\",\"name\":\"Alpha\",\"balance\":5,\"is_closed\":false,\"opened_at\":\"2026-11-01T09:00:00.000Z\",\"updated_at\":\"2026-11-01T09:00:00.000Z\"}}},{\"upsert\":{\"ledger_entry\":{\"id\":\"ledger:entry:e1\",\"account_id\":\"ledger:account:a\",\"amount\":5,\"rate\":1.25,\"memo\":\"Rope\",\"details\":{\"till\":\"north\"},\"posted_at\":\"2026-11-01T09:00:00.000Z\"}}},{\"upsert\":{\"ledger_entry\":{\"id\":\"ledger:entry:e2\",\"account_id\":\"ledger:account:a\",\"amount\":5,\"rate\":1.25,\"memo\":\"Rope\",\"details\":{\"till\":\"north\"},\"posted_at\":\"2026-11-01T09:00:00.000Z\"}}},{\"link\":{\"type\":\"entry_of\",\"from\":\"ledger:entry:e1\",\"to\":\"ledger:account:a\",\"confidence\":1,\"evidence\":\"event:ledger:e1\"}},{\"link\":{\"type\":\"entry_of\",\"from\":\"ledger:entry:e2\",\"to\":\"ledger:account:a\",\"confidence\":1,\"evidence\":\"event:ledger:e2\"}}]"
522
+ }
523
+ ],
524
+ "changes": [
525
+ {
526
+ "type": "ledger_account",
527
+ "key": "[\"ledger:account:a\"]",
528
+ "value": {
529
+ "id": "ledger:account:a",
530
+ "name": "Alpha",
531
+ "balance": 5,
532
+ "is_closed": false,
533
+ "opened_at": "2026-11-01T09:00:00.000Z",
534
+ "updated_at": "2026-11-01T09:00:00.000Z"
535
+ },
536
+ "headers": {
537
+ "operation": "insert"
538
+ }
539
+ },
540
+ {
541
+ "type": "ledger_entry",
542
+ "key": "[\"ledger:entry:e1\"]",
543
+ "value": {
544
+ "id": "ledger:entry:e1",
545
+ "account_id": "ledger:account:a",
546
+ "amount": 5,
547
+ "rate": 1.25,
548
+ "memo": "Rope",
549
+ "details": {
550
+ "till": "north"
551
+ },
552
+ "posted_at": "2026-11-01T09:00:00.000Z"
553
+ },
554
+ "headers": {
555
+ "operation": "insert"
556
+ }
557
+ },
558
+ {
559
+ "type": "ledger_entry",
560
+ "key": "[\"ledger:entry:e2\"]",
561
+ "value": {
562
+ "id": "ledger:entry:e2",
563
+ "account_id": "ledger:account:a",
564
+ "amount": 5,
565
+ "rate": 1.25,
566
+ "memo": "Rope",
567
+ "details": {
568
+ "till": "north"
569
+ },
570
+ "posted_at": "2026-11-01T09:00:00.000Z"
571
+ },
572
+ "headers": {
573
+ "operation": "insert"
574
+ }
575
+ },
576
+ {
577
+ "type": "link",
578
+ "key": "[\"entry_of\",\"ledger:entry:e1\",\"ledger:account:a\"]",
579
+ "value": {
580
+ "type": "entry_of",
581
+ "from": "ledger:entry:e1",
582
+ "to": "ledger:account:a",
583
+ "confidence": 1,
584
+ "evidence": "event:ledger:e1"
585
+ },
586
+ "headers": {
587
+ "operation": "insert"
588
+ }
589
+ },
590
+ {
591
+ "type": "link",
592
+ "key": "[\"entry_of\",\"ledger:entry:e2\",\"ledger:account:a\"]",
593
+ "value": {
594
+ "type": "entry_of",
595
+ "from": "ledger:entry:e2",
596
+ "to": "ledger:account:a",
597
+ "confidence": 1,
598
+ "evidence": "event:ledger:e2"
599
+ },
600
+ "headers": {
601
+ "operation": "insert"
602
+ }
603
+ }
604
+ ]
605
+ },
606
+ {
607
+ "scopes": [
608
+ {
609
+ "scope": "ledger:account:a",
610
+ "output": "[{\"link\":{\"type\":\"entry_of\",\"from\":\"ledger:entry:e3\",\"to\":\"ledger:account:a\",\"confidence\":1,\"evidence\":\"event:ledger:e3\"}},{\"upsert\":{\"ledger_entry\":{\"id\":\"ledger:entry:e3\",\"account_id\":\"ledger:account:a\",\"amount\":5,\"rate\":1.25,\"memo\":\"Rope\",\"details\":{\"till\":\"north\"},\"posted_at\":\"2026-11-01T09:00:00.000Z\"}}},{\"link\":{\"type\":\"entry_of\",\"from\":\"ledger:entry:e1\",\"to\":\"ledger:account:a\",\"confidence\":0.5,\"evidence\":\"event:ledger:e1\"}},{\"upsert\":{\"ledger_entry\":{\"id\":\"ledger:entry:e1\",\"account_id\":\"ledger:account:a\",\"amount\":5,\"rate\":1.25,\"memo\":\"Rope\",\"details\":{\"till\":\"north\"},\"posted_at\":\"2026-11-01T09:00:00.000Z\"}}},{\"upsert\":{\"ledger_account\":{\"id\":\"ledger:account:a\",\"name\":\"Alpha\",\"balance\":10,\"is_closed\":false,\"opened_at\":\"2026-11-01T09:00:00.000Z\",\"updated_at\":\"2026-11-01T09:00:00.000Z\"}}}]"
611
+ }
612
+ ],
613
+ "changes": [
614
+ {
615
+ "type": "ledger_account",
616
+ "key": "[\"ledger:account:a\"]",
617
+ "value": {
618
+ "id": "ledger:account:a",
619
+ "name": "Alpha",
620
+ "balance": 10,
621
+ "is_closed": false,
622
+ "opened_at": "2026-11-01T09:00:00.000Z",
623
+ "updated_at": "2026-11-01T09:00:00.000Z"
624
+ },
625
+ "old_value": {
626
+ "id": "ledger:account:a",
627
+ "name": "Alpha",
628
+ "balance": 5,
629
+ "is_closed": false,
630
+ "opened_at": "2026-11-01T09:00:00.000Z",
631
+ "updated_at": "2026-11-01T09:00:00.000Z"
632
+ },
633
+ "headers": {
634
+ "operation": "update"
635
+ }
636
+ },
637
+ {
638
+ "type": "ledger_entry",
639
+ "key": "[\"ledger:entry:e3\"]",
640
+ "value": {
641
+ "id": "ledger:entry:e3",
642
+ "account_id": "ledger:account:a",
643
+ "amount": 5,
644
+ "rate": 1.25,
645
+ "memo": "Rope",
646
+ "details": {
647
+ "till": "north"
648
+ },
649
+ "posted_at": "2026-11-01T09:00:00.000Z"
650
+ },
651
+ "headers": {
652
+ "operation": "insert"
653
+ }
654
+ },
655
+ {
656
+ "type": "link",
657
+ "key": "[\"entry_of\",\"ledger:entry:e1\",\"ledger:account:a\"]",
658
+ "value": {
659
+ "type": "entry_of",
660
+ "from": "ledger:entry:e1",
661
+ "to": "ledger:account:a",
662
+ "confidence": 0.5,
663
+ "evidence": "event:ledger:e1"
664
+ },
665
+ "old_value": {
666
+ "type": "entry_of",
667
+ "from": "ledger:entry:e1",
668
+ "to": "ledger:account:a",
669
+ "confidence": 1,
670
+ "evidence": "event:ledger:e1"
671
+ },
672
+ "headers": {
673
+ "operation": "update"
674
+ }
675
+ },
676
+ {
677
+ "type": "link",
678
+ "key": "[\"entry_of\",\"ledger:entry:e3\",\"ledger:account:a\"]",
679
+ "value": {
680
+ "type": "entry_of",
681
+ "from": "ledger:entry:e3",
682
+ "to": "ledger:account:a",
683
+ "confidence": 1,
684
+ "evidence": "event:ledger:e3"
685
+ },
686
+ "headers": {
687
+ "operation": "insert"
688
+ }
689
+ },
690
+ {
691
+ "type": "ledger_entry",
692
+ "key": "[\"ledger:entry:e2\"]",
693
+ "old_value": {
694
+ "id": "ledger:entry:e2",
695
+ "account_id": "ledger:account:a",
696
+ "amount": 5,
697
+ "rate": 1.25,
698
+ "memo": "Rope",
699
+ "details": {
700
+ "till": "north"
701
+ },
702
+ "posted_at": "2026-11-01T09:00:00.000Z"
703
+ },
704
+ "headers": {
705
+ "operation": "delete"
706
+ }
707
+ },
708
+ {
709
+ "type": "link",
710
+ "key": "[\"entry_of\",\"ledger:entry:e2\",\"ledger:account:a\"]",
711
+ "headers": {
712
+ "operation": "delete"
713
+ }
714
+ }
715
+ ]
716
+ }
717
+ ],
718
+ "rows": {
719
+ "ledger_account": [
720
+ {
721
+ "id": "ledger:account:a",
722
+ "name": "Alpha",
723
+ "balance": 10,
724
+ "is_closed": false,
725
+ "opened_at": "2026-11-01T09:00:00.000Z",
726
+ "updated_at": "2026-11-01T09:00:00.000Z"
727
+ }
728
+ ],
729
+ "ledger_entry": [
730
+ {
731
+ "id": "ledger:entry:e1",
732
+ "account_id": "ledger:account:a",
733
+ "amount": 5,
734
+ "rate": 1.25,
735
+ "memo": "Rope",
736
+ "details": {
737
+ "till": "north"
738
+ },
739
+ "posted_at": "2026-11-01T09:00:00.000Z"
740
+ },
741
+ {
742
+ "id": "ledger:entry:e3",
743
+ "account_id": "ledger:account:a",
744
+ "amount": 5,
745
+ "rate": 1.25,
746
+ "memo": "Rope",
747
+ "details": {
748
+ "till": "north"
749
+ },
750
+ "posted_at": "2026-11-01T09:00:00.000Z"
751
+ }
752
+ ],
753
+ "ledger_rate": [],
754
+ "ledger_tag": []
755
+ },
756
+ "links": [
757
+ {
758
+ "type": "entry_of",
759
+ "from_id": "ledger:entry:e1",
760
+ "to_id": "ledger:account:a",
761
+ "scope": "ledger:account:a",
762
+ "origin": "ledger_account",
763
+ "confidence": 0.5,
764
+ "evidence": "event:ledger:e1"
765
+ },
766
+ {
767
+ "type": "entry_of",
768
+ "from_id": "ledger:entry:e3",
769
+ "to_id": "ledger:account:a",
770
+ "scope": "ledger:account:a",
771
+ "origin": "ledger_account",
772
+ "confidence": 1,
773
+ "evidence": "event:ledger:e3"
774
+ }
775
+ ]
776
+ },
777
+ {
778
+ "title": "a real column and a confidence compare as floats, so 2 and 2.0 are one value",
779
+ "batches": [
780
+ {
781
+ "scopes": [
782
+ {
783
+ "scope": "ledger:account:a",
784
+ "output": "[{\"upsert\":{\"ledger_entry\":{\"id\":\"ledger:entry:e1\",\"account_id\":\"ledger:account:a\",\"amount\":5,\"rate\":2,\"memo\":\"Rope\",\"details\":{\"till\":\"north\"},\"posted_at\":\"2026-11-01T09:00:00.000Z\"}}},{\"link\":{\"type\":\"entry_of\",\"from\":\"ledger:entry:e1\",\"to\":\"ledger:account:a\",\"confidence\":1,\"evidence\":\"event:ledger:e1\"}}]"
785
+ }
786
+ ],
787
+ "changes": [
788
+ {
789
+ "type": "ledger_entry",
790
+ "key": "[\"ledger:entry:e1\"]",
791
+ "value": {
792
+ "id": "ledger:entry:e1",
793
+ "account_id": "ledger:account:a",
794
+ "amount": 5,
795
+ "rate": 2,
796
+ "memo": "Rope",
797
+ "details": {
798
+ "till": "north"
799
+ },
800
+ "posted_at": "2026-11-01T09:00:00.000Z"
801
+ },
802
+ "headers": {
803
+ "operation": "insert"
804
+ }
805
+ },
806
+ {
807
+ "type": "link",
808
+ "key": "[\"entry_of\",\"ledger:entry:e1\",\"ledger:account:a\"]",
809
+ "value": {
810
+ "type": "entry_of",
811
+ "from": "ledger:entry:e1",
812
+ "to": "ledger:account:a",
813
+ "confidence": 1,
814
+ "evidence": "event:ledger:e1"
815
+ },
816
+ "headers": {
817
+ "operation": "insert"
818
+ }
819
+ }
820
+ ]
821
+ },
822
+ {
823
+ "scopes": [
824
+ {
825
+ "scope": "ledger:account:a",
826
+ "output": "[{\"upsert\":{\"ledger_entry\":{\"id\":\"ledger:entry:e1\",\"account_id\":\"ledger:account:a\",\"amount\":5,\"rate\":2.0,\"memo\":\"Rope\",\"details\":{\"till\":\"north\"},\"posted_at\":\"2026-11-01T09:00:00.000Z\"}}},{\"link\":{\"type\":\"entry_of\",\"from\":\"ledger:entry:e1\",\"to\":\"ledger:account:a\",\"confidence\":1.0,\"evidence\":\"event:ledger:e1\"}}]"
827
+ }
828
+ ],
829
+ "changes": []
830
+ }
831
+ ],
832
+ "rows": {
833
+ "ledger_account": [],
834
+ "ledger_entry": [
835
+ {
836
+ "id": "ledger:entry:e1",
837
+ "account_id": "ledger:account:a",
838
+ "amount": 5,
839
+ "rate": 2,
840
+ "memo": "Rope",
841
+ "details": {
842
+ "till": "north"
843
+ },
844
+ "posted_at": "2026-11-01T09:00:00.000Z"
845
+ }
846
+ ],
847
+ "ledger_rate": [],
848
+ "ledger_tag": []
849
+ },
850
+ "links": [
851
+ {
852
+ "type": "entry_of",
853
+ "from_id": "ledger:entry:e1",
854
+ "to_id": "ledger:account:a",
855
+ "scope": "ledger:account:a",
856
+ "origin": "ledger_account",
857
+ "confidence": 1,
858
+ "evidence": "event:ledger:e1"
859
+ }
860
+ ]
861
+ },
862
+ {
863
+ "title": "a JSON column compares as a value, whatever the order of its keys",
864
+ "batches": [
865
+ {
866
+ "scopes": [
867
+ {
868
+ "scope": "ledger:account:a",
869
+ "output": "[{\"upsert\":{\"ledger_entry\":{\"id\":\"ledger:entry:e1\",\"account_id\":\"ledger:account:a\",\"amount\":5,\"rate\":1.25,\"memo\":\"Rope\",\"details\":{\"till\":\"north\",\"desk\":\"7\"},\"posted_at\":\"2026-11-01T09:00:00.000Z\"}}}]"
870
+ }
871
+ ],
872
+ "changes": [
873
+ {
874
+ "type": "ledger_entry",
875
+ "key": "[\"ledger:entry:e1\"]",
876
+ "value": {
877
+ "id": "ledger:entry:e1",
878
+ "account_id": "ledger:account:a",
879
+ "amount": 5,
880
+ "rate": 1.25,
881
+ "memo": "Rope",
882
+ "details": {
883
+ "till": "north",
884
+ "desk": "7"
885
+ },
886
+ "posted_at": "2026-11-01T09:00:00.000Z"
887
+ },
888
+ "headers": {
889
+ "operation": "insert"
890
+ }
891
+ }
892
+ ]
893
+ },
894
+ {
895
+ "scopes": [
896
+ {
897
+ "scope": "ledger:account:a",
898
+ "output": "[{\"upsert\":{\"ledger_entry\":{\"id\":\"ledger:entry:e1\",\"account_id\":\"ledger:account:a\",\"amount\":5,\"rate\":1.25,\"memo\":\"Rope\",\"details\":{\"desk\":\"7\",\"till\":\"north\"},\"posted_at\":\"2026-11-01T09:00:00.000Z\"}}}]"
899
+ }
900
+ ],
901
+ "changes": []
902
+ }
903
+ ],
904
+ "rows": {
905
+ "ledger_account": [],
906
+ "ledger_entry": [
907
+ {
908
+ "id": "ledger:entry:e1",
909
+ "account_id": "ledger:account:a",
910
+ "amount": 5,
911
+ "rate": 1.25,
912
+ "memo": "Rope",
913
+ "details": {
914
+ "till": "north",
915
+ "desk": "7"
916
+ },
917
+ "posted_at": "2026-11-01T09:00:00.000Z"
918
+ }
919
+ ],
920
+ "ledger_rate": [],
921
+ "ledger_tag": []
922
+ },
923
+ "links": []
924
+ },
925
+ {
926
+ "title": "two scopes of one batch give their change entries in the order of the scopes",
927
+ "batches": [
928
+ {
929
+ "scopes": [
930
+ {
931
+ "scope": "ledger:account:a",
932
+ "output": "[{\"upsert\":{\"ledger_account\":{\"id\":\"ledger:account:a\",\"name\":\"Alpha\",\"balance\":5,\"is_closed\":false,\"opened_at\":\"2026-11-01T09:00:00.000Z\",\"updated_at\":\"2026-11-01T09:00:00.000Z\"}}}]"
933
+ },
934
+ {
935
+ "scope": "ledger:account:b",
936
+ "output": "[{\"upsert\":{\"ledger_account\":{\"id\":\"ledger:account:b\",\"name\":\"Beta\",\"balance\":5,\"is_closed\":false,\"opened_at\":\"2026-11-01T09:00:00.000Z\",\"updated_at\":\"2026-11-01T09:00:00.000Z\"}}}]"
937
+ }
938
+ ],
939
+ "changes": [
940
+ {
941
+ "type": "ledger_account",
942
+ "key": "[\"ledger:account:a\"]",
943
+ "value": {
944
+ "id": "ledger:account:a",
945
+ "name": "Alpha",
946
+ "balance": 5,
947
+ "is_closed": false,
948
+ "opened_at": "2026-11-01T09:00:00.000Z",
949
+ "updated_at": "2026-11-01T09:00:00.000Z"
950
+ },
951
+ "headers": {
952
+ "operation": "insert"
953
+ }
954
+ },
955
+ {
956
+ "type": "ledger_account",
957
+ "key": "[\"ledger:account:b\"]",
958
+ "value": {
959
+ "id": "ledger:account:b",
960
+ "name": "Beta",
961
+ "balance": 5,
962
+ "is_closed": false,
963
+ "opened_at": "2026-11-01T09:00:00.000Z",
964
+ "updated_at": "2026-11-01T09:00:00.000Z"
965
+ },
966
+ "headers": {
967
+ "operation": "insert"
968
+ }
969
+ }
970
+ ]
971
+ }
972
+ ],
973
+ "rows": {
974
+ "ledger_account": [
975
+ {
976
+ "id": "ledger:account:a",
977
+ "name": "Alpha",
978
+ "balance": 5,
979
+ "is_closed": false,
980
+ "opened_at": "2026-11-01T09:00:00.000Z",
981
+ "updated_at": "2026-11-01T09:00:00.000Z"
982
+ },
983
+ {
984
+ "id": "ledger:account:b",
985
+ "name": "Beta",
986
+ "balance": 5,
987
+ "is_closed": false,
988
+ "opened_at": "2026-11-01T09:00:00.000Z",
989
+ "updated_at": "2026-11-01T09:00:00.000Z"
990
+ }
991
+ ],
992
+ "ledger_entry": [],
993
+ "ledger_rate": [],
994
+ "ledger_tag": []
995
+ },
996
+ "links": []
997
+ },
998
+ {
999
+ "title": "a row that another scope owns is a scope conflict",
1000
+ "batches": [
1001
+ {
1002
+ "scopes": [
1003
+ {
1004
+ "scope": "ledger:account:a",
1005
+ "output": "[{\"upsert\":{\"ledger_entry\":{\"id\":\"ledger:entry:e1\",\"account_id\":\"ledger:account:a\",\"amount\":5,\"rate\":1.25,\"memo\":\"Rope\",\"details\":{\"till\":\"north\"},\"posted_at\":\"2026-11-01T09:00:00.000Z\"}}}]"
1006
+ }
1007
+ ],
1008
+ "changes": [
1009
+ {
1010
+ "type": "ledger_entry",
1011
+ "key": "[\"ledger:entry:e1\"]",
1012
+ "value": {
1013
+ "id": "ledger:entry:e1",
1014
+ "account_id": "ledger:account:a",
1015
+ "amount": 5,
1016
+ "rate": 1.25,
1017
+ "memo": "Rope",
1018
+ "details": {
1019
+ "till": "north"
1020
+ },
1021
+ "posted_at": "2026-11-01T09:00:00.000Z"
1022
+ },
1023
+ "headers": {
1024
+ "operation": "insert"
1025
+ }
1026
+ }
1027
+ ]
1028
+ },
1029
+ {
1030
+ "scopes": [
1031
+ {
1032
+ "scope": "ledger:account:b",
1033
+ "output": "[{\"upsert\":{\"ledger_account\":{\"id\":\"ledger:account:b\",\"name\":\"Beta\",\"balance\":5,\"is_closed\":false,\"opened_at\":\"2026-11-01T09:00:00.000Z\",\"updated_at\":\"2026-11-01T09:00:00.000Z\"}}},{\"upsert\":{\"ledger_entry\":{\"id\":\"ledger:entry:e1\",\"account_id\":\"ledger:account:a\",\"amount\":5,\"rate\":1.25,\"memo\":\"Rope\",\"details\":{\"till\":\"north\"},\"posted_at\":\"2026-11-01T09:00:00.000Z\"}}}]"
1034
+ }
1035
+ ],
1036
+ "refused": "scope-conflict"
1037
+ }
1038
+ ],
1039
+ "rows": {
1040
+ "ledger_account": [],
1041
+ "ledger_entry": [
1042
+ {
1043
+ "id": "ledger:entry:e1",
1044
+ "account_id": "ledger:account:a",
1045
+ "amount": 5,
1046
+ "rate": 1.25,
1047
+ "memo": "Rope",
1048
+ "details": {
1049
+ "till": "north"
1050
+ },
1051
+ "posted_at": "2026-11-01T09:00:00.000Z"
1052
+ }
1053
+ ],
1054
+ "ledger_rate": [],
1055
+ "ledger_tag": []
1056
+ },
1057
+ "links": []
1058
+ },
1059
+ {
1060
+ "title": "a link that another scope owns is a scope conflict",
1061
+ "batches": [
1062
+ {
1063
+ "scopes": [
1064
+ {
1065
+ "scope": "ledger:account:a",
1066
+ "output": "[{\"link\":{\"type\":\"entry_of\",\"from\":\"ledger:entry:e1\",\"to\":\"ledger:account:a\",\"confidence\":1,\"evidence\":\"event:ledger:e1\"}}]"
1067
+ }
1068
+ ],
1069
+ "changes": [
1070
+ {
1071
+ "type": "link",
1072
+ "key": "[\"entry_of\",\"ledger:entry:e1\",\"ledger:account:a\"]",
1073
+ "value": {
1074
+ "type": "entry_of",
1075
+ "from": "ledger:entry:e1",
1076
+ "to": "ledger:account:a",
1077
+ "confidence": 1,
1078
+ "evidence": "event:ledger:e1"
1079
+ },
1080
+ "headers": {
1081
+ "operation": "insert"
1082
+ }
1083
+ }
1084
+ ]
1085
+ },
1086
+ {
1087
+ "scopes": [
1088
+ {
1089
+ "scope": "ledger:account:b",
1090
+ "output": "[{\"upsert\":{\"ledger_account\":{\"id\":\"ledger:account:b\",\"name\":\"Beta\",\"balance\":5,\"is_closed\":false,\"opened_at\":\"2026-11-01T09:00:00.000Z\",\"updated_at\":\"2026-11-01T09:00:00.000Z\"}}},{\"link\":{\"type\":\"entry_of\",\"from\":\"ledger:entry:e1\",\"to\":\"ledger:account:a\",\"confidence\":1,\"evidence\":\"event:ledger:e1\"}}]"
1091
+ }
1092
+ ],
1093
+ "refused": "scope-conflict"
1094
+ }
1095
+ ],
1096
+ "rows": {
1097
+ "ledger_account": [],
1098
+ "ledger_entry": [],
1099
+ "ledger_rate": [],
1100
+ "ledger_tag": []
1101
+ },
1102
+ "links": [
1103
+ {
1104
+ "type": "entry_of",
1105
+ "from_id": "ledger:entry:e1",
1106
+ "to_id": "ledger:account:a",
1107
+ "scope": "ledger:account:a",
1108
+ "origin": "ledger_account",
1109
+ "confidence": 1,
1110
+ "evidence": "event:ledger:e1"
1111
+ }
1112
+ ]
1113
+ },
1114
+ {
1115
+ "title": "a link that is stored with no scope is a scope conflict",
1116
+ "stored": {
1117
+ "links": [
1118
+ {
1119
+ "type": "entry_of",
1120
+ "from_id": "ledger:entry:e1",
1121
+ "to_id": "ledger:account:a",
1122
+ "scope": null,
1123
+ "origin": "ledger_account",
1124
+ "confidence": 1,
1125
+ "evidence": "event:ledger:e1"
1126
+ }
1127
+ ]
1128
+ },
1129
+ "batches": [
1130
+ {
1131
+ "scopes": [
1132
+ {
1133
+ "scope": "ledger:account:a",
1134
+ "output": "[{\"upsert\":{\"ledger_account\":{\"id\":\"ledger:account:a\",\"name\":\"Alpha\",\"balance\":5,\"is_closed\":false,\"opened_at\":\"2026-11-01T09:00:00.000Z\",\"updated_at\":\"2026-11-01T09:00:00.000Z\"}}},{\"upsert\":{\"ledger_entry\":{\"id\":\"ledger:entry:e1\",\"account_id\":\"ledger:account:a\",\"amount\":5,\"rate\":1.25,\"memo\":\"Rope\",\"details\":{\"till\":\"north\"},\"posted_at\":\"2026-11-01T09:00:00.000Z\"}}},{\"link\":{\"type\":\"entry_of\",\"from\":\"ledger:entry:e1\",\"to\":\"ledger:account:a\",\"confidence\":1,\"evidence\":\"event:ledger:e1\"}}]"
1135
+ }
1136
+ ],
1137
+ "refused": "scope-conflict"
1138
+ }
1139
+ ],
1140
+ "rows": {
1141
+ "ledger_account": [],
1142
+ "ledger_entry": [],
1143
+ "ledger_rate": [],
1144
+ "ledger_tag": []
1145
+ },
1146
+ "links": [
1147
+ {
1148
+ "type": "entry_of",
1149
+ "from_id": "ledger:entry:e1",
1150
+ "to_id": "ledger:account:a",
1151
+ "scope": null,
1152
+ "origin": "ledger_account",
1153
+ "confidence": 1,
1154
+ "evidence": "event:ledger:e1"
1155
+ }
1156
+ ]
1157
+ },
1158
+ {
1159
+ "title": "a row key twice in one scope result is refused",
1160
+ "batches": [
1161
+ {
1162
+ "scopes": [
1163
+ {
1164
+ "scope": "ledger:account:a",
1165
+ "output": "[{\"upsert\":{\"ledger_account\":{\"id\":\"ledger:account:a\",\"name\":\"Alpha\",\"balance\":5,\"is_closed\":false,\"opened_at\":\"2026-11-01T09:00:00.000Z\",\"updated_at\":\"2026-11-01T09:00:00.000Z\"}}},{\"upsert\":{\"ledger_account\":{\"id\":\"ledger:account:a\",\"name\":\"Alpha\",\"balance\":6,\"is_closed\":false,\"opened_at\":\"2026-11-01T09:00:00.000Z\",\"updated_at\":\"2026-11-01T09:00:00.000Z\"}}}]"
1166
+ }
1167
+ ],
1168
+ "refused": "duplicate-row"
1169
+ }
1170
+ ],
1171
+ "rows": {
1172
+ "ledger_account": [],
1173
+ "ledger_entry": [],
1174
+ "ledger_rate": [],
1175
+ "ledger_tag": []
1176
+ },
1177
+ "links": []
1178
+ },
1179
+ {
1180
+ "title": "a link key twice in one scope result is refused",
1181
+ "batches": [
1182
+ {
1183
+ "scopes": [
1184
+ {
1185
+ "scope": "ledger:account:a",
1186
+ "output": "[{\"link\":{\"type\":\"entry_of\",\"from\":\"ledger:entry:e1\",\"to\":\"ledger:account:a\",\"confidence\":1,\"evidence\":\"event:ledger:e1\"}},{\"link\":{\"type\":\"entry_of\",\"from\":\"ledger:entry:e1\",\"to\":\"ledger:account:a\",\"confidence\":0.5,\"evidence\":\"event:ledger:e1\"}}]"
1187
+ }
1188
+ ],
1189
+ "refused": "duplicate-link"
1190
+ }
1191
+ ],
1192
+ "rows": {
1193
+ "ledger_account": [],
1194
+ "ledger_entry": [],
1195
+ "ledger_rate": [],
1196
+ "ledger_tag": []
1197
+ },
1198
+ "links": []
1199
+ },
1200
+ {
1201
+ "title": "a refused link stops the whole batch, and the scopes before it write nothing",
1202
+ "batches": [
1203
+ {
1204
+ "scopes": [
1205
+ {
1206
+ "scope": "ledger:account:a",
1207
+ "output": "[{\"upsert\":{\"ledger_account\":{\"id\":\"ledger:account:a\",\"name\":\"Alpha\",\"balance\":5,\"is_closed\":false,\"opened_at\":\"2026-11-01T09:00:00.000Z\",\"updated_at\":\"2026-11-01T09:00:00.000Z\"}}}]"
1208
+ },
1209
+ {
1210
+ "scope": "ledger:account:b",
1211
+ "output": "[{\"upsert\":{\"ledger_account\":{\"id\":\"ledger:account:b\",\"name\":\"Beta\",\"balance\":5,\"is_closed\":false,\"opened_at\":\"2026-11-01T09:00:00.000Z\",\"updated_at\":\"2026-11-01T09:00:00.000Z\"}}},{\"link\":{\"type\":\"entry_of\",\"from\":\"ledger:entry:e1\",\"to\":\"ledger:entry:e2\",\"confidence\":1,\"evidence\":\"event:ledger:e1\"}}]"
1212
+ }
1213
+ ],
1214
+ "refused": "bad-link"
1215
+ }
1216
+ ],
1217
+ "rows": {
1218
+ "ledger_account": [],
1219
+ "ledger_entry": [],
1220
+ "ledger_rate": [],
1221
+ "ledger_tag": []
1222
+ },
1223
+ "links": []
1224
+ },
1225
+ {
1226
+ "title": "an integer column refuses a number written with a fraction, even 5.0",
1227
+ "batches": [
1228
+ {
1229
+ "scopes": [
1230
+ {
1231
+ "scope": "ledger:account:a",
1232
+ "output": "[{\"upsert\":{\"ledger_account\":{\"id\":\"ledger:account:a\",\"name\":\"Alpha\",\"balance\":5.0,\"is_closed\":false,\"opened_at\":\"2026-11-01T09:00:00.000Z\",\"updated_at\":\"2026-11-01T09:00:00.000Z\"}}}]"
1233
+ }
1234
+ ],
1235
+ "refused": "bad-row"
1236
+ }
1237
+ ],
1238
+ "rows": {
1239
+ "ledger_account": [],
1240
+ "ledger_entry": [],
1241
+ "ledger_rate": [],
1242
+ "ledger_tag": []
1243
+ },
1244
+ "links": []
1245
+ },
1246
+ {
1247
+ "title": "a row with a column left out is refused",
1248
+ "batches": [
1249
+ {
1250
+ "scopes": [
1251
+ {
1252
+ "scope": "ledger:account:a",
1253
+ "output": "[{\"upsert\":{\"ledger_account\":{\"id\":\"ledger:account:a\",\"name\":\"Alpha\",\"balance\":5,\"opened_at\":\"2026-11-01T09:00:00.000Z\",\"updated_at\":\"2026-11-01T09:00:00.000Z\"}}}]"
1254
+ }
1255
+ ],
1256
+ "refused": "bad-row"
1257
+ }
1258
+ ],
1259
+ "rows": {
1260
+ "ledger_account": [],
1261
+ "ledger_entry": [],
1262
+ "ledger_rate": [],
1263
+ "ledger_tag": []
1264
+ },
1265
+ "links": []
1266
+ },
1267
+ {
1268
+ "title": "a row with a key that is no column is refused",
1269
+ "batches": [
1270
+ {
1271
+ "scopes": [
1272
+ {
1273
+ "scope": "ledger:account:a",
1274
+ "output": "[{\"upsert\":{\"ledger_account\":{\"id\":\"ledger:account:a\",\"name\":\"Alpha\",\"balance\":5,\"is_closed\":false,\"opened_at\":\"2026-11-01T09:00:00.000Z\",\"updated_at\":\"2026-11-01T09:00:00.000Z\",\"colour\":\"red\"}}}]"
1275
+ }
1276
+ ],
1277
+ "refused": "bad-row"
1278
+ }
1279
+ ],
1280
+ "rows": {
1281
+ "ledger_account": [],
1282
+ "ledger_entry": [],
1283
+ "ledger_rate": [],
1284
+ "ledger_tag": []
1285
+ },
1286
+ "links": []
1287
+ },
1288
+ {
1289
+ "title": "a row with null in a column that is not nullable is refused",
1290
+ "batches": [
1291
+ {
1292
+ "scopes": [
1293
+ {
1294
+ "scope": "ledger:account:a",
1295
+ "output": "[{\"upsert\":{\"ledger_account\":{\"id\":\"ledger:account:a\",\"name\":\"Alpha\",\"balance\":5,\"is_closed\":false,\"opened_at\":\"2026-11-01T09:00:00.000Z\",\"updated_at\":null}}}]"
1296
+ }
1297
+ ],
1298
+ "refused": "bad-row"
1299
+ }
1300
+ ],
1301
+ "rows": {
1302
+ "ledger_account": [],
1303
+ "ledger_entry": [],
1304
+ "ledger_rate": [],
1305
+ "ledger_tag": []
1306
+ },
1307
+ "links": []
1308
+ },
1309
+ {
1310
+ "title": "a boolean column refuses a number",
1311
+ "batches": [
1312
+ {
1313
+ "scopes": [
1314
+ {
1315
+ "scope": "ledger:account:a",
1316
+ "output": "[{\"upsert\":{\"ledger_account\":{\"id\":\"ledger:account:a\",\"name\":\"Alpha\",\"balance\":5,\"is_closed\":0,\"opened_at\":\"2026-11-01T09:00:00.000Z\",\"updated_at\":\"2026-11-01T09:00:00.000Z\"}}}]"
1317
+ }
1318
+ ],
1319
+ "refused": "bad-row"
1320
+ }
1321
+ ],
1322
+ "rows": {
1323
+ "ledger_account": [],
1324
+ "ledger_entry": [],
1325
+ "ledger_rate": [],
1326
+ "ledger_tag": []
1327
+ },
1328
+ "links": []
1329
+ },
1330
+ {
1331
+ "title": "a delete in a scope result is refused",
1332
+ "batches": [
1333
+ {
1334
+ "scopes": [
1335
+ {
1336
+ "scope": "ledger:account:a",
1337
+ "output": "[{\"delete\":{\"ledger_account\":{\"id\":\"ledger:account:a\"}}}]"
1338
+ }
1339
+ ],
1340
+ "refused": "bad-operation"
1341
+ }
1342
+ ],
1343
+ "rows": {
1344
+ "ledger_account": [],
1345
+ "ledger_entry": [],
1346
+ "ledger_rate": [],
1347
+ "ledger_tag": []
1348
+ },
1349
+ "links": []
1350
+ },
1351
+ {
1352
+ "title": "an unlink in a scope result is refused",
1353
+ "batches": [
1354
+ {
1355
+ "scopes": [
1356
+ {
1357
+ "scope": "ledger:account:a",
1358
+ "output": "[{\"unlink\":{\"type\":\"entry_of\",\"from\":\"ledger:entry:e1\",\"to\":\"ledger:account:a\"}}]"
1359
+ }
1360
+ ],
1361
+ "refused": "bad-operation"
1362
+ }
1363
+ ],
1364
+ "rows": {
1365
+ "ledger_account": [],
1366
+ "ledger_entry": [],
1367
+ "ledger_rate": [],
1368
+ "ledger_tag": []
1369
+ },
1370
+ "links": []
1371
+ },
1372
+ {
1373
+ "title": "an operation with two verbs is refused",
1374
+ "batches": [
1375
+ {
1376
+ "scopes": [
1377
+ {
1378
+ "scope": "ledger:account:a",
1379
+ "output": "[{\"upsert\":{\"ledger_account\":{\"id\":\"ledger:account:a\",\"name\":\"Alpha\",\"balance\":5,\"is_closed\":false,\"opened_at\":\"2026-11-01T09:00:00.000Z\",\"updated_at\":\"2026-11-01T09:00:00.000Z\"}},\"link\":{\"type\":\"entry_of\",\"from\":\"ledger:entry:e1\",\"to\":\"ledger:account:a\",\"confidence\":1,\"evidence\":\"event:ledger:e1\"}}]"
1380
+ }
1381
+ ],
1382
+ "refused": "bad-operation"
1383
+ }
1384
+ ],
1385
+ "rows": {
1386
+ "ledger_account": [],
1387
+ "ledger_entry": [],
1388
+ "ledger_rate": [],
1389
+ "ledger_tag": []
1390
+ },
1391
+ "links": []
1392
+ },
1393
+ {
1394
+ "title": "a result that is no list is refused",
1395
+ "batches": [
1396
+ {
1397
+ "scopes": [
1398
+ {
1399
+ "scope": "ledger:account:a",
1400
+ "output": "{\"upsert\":{\"ledger_account\":{\"id\":\"ledger:account:a\",\"name\":\"Alpha\",\"balance\":5,\"is_closed\":false,\"opened_at\":\"2026-11-01T09:00:00.000Z\",\"updated_at\":\"2026-11-01T09:00:00.000Z\"}}}"
1401
+ }
1402
+ ],
1403
+ "refused": "bad-operation"
1404
+ }
1405
+ ],
1406
+ "rows": {
1407
+ "ledger_account": [],
1408
+ "ledger_entry": [],
1409
+ "ledger_rate": [],
1410
+ "ledger_tag": []
1411
+ },
1412
+ "links": []
1413
+ },
1414
+ {
1415
+ "title": "an upsert to a table that the consumer does not own is refused",
1416
+ "batches": [
1417
+ {
1418
+ "scopes": [
1419
+ {
1420
+ "scope": "ledger:account:a",
1421
+ "output": "[{\"upsert\":{\"harbour_berth\":{\"id\":\"harbour:berth:1\"}}}]"
1422
+ }
1423
+ ],
1424
+ "refused": "not-owned"
1425
+ }
1426
+ ],
1427
+ "rows": {
1428
+ "ledger_account": [],
1429
+ "ledger_entry": [],
1430
+ "ledger_rate": [],
1431
+ "ledger_tag": []
1432
+ },
1433
+ "links": []
1434
+ },
1435
+ {
1436
+ "title": "a bad row before a bad link is refused as a bad link, because every link is checked first",
1437
+ "batches": [
1438
+ {
1439
+ "scopes": [
1440
+ {
1441
+ "scope": "ledger:account:a",
1442
+ "output": "[{\"upsert\":{\"ledger_account\":{\"id\":\"ledger:account:a\",\"name\":\"Alpha\",\"balance\":5.0,\"is_closed\":false,\"opened_at\":\"2026-11-01T09:00:00.000Z\",\"updated_at\":\"2026-11-01T09:00:00.000Z\"}}},{\"link\":{\"type\":\"paid_by\",\"from\":\"ledger:entry:e1\",\"to\":\"ledger:account:a\",\"confidence\":1,\"evidence\":\"event:ledger:e1\"}}]"
1443
+ }
1444
+ ],
1445
+ "refused": "bad-link"
1446
+ }
1447
+ ],
1448
+ "rows": {
1449
+ "ledger_account": [],
1450
+ "ledger_entry": [],
1451
+ "ledger_rate": [],
1452
+ "ledger_tag": []
1453
+ },
1454
+ "links": []
1455
+ },
1456
+ {
1457
+ "title": "a bad link before an upsert to a table that the consumer does not own is refused as not owned, because the parse checks each table first",
1458
+ "batches": [
1459
+ {
1460
+ "scopes": [
1461
+ {
1462
+ "scope": "ledger:account:a",
1463
+ "output": "[{\"link\":{\"type\":\"paid_by\",\"from\":\"ledger:entry:e1\",\"to\":\"ledger:account:a\",\"confidence\":1,\"evidence\":\"event:ledger:e1\"}},{\"upsert\":{\"harbour_berth\":{\"id\":\"harbour:berth:1\"}}}]"
1464
+ }
1465
+ ],
1466
+ "refused": "not-owned"
1467
+ }
1468
+ ],
1469
+ "rows": {
1470
+ "ledger_account": [],
1471
+ "ledger_entry": [],
1472
+ "ledger_rate": [],
1473
+ "ledger_tag": []
1474
+ },
1475
+ "links": []
1476
+ },
1477
+ {
1478
+ "title": "a real key written as 2 and then as 2.0 is one key, so the second result changes nothing",
1479
+ "batches": [
1480
+ {
1481
+ "scopes": [
1482
+ {
1483
+ "scope": "ledger:account:a",
1484
+ "output": "[{\"upsert\":{\"ledger_rate\":{\"rate\":2,\"label\":\"Two\"}}}]"
1485
+ }
1486
+ ],
1487
+ "changes": [
1488
+ {
1489
+ "type": "ledger_rate",
1490
+ "key": "[2]",
1491
+ "value": {
1492
+ "rate": 2,
1493
+ "label": "Two"
1494
+ },
1495
+ "headers": {
1496
+ "operation": "insert"
1497
+ }
1498
+ }
1499
+ ]
1500
+ },
1501
+ {
1502
+ "scopes": [
1503
+ {
1504
+ "scope": "ledger:account:a",
1505
+ "output": "[{\"upsert\":{\"ledger_rate\":{\"rate\":2.0,\"label\":\"Two\"}}}]"
1506
+ }
1507
+ ],
1508
+ "changes": []
1509
+ }
1510
+ ],
1511
+ "rows": {
1512
+ "ledger_account": [],
1513
+ "ledger_entry": [],
1514
+ "ledger_rate": [
1515
+ {
1516
+ "rate": 2,
1517
+ "label": "Two"
1518
+ }
1519
+ ],
1520
+ "ledger_tag": []
1521
+ },
1522
+ "links": []
1523
+ },
1524
+ {
1525
+ "title": "a real key that another scope wrote as 2 is a scope conflict when written as 2.0",
1526
+ "batches": [
1527
+ {
1528
+ "scopes": [
1529
+ {
1530
+ "scope": "ledger:account:a",
1531
+ "output": "[{\"upsert\":{\"ledger_rate\":{\"rate\":2,\"label\":\"Two\"}}}]"
1532
+ }
1533
+ ],
1534
+ "changes": [
1535
+ {
1536
+ "type": "ledger_rate",
1537
+ "key": "[2]",
1538
+ "value": {
1539
+ "rate": 2,
1540
+ "label": "Two"
1541
+ },
1542
+ "headers": {
1543
+ "operation": "insert"
1544
+ }
1545
+ }
1546
+ ]
1547
+ },
1548
+ {
1549
+ "scopes": [
1550
+ {
1551
+ "scope": "ledger:account:b",
1552
+ "output": "[{\"upsert\":{\"ledger_rate\":{\"rate\":2.0,\"label\":\"Deux\"}}}]"
1553
+ }
1554
+ ],
1555
+ "refused": "scope-conflict"
1556
+ }
1557
+ ],
1558
+ "rows": {
1559
+ "ledger_account": [],
1560
+ "ledger_entry": [],
1561
+ "ledger_rate": [
1562
+ {
1563
+ "rate": 2,
1564
+ "label": "Two"
1565
+ }
1566
+ ],
1567
+ "ledger_tag": []
1568
+ },
1569
+ "links": []
1570
+ },
1571
+ {
1572
+ "title": "an integer column refuses a number past the safe integers as a bad row",
1573
+ "batches": [
1574
+ {
1575
+ "scopes": [
1576
+ {
1577
+ "scope": "ledger:account:a",
1578
+ "output": "[{\"upsert\":{\"ledger_account\":{\"id\":\"ledger:account:a\",\"name\":\"Alpha\",\"balance\":9007199254740993,\"is_closed\":false,\"opened_at\":null,\"updated_at\":\"2026-11-01T09:00:00.000Z\"}}}]"
1579
+ }
1580
+ ],
1581
+ "refused": "bad-row"
1582
+ }
1583
+ ],
1584
+ "rows": {
1585
+ "ledger_account": [],
1586
+ "ledger_entry": [],
1587
+ "ledger_rate": [],
1588
+ "ledger_tag": []
1589
+ },
1590
+ "links": []
1591
+ },
1592
+ {
1593
+ "title": "a real key keeps the double that JSON.parse gives, near 2^53 and with a long fraction, so a result without the rows deletes each",
1594
+ "batches": [
1595
+ {
1596
+ "scopes": [
1597
+ {
1598
+ "scope": "ledger:account:a",
1599
+ "output": "[{\"upsert\":{\"ledger_rate\":{\"rate\":1000000000000000.2,\"label\":\"Wide\"}}},{\"upsert\":{\"ledger_rate\":{\"rate\":9007199254740993,\"label\":\"Edge\"}}},{\"upsert\":{\"ledger_rate\":{\"rate\":3713064193725.5859,\"label\":\"Long\"}}}]"
1600
+ }
1601
+ ],
1602
+ "changes": [
1603
+ {
1604
+ "type": "ledger_rate",
1605
+ "key": "[1000000000000000.2]",
1606
+ "value": {
1607
+ "rate": 1000000000000000.2,
1608
+ "label": "Wide"
1609
+ },
1610
+ "headers": {
1611
+ "operation": "insert"
1612
+ }
1613
+ },
1614
+ {
1615
+ "type": "ledger_rate",
1616
+ "key": "[3713064193725.586]",
1617
+ "value": {
1618
+ "rate": 3713064193725.586,
1619
+ "label": "Long"
1620
+ },
1621
+ "headers": {
1622
+ "operation": "insert"
1623
+ }
1624
+ },
1625
+ {
1626
+ "type": "ledger_rate",
1627
+ "key": "[9007199254740992]",
1628
+ "value": {
1629
+ "rate": 9007199254740992,
1630
+ "label": "Edge"
1631
+ },
1632
+ "headers": {
1633
+ "operation": "insert"
1634
+ }
1635
+ }
1636
+ ]
1637
+ },
1638
+ {
1639
+ "scopes": [
1640
+ {
1641
+ "scope": "ledger:account:a",
1642
+ "output": "[]"
1643
+ }
1644
+ ],
1645
+ "changes": [
1646
+ {
1647
+ "type": "ledger_rate",
1648
+ "key": "[1000000000000000.2]",
1649
+ "old_value": {
1650
+ "rate": 1000000000000000.2,
1651
+ "label": "Wide"
1652
+ },
1653
+ "headers": {
1654
+ "operation": "delete"
1655
+ }
1656
+ },
1657
+ {
1658
+ "type": "ledger_rate",
1659
+ "key": "[3713064193725.586]",
1660
+ "old_value": {
1661
+ "rate": 3713064193725.586,
1662
+ "label": "Long"
1663
+ },
1664
+ "headers": {
1665
+ "operation": "delete"
1666
+ }
1667
+ },
1668
+ {
1669
+ "type": "ledger_rate",
1670
+ "key": "[9007199254740992]",
1671
+ "old_value": {
1672
+ "rate": 9007199254740992,
1673
+ "label": "Edge"
1674
+ },
1675
+ "headers": {
1676
+ "operation": "delete"
1677
+ }
1678
+ }
1679
+ ]
1680
+ }
1681
+ ],
1682
+ "rows": {
1683
+ "ledger_account": [],
1684
+ "ledger_entry": [],
1685
+ "ledger_rate": [],
1686
+ "ledger_tag": []
1687
+ },
1688
+ "links": []
1689
+ },
1690
+ {
1691
+ "title": "a json key written as 2.0 is stored and keyed as 2, so a result without the row deletes it",
1692
+ "batches": [
1693
+ {
1694
+ "scopes": [
1695
+ {
1696
+ "scope": "ledger:account:a",
1697
+ "output": "[{\"upsert\":{\"ledger_tag\":{\"tag\":2.0,\"label\":\"Two\"}}}]"
1698
+ }
1699
+ ],
1700
+ "changes": [
1701
+ {
1702
+ "type": "ledger_tag",
1703
+ "key": "[2]",
1704
+ "value": {
1705
+ "tag": 2,
1706
+ "label": "Two"
1707
+ },
1708
+ "headers": {
1709
+ "operation": "insert"
1710
+ }
1711
+ }
1712
+ ]
1713
+ },
1714
+ {
1715
+ "scopes": [
1716
+ {
1717
+ "scope": "ledger:account:a",
1718
+ "output": "[]"
1719
+ }
1720
+ ],
1721
+ "changes": [
1722
+ {
1723
+ "type": "ledger_tag",
1724
+ "key": "[2]",
1725
+ "old_value": {
1726
+ "tag": 2,
1727
+ "label": "Two"
1728
+ },
1729
+ "headers": {
1730
+ "operation": "delete"
1731
+ }
1732
+ }
1733
+ ]
1734
+ }
1735
+ ],
1736
+ "rows": {
1737
+ "ledger_account": [],
1738
+ "ledger_entry": [],
1739
+ "ledger_rate": [],
1740
+ "ledger_tag": []
1741
+ },
1742
+ "links": []
1743
+ }
1744
+ ]
1745
+ }