@cleocode/core 2026.10.7 → 2026.10.8-rc.ci.675
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.
- package/dist/build-provenance.json +2 -2
- package/dist/cloud/nexus-link.d.ts +15 -0
- package/dist/cloud/nexus-link.d.ts.map +1 -1
- package/dist/cloud/nexus-link.js +25 -0
- package/dist/cloud/nexus-link.js.map +1 -1
- package/dist/cloud/nexus-vault.d.ts +14 -1
- package/dist/cloud/nexus-vault.d.ts.map +1 -1
- package/dist/cloud/nexus-vault.js +72 -5
- package/dist/cloud/nexus-vault.js.map +1 -1
- package/dist/cloud/replica-rebind.d.ts +51 -0
- package/dist/cloud/replica-rebind.d.ts.map +1 -0
- package/dist/cloud/replica-rebind.js +101 -0
- package/dist/cloud/replica-rebind.js.map +1 -0
- package/dist/doctor/container-reaper.d.ts +107 -0
- package/dist/doctor/container-reaper.d.ts.map +1 -0
- package/dist/doctor/container-reaper.js +194 -0
- package/dist/doctor/container-reaper.js.map +1 -0
- package/dist/store/sync/apply/applier.d.ts +3 -0
- package/dist/store/sync/apply/applier.d.ts.map +1 -1
- package/dist/store/sync/apply/applier.js +208 -85
- package/dist/store/sync/apply/applier.js.map +1 -1
- package/dist/store/sync/capture.d.ts +23 -3
- package/dist/store/sync/capture.d.ts.map +1 -1
- package/dist/store/sync/capture.js +31 -3
- package/dist/store/sync/capture.js.map +1 -1
- package/dist/store/sync/collision-settle.d.ts +62 -0
- package/dist/store/sync/collision-settle.d.ts.map +1 -0
- package/dist/store/sync/collision-settle.js +156 -0
- package/dist/store/sync/collision-settle.js.map +1 -0
- package/dist/store/sync/conflicts.d.ts +11 -0
- package/dist/store/sync/conflicts.d.ts.map +1 -1
- package/dist/store/sync/conflicts.js +18 -0
- package/dist/store/sync/conflicts.js.map +1 -1
- package/dist/store/sync/inherit.d.ts +15 -8
- package/dist/store/sync/inherit.d.ts.map +1 -1
- package/dist/store/sync/inherit.js +28 -11
- package/dist/store/sync/inherit.js.map +1 -1
- package/dist/store/sync/merge/types.d.ts +6 -0
- package/dist/store/sync/merge/types.d.ts.map +1 -1
- package/dist/store/sync/pull.d.ts +23 -2
- package/dist/store/sync/pull.d.ts.map +1 -1
- package/dist/store/sync/pull.js +27 -2
- package/dist/store/sync/pull.js.map +1 -1
- package/dist/store/sync/rebind.d.ts +112 -0
- package/dist/store/sync/rebind.d.ts.map +1 -0
- package/dist/store/sync/rebind.js +187 -0
- package/dist/store/sync/rebind.js.map +1 -0
- package/dist/store/sync/reconcile.d.ts +93 -0
- package/dist/store/sync/reconcile.d.ts.map +1 -0
- package/dist/store/sync/reconcile.js +330 -0
- package/dist/store/sync/reconcile.js.map +1 -0
- package/dist/store/sync/repair.d.ts +5 -0
- package/dist/store/sync/repair.d.ts.map +1 -1
- package/dist/store/sync/repair.js +2 -2
- package/dist/store/sync/repair.js.map +1 -1
- package/dist/store/sync/replica.d.ts +28 -1
- package/dist/store/sync/replica.d.ts.map +1 -1
- package/dist/store/sync/replica.js +17 -3
- package/dist/store/sync/replica.js.map +1 -1
- package/dist/store/sync/retire.d.ts +161 -0
- package/dist/store/sync/retire.d.ts.map +1 -0
- package/dist/store/sync/retire.js +193 -0
- package/dist/store/sync/retire.js.map +1 -0
- package/dist/store/sync/schema.d.ts +27 -0
- package/dist/store/sync/schema.d.ts.map +1 -1
- package/dist/store/sync/schema.js +43 -0
- package/dist/store/sync/schema.js.map +1 -1
- package/dist/store/sync/sealer.d.ts +10 -0
- package/dist/store/sync/sealer.d.ts.map +1 -1
- package/dist/store/sync/sealer.js +51 -5
- package/dist/store/sync/sealer.js.map +1 -1
- package/dist/store/sync/segments.d.ts.map +1 -1
- package/dist/store/sync/segments.js +6 -2
- package/dist/store/sync/segments.js.map +1 -1
- package/dist/store/sync/uid-alias.d.ts +100 -0
- package/dist/store/sync/uid-alias.d.ts.map +1 -0
- package/dist/store/sync/uid-alias.js +169 -0
- package/dist/store/sync/uid-alias.js.map +1 -0
- package/dist/store/table-classification.d.ts.map +1 -1
- package/dist/store/table-classification.js +48 -0
- package/dist/store/table-classification.js.map +1 -1
- package/dist/tasks/change-set.d.ts.map +1 -1
- package/dist/tasks/change-set.js +16 -5
- package/dist/tasks/change-set.js.map +1 -1
- package/dist/tasks/complete-ci.d.ts.map +1 -1
- package/dist/tasks/complete-ci.js +7 -1
- package/dist/tasks/complete-ci.js.map +1 -1
- package/dist/tasks/done-plan.d.ts.map +1 -1
- package/dist/tasks/done-plan.js +24 -17
- package/dist/tasks/done-plan.js.map +1 -1
- package/dist/tasks/evidence.d.ts.map +1 -1
- package/dist/tasks/evidence.js +3 -2
- package/dist/tasks/evidence.js.map +1 -1
- package/dist/tasks/gate-runner.d.ts +20 -0
- package/dist/tasks/gate-runner.d.ts.map +1 -1
- package/dist/tasks/gate-runner.js +141 -19
- package/dist/tasks/gate-runner.js.map +1 -1
- package/dist/validation/engine-ops.d.ts.map +1 -1
- package/dist/validation/engine-ops.js +2 -0
- package/dist/validation/engine-ops.js.map +1 -1
- package/migrations/sync-journal/20261009130000_t13278-replica-retire/migration.sql +29 -0
- package/migrations/sync-journal/20261009140000_t13366-retire-confirm/migration.sql +18 -0
- package/migrations/sync-journal/20261010150000_t13397-uid-alias/migration.sql +32 -0
- package/migrations/sync-journal/20261010160000_t13399-rekey-follow/migration.sql +57 -0
- 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":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAIH,OAAO,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAE5D,OAAO,EAAwB,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACxE,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAwB,MAAM,cAAc,CAAC;AACtF,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EACL,uBAAuB,EACvB,4BAA4B,EAC5B,wBAAwB,GACzB,MAAM,iBAAiB,CAAC;AAEzB;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,qBAAqB,CAAC;AAuCxD,MAAM,OAAO,GAAG,CAAC,EAAgB,EAAE,GAAW,EAAsB,EAAE,CACpE,QAAQ,CAAC,EAAE,EAAE,YAAY,CAAC;IACxB,CAAC,CACG,EAAE,CAAC,OAAO,CAAC,4CAA4C,CAAC,CAAC,GAAG,CAAC,GAAG,CAGjE,EAAE,KAAK;IACV,CAAC,CAAC,SAAS,CAAC;AAEhB;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,EAAgB;IAClD,OAAO,OAAO,CAAC,EAAE,EAAE,wBAAwB,CAAC,KAAK,SAAS,CAAC;AAC7D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,EAAgB;IAC5C,MAAM,GAAG,GAAG,OAAO,CAAC,EAAE,EAAE,kBAAkB,CAAC,CAAC;IAC5C,OAAO,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAmB,CAAC;AACvE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,EAAgB,EAAE,EAAU;IAC7D,wBAAwB,CAAC,EAAE,EAAE,GAAG,EAAE;QAChC,MAAM,GAAG,GAAG,aAAa,CAAC,EAAE,CAAC,CAAC;QAC9B,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC;YAClC,EAAE,CAAC,OAAO,CAAC,sCAAsC,CAAC,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;QAC7E,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,YAAY,CAAC,EAAgB,EAAE,CAAsB;IACnE,IAAI,CAAC,mBAAmB,CAAC,EAAE,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1C,MAAM,GAAG,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,IAAI,IAAI,IAAI,EAAE,CAAC;IACpC,MAAM,MAAM,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;IAEjC,sEAAsE;IACtE,0EAA0E;IAC1E,sEAAsE;IACtE,MAAM,MAAM,GAAG,wBAAwB,CAAC,EAAE,EAAE,GAAG,EAAE;QAC/C,MAAM,MAAM,GAAG,aAAa,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC;QACrD,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC;QACxE,IAAI,CAAC,MAAM,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,eAAe,CAAC;YAAE,OAAO,CAAC,CAAC;QAC9E,OAAO,MAAM,CACX,EAAE;aACC,OAAO,CACN;0FACgF,CACjF;aACA,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC,OAAO,CAC/C,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,4CAA4C;IAC5C,MAAM,GAAG,GAAG,iBAAiB,CAAC,EAAE,EAAE,CAAC,EAAE,aAAa,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE;QACpF,MAAM,WAAW,GAAG,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;QACrD,MAAM,SAAS,GAAG,gBAAgB,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,eAAe,EAAE,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC;QAChG,MAAM,UAAU,GAAG,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,UAAU,IAAI,IAAI,CAAC;QAC7E,MAAM,WAAW,GAAG,QAAQ,CAAC,EAAE,EAAE,eAAe,CAAC;YAC/C,CAAC,CACG,EAAE;iBACC,OAAO,CACN,0GAA0G,CAC3G;iBACA,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,SAAS,CACpC,CAAC,CAAC;YACL,CAAC,CAAC,IAAI,CAAC;QACT,MAAM,UAAU,GAAG,CAAC,UAAU,EAAE,WAAW,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CACvF,CAAC,CAAC,EAAe,EAAE,CAAC,CAAC,KAAK,IAAI,CAC/B,CAAC;QACF,MAAM,cAAc,GAAG,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAC9E,MAAM,SAAS,GACb,cAAc,KAAK,IAAI;YACrB,CAAC,CAAC,IAAI;YACN,CAAC,CAAC,cAAc,CAAC,EAAE,EAAE;gBACjB,KAAK,EAAE,CAAC,CAAC,KAAK;gBACd,MAAM,EAAE,CAAC,CAAC,MAAM;gBAChB,MAAM,EAAE;oBACN,OAAO,EAAE,QAAQ,CAAC,SAAS;oBAC3B,SAAS,EAAE,OAAO,CAAC,SAAS;oBAC5B,cAAc;iBACf;gBACD,KAAK,EAAE,GAAG,CAAC,OAAO,EAAE;aACrB,CAAC,CAAC;QACT,MAAM,OAAO,GAAkB;YAC7B,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,IAAI,EAAE,QAAQ,CAAC,SAAS;YACxB,EAAE,EAAE,OAAO,CAAC,SAAS;YACrB,cAAc;YACd,SAAS;YACT,EAAE,EAAE,MAAM;SACX,CAAC;QACF,EAAE,CAAC,OAAO,CAAC,sCAAsC,CAAC,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;QACjF,EAAE,CAAC,OAAO,CACR,mEAAmE;YACjE,yFAAyF,CAC5F,CAAC,GAAG,CAAC,kBAAkB,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC;QAC3D,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;IAC7C,CAAC,CAAC,CAAC;IACH,OAAO;QACL,iBAAiB,EAAE,GAAG,CAAC,iBAAiB;QACxC,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,OAAO;QAC3B,oBAAoB,EAAE,MAAM;QAC5B,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,WAAW;QACnC,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS;KAChC,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAS,QAAQ,CAAC,EAAgB,EAAE,eAAuB;IACzD,IAAI,QAAQ,CAAC,EAAE,EAAE,WAAW,CAAC,IAAI,QAAQ,CAAC,EAAE,EAAE,iBAAiB,CAAC,EAAE,CAAC;QACjE,EAAE,CAAC,OAAO,CACR;sFACgF,CACjF,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IACzB,CAAC;IACD,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,QAAQ,CAAC,EAAE,EAAE,YAAY,CAAC,EAAE,CAAC;QAC/B,OAAO,IAAI,MAAM,CAAC,EAAE,CAAC,OAAO,CAAC,wBAAwB,CAAC,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC;IACxE,CAAC;IACD,IAAI,QAAQ,CAAC,EAAE,EAAE,gBAAgB,CAAC,EAAE,CAAC;QACnC,OAAO,IAAI,MAAM,CAAC,EAAE,CAAC,OAAO,CAAC,4BAA4B,CAAC,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC;IAC5E,CAAC;IACD,IAAI,QAAQ,CAAC,EAAE,EAAE,qBAAqB,CAAC,EAAE,CAAC;QACxC,EAAE,CAAC,OAAO,CAAC,iCAAiC,CAAC,CAAC,GAAG,EAAE,CAAC;QACpD,EAAE,CAAC,OAAO,CAAC,4CAA4C,CAAC,CAAC,GAAG,CAC1D,4BAA4B,EAC5B,uBAAuB,CACxB,CAAC;IACJ,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -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"}
|