@cleocode/core 2026.10.7 → 2026.10.8

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 (105) hide show
  1. package/dist/build-provenance.json +2 -2
  2. package/dist/cloud/nexus-link.d.ts +15 -0
  3. package/dist/cloud/nexus-link.d.ts.map +1 -1
  4. package/dist/cloud/nexus-link.js +25 -0
  5. package/dist/cloud/nexus-link.js.map +1 -1
  6. package/dist/cloud/nexus-vault.d.ts +14 -1
  7. package/dist/cloud/nexus-vault.d.ts.map +1 -1
  8. package/dist/cloud/nexus-vault.js +72 -5
  9. package/dist/cloud/nexus-vault.js.map +1 -1
  10. package/dist/cloud/replica-rebind.d.ts +51 -0
  11. package/dist/cloud/replica-rebind.d.ts.map +1 -0
  12. package/dist/cloud/replica-rebind.js +101 -0
  13. package/dist/cloud/replica-rebind.js.map +1 -0
  14. package/dist/doctor/container-reaper.d.ts +107 -0
  15. package/dist/doctor/container-reaper.d.ts.map +1 -0
  16. package/dist/doctor/container-reaper.js +194 -0
  17. package/dist/doctor/container-reaper.js.map +1 -0
  18. package/dist/store/sync/apply/applier.d.ts +3 -0
  19. package/dist/store/sync/apply/applier.d.ts.map +1 -1
  20. package/dist/store/sync/apply/applier.js +208 -85
  21. package/dist/store/sync/apply/applier.js.map +1 -1
  22. package/dist/store/sync/capture.d.ts +23 -3
  23. package/dist/store/sync/capture.d.ts.map +1 -1
  24. package/dist/store/sync/capture.js +31 -3
  25. package/dist/store/sync/capture.js.map +1 -1
  26. package/dist/store/sync/collision-settle.d.ts +62 -0
  27. package/dist/store/sync/collision-settle.d.ts.map +1 -0
  28. package/dist/store/sync/collision-settle.js +156 -0
  29. package/dist/store/sync/collision-settle.js.map +1 -0
  30. package/dist/store/sync/conflicts.d.ts +11 -0
  31. package/dist/store/sync/conflicts.d.ts.map +1 -1
  32. package/dist/store/sync/conflicts.js +18 -0
  33. package/dist/store/sync/conflicts.js.map +1 -1
  34. package/dist/store/sync/inherit.d.ts +15 -8
  35. package/dist/store/sync/inherit.d.ts.map +1 -1
  36. package/dist/store/sync/inherit.js +28 -11
  37. package/dist/store/sync/inherit.js.map +1 -1
  38. package/dist/store/sync/merge/types.d.ts +6 -0
  39. package/dist/store/sync/merge/types.d.ts.map +1 -1
  40. package/dist/store/sync/pull.d.ts +23 -2
  41. package/dist/store/sync/pull.d.ts.map +1 -1
  42. package/dist/store/sync/pull.js +27 -2
  43. package/dist/store/sync/pull.js.map +1 -1
  44. package/dist/store/sync/rebind.d.ts +112 -0
  45. package/dist/store/sync/rebind.d.ts.map +1 -0
  46. package/dist/store/sync/rebind.js +187 -0
  47. package/dist/store/sync/rebind.js.map +1 -0
  48. package/dist/store/sync/reconcile.d.ts +93 -0
  49. package/dist/store/sync/reconcile.d.ts.map +1 -0
  50. package/dist/store/sync/reconcile.js +330 -0
  51. package/dist/store/sync/reconcile.js.map +1 -0
  52. package/dist/store/sync/repair.d.ts +5 -0
  53. package/dist/store/sync/repair.d.ts.map +1 -1
  54. package/dist/store/sync/repair.js +2 -2
  55. package/dist/store/sync/repair.js.map +1 -1
  56. package/dist/store/sync/replica.d.ts +28 -1
  57. package/dist/store/sync/replica.d.ts.map +1 -1
  58. package/dist/store/sync/replica.js +17 -3
  59. package/dist/store/sync/replica.js.map +1 -1
  60. package/dist/store/sync/retire.d.ts +161 -0
  61. package/dist/store/sync/retire.d.ts.map +1 -0
  62. package/dist/store/sync/retire.js +193 -0
  63. package/dist/store/sync/retire.js.map +1 -0
  64. package/dist/store/sync/schema.d.ts +27 -0
  65. package/dist/store/sync/schema.d.ts.map +1 -1
  66. package/dist/store/sync/schema.js +43 -0
  67. package/dist/store/sync/schema.js.map +1 -1
  68. package/dist/store/sync/sealer.d.ts +10 -0
  69. package/dist/store/sync/sealer.d.ts.map +1 -1
  70. package/dist/store/sync/sealer.js +51 -5
  71. package/dist/store/sync/sealer.js.map +1 -1
  72. package/dist/store/sync/segments.d.ts.map +1 -1
  73. package/dist/store/sync/segments.js +6 -2
  74. package/dist/store/sync/segments.js.map +1 -1
  75. package/dist/store/sync/uid-alias.d.ts +100 -0
  76. package/dist/store/sync/uid-alias.d.ts.map +1 -0
  77. package/dist/store/sync/uid-alias.js +169 -0
  78. package/dist/store/sync/uid-alias.js.map +1 -0
  79. package/dist/store/table-classification.d.ts.map +1 -1
  80. package/dist/store/table-classification.js +48 -0
  81. package/dist/store/table-classification.js.map +1 -1
  82. package/dist/tasks/change-set.d.ts.map +1 -1
  83. package/dist/tasks/change-set.js +16 -5
  84. package/dist/tasks/change-set.js.map +1 -1
  85. package/dist/tasks/complete-ci.d.ts.map +1 -1
  86. package/dist/tasks/complete-ci.js +7 -1
  87. package/dist/tasks/complete-ci.js.map +1 -1
  88. package/dist/tasks/done-plan.d.ts.map +1 -1
  89. package/dist/tasks/done-plan.js +24 -17
  90. package/dist/tasks/done-plan.js.map +1 -1
  91. package/dist/tasks/evidence.d.ts.map +1 -1
  92. package/dist/tasks/evidence.js +3 -2
  93. package/dist/tasks/evidence.js.map +1 -1
  94. package/dist/tasks/gate-runner.d.ts +20 -0
  95. package/dist/tasks/gate-runner.d.ts.map +1 -1
  96. package/dist/tasks/gate-runner.js +141 -19
  97. package/dist/tasks/gate-runner.js.map +1 -1
  98. package/dist/validation/engine-ops.d.ts.map +1 -1
  99. package/dist/validation/engine-ops.js +2 -0
  100. package/dist/validation/engine-ops.js.map +1 -1
  101. package/migrations/sync-journal/20261009130000_t13278-replica-retire/migration.sql +29 -0
  102. package/migrations/sync-journal/20261009140000_t13366-retire-confirm/migration.sql +18 -0
  103. package/migrations/sync-journal/20261010150000_t13397-uid-alias/migration.sql +32 -0
  104. package/migrations/sync-journal/20261010160000_t13399-rekey-follow/migration.sql +57 -0
  105. package/package.json +13 -13
@@ -0,0 +1,112 @@
1
+ /**
2
+ * The rebind at the next pull to head (journal spec §3.5 Rule 2 D5, §1.5;
3
+ * T13278).
4
+ *
5
+ * Undo grows exactly while a replica cannot reach the stream. At 100% of its
6
+ * budget the store keeps writing undo and records `sync.undo_budget_exceeded`
7
+ * (`sequencing.ts`); a forced rebind then would need the network it does not
8
+ * have. So the rebind waits for the next pull that reaches the stream's head,
9
+ * and {@link rebindAtHead} runs it, in one transaction with the rebind:
10
+ *
11
+ * 1. segments of the old replica the server already holds (at or below the
12
+ * pull cursor's replicaSeq for it) are marked pushed first, so they are
13
+ * never mistaken for an unsent outbox;
14
+ * 2. the store rebinds to a new replica (`undo-budget`): the old outbox
15
+ * (live captures, sealed transactions, unpushed segments) is inherited;
16
+ * 3. the undo log is emptied; every transaction of the old replica the
17
+ * stream has not sequenced is inherited first;
18
+ * 4. the reconcile re-emits what the old replica changed and never sent,
19
+ * under the new replica (`reconcileInPlace`, the §1.5 three-way rule);
20
+ * 5. a signed `retire` transaction for the old id is queued
21
+ * (`queueRetireTxn`), naming the successor and the last replicaSeq the
22
+ * server holds of the old replica; a replica that never landed a segment
23
+ * pins nothing and retires with no journal transaction;
24
+ * 6. `sync.undo_budget_exceeded` is cleared. Only this clears it.
25
+ *
26
+ * The server half (attach the new replica to this device, sign the E31
27
+ * retirement, update the link) needs the network, so the transaction also
28
+ * records {@link REBIND_PENDING_KEY}: the cloud layer completes it before the
29
+ * store pushes or pulls again, and a crash in between resumes there.
30
+ *
31
+ * @task T13278
32
+ * @module store/sync/rebind
33
+ */
34
+ import type { DatabaseSync } from 'node:sqlite';
35
+ import type { TableScope } from '@cleocode/contracts';
36
+ import type { StreamCursor } from './pull.js';
37
+ import { type ReconcileReport } from './reconcile.js';
38
+ import { type SyncOpenOptions } from './replica.js';
39
+ /**
40
+ * `_sync_meta` key holding the server half of a rebind the store committed
41
+ * and the cloud has not completed yet ({@link PendingRebind}).
42
+ */
43
+ export declare const REBIND_PENDING_KEY = "sync.rebind_pending";
44
+ /** The server half of a committed rebind: attach `to`, retire `from` (E31). */
45
+ export interface PendingRebind {
46
+ readonly stream: string;
47
+ readonly scope: TableScope;
48
+ /** The retired replica. */
49
+ readonly from: string;
50
+ /** Its successor, this store's replica now. */
51
+ readonly to: string;
52
+ /** The last replicaSeq of `from` the server holds, or null when it holds none. */
53
+ readonly lastReplicaSeq: number | null;
54
+ /** The journal `retire` transaction, or null when `from` landed nothing. */
55
+ readonly retireTxn: string | null;
56
+ /** When the store rebound. */
57
+ readonly at: string;
58
+ }
59
+ /** Options for {@link rebindAtHead}. */
60
+ export interface RebindAtHeadOptions extends SyncOpenOptions {
61
+ /** The stream the pull reached the head of. */
62
+ readonly stream: string;
63
+ /** The pull cursor at head: the server's last replicaSeq per replica. */
64
+ readonly cursor: StreamCursor;
65
+ }
66
+ /** What {@link rebindAtHead} did. */
67
+ export interface RebindAtHeadReport {
68
+ readonly previousReplicaId: string;
69
+ readonly replicaId: string;
70
+ /** The server half the cloud must complete. */
71
+ readonly pending: PendingRebind;
72
+ /** Old segments the server held, marked pushed before the rebind. */
73
+ readonly segmentsMarkedPushed: number;
74
+ /** Undo rows dropped (`_sync_undo` plus `_sync_row_undo`). */
75
+ readonly undoDropped: number;
76
+ readonly reconcile: ReconcileReport;
77
+ }
78
+ /**
79
+ * Whether the undo budget scheduled a rebind (D5): `sync.undo_budget_exceeded`
80
+ * is set. Read-only.
81
+ *
82
+ * @param db - The store.
83
+ * @returns True when the next pull to head must rebind.
84
+ */
85
+ export declare function undoBudgetRebindDue(db: DatabaseSync): boolean;
86
+ /**
87
+ * The server half of a rebind still to complete, or null. Read-only.
88
+ *
89
+ * @param db - The store.
90
+ * @returns The pending rebind, or null.
91
+ */
92
+ export declare function pendingRebind(db: DatabaseSync): PendingRebind | null;
93
+ /**
94
+ * Record that the server half of a rebind completed (the new replica is
95
+ * attached and the old one retired on the server).
96
+ *
97
+ * @param db - The store, outside a transaction.
98
+ * @param to - The replica the completed rebind bound; a newer pending rebind is kept.
99
+ */
100
+ export declare function clearPendingRebind(db: DatabaseSync, to: string): void;
101
+ /**
102
+ * Run the scheduled rebind after a pull reached the stream's head (module
103
+ * docs). Must run outside a transaction, and only after a pull that staged
104
+ * and applied everything up to the server's head.
105
+ *
106
+ * @param db - The store.
107
+ * @param o - The open options of the canonical store, the stream and the pull cursor.
108
+ * @returns What was done, or null when no rebind is due.
109
+ * @throws {Error} When the store has no active replica.
110
+ */
111
+ export declare function rebindAtHead(db: DatabaseSync, o: RebindAtHeadOptions): RebindAtHeadReport | null;
112
+ //# sourceMappingURL=rebind.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rebind.d.ts","sourceRoot":"","sources":["../../../src/store/sync/rebind.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,KAAK,eAAe,EAAoB,MAAM,gBAAgB,CAAC;AACxE,OAAO,EAAoC,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAStF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,wBAAwB,CAAC;AAExD,+EAA+E;AAC/E,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,2BAA2B;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,kFAAkF;IAClF,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,8BAA8B;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;CACrB;AAED,wCAAwC;AACxC,MAAM,WAAW,mBAAoB,SAAQ,eAAe;IAC1D,+CAA+C;IAC/C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,yEAAyE;IACzE,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;CAC/B;AAED,qCAAqC;AACrC,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,+CAA+C;IAC/C,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,qEAAqE;IACrE,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,8DAA8D;IAC9D,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;CACrC;AAWD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,YAAY,GAAG,OAAO,CAE7D;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,YAAY,GAAG,aAAa,GAAG,IAAI,CAGpE;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAOrE;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,YAAY,EAAE,CAAC,EAAE,mBAAmB,GAAG,kBAAkB,GAAG,IAAI,CA6EhG"}
@@ -0,0 +1,187 @@
1
+ /**
2
+ * The rebind at the next pull to head (journal spec §3.5 Rule 2 D5, §1.5;
3
+ * T13278).
4
+ *
5
+ * Undo grows exactly while a replica cannot reach the stream. At 100% of its
6
+ * budget the store keeps writing undo and records `sync.undo_budget_exceeded`
7
+ * (`sequencing.ts`); a forced rebind then would need the network it does not
8
+ * have. So the rebind waits for the next pull that reaches the stream's head,
9
+ * and {@link rebindAtHead} runs it, in one transaction with the rebind:
10
+ *
11
+ * 1. segments of the old replica the server already holds (at or below the
12
+ * pull cursor's replicaSeq for it) are marked pushed first, so they are
13
+ * never mistaken for an unsent outbox;
14
+ * 2. the store rebinds to a new replica (`undo-budget`): the old outbox
15
+ * (live captures, sealed transactions, unpushed segments) is inherited;
16
+ * 3. the undo log is emptied; every transaction of the old replica the
17
+ * stream has not sequenced is inherited first;
18
+ * 4. the reconcile re-emits what the old replica changed and never sent,
19
+ * under the new replica (`reconcileInPlace`, the §1.5 three-way rule);
20
+ * 5. a signed `retire` transaction for the old id is queued
21
+ * (`queueRetireTxn`), naming the successor and the last replicaSeq the
22
+ * server holds of the old replica; a replica that never landed a segment
23
+ * pins nothing and retires with no journal transaction;
24
+ * 6. `sync.undo_budget_exceeded` is cleared. Only this clears it.
25
+ *
26
+ * The server half (attach the new replica to this device, sign the E31
27
+ * retirement, update the link) needs the network, so the transaction also
28
+ * records {@link REBIND_PENDING_KEY}: the cloud layer completes it before the
29
+ * store pushes or pulls again, and a crash in between resumes there.
30
+ *
31
+ * @task T13278
32
+ * @module store/sync/rebind
33
+ */
34
+ import { withImmediateTransaction } from './clock-store.js';
35
+ import { reconcileInPlace } from './reconcile.js';
36
+ import { activeReplica, rebindReplicaWith } from './replica.js';
37
+ import { queueRetireTxn } from './retire.js';
38
+ import { hasTable } from './schema.js';
39
+ import { FOREIGN_TOUCH_COUNT_KEY, FOREIGN_TOUCH_INCOMPLETE_KEY, UNDO_BUDGET_EXCEEDED_KEY, } from './sequencing.js';
40
+ /**
41
+ * `_sync_meta` key holding the server half of a rebind the store committed
42
+ * and the cloud has not completed yet ({@link PendingRebind}).
43
+ */
44
+ export const REBIND_PENDING_KEY = 'sync.rebind_pending';
45
+ const metaGet = (db, key) => hasTable(db, '_sync_meta')
46
+ ? db.prepare('SELECT value FROM _sync_meta WHERE key = ?').get(key)?.value
47
+ : undefined;
48
+ /**
49
+ * Whether the undo budget scheduled a rebind (D5): `sync.undo_budget_exceeded`
50
+ * is set. Read-only.
51
+ *
52
+ * @param db - The store.
53
+ * @returns True when the next pull to head must rebind.
54
+ */
55
+ export function undoBudgetRebindDue(db) {
56
+ return metaGet(db, UNDO_BUDGET_EXCEEDED_KEY) !== undefined;
57
+ }
58
+ /**
59
+ * The server half of a rebind still to complete, or null. Read-only.
60
+ *
61
+ * @param db - The store.
62
+ * @returns The pending rebind, or null.
63
+ */
64
+ export function pendingRebind(db) {
65
+ const raw = metaGet(db, REBIND_PENDING_KEY);
66
+ return raw === undefined ? null : JSON.parse(raw);
67
+ }
68
+ /**
69
+ * Record that the server half of a rebind completed (the new replica is
70
+ * attached and the old one retired on the server).
71
+ *
72
+ * @param db - The store, outside a transaction.
73
+ * @param to - The replica the completed rebind bound; a newer pending rebind is kept.
74
+ */
75
+ export function clearPendingRebind(db, to) {
76
+ withImmediateTransaction(db, () => {
77
+ const now = pendingRebind(db);
78
+ if (now !== null && now.to === to) {
79
+ db.prepare('DELETE FROM _sync_meta WHERE key = ?').run(REBIND_PENDING_KEY);
80
+ }
81
+ });
82
+ }
83
+ /**
84
+ * Run the scheduled rebind after a pull reached the stream's head (module
85
+ * docs). Must run outside a transaction, and only after a pull that staged
86
+ * and applied everything up to the server's head.
87
+ *
88
+ * @param db - The store.
89
+ * @param o - The open options of the canonical store, the stream and the pull cursor.
90
+ * @returns What was done, or null when no rebind is due.
91
+ * @throws {Error} When the store has no active replica.
92
+ */
93
+ export function rebindAtHead(db, o) {
94
+ if (!undoBudgetRebindDue(db))
95
+ return null;
96
+ const now = o.now?.() ?? new Date();
97
+ const nowIso = now.toISOString();
98
+ // 1. What the server holds of the old replica is pushed, whatever the
99
+ // local state says (an upload whose answer was lost): never inherited.
100
+ // Its own transaction, before the rebind's: true whatever follows.
101
+ const marked = withImmediateTransaction(db, () => {
102
+ const active = activeReplica(db, o.scope)?.replicaId;
103
+ const held = active ? o.cursor.replicas[active]?.replicaSeq : undefined;
104
+ if (!active || held === undefined || !hasTable(db, '_sync_segment'))
105
+ return 0;
106
+ return Number(db
107
+ .prepare(`UPDATE _sync_segment SET state = 'pushed', pushed_at = coalesce(pushed_at, ?)
108
+ WHERE stream = ? AND replica_id = ? AND replica_seq <= ? AND state = 'sealed'`)
109
+ .run(nowIso, o.stream, active, held).changes);
110
+ });
111
+ // 2–6, with the rebind, in one transaction.
112
+ const out = rebindReplicaWith(db, o, 'undo-budget', {}, (tx, { previous, current }) => {
113
+ const undoDropped = dropUndo(tx, previous.replicaId);
114
+ const reconcile = reconcileInPlace(tx, { scope: o.scope, previousReplica: previous.replicaId });
115
+ const serverHeld = o.cursor.replicas[previous.replicaId]?.replicaSeq ?? null;
116
+ const localPushed = hasTable(tx, '_sync_segment')
117
+ ? tx
118
+ .prepare("SELECT max(replica_seq) AS s FROM _sync_segment WHERE stream = ? AND replica_id = ? AND state = 'pushed'")
119
+ .get(o.stream, previous.replicaId).s
120
+ : null;
121
+ const candidates = [serverHeld, localPushed === null ? null : Number(localPushed)].filter((n) => n !== null);
122
+ const lastReplicaSeq = candidates.length > 0 ? Math.max(...candidates) : null;
123
+ const retireTxn = lastReplicaSeq === null
124
+ ? null
125
+ : queueRetireTxn(tx, {
126
+ scope: o.scope,
127
+ stream: o.stream,
128
+ retire: {
129
+ replica: previous.replicaId,
130
+ successor: current.replicaId,
131
+ lastReplicaSeq,
132
+ },
133
+ nowMs: now.getTime(),
134
+ });
135
+ const pending = {
136
+ stream: o.stream,
137
+ scope: o.scope,
138
+ from: previous.replicaId,
139
+ to: current.replicaId,
140
+ lastReplicaSeq,
141
+ retireTxn,
142
+ at: nowIso,
143
+ };
144
+ tx.prepare('DELETE FROM _sync_meta WHERE key = ?').run(UNDO_BUDGET_EXCEEDED_KEY);
145
+ tx.prepare('INSERT INTO _sync_meta (key, value, updated_at) VALUES (?, ?, ?) ' +
146
+ 'ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at').run(REBIND_PENDING_KEY, JSON.stringify(pending), nowIso);
147
+ return { undoDropped, reconcile, pending };
148
+ });
149
+ return {
150
+ previousReplicaId: out.previousReplicaId,
151
+ replicaId: out.replicaId,
152
+ pending: out.result.pending,
153
+ segmentsMarkedPushed: marked,
154
+ undoDropped: out.result.undoDropped,
155
+ reconcile: out.result.reconcile,
156
+ };
157
+ }
158
+ /**
159
+ * Empty the undo log in the rebind transaction (D5). First every transaction
160
+ * of the old replica the stream has not sequenced is inherited, including
161
+ * one carried by a segment the server stored after this pull reached head:
162
+ * its echo would arrive under a replica this store no longer is, so it could
163
+ * never be sequenced as its own. It lands as history instead, and the
164
+ * reconcile re-emits its fields. Nothing is left to rewind, so the
165
+ * foreign-touch index resets, as after the last echo.
166
+ *
167
+ * @returns The undo rows dropped.
168
+ */
169
+ function dropUndo(db, previousReplica) {
170
+ if (hasTable(db, '_sync_txn') && hasTable(db, '_sync_sequenced')) {
171
+ db.prepare(`UPDATE _sync_txn SET state = 'inherited' WHERE replica = ? AND state IN ('sealed', 'segmented')
172
+ AND NOT EXISTS (SELECT 1 FROM _sync_sequenced s WHERE s.txn = _sync_txn.txn)`).run(previousReplica);
173
+ }
174
+ let dropped = 0;
175
+ if (hasTable(db, '_sync_undo')) {
176
+ dropped += Number(db.prepare('DELETE FROM _sync_undo').run().changes);
177
+ }
178
+ if (hasTable(db, '_sync_row_undo')) {
179
+ dropped += Number(db.prepare('DELETE FROM _sync_row_undo').run().changes);
180
+ }
181
+ if (hasTable(db, '_sync_foreign_touch')) {
182
+ db.prepare('DELETE FROM _sync_foreign_touch').run();
183
+ db.prepare('DELETE FROM _sync_meta WHERE key IN (?, ?)').run(FOREIGN_TOUCH_INCOMPLETE_KEY, FOREIGN_TOUCH_COUNT_KEY);
184
+ }
185
+ return dropped;
186
+ }
187
+ //# sourceMappingURL=rebind.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rebind.js","sourceRoot":"","sources":["../../../src/store/sync/rebind.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * The rebind reconcile (journal spec §1.5 N7; T12763, T13278).
3
+ *
4
+ * After a rebind, what the old replica changed and the stream never saw is
5
+ * `inherited` (`inherit.ts`): live captures, sealed transactions, and the
6
+ * transactions of segments it never pushed. The new replica re-emits those
7
+ * fields, deciding each one by the three-way rule
8
+ * ({@link decideReconcileField}):
9
+ *
10
+ * 1. changed by an inherited capture or an inherited sealed-but-unpushed op:
11
+ * emit it at `tick(at_ms)` of that change, under the new replica;
12
+ * 2. untouched, and the local field HLC is below the merged one: adopt the
13
+ * merged value locally, emitting nothing;
14
+ * 3. otherwise (a local HLC at or above the merged one that no inherited
15
+ * change explains): emit it with that HLC unchanged.
16
+ *
17
+ * These are `repair` transactions with `via: 'rebind'`, and they break the
18
+ * per-replica monotonic-HLC promise on purpose (§1.3, K10).
19
+ *
20
+ * {@link reconcileInPlace} is the reconcile of a rebind of the SAME file
21
+ * after a pull to head (the undo-budget rebind, D5): the store itself is the
22
+ * merged state, since the pull applied the stream and rebased the old
23
+ * replica's unsequenced writes over it. Every field no inherited change
24
+ * touched therefore already equals the merged value (rule 2 has nothing left
25
+ * to adopt and rule 3 nothing to explain), and every touched field is rule 1.
26
+ * It writes the touched rows as the captures of one `rebind` frame, read
27
+ * from the live rows (the current value, which is what every replica must
28
+ * converge on, whichever side the rebase let win), at the time of the last
29
+ * inherited change of each row; the sealer seals the frame as one `repair`
30
+ * transaction, `via: 'rebind'`, ticking each op at its row's time. A copy's
31
+ * reconcile against a restored checkpoint (rules 2 and 3 over a scratch
32
+ * store) is a separate pass.
33
+ *
34
+ * @task T12763
35
+ * @task T13278
36
+ * @module store/sync/reconcile
37
+ */
38
+ import type { DatabaseSync } from 'node:sqlite';
39
+ import type { TableScope } from '@cleocode/contracts';
40
+ /** What the three-way rule does with one differing field (§1.5 N7). */
41
+ export type ReconcileRule = 'emit-tick' | 'adopt-merged' | 'emit-pinned';
42
+ /** The facts the three-way rule decides one field by. */
43
+ export interface ReconcileField {
44
+ /** An inherited capture, or an inherited sealed-but-unpushed op, changed the field. */
45
+ readonly touchedByInherited: boolean;
46
+ /** The field's HLC in this store's row meta, or null when the store has no meta for it. */
47
+ readonly localHlc: string | null;
48
+ /** The field's HLC in the merged state (checkpoint plus stream to head). */
49
+ readonly mergedHlc: string;
50
+ }
51
+ /**
52
+ * The three-way rule for one field that differs from the merged state.
53
+ * Pure.
54
+ *
55
+ * @param f - Whether an inherited change touched it, and both HLCs.
56
+ * @returns `emit-tick` (rule 1), `adopt-merged` (rule 2) or `emit-pinned` (rule 3).
57
+ */
58
+ export declare function decideReconcileField(f: ReconcileField): ReconcileRule;
59
+ /** What {@link reconcileInPlace} did. */
60
+ export interface ReconcileReport {
61
+ /** The `rebind` frame the re-emitted rows were captured in, or null when nothing was touched. */
62
+ readonly frame: string | null;
63
+ /** Rows re-emitted as I, U and D, and re-keys re-emitted. */
64
+ readonly inserts: number;
65
+ readonly updates: number;
66
+ readonly deletes: number;
67
+ readonly rekeys: number;
68
+ /** Inherited captures consumed. */
69
+ readonly captures: number;
70
+ /** Inherited transactions whose ops were read. */
71
+ readonly txns: number;
72
+ }
73
+ /**
74
+ * Re-emit what the previous replica changed and the stream never saw, as the
75
+ * captures of one `rebind` frame, in the caller's (rebind) transaction
76
+ * (module docs). Afterwards:
77
+ * - the inherited captures are consumed (deleted): their changes now travel
78
+ * in the frame;
79
+ * - the ledger forgets the row-count effect of the previous replica's
80
+ * inherited sealed transactions, which the frame's I and D ops count again
81
+ * when they seal;
82
+ * - while undo is on, each capture gets its undo row (a reconcile repair
83
+ * keeps its undo until its echo, D1).
84
+ *
85
+ * @param db - The store, inside the rebind transaction.
86
+ * @param o - Scope and the previous (retired) replica.
87
+ * @returns What was re-emitted.
88
+ */
89
+ export declare function reconcileInPlace(db: DatabaseSync, o: {
90
+ readonly scope: TableScope;
91
+ readonly previousReplica: string;
92
+ }): ReconcileReport;
93
+ //# sourceMappingURL=reconcile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../../../src/store/sync/reconcile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAStD,uEAAuE;AACvE,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG,cAAc,GAAG,aAAa,CAAC;AAEzE,yDAAyD;AACzD,MAAM,WAAW,cAAc;IAC7B,uFAAuF;IACvF,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAC;IACrC,2FAA2F;IAC3F,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,cAAc,GAAG,aAAa,CAIrE;AAyBD,yCAAyC;AACzC,MAAM,WAAW,eAAe;IAC9B,iGAAiG;IACjG,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,6DAA6D;IAC7D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,mCAAmC;IACnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,kDAAkD;IAClD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAkBD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,YAAY,EAChB,CAAC,EAAE;IAAE,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;CAAE,GAClE,eAAe,CA+NjB"}