@cleocode/core 2026.10.6 → 2026.10.7
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/http.d.ts +8 -0
- package/dist/cloud/http.d.ts.map +1 -1
- package/dist/cloud/http.js +11 -0
- package/dist/cloud/http.js.map +1 -1
- package/dist/cloud/journal.d.ts +6 -0
- package/dist/cloud/journal.d.ts.map +1 -1
- package/dist/cloud/journal.js +1 -1
- package/dist/cloud/journal.js.map +1 -1
- package/dist/cloud/nexus-cloud-status.d.ts.map +1 -1
- package/dist/cloud/nexus-cloud-status.js +3 -1
- package/dist/cloud/nexus-cloud-status.js.map +1 -1
- package/dist/cloud/nexus-vault.d.ts +86 -1
- package/dist/cloud/nexus-vault.d.ts.map +1 -1
- package/dist/cloud/nexus-vault.js +875 -98
- package/dist/cloud/nexus-vault.js.map +1 -1
- package/dist/docs/doctor.d.ts +183 -0
- package/dist/docs/doctor.d.ts.map +1 -0
- package/dist/docs/doctor.js +537 -0
- package/dist/docs/doctor.js.map +1 -0
- package/dist/doctor/system-health.d.ts +189 -0
- package/dist/doctor/system-health.d.ts.map +1 -0
- package/dist/doctor/system-health.js +783 -0
- package/dist/doctor/system-health.js.map +1 -0
- package/dist/git/hooks-install.d.ts +17 -34
- package/dist/git/hooks-install.d.ts.map +1 -1
- package/dist/git/hooks-install.js +173 -70
- package/dist/git/hooks-install.js.map +1 -1
- package/dist/harness/ask-enforce.d.ts +43 -0
- package/dist/harness/ask-enforce.d.ts.map +1 -0
- package/dist/harness/ask-enforce.js +165 -0
- package/dist/harness/ask-enforce.js.map +1 -0
- package/dist/hooks.d.ts +8 -30
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +36 -116
- package/dist/hooks.js.map +1 -1
- package/dist/init.d.ts +2 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +5 -14
- package/dist/init.js.map +1 -1
- package/dist/injection.d.ts +2 -2
- package/dist/injection.d.ts.map +1 -1
- package/dist/injection.js +108 -10
- package/dist/injection.js.map +1 -1
- package/dist/internal.d.ts +1 -1
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +1 -1
- package/dist/internal.js.map +1 -1
- package/dist/issue/create.d.ts +4 -0
- package/dist/issue/create.d.ts.map +1 -1
- package/dist/issue/create.js +12 -0
- package/dist/issue/create.js.map +1 -1
- package/dist/issue/index.d.ts +1 -1
- package/dist/issue/index.d.ts.map +1 -1
- package/dist/issue/index.js +1 -1
- package/dist/issue/index.js.map +1 -1
- package/dist/orchestrate/spawn-ops.d.ts.map +1 -1
- package/dist/orchestrate/spawn-ops.js +8 -2
- package/dist/orchestrate/spawn-ops.js.map +1 -1
- package/dist/orchestration/lean-change.d.ts +15 -0
- package/dist/orchestration/lean-change.d.ts.map +1 -0
- package/dist/orchestration/lean-change.js +17 -0
- package/dist/orchestration/lean-change.js.map +1 -0
- package/dist/orchestration/spawn-prompt.d.ts.map +1 -1
- package/dist/orchestration/spawn-prompt.js +6 -4
- package/dist/orchestration/spawn-prompt.js.map +1 -1
- package/dist/release/ci-evidence.d.ts +59 -0
- package/dist/release/ci-evidence.d.ts.map +1 -1
- package/dist/release/ci-evidence.js +150 -5
- package/dist/release/ci-evidence.js.map +1 -1
- package/dist/release/plan.d.ts.map +1 -1
- package/dist/release/plan.js +11 -2
- package/dist/release/plan.js.map +1 -1
- package/dist/release/pr-evidence.d.ts +15 -0
- package/dist/release/pr-evidence.d.ts.map +1 -1
- package/dist/release/pr-evidence.js +42 -1
- package/dist/release/pr-evidence.js.map +1 -1
- package/dist/release/reconcile.js +2 -2
- package/dist/release/reconcile.js.map +1 -1
- package/dist/release/verify-provenance.js +2 -2
- package/dist/release/verify-provenance.js.map +1 -1
- package/dist/resources/admission-ledger.d.ts +8 -0
- package/dist/resources/admission-ledger.d.ts.map +1 -1
- package/dist/resources/admission-ledger.js +5 -0
- package/dist/resources/admission-ledger.js.map +1 -1
- package/dist/resources/claim-keepalive.d.ts +34 -0
- package/dist/resources/claim-keepalive.d.ts.map +1 -0
- package/dist/resources/claim-keepalive.js +49 -0
- package/dist/resources/claim-keepalive.js.map +1 -0
- package/dist/resources/governor.d.ts.map +1 -1
- package/dist/resources/governor.js +3 -0
- package/dist/resources/governor.js.map +1 -1
- package/dist/scaffold/ensure-config.js +1 -1
- package/dist/scaffold/ensure-config.js.map +1 -1
- package/dist/store/attachment-store.d.ts.map +1 -1
- package/dist/store/attachment-store.js +2 -1
- package/dist/store/attachment-store.js.map +1 -1
- package/dist/store/restore-marker.d.ts +50 -7
- package/dist/store/restore-marker.d.ts.map +1 -1
- package/dist/store/restore-marker.js +77 -25
- package/dist/store/restore-marker.js.map +1 -1
- package/dist/store/row-identity-registry.d.ts +6 -4
- package/dist/store/row-identity-registry.d.ts.map +1 -1
- package/dist/store/row-identity-registry.js +156 -33
- package/dist/store/row-identity-registry.js.map +1 -1
- package/dist/store/schema/cleo-project/provenance-orphans.d.ts +16 -0
- package/dist/store/schema/cleo-project/provenance-orphans.d.ts.map +1 -1
- package/dist/store/schema/cleo-project/provenance-orphans.js +4 -1
- package/dist/store/schema/cleo-project/provenance-orphans.js.map +1 -1
- package/dist/store/schema/cleo-shared/brain.d.ts +240 -0
- package/dist/store/schema/cleo-shared/brain.d.ts.map +1 -1
- package/dist/store/schema/cleo-shared/brain.js +69 -0
- package/dist/store/schema/cleo-shared/brain.js.map +1 -1
- package/dist/store/schema/tasks.d.ts +1 -1
- package/dist/store/schema/tasks.d.ts.map +1 -1
- package/dist/store/schema/tasks.js +9 -1
- package/dist/store/schema/tasks.js.map +1 -1
- package/dist/store/sync/apply/applier.d.ts.map +1 -1
- package/dist/store/sync/apply/applier.js +85 -3
- package/dist/store/sync/apply/applier.js.map +1 -1
- package/dist/store/sync/apply/parent-delete.d.ts.map +1 -1
- package/dist/store/sync/apply/parent-delete.js +4 -0
- package/dist/store/sync/apply/parent-delete.js.map +1 -1
- package/dist/store/sync/capture.d.ts +8 -0
- package/dist/store/sync/capture.d.ts.map +1 -1
- package/dist/store/sync/capture.js +20 -0
- package/dist/store/sync/capture.js.map +1 -1
- package/dist/store/sync/clock-store.d.ts +21 -0
- package/dist/store/sync/clock-store.d.ts.map +1 -1
- package/dist/store/sync/clock-store.js +47 -0
- package/dist/store/sync/clock-store.js.map +1 -1
- package/dist/store/sync/conflicts.d.ts +13 -0
- package/dist/store/sync/conflicts.d.ts.map +1 -1
- package/dist/store/sync/conflicts.js +22 -0
- package/dist/store/sync/conflicts.js.map +1 -1
- package/dist/store/sync/genesis.d.ts +113 -12
- package/dist/store/sync/genesis.d.ts.map +1 -1
- package/dist/store/sync/genesis.js +325 -27
- package/dist/store/sync/genesis.js.map +1 -1
- package/dist/store/sync/merge/types.d.ts +8 -2
- package/dist/store/sync/merge/types.d.ts.map +1 -1
- package/dist/store/sync/pull.d.ts +192 -0
- package/dist/store/sync/pull.d.ts.map +1 -0
- package/dist/store/sync/pull.js +429 -0
- package/dist/store/sync/pull.js.map +1 -0
- package/dist/store/sync/push.d.ts +110 -0
- package/dist/store/sync/push.d.ts.map +1 -0
- package/dist/store/sync/push.js +172 -0
- package/dist/store/sync/push.js.map +1 -0
- package/dist/store/sync/replay-pin.d.ts +70 -0
- package/dist/store/sync/replay-pin.d.ts.map +1 -0
- package/dist/store/sync/replay-pin.js +172 -0
- package/dist/store/sync/replay-pin.js.map +1 -0
- package/dist/store/sync/sealer.d.ts.map +1 -1
- package/dist/store/sync/sealer.js +6 -2
- package/dist/store/sync/sealer.js.map +1 -1
- package/dist/store/sync/timestamp-canon.d.ts.map +1 -1
- package/dist/store/sync/timestamp-canon.js +15 -0
- package/dist/store/sync/timestamp-canon.js.map +1 -1
- package/dist/store/table-classification.d.ts.map +1 -1
- package/dist/store/table-classification.js +51 -15
- package/dist/store/table-classification.js.map +1 -1
- package/dist/store/tasks-schema.d.ts +1 -1
- package/dist/store/tasks-schema.d.ts.map +1 -1
- package/dist/store/tasks-schema.js +1 -1
- package/dist/store/tasks-schema.js.map +1 -1
- package/dist/store/twin-collapse.d.ts.map +1 -1
- package/dist/store/twin-collapse.js +5 -0
- package/dist/store/twin-collapse.js.map +1 -1
- package/dist/store/vault-manifest.d.ts +7 -0
- package/dist/store/vault-manifest.d.ts.map +1 -1
- package/dist/store/vault-manifest.js +4 -0
- package/dist/store/vault-manifest.js.map +1 -1
- package/dist/tasks/ac-table.d.ts +17 -0
- package/dist/tasks/ac-table.d.ts.map +1 -1
- package/dist/tasks/ac-table.js +49 -0
- package/dist/tasks/ac-table.js.map +1 -1
- package/dist/tasks/affected-scope.d.ts +52 -3
- package/dist/tasks/affected-scope.d.ts.map +1 -1
- package/dist/tasks/affected-scope.js +94 -3
- package/dist/tasks/affected-scope.js.map +1 -1
- package/dist/tasks/change-set.d.ts.map +1 -1
- package/dist/tasks/change-set.js +9 -5
- package/dist/tasks/change-set.js.map +1 -1
- package/dist/tasks/complete-ci.d.ts +2 -0
- package/dist/tasks/complete-ci.d.ts.map +1 -1
- package/dist/tasks/complete-ci.js +26 -7
- package/dist/tasks/complete-ci.js.map +1 -1
- package/dist/tasks/complete.d.ts.map +1 -1
- package/dist/tasks/complete.js +10 -0
- package/dist/tasks/complete.js.map +1 -1
- package/dist/tasks/done-plan.d.ts.map +1 -1
- package/dist/tasks/done-plan.js +63 -6
- package/dist/tasks/done-plan.js.map +1 -1
- package/dist/tasks/evidence.d.ts +3 -0
- package/dist/tasks/evidence.d.ts.map +1 -1
- package/dist/tasks/evidence.js +106 -2
- package/dist/tasks/evidence.js.map +1 -1
- package/dist/tasks/gate-preview.d.ts +19 -1
- package/dist/tasks/gate-preview.d.ts.map +1 -1
- package/dist/tasks/gate-preview.js +44 -2
- package/dist/tasks/gate-preview.js.map +1 -1
- package/dist/tasks/gate-runner.d.ts +16 -0
- package/dist/tasks/gate-runner.d.ts.map +1 -1
- package/dist/tasks/gate-runner.js +25 -2
- package/dist/tasks/gate-runner.js.map +1 -1
- package/dist/tasks/qa-run-binding.d.ts +88 -0
- package/dist/tasks/qa-run-binding.d.ts.map +1 -0
- package/dist/tasks/qa-run-binding.js +188 -0
- package/dist/tasks/qa-run-binding.js.map +1 -0
- package/dist/tasks/req.d.ts +50 -0
- package/dist/tasks/req.d.ts.map +1 -1
- package/dist/tasks/req.js +99 -1
- package/dist/tasks/req.js.map +1 -1
- package/dist/tasks/test-run-binding.d.ts +29 -0
- package/dist/tasks/test-run-binding.d.ts.map +1 -1
- package/dist/tasks/test-run-binding.js +47 -23
- package/dist/tasks/test-run-binding.js.map +1 -1
- package/dist/tasks/tool-resolver.d.ts +9 -0
- package/dist/tasks/tool-resolver.d.ts.map +1 -1
- package/dist/tasks/tool-resolver.js +14 -3
- package/dist/tasks/tool-resolver.js.map +1 -1
- package/dist/tasks/tool-semaphore.d.ts.map +1 -1
- package/dist/tasks/tool-semaphore.js +3 -0
- package/dist/tasks/tool-semaphore.js.map +1 -1
- package/dist/upgrade.d.ts.map +1 -1
- package/dist/upgrade.js +10 -1
- package/dist/upgrade.js.map +1 -1
- package/dist/validation/engine-ops.d.ts.map +1 -1
- package/dist/validation/engine-ops.js +14 -3
- package/dist/validation/engine-ops.js.map +1 -1
- package/migrations/drizzle-cleo-global/20261010130000_t12895-brain-natural-uids/migration.sql +28 -0
- package/migrations/drizzle-cleo-global/20261010140000_t12896-brain-autoinc-uids/migration.sql +54 -0
- package/migrations/drizzle-cleo-project/20261010130000_t12895-brain-natural-uids/migration.sql +36 -0
- package/migrations/drizzle-cleo-project/20261010140000_t12896-brain-autoinc-uids/migration.sql +58 -0
- package/migrations/sync-journal/20261008120000_t12343-pull-cursor/migration.sql +28 -0
- package/migrations/sync-journal/20261009120000_t13318-seen-floor/migration.sql +41 -0
- package/package.json +18 -18
- package/templates/CLEO-INJECTION.md +5 -3
- package/templates/CLEO-REFERENCE.md +1 -1
- package/templates/git-hooks/legacy-hashes.json +20 -0
- package/templates/git-hooks/pre-commit +1 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Push one stream's journal (journal spec §2.8 "Persist before push",
|
|
3
|
+
* "Upload", §2.9, §1.3 "Local clock ahead"; T12343 S4-2).
|
|
4
|
+
*
|
|
5
|
+
* {@link pushStream} runs, in order:
|
|
6
|
+
* 1. the preconditions: `sync.push` on, the stream cut and its genesis
|
|
7
|
+
* checkpoint stored (`genesis_pending` clear), so no segment ever
|
|
8
|
+
* precedes the checkpoint it extends;
|
|
9
|
+
* 2. the local-clock check: the server's date is recorded for the sealer's
|
|
10
|
+
* clamp (`sync.server_clock`, read by `sealWallCeiling`); when this
|
|
11
|
+
* device's clock runs more than `MAX_DRIFT` ahead of it, push pauses
|
|
12
|
+
* (persisted as `sync.clock_ahead` for `cleo doctor`), so future-stamped
|
|
13
|
+
* HLCs do not spread; local writes are never refused;
|
|
14
|
+
* 3. the rollback check: when the server already holds a later segment from
|
|
15
|
+
* this replica than the store has persisted, the store is behind (a
|
|
16
|
+
* restore or a copy) and must rebind (§1.5, §2.9); nothing is sent;
|
|
17
|
+
* 4. seal everything pending, then pack and persist segments
|
|
18
|
+
* ({@link buildSegment}), advancing the device registry's high-water mark
|
|
19
|
+
* after each persist (the store ahead of the registry is the safe
|
|
20
|
+
* direction);
|
|
21
|
+
* 5. upload the persisted segments, lowest `replicaSeq` first, one at a time,
|
|
22
|
+
* always the exact persisted bytes; a duplicate counts as stored.
|
|
23
|
+
*
|
|
24
|
+
* The upload goes through a {@link SegmentUploader} port: the journal client
|
|
25
|
+
* in production (`cloud/nexus-vault.ts`), a fake in tests.
|
|
26
|
+
*
|
|
27
|
+
* @task T12343
|
|
28
|
+
* @module store/sync/push
|
|
29
|
+
*/
|
|
30
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
31
|
+
import type { TableScope } from '@cleocode/contracts';
|
|
32
|
+
import type { ReplicaRegistry } from './replica-registry.js';
|
|
33
|
+
import { type BuildSegmentOptions, type PersistedSegment, type SegmentSealer } from './segments.js';
|
|
34
|
+
/** `MAX_DRIFT` (§1.3, Q6): how far this device's clock may run ahead of the server's. */
|
|
35
|
+
export declare const MAX_DRIFT_MS: number;
|
|
36
|
+
/** `_sync_meta` key set while push is paused for a clock ahead of the server's (`cleo doctor`). */
|
|
37
|
+
export declare const CLOCK_AHEAD_KEY = "sync.clock_ahead";
|
|
38
|
+
/** Uploads one persisted segment; resolves with the server's seq, `duplicate` when it was stored already. */
|
|
39
|
+
export type SegmentUploader = (segment: PersistedSegment) => Promise<{
|
|
40
|
+
readonly seq: number;
|
|
41
|
+
readonly duplicate: boolean;
|
|
42
|
+
}>;
|
|
43
|
+
/** Options for {@link pushStream}. */
|
|
44
|
+
export interface PushStreamOptions {
|
|
45
|
+
readonly scope: TableScope;
|
|
46
|
+
readonly stream: string;
|
|
47
|
+
/** This store's bound replica (the stream knows it by the same id). */
|
|
48
|
+
readonly replica: string;
|
|
49
|
+
/** The merge key's project, or null on a home stream. */
|
|
50
|
+
readonly project: string | null;
|
|
51
|
+
readonly sealer: SegmentSealer;
|
|
52
|
+
/** Signs each packed transaction for the stream ({@link BuildSegmentOptions.signTxn}). */
|
|
53
|
+
readonly signTxn: BuildSegmentOptions['signTxn'];
|
|
54
|
+
readonly upload: SegmentUploader;
|
|
55
|
+
/**
|
|
56
|
+
* The server's clock minus this device's, measured when its latest response
|
|
57
|
+
* arrived (negative: this device runs ahead), or null when unknown.
|
|
58
|
+
*/
|
|
59
|
+
readonly serverOffsetMs: number | null;
|
|
60
|
+
/** The server's last stored `replicaSeq` for this replica on the stream, or null when unknown or none. */
|
|
61
|
+
readonly serverLastReplicaSeq: number | null;
|
|
62
|
+
/** The device registry, whose high-water mark advances after each persist. */
|
|
63
|
+
readonly registry?: ReplicaRegistry;
|
|
64
|
+
/** Wall clock. @defaultValue Date.now */
|
|
65
|
+
readonly now?: () => number;
|
|
66
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
67
|
+
/** Seal and push although the flags are unreleased (tests and staging only). Never set from user input. */
|
|
68
|
+
readonly allowUnreleased?: boolean;
|
|
69
|
+
/** Override {@link MAX_DRIFT_MS} (tests). */
|
|
70
|
+
readonly maxDriftMs?: number;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Why {@link pushStream} sent nothing, as a kind callers branch on (the
|
|
74
|
+
* message in `refused` is for people and may be reworded):
|
|
75
|
+
* - `schema-missing`: the sync schema is not installed;
|
|
76
|
+
* - `push-off`: `sync.push` is off;
|
|
77
|
+
* - `no-genesis`: this store has no genesis cut on the stream;
|
|
78
|
+
* - `genesis-pending`: the genesis checkpoint is not stored yet;
|
|
79
|
+
* - `store-behind`: the server holds a later segment of this replica.
|
|
80
|
+
*/
|
|
81
|
+
export type PushRefusal = 'schema-missing' | 'push-off' | 'no-genesis' | 'genesis-pending' | 'store-behind';
|
|
82
|
+
/** What {@link pushStream} did. */
|
|
83
|
+
export interface PushStreamReport {
|
|
84
|
+
readonly stream: string;
|
|
85
|
+
/** Why nothing was sent, or null. */
|
|
86
|
+
readonly refused: string | null;
|
|
87
|
+
/** The kind of `refused`, or null when nothing was refused. */
|
|
88
|
+
readonly refusedKind: PushRefusal | null;
|
|
89
|
+
/** Push paused because this device's clock is ahead of the server's. */
|
|
90
|
+
readonly clockAhead: boolean;
|
|
91
|
+
/** Transactions sealed, segments packed and persisted, segments the server stored (`duplicates` of them retries). */
|
|
92
|
+
readonly sealed: number;
|
|
93
|
+
readonly built: number;
|
|
94
|
+
readonly pushed: number;
|
|
95
|
+
readonly duplicates: number;
|
|
96
|
+
/** The server seq of the last segment stored, or null. */
|
|
97
|
+
readonly lastServerSeq: number | null;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Seal, persist and upload one stream's pending journal (module docs).
|
|
101
|
+
* Must run outside a transaction. An upload failure propagates after the
|
|
102
|
+
* segments before it were recorded; the next run resends from the lowest
|
|
103
|
+
* unpushed `replicaSeq`, with the same bytes.
|
|
104
|
+
*
|
|
105
|
+
* @param db - The store.
|
|
106
|
+
* @param o - {@link PushStreamOptions}.
|
|
107
|
+
* @returns What was sealed, persisted and stored, or why nothing was sent.
|
|
108
|
+
*/
|
|
109
|
+
export declare function pushStream(db: DatabaseSync, o: PushStreamOptions): Promise<PushStreamReport>;
|
|
110
|
+
//# sourceMappingURL=push.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"push.d.ts","sourceRoot":"","sources":["../../../src/store/sync/push.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAKtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAG7D,OAAO,EACL,KAAK,mBAAmB,EAGxB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAEnB,MAAM,eAAe,CAAC;AAEvB,yFAAyF;AACzF,eAAO,MAAM,YAAY,QAAgB,CAAC;AAK1C,mGAAmG;AACnG,eAAO,MAAM,eAAe,qBAAqB,CAAC;AAElD,6GAA6G;AAC7G,MAAM,MAAM,eAAe,GAAG,CAC5B,OAAO,EAAE,gBAAgB,KACtB,OAAO,CAAC;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AAEpE,sCAAsC;AACtC,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,uEAAuE;IACvE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,yDAAyD;IACzD,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,0FAA0F;IAC1F,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC,SAAS,CAAC,CAAC;IACjD,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IACjC;;;OAGG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,0GAA0G;IAC1G,QAAQ,CAAC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,8EAA8E;IAC9E,QAAQ,CAAC,QAAQ,CAAC,EAAE,eAAe,CAAC;IACpC,yCAAyC;IACzC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACjC,2GAA2G;IAC3G,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IACnC,6CAA6C;IAC7C,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,WAAW,GACnB,gBAAgB,GAChB,UAAU,GACV,YAAY,GACZ,iBAAiB,GACjB,cAAc,CAAC;AAEnB,mCAAmC;AACnC,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,qCAAqC;IACrC,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,+DAA+D;IAC/D,QAAQ,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI,CAAC;IACzC,wEAAwE;IACxE,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,qHAAqH;IACrH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,0DAA0D;IAC1D,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CACvC;AA4BD;;;;;;;;;GASG;AACH,wBAAsB,UAAU,CAC9B,EAAE,EAAE,YAAY,EAChB,CAAC,EAAE,iBAAiB,GACnB,OAAO,CAAC,gBAAgB,CAAC,CAmG3B"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Push one stream's journal (journal spec §2.8 "Persist before push",
|
|
3
|
+
* "Upload", §2.9, §1.3 "Local clock ahead"; T12343 S4-2).
|
|
4
|
+
*
|
|
5
|
+
* {@link pushStream} runs, in order:
|
|
6
|
+
* 1. the preconditions: `sync.push` on, the stream cut and its genesis
|
|
7
|
+
* checkpoint stored (`genesis_pending` clear), so no segment ever
|
|
8
|
+
* precedes the checkpoint it extends;
|
|
9
|
+
* 2. the local-clock check: the server's date is recorded for the sealer's
|
|
10
|
+
* clamp (`sync.server_clock`, read by `sealWallCeiling`); when this
|
|
11
|
+
* device's clock runs more than `MAX_DRIFT` ahead of it, push pauses
|
|
12
|
+
* (persisted as `sync.clock_ahead` for `cleo doctor`), so future-stamped
|
|
13
|
+
* HLCs do not spread; local writes are never refused;
|
|
14
|
+
* 3. the rollback check: when the server already holds a later segment from
|
|
15
|
+
* this replica than the store has persisted, the store is behind (a
|
|
16
|
+
* restore or a copy) and must rebind (§1.5, §2.9); nothing is sent;
|
|
17
|
+
* 4. seal everything pending, then pack and persist segments
|
|
18
|
+
* ({@link buildSegment}), advancing the device registry's high-water mark
|
|
19
|
+
* after each persist (the store ahead of the registry is the safe
|
|
20
|
+
* direction);
|
|
21
|
+
* 5. upload the persisted segments, lowest `replicaSeq` first, one at a time,
|
|
22
|
+
* always the exact persisted bytes; a duplicate counts as stored.
|
|
23
|
+
*
|
|
24
|
+
* The upload goes through a {@link SegmentUploader} port: the journal client
|
|
25
|
+
* in production (`cloud/nexus-vault.ts`), a fake in tests.
|
|
26
|
+
*
|
|
27
|
+
* @task T12343
|
|
28
|
+
* @module store/sync/push
|
|
29
|
+
*/
|
|
30
|
+
import { SERVER_CLOCK_KEY } from './clock-store.js';
|
|
31
|
+
import { isSyncFlagOn } from './flags.js';
|
|
32
|
+
import { genesisCutOf, genesisPending } from './genesis.js';
|
|
33
|
+
import { storeHwm } from './replica.js';
|
|
34
|
+
import { hasTable } from './schema.js';
|
|
35
|
+
import { sealPending } from './sealer.js';
|
|
36
|
+
import { buildSegment, markSegmentPushed, unpushedSegments, } from './segments.js';
|
|
37
|
+
/** `MAX_DRIFT` (§1.3, Q6): how far this device's clock may run ahead of the server's. */
|
|
38
|
+
export const MAX_DRIFT_MS = 5 * 60 * 1000;
|
|
39
|
+
/** Sealing passes per push before it sends what is persisted (the rest waits for the next run). */
|
|
40
|
+
const SEAL_PASSES = 8;
|
|
41
|
+
/** `_sync_meta` key set while push is paused for a clock ahead of the server's (`cleo doctor`). */
|
|
42
|
+
export const CLOCK_AHEAD_KEY = 'sync.clock_ahead';
|
|
43
|
+
const empty = (stream, fields) => ({
|
|
44
|
+
stream,
|
|
45
|
+
refused: null,
|
|
46
|
+
refusedKind: null,
|
|
47
|
+
clockAhead: false,
|
|
48
|
+
sealed: 0,
|
|
49
|
+
built: 0,
|
|
50
|
+
pushed: 0,
|
|
51
|
+
duplicates: 0,
|
|
52
|
+
lastServerSeq: null,
|
|
53
|
+
...fields,
|
|
54
|
+
});
|
|
55
|
+
/** Set or clear {@link CLOCK_AHEAD_KEY}. */
|
|
56
|
+
/** Set (or, with null, clear) one of push's `_sync_meta` keys. */
|
|
57
|
+
function setPushMeta(db, key, value, atIso) {
|
|
58
|
+
if (value === null) {
|
|
59
|
+
db.prepare('DELETE FROM _sync_meta WHERE key = ?').run(key);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
db.prepare('INSERT INTO _sync_meta (key, value, updated_at) VALUES (?, ?, ?) ' +
|
|
63
|
+
'ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at').run(key, value, atIso);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Seal, persist and upload one stream's pending journal (module docs).
|
|
67
|
+
* Must run outside a transaction. An upload failure propagates after the
|
|
68
|
+
* segments before it were recorded; the next run resends from the lowest
|
|
69
|
+
* unpushed `replicaSeq`, with the same bytes.
|
|
70
|
+
*
|
|
71
|
+
* @param db - The store.
|
|
72
|
+
* @param o - {@link PushStreamOptions}.
|
|
73
|
+
* @returns What was sealed, persisted and stored, or why nothing was sent.
|
|
74
|
+
*/
|
|
75
|
+
export async function pushStream(db, o) {
|
|
76
|
+
const env = o.env ?? process.env;
|
|
77
|
+
const now = o.now ?? Date.now;
|
|
78
|
+
if (!hasTable(db, '_sync_segment'))
|
|
79
|
+
return empty(o.stream, {
|
|
80
|
+
refused: 'sync schema not installed',
|
|
81
|
+
refusedKind: 'schema-missing',
|
|
82
|
+
});
|
|
83
|
+
if (!isSyncFlagOn(db, 'sync.push', env))
|
|
84
|
+
return empty(o.stream, { refused: 'sync.push is off', refusedKind: 'push-off' });
|
|
85
|
+
if (genesisCutOf(db, o.stream) === undefined) {
|
|
86
|
+
return empty(o.stream, {
|
|
87
|
+
refused: `${o.stream} has no genesis cut on this store: run \`cleo sync enable push\``,
|
|
88
|
+
refusedKind: 'no-genesis',
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
if (genesisPending(db, o.stream)) {
|
|
92
|
+
return empty(o.stream, {
|
|
93
|
+
refused: `${o.stream}'s genesis checkpoint is not stored yet: run \`cleo sync enable push\` again`,
|
|
94
|
+
refusedKind: 'genesis-pending',
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
const nowMs = now();
|
|
98
|
+
const atIso = new Date(nowMs).toISOString();
|
|
99
|
+
// §1.3: the observed server date bounds what the sealer stamps
|
|
100
|
+
// (sealWallCeiling), and a clock running ahead of it pauses push, never writes.
|
|
101
|
+
if (o.serverOffsetMs !== null) {
|
|
102
|
+
setPushMeta(db, SERVER_CLOCK_KEY, JSON.stringify({ offsetMs: o.serverOffsetMs, atMs: nowMs }), atIso);
|
|
103
|
+
}
|
|
104
|
+
const drift = o.serverOffsetMs === null ? 0 : -o.serverOffsetMs;
|
|
105
|
+
if (drift > (o.maxDriftMs ?? MAX_DRIFT_MS)) {
|
|
106
|
+
setPushMeta(db, CLOCK_AHEAD_KEY, JSON.stringify({ driftMs: drift }), atIso);
|
|
107
|
+
return empty(o.stream, { clockAhead: true });
|
|
108
|
+
}
|
|
109
|
+
setPushMeta(db, CLOCK_AHEAD_KEY, null, atIso);
|
|
110
|
+
// §1.5 / §2.9: the server is authoritative. A later segment there than this
|
|
111
|
+
// store ever persisted means the store was restored or copied behind it.
|
|
112
|
+
const local = storeHwm(db, o.replica)[o.stream] ?? null;
|
|
113
|
+
if (o.serverLastReplicaSeq !== null && (local === null || o.serverLastReplicaSeq > local)) {
|
|
114
|
+
return empty(o.stream, {
|
|
115
|
+
refused: `the server holds replicaSeq ${o.serverLastReplicaSeq} of replica ${o.replica} on ${o.stream}, but this store persisted only ${local ?? 'none'}: the store is behind (restored or copied) and must rebind to a new replica (T12753)`,
|
|
116
|
+
refusedKind: 'store-behind',
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
let sealed = 0;
|
|
120
|
+
// Bounded: steady concurrent writers never keep the push from sending; the
|
|
121
|
+
// next run seals the rest.
|
|
122
|
+
for (let pass = 0; pass < SEAL_PASSES; pass++) {
|
|
123
|
+
const r = sealPending(db, {
|
|
124
|
+
scope: o.scope,
|
|
125
|
+
replica: o.replica,
|
|
126
|
+
now,
|
|
127
|
+
env,
|
|
128
|
+
...(o.allowUnreleased ? { allowUnreleased: true } : {}),
|
|
129
|
+
});
|
|
130
|
+
sealed += r.txns;
|
|
131
|
+
if (r.refused !== null || r.captures === 0 || r.pending.length > 0)
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
let built = 0;
|
|
135
|
+
for (;;) {
|
|
136
|
+
const seg = buildSegment(db, {
|
|
137
|
+
stream: o.stream,
|
|
138
|
+
replica: o.replica,
|
|
139
|
+
scope: o.scope,
|
|
140
|
+
project: o.project,
|
|
141
|
+
sealer: o.sealer,
|
|
142
|
+
signTxn: o.signTxn,
|
|
143
|
+
nowIso: new Date(now()).toISOString(),
|
|
144
|
+
});
|
|
145
|
+
if (seg === null)
|
|
146
|
+
break;
|
|
147
|
+
built += 1;
|
|
148
|
+
// §2.8: the registry follows the store; a crash before this leaves the
|
|
149
|
+
// store ahead of it, the safe direction.
|
|
150
|
+
if (o.registry?.get(o.replica))
|
|
151
|
+
o.registry.advanceHwm(o.replica, o.stream, seg.replicaSeq);
|
|
152
|
+
}
|
|
153
|
+
let pushed = 0;
|
|
154
|
+
let duplicates = 0;
|
|
155
|
+
let lastServerSeq = null;
|
|
156
|
+
for (const seg of unpushedSegments(db, o.stream, o.replica)) {
|
|
157
|
+
const stored = await o.upload(seg);
|
|
158
|
+
markSegmentPushed(db, {
|
|
159
|
+
stream: o.stream,
|
|
160
|
+
replica: o.replica,
|
|
161
|
+
replicaSeq: seg.replicaSeq,
|
|
162
|
+
serverSeq: stored.seq,
|
|
163
|
+
nowIso: new Date(now()).toISOString(),
|
|
164
|
+
});
|
|
165
|
+
pushed += 1;
|
|
166
|
+
if (stored.duplicate)
|
|
167
|
+
duplicates += 1;
|
|
168
|
+
lastServerSeq = stored.seq;
|
|
169
|
+
}
|
|
170
|
+
return empty(o.stream, { sealed, built, pushed, duplicates, lastServerSeq });
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=push.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"push.js","sourceRoot":"","sources":["../../../src/store/sync/push.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The replay pin of a checkpoint (journal spec §2.11 §7; NEW-9, R5-8,
|
|
3
|
+
* T12797): what an endorsing device must replay under to recompute the
|
|
4
|
+
* checkpoint's tallies.
|
|
5
|
+
*
|
|
6
|
+
* - `journal`: sha256 over the store's WHOLE ordered migration journal,
|
|
7
|
+
* `(created_at, name, hash)` per applied migration, in drizzle's order
|
|
8
|
+
* `(created_at, name)`. A single head is not enough: stamped and drifted
|
|
9
|
+
* stores differ below it.
|
|
10
|
+
* - `triggerSetHash`: sha256 over the normalized DDL of the store's OWNED
|
|
11
|
+
* triggers ({@link OWNED_TRIGGERS}: the guard and side-effect triggers a
|
|
12
|
+
* replay runs under), sorted by name. Device-local maintenance triggers
|
|
13
|
+
* (the twin-collapse docs freeze, whose message names the build that froze
|
|
14
|
+
* that store, and the legacy track triggers) never touch a replayed table,
|
|
15
|
+
* so they stay out: two devices with the same replay semantics agree
|
|
16
|
+
* (T13298). Normalization is literal-safe ({@link normalizeTriggerDdl}).
|
|
17
|
+
* - `transitions`: the schema rise points inside the checkpoint's window;
|
|
18
|
+
* the caller computes them from the segments it replayed (empty for a
|
|
19
|
+
* genesis checkpoint, which has no window to replay).
|
|
20
|
+
*
|
|
21
|
+
* The server cannot check the two hashes; another device recomputes them
|
|
22
|
+
* from its own store and requires a match before it endorses.
|
|
23
|
+
*
|
|
24
|
+
* @task T12343
|
|
25
|
+
* @module store/sync/replay-pin
|
|
26
|
+
*/
|
|
27
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
28
|
+
import type { ReplayPin } from '@cleocode/contracts/cloud';
|
|
29
|
+
/**
|
|
30
|
+
* Normalize trigger DDL for hashing, literal-safely (§2.11 §7): comments are
|
|
31
|
+
* dropped; outside string literals and quoted identifiers, whitespace runs
|
|
32
|
+
* collapse to one space (and vanish next to punctuation) and words are
|
|
33
|
+
* lower-cased; a string literal is kept byte for byte (`'Done'` stays
|
|
34
|
+
* `'Done'`); every quoted identifier (`"x"`, `` `x` ``, `[x]`) is written
|
|
35
|
+
* `"x"`, and a bare identifier stays bare (SQLite treats both alike for the
|
|
36
|
+
* names a trigger uses, so the two spellings of one trigger hash alike only
|
|
37
|
+
* when quoting is unified this way).
|
|
38
|
+
*
|
|
39
|
+
* @param sql - The trigger's `CREATE TRIGGER` text.
|
|
40
|
+
* @returns The normalized text.
|
|
41
|
+
*/
|
|
42
|
+
export declare function normalizeTriggerDdl(sql: string): string;
|
|
43
|
+
/**
|
|
44
|
+
* sha256 of the store's whole ordered migration journal (§2.11 §7): every
|
|
45
|
+
* `__drizzle_migrations` row as `[created_at, name, hash]`, ordered by
|
|
46
|
+
* `(created_at, name)`. A store without the journal hashes an empty list.
|
|
47
|
+
*
|
|
48
|
+
* @param db - The store.
|
|
49
|
+
* @returns The hex digest.
|
|
50
|
+
*/
|
|
51
|
+
export declare function migrationJournalHash(db: DatabaseSync): string;
|
|
52
|
+
/**
|
|
53
|
+
* sha256 over the normalized DDL of every owned trigger present in the store
|
|
54
|
+
* ({@link OWNED_TRIGGERS}), sorted by name (§2.11 §7; T13298). Capture
|
|
55
|
+
* triggers (generated per build) and device-local maintenance triggers are
|
|
56
|
+
* excluded: they never run in a replay.
|
|
57
|
+
*
|
|
58
|
+
* @param db - The store.
|
|
59
|
+
* @returns The hex digest.
|
|
60
|
+
*/
|
|
61
|
+
export declare function triggerSetHash(db: DatabaseSync): string;
|
|
62
|
+
/**
|
|
63
|
+
* A checkpoint's replay pin, from the store it snapshots (§2.11 §7).
|
|
64
|
+
*
|
|
65
|
+
* @param db - The store (or the snapshot of it the checkpoint carries).
|
|
66
|
+
* @param transitions - The window's schema rise points (empty for genesis).
|
|
67
|
+
* @returns The pin.
|
|
68
|
+
*/
|
|
69
|
+
export declare function replayPinOf(db: DatabaseSync, transitions?: ReplayPin['transitions']): ReplayPin;
|
|
70
|
+
//# sourceMappingURL=replay-pin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replay-pin.d.ts","sourceRoot":"","sources":["../../../src/store/sync/replay-pin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAO3D;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAgEvD;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,YAAY,GAAG,MAAM,CAa7D;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,YAAY,GAAG,MAAM,CAOvD;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,EAAE,EAAE,YAAY,EAChB,WAAW,GAAE,SAAS,CAAC,aAAa,CAAM,GACzC,SAAS,CAMX"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The replay pin of a checkpoint (journal spec §2.11 §7; NEW-9, R5-8,
|
|
3
|
+
* T12797): what an endorsing device must replay under to recompute the
|
|
4
|
+
* checkpoint's tallies.
|
|
5
|
+
*
|
|
6
|
+
* - `journal`: sha256 over the store's WHOLE ordered migration journal,
|
|
7
|
+
* `(created_at, name, hash)` per applied migration, in drizzle's order
|
|
8
|
+
* `(created_at, name)`. A single head is not enough: stamped and drifted
|
|
9
|
+
* stores differ below it.
|
|
10
|
+
* - `triggerSetHash`: sha256 over the normalized DDL of the store's OWNED
|
|
11
|
+
* triggers ({@link OWNED_TRIGGERS}: the guard and side-effect triggers a
|
|
12
|
+
* replay runs under), sorted by name. Device-local maintenance triggers
|
|
13
|
+
* (the twin-collapse docs freeze, whose message names the build that froze
|
|
14
|
+
* that store, and the legacy track triggers) never touch a replayed table,
|
|
15
|
+
* so they stay out: two devices with the same replay semantics agree
|
|
16
|
+
* (T13298). Normalization is literal-safe ({@link normalizeTriggerDdl}).
|
|
17
|
+
* - `transitions`: the schema rise points inside the checkpoint's window;
|
|
18
|
+
* the caller computes them from the segments it replayed (empty for a
|
|
19
|
+
* genesis checkpoint, which has no window to replay).
|
|
20
|
+
*
|
|
21
|
+
* The server cannot check the two hashes; another device recomputes them
|
|
22
|
+
* from its own store and requires a match before it endorses.
|
|
23
|
+
*
|
|
24
|
+
* @task T12343
|
|
25
|
+
* @module store/sync/replay-pin
|
|
26
|
+
*/
|
|
27
|
+
import { createHash } from 'node:crypto';
|
|
28
|
+
import { hasTable } from './schema.js';
|
|
29
|
+
import { canonicalJson } from './sealer-values.js';
|
|
30
|
+
import { OWNED_TRIGGERS } from './trigger-classes.js';
|
|
31
|
+
const sha256Hex = (s) => createHash('sha256').update(s, 'utf8').digest('hex');
|
|
32
|
+
/**
|
|
33
|
+
* Normalize trigger DDL for hashing, literal-safely (§2.11 §7): comments are
|
|
34
|
+
* dropped; outside string literals and quoted identifiers, whitespace runs
|
|
35
|
+
* collapse to one space (and vanish next to punctuation) and words are
|
|
36
|
+
* lower-cased; a string literal is kept byte for byte (`'Done'` stays
|
|
37
|
+
* `'Done'`); every quoted identifier (`"x"`, `` `x` ``, `[x]`) is written
|
|
38
|
+
* `"x"`, and a bare identifier stays bare (SQLite treats both alike for the
|
|
39
|
+
* names a trigger uses, so the two spellings of one trigger hash alike only
|
|
40
|
+
* when quoting is unified this way).
|
|
41
|
+
*
|
|
42
|
+
* @param sql - The trigger's `CREATE TRIGGER` text.
|
|
43
|
+
* @returns The normalized text.
|
|
44
|
+
*/
|
|
45
|
+
export function normalizeTriggerDdl(sql) {
|
|
46
|
+
const out = [];
|
|
47
|
+
let i = 0;
|
|
48
|
+
let space = false;
|
|
49
|
+
const push = (tok, word) => {
|
|
50
|
+
const prev = out[out.length - 1];
|
|
51
|
+
// One space between two word-like tokens; none around punctuation.
|
|
52
|
+
if (space && prev !== undefined && word && /[\w"'\]`]$/.test(prev))
|
|
53
|
+
out.push(' ');
|
|
54
|
+
out.push(tok);
|
|
55
|
+
space = false;
|
|
56
|
+
};
|
|
57
|
+
while (i < sql.length) {
|
|
58
|
+
const c = sql[i];
|
|
59
|
+
if (/\s/.test(c)) {
|
|
60
|
+
space = true;
|
|
61
|
+
i += 1;
|
|
62
|
+
}
|
|
63
|
+
else if (sql.startsWith('--', i)) {
|
|
64
|
+
const end = sql.indexOf('\n', i);
|
|
65
|
+
i = end === -1 ? sql.length : end;
|
|
66
|
+
space = true;
|
|
67
|
+
}
|
|
68
|
+
else if (sql.startsWith('/*', i)) {
|
|
69
|
+
const end = sql.indexOf('*/', i + 2);
|
|
70
|
+
i = end === -1 ? sql.length : end + 2;
|
|
71
|
+
space = true;
|
|
72
|
+
}
|
|
73
|
+
else if (c === "'") {
|
|
74
|
+
let j = i + 1;
|
|
75
|
+
for (;;) {
|
|
76
|
+
if (j >= sql.length)
|
|
77
|
+
break;
|
|
78
|
+
if (sql[j] === "'") {
|
|
79
|
+
if (sql[j + 1] === "'")
|
|
80
|
+
j += 2;
|
|
81
|
+
else
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
else
|
|
85
|
+
j += 1;
|
|
86
|
+
}
|
|
87
|
+
push(sql.slice(i, j + 1), true);
|
|
88
|
+
i = j + 1;
|
|
89
|
+
}
|
|
90
|
+
else if (c === '"' || c === '`' || c === '[') {
|
|
91
|
+
const close = c === '[' ? ']' : c;
|
|
92
|
+
let j = i + 1;
|
|
93
|
+
let name = '';
|
|
94
|
+
for (;;) {
|
|
95
|
+
if (j >= sql.length)
|
|
96
|
+
break;
|
|
97
|
+
if (sql[j] === close) {
|
|
98
|
+
if (close !== ']' && sql[j + 1] === close) {
|
|
99
|
+
name += close;
|
|
100
|
+
j += 2;
|
|
101
|
+
}
|
|
102
|
+
else
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
name += sql[j];
|
|
107
|
+
j += 1;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
push(`"${name.replaceAll('"', '""')}"`, true);
|
|
111
|
+
i = j + 1;
|
|
112
|
+
}
|
|
113
|
+
else if (/[\w$]/.test(c)) {
|
|
114
|
+
let j = i;
|
|
115
|
+
while (j < sql.length && /[\w$]/.test(sql[j]))
|
|
116
|
+
j += 1;
|
|
117
|
+
push(sql.slice(i, j).toLowerCase(), true);
|
|
118
|
+
i = j;
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
push(c, false);
|
|
122
|
+
i += 1;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return out.join('').replace(/;$/, '');
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* sha256 of the store's whole ordered migration journal (§2.11 §7): every
|
|
129
|
+
* `__drizzle_migrations` row as `[created_at, name, hash]`, ordered by
|
|
130
|
+
* `(created_at, name)`. A store without the journal hashes an empty list.
|
|
131
|
+
*
|
|
132
|
+
* @param db - The store.
|
|
133
|
+
* @returns The hex digest.
|
|
134
|
+
*/
|
|
135
|
+
export function migrationJournalHash(db) {
|
|
136
|
+
const rows = hasTable(db, '__drizzle_migrations')
|
|
137
|
+
? db
|
|
138
|
+
.prepare('SELECT created_at, name, hash FROM main."__drizzle_migrations" ORDER BY created_at, name')
|
|
139
|
+
.all()
|
|
140
|
+
: [];
|
|
141
|
+
return sha256Hex(canonicalJson(rows.map((r) => [r.created_at === null ? null : Number(r.created_at), r.name, r.hash])));
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* sha256 over the normalized DDL of every owned trigger present in the store
|
|
145
|
+
* ({@link OWNED_TRIGGERS}), sorted by name (§2.11 §7; T13298). Capture
|
|
146
|
+
* triggers (generated per build) and device-local maintenance triggers are
|
|
147
|
+
* excluded: they never run in a replay.
|
|
148
|
+
*
|
|
149
|
+
* @param db - The store.
|
|
150
|
+
* @returns The hex digest.
|
|
151
|
+
*/
|
|
152
|
+
export function triggerSetHash(db) {
|
|
153
|
+
const rows = db
|
|
154
|
+
.prepare("SELECT name, sql FROM main.sqlite_master WHERE type = 'trigger' ORDER BY name")
|
|
155
|
+
.all().filter((r) => Object.hasOwn(OWNED_TRIGGERS, r.name) && r.sql !== null);
|
|
156
|
+
return sha256Hex(canonicalJson(rows.map((r) => [r.name, normalizeTriggerDdl(r.sql)])));
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* A checkpoint's replay pin, from the store it snapshots (§2.11 §7).
|
|
160
|
+
*
|
|
161
|
+
* @param db - The store (or the snapshot of it the checkpoint carries).
|
|
162
|
+
* @param transitions - The window's schema rise points (empty for genesis).
|
|
163
|
+
* @returns The pin.
|
|
164
|
+
*/
|
|
165
|
+
export function replayPinOf(db, transitions = []) {
|
|
166
|
+
return {
|
|
167
|
+
journal: migrationJournalHash(db),
|
|
168
|
+
triggerSetHash: triggerSetHash(db),
|
|
169
|
+
transitions: [...transitions],
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=replay-pin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replay-pin.js","sourceRoot":"","sources":["../../../src/store/sync/replay-pin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,MAAM,SAAS,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAE9F;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAW;IAC7C,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,IAAI,KAAK,GAAG,KAAK,CAAC;IAClB,MAAM,IAAI,GAAG,CAAC,GAAW,EAAE,IAAa,EAAQ,EAAE;QAChD,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACjC,mEAAmE;QACnE,IAAI,KAAK,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAClF,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACd,KAAK,GAAG,KAAK,CAAC;IAChB,CAAC,CAAC;IACF,OAAO,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC;QACtB,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,CAAW,CAAC;QAC3B,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YACjB,KAAK,GAAG,IAAI,CAAC;YACb,CAAC,IAAI,CAAC,CAAC;QACT,CAAC;aAAM,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;YACnC,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;YACjC,CAAC,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC;YAClC,KAAK,GAAG,IAAI,CAAC;QACf,CAAC;aAAM,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;YACnC,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;YACrC,CAAC,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC;YACtC,KAAK,GAAG,IAAI,CAAC;QACf,CAAC;aAAM,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;YACrB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACd,SAAS,CAAC;gBACR,IAAI,CAAC,IAAI,GAAG,CAAC,MAAM;oBAAE,MAAM;gBAC3B,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;oBACnB,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG;wBAAE,CAAC,IAAI,CAAC,CAAC;;wBAC1B,MAAM;gBACb,CAAC;;oBAAM,CAAC,IAAI,CAAC,CAAC;YAChB,CAAC;YACD,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;YAChC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACZ,CAAC;aAAM,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;YAC/C,MAAM,KAAK,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YAClC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACd,IAAI,IAAI,GAAG,EAAE,CAAC;YACd,SAAS,CAAC;gBACR,IAAI,CAAC,IAAI,GAAG,CAAC,MAAM;oBAAE,MAAM;gBAC3B,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,KAAK,EAAE,CAAC;oBACrB,IAAI,KAAK,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,KAAK,EAAE,CAAC;wBAC1C,IAAI,IAAI,KAAK,CAAC;wBACd,CAAC,IAAI,CAAC,CAAC;oBACT,CAAC;;wBAAM,MAAM;gBACf,CAAC;qBAAM,CAAC;oBACN,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC;oBACf,CAAC,IAAI,CAAC,CAAC;gBACT,CAAC;YACH,CAAC;YACD,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YAC9C,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACZ,CAAC;aAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YAC3B,IAAI,CAAC,GAAG,CAAC,CAAC;YACV,OAAO,CAAC,GAAG,GAAG,CAAC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAW,CAAC;gBAAE,CAAC,IAAI,CAAC,CAAC;YAChE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,EAAE,IAAI,CAAC,CAAC;YAC1C,CAAC,GAAG,CAAC,CAAC;QACR,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;YACf,CAAC,IAAI,CAAC,CAAC;QACT,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;AACxC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAC,EAAgB;IACnD,MAAM,IAAI,GAAG,QAAQ,CAAC,EAAE,EAAE,sBAAsB,CAAC;QAC/C,CAAC,CAAE,EAAE;aACA,OAAO,CACN,0FAA0F,CAC3F;aACA,GAAG,EAAuF;QAC/F,CAAC,CAAC,EAAE,CAAC;IACP,OAAO,SAAS,CACd,aAAa,CACX,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CACvF,CACF,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,EAAgB;IAC7C,MAAM,IAAI,GACR,EAAE;SACC,OAAO,CAAC,+EAA+E,CAAC;SACxF,GAAG,EACP,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,KAAK,IAAI,CAAC,CAAC;IACzE,OAAO,SAAS,CAAC,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACnG,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CACzB,EAAgB,EAChB,cAAwC,EAAE;IAE1C,OAAO;QACL,OAAO,EAAE,oBAAoB,CAAC,EAAE,CAAC;QACjC,cAAc,EAAE,cAAc,CAAC,EAAE,CAAC;QAClC,WAAW,EAAE,CAAC,GAAG,WAAW,CAAC;KAC9B,CAAC;AACJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sealer.d.ts","sourceRoot":"","sources":["../../../src/store/sync/sealer.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"sealer.d.ts","sourceRoot":"","sources":["../../../src/store/sync/sealer.ts"],"names":[],"mappings":"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"}
|
|
@@ -50,7 +50,7 @@ import { naturalRowUid } from '../row-identity.js';
|
|
|
50
50
|
import { BIRTH_FP_COLUMN, rowIdentitySpec, UID_COLUMN } from '../row-identity-registry.js';
|
|
51
51
|
import { loadFrameIntents, subtractApplyIntents } from './apply-intent.js';
|
|
52
52
|
import { captureTableDef, chunkedObject, enc, SECRET_MARKER, syncSetTables, } from './capture.js';
|
|
53
|
-
import { tickClock, withImmediateTransaction } from './clock-store.js';
|
|
53
|
+
import { sealWallCeiling, tickClock, withImmediateTransaction } from './clock-store.js';
|
|
54
54
|
import { actorOpOf, clearFieldLeaves, localFrontierUpdates, localLeaves, recordFieldLeaves, setFieldFrontiers, } from './field-leave.js';
|
|
55
55
|
import { isSyncFlagOn, legacyStrandRemedy, legacyStrandsCached, UNRELEASED_FLAGS, } from './flags.js';
|
|
56
56
|
import { mergeGroupsOf } from './merge/rules.js';
|
|
@@ -887,6 +887,10 @@ function sealInTransaction(db, opts, budget, now) {
|
|
|
887
887
|
const started = performance.now();
|
|
888
888
|
const maxMs = opts.maxMs ?? 200;
|
|
889
889
|
const replica = opts.replica;
|
|
890
|
+
// §1.3: a capture stamped while this clock ran ahead seals no later than
|
|
891
|
+
// the server's date plus MAX_DRIFT, once a recent server date is known.
|
|
892
|
+
const ceiling = sealWallCeiling(db, now());
|
|
893
|
+
const wall = (ms) => (ceiling === null ? ms : Math.min(ms, ceiling));
|
|
890
894
|
// 1. Read a batch. A frame cut by the budget is completed only when it is
|
|
891
895
|
// the batch's first group (it must seal whole); otherwise it is left for
|
|
892
896
|
// the next batch, so the extension stays bounded (T13032).
|
|
@@ -1118,7 +1122,7 @@ function sealInTransaction(db, opts, budget, now) {
|
|
|
1118
1122
|
...rest,
|
|
1119
1123
|
seq,
|
|
1120
1124
|
last,
|
|
1121
|
-
h: tickClock(db, replica, at(op)?.at_ms ?? now()),
|
|
1125
|
+
h: tickClock(db, replica, wall(at(op)?.at_ms ?? now())),
|
|
1122
1126
|
};
|
|
1123
1127
|
});
|
|
1124
1128
|
const txnHlc = sealedOps.reduce((m, o) => (o.h > m ? o.h : m), sealedOps[0]?.h ?? '');
|