@origintrail-official/dkg-node-ui 10.0.13 → 10.0.15
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/chat-memory.d.ts +43 -2
- package/dist/chat-memory.d.ts.map +1 -1
- package/dist/chat-memory.js +31 -7
- package/dist/chat-memory.js.map +1 -1
- package/dist/db.d.ts +23 -16
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +217 -108
- package/dist/db.js.map +1 -1
- package/dist/gelf-push-worker.d.ts +1 -1
- package/dist/gelf-push-worker.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/otlp-log-worker.d.ts +3 -5
- package/dist/otlp-log-worker.d.ts.map +1 -1
- package/dist/otlp-log-worker.js +7 -5
- package/dist/otlp-log-worker.js.map +1 -1
- package/dist/routine-log-retention.d.ts +40 -0
- package/dist/routine-log-retention.d.ts.map +1 -0
- package/dist/routine-log-retention.js +262 -0
- package/dist/routine-log-retention.js.map +1 -0
- package/dist/structured-logger.d.ts.map +1 -1
- package/dist/structured-logger.js.map +1 -1
- package/dist-ui/assets/{3d-force-graph-C9KowW4u.js → 3d-force-graph-CnBoK4hA.js} +1 -1
- package/dist-ui/assets/{AgentHub-BVrcom9_.js → AgentHub-BOUZTvCO.js} +1 -1
- package/dist-ui/assets/{AgentProfilePage-C9XaTt7j.js → AgentProfilePage-DpF722ai.js} +1 -1
- package/dist-ui/assets/{ApproveWalletsModal-BdR2mshx.js → ApproveWalletsModal-CYQqAWZs.js} +3 -3
- package/dist-ui/assets/{ConvictionDetailView-cao48GL1.js → ConvictionDetailView-DYsWNc9M.js} +1 -1
- package/dist-ui/assets/{Network-B3jCYzr5.js → Network-JVFsFYhR.js} +1 -1
- package/dist-ui/assets/{OnChainProvenanceCard-DboMX4Aq.js → OnChainProvenanceCard-DISbYKx1.js} +1 -1
- package/dist-ui/assets/{Operations-Dg1gfLXv.js → Operations-Cmt3YpaX.js} +1 -1
- package/dist-ui/assets/{PublishingConviction-Byp6t69e.js → PublishingConviction-L-wHlnRP.js} +1 -1
- package/dist-ui/assets/{Settings-CxI0SKeV.js → Settings-CvBKeoTW.js} +1 -1
- package/dist-ui/assets/{ccip-wJ3OMRuz.js → ccip-m-rmKwvf.js} +1 -1
- package/dist-ui/assets/index-BfrYentv.js +1645 -0
- package/dist-ui/assets/{index-5DS6xjw0.js → index-UAg6wmCv.js} +3 -3
- package/dist-ui/assets/index-VYV5TH6q.css +1 -0
- package/dist-ui/assets/{jsonld-32FQRO67-CDirPi8u.js → jsonld-32FQRO67-iGNyLix6.js} +2 -2
- package/dist-ui/assets/{jsonld-CwHW9UDN.js → jsonld-CDhTpz6H.js} +1 -1
- package/dist-ui/assets/{renderer-3d-2EVDZII7-BNXV6iSu.js → renderer-3d-2EVDZII7-BdEgi2VZ.js} +2 -2
- package/dist-ui/assets/{shikiHighlighter-C-LWPPSu.js → shikiHighlighter-Dhz3-Gg5.js} +1 -1
- package/dist-ui/index.html +2 -2
- package/package.json +5 -4
- package/dist-ui/assets/index-DOjsv0r7.css +0 -1
- package/dist-ui/assets/index-KoAnx9JE.js +0 -1661
package/dist/db.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import Database from 'better-sqlite3';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import { RESPONSE_CACHE_BYTES, parseContextGraphJoinPolicyRecord, } from '@origintrail-official/dkg-core';
|
|
3
|
+
import { RESPONSE_CACHE_BYTES, parseContextGraphJoinPolicyRecord, DEFAULT_SYNC_CHECKPOINT_TTL_MS, isValidSyncCheckpointEntry, transitionSyncCheckpointManifestOffset, transitionSyncCheckpointOffset, transitionSyncCheckpointResponderSession, withoutSyncCheckpointResponderSession, } from '@origintrail-official/dkg-core';
|
|
4
|
+
import { RoutineLogRetention, installRoutineLogRetentionSchema, } from './routine-log-retention.js';
|
|
4
5
|
export { SqliteChainEventCursorStore, SqliteContextGraphRegistryScanCursorStore, } from './chain-cursor-stores.js';
|
|
5
|
-
const SCHEMA_VERSION =
|
|
6
|
+
export const SCHEMA_VERSION = 35;
|
|
6
7
|
// Default operator retention. Lowered from 90 → 14 days on V15 (2026-05) after
|
|
7
8
|
// a production incident in which the `logs` table + its FTS5 shadow tables
|
|
8
9
|
// grew to ~9 GB on a 12-day-old node and corrupted the SQLite page (header
|
|
@@ -12,9 +13,9 @@ const SCHEMA_VERSION = 31;
|
|
|
12
13
|
// operator-driven post-mortem, but the mainnet sync storm proved that time
|
|
13
14
|
// retention alone cannot bound worst-case growth. Operators who want longer
|
|
14
15
|
// retention can override via `setRetentionDays()`; the setting is persisted
|
|
15
|
-
// in the `settings` table and re-read on next boot.
|
|
16
|
-
//
|
|
17
|
-
//
|
|
16
|
+
// in the `settings` table and re-read on next boot. The daemon no longer writes
|
|
17
|
+
// routine logs to SQLite; the established count cap still bounds compatibility
|
|
18
|
+
// writers while warning/error rows keep the operator-selected time window.
|
|
18
19
|
const DEFAULT_RETENTION_DAYS = 14;
|
|
19
20
|
const LEGACY_IMPLICIT_RETENTION_DAYS = 90;
|
|
20
21
|
const DEFAULT_ROUTINE_LOG_ROW_CAP = 1_000_000;
|
|
@@ -104,17 +105,17 @@ export class DashboardDB {
|
|
|
104
105
|
dataDir;
|
|
105
106
|
retentionDays;
|
|
106
107
|
explicitRetentionDays;
|
|
107
|
-
|
|
108
|
-
logVolumePruneBatchRows;
|
|
108
|
+
routineLogRetention;
|
|
109
109
|
constructor(opts) {
|
|
110
110
|
this.dataDir = opts.dataDir;
|
|
111
111
|
this.explicitRetentionDays = opts.retentionDays !== undefined;
|
|
112
112
|
this.retentionDays = opts.retentionDays ?? DEFAULT_RETENTION_DAYS;
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
const routineLogRowCap = Math.max(0, Math.floor(opts.routineLogRowCap ?? DEFAULT_ROUTINE_LOG_ROW_CAP));
|
|
114
|
+
const logVolumePruneBatchRows = Math.max(1, Math.floor(opts.logVolumePruneBatchRows ?? DEFAULT_LOG_VOLUME_PRUNE_BATCH_ROWS));
|
|
115
115
|
this._memoTtlMs = resolveCacheTtlMs(opts.cacheTtlMs);
|
|
116
116
|
const dbPath = join(opts.dataDir, 'node-ui.db');
|
|
117
117
|
this.db = new Database(dbPath);
|
|
118
|
+
this.routineLogRetention = new RoutineLogRetention(this.db, routineLogRowCap, logVolumePruneBatchRows);
|
|
118
119
|
this.db.pragma('journal_mode = WAL');
|
|
119
120
|
this.db.pragma('synchronous = NORMAL');
|
|
120
121
|
// Cap the persisted WAL file. A PASSIVE autocheckpoint resets WAL
|
|
@@ -172,7 +173,7 @@ export class DashboardDB {
|
|
|
172
173
|
);
|
|
173
174
|
`);
|
|
174
175
|
};
|
|
175
|
-
const
|
|
176
|
+
const ensureSyncCheckpointResumeColumns = () => {
|
|
176
177
|
const table = this.db.prepare(`
|
|
177
178
|
SELECT 1 AS found FROM sqlite_master
|
|
178
179
|
WHERE type = 'table' AND name = 'sync_checkpoints'
|
|
@@ -187,6 +188,18 @@ export class DashboardDB {
|
|
|
187
188
|
if (!columns.has('responder_session_expires_at')) {
|
|
188
189
|
this.db.exec(`ALTER TABLE sync_checkpoints ADD COLUMN responder_session_expires_at INTEGER;`);
|
|
189
190
|
}
|
|
191
|
+
if (!columns.has('responder_session_offset')) {
|
|
192
|
+
this.db.exec(`ALTER TABLE sync_checkpoints ADD COLUMN responder_session_offset INTEGER;`);
|
|
193
|
+
}
|
|
194
|
+
if (!columns.has('manifest_digest')) {
|
|
195
|
+
this.db.exec(`ALTER TABLE sync_checkpoints ADD COLUMN manifest_digest TEXT;`);
|
|
196
|
+
}
|
|
197
|
+
if (!columns.has('manifest_prefix_digest')) {
|
|
198
|
+
this.db.exec(`ALTER TABLE sync_checkpoints ADD COLUMN manifest_prefix_digest TEXT;`);
|
|
199
|
+
}
|
|
200
|
+
if (!columns.has('terminal')) {
|
|
201
|
+
this.db.exec(`ALTER TABLE sync_checkpoints ADD COLUMN terminal INTEGER NOT NULL DEFAULT 0 CHECK (terminal IN (0, 1));`);
|
|
202
|
+
}
|
|
190
203
|
};
|
|
191
204
|
if (version > SCHEMA_VERSION)
|
|
192
205
|
return;
|
|
@@ -194,8 +207,9 @@ export class DashboardDB {
|
|
|
194
207
|
// Repair restored/development databases that carry the current version
|
|
195
208
|
// but lost an idempotent schema adjunct.
|
|
196
209
|
ensureJoinApprovalRepairMarker();
|
|
197
|
-
|
|
210
|
+
ensureSyncCheckpointResumeColumns();
|
|
198
211
|
ensureJoinPolicyAuditCapTrigger();
|
|
212
|
+
installRoutineLogRetentionSchema(this.db);
|
|
199
213
|
return;
|
|
200
214
|
}
|
|
201
215
|
if (version < 1) {
|
|
@@ -789,7 +803,13 @@ export class DashboardDB {
|
|
|
789
803
|
key TEXT PRIMARY KEY,
|
|
790
804
|
offset INTEGER NOT NULL CHECK (offset >= 0),
|
|
791
805
|
updated_at INTEGER NOT NULL,
|
|
792
|
-
expires_at INTEGER NOT NULL
|
|
806
|
+
expires_at INTEGER NOT NULL,
|
|
807
|
+
responder_session_id TEXT,
|
|
808
|
+
responder_session_expires_at INTEGER,
|
|
809
|
+
responder_session_offset INTEGER CHECK (responder_session_offset >= 0),
|
|
810
|
+
manifest_digest TEXT,
|
|
811
|
+
manifest_prefix_digest TEXT,
|
|
812
|
+
terminal INTEGER NOT NULL DEFAULT 0 CHECK (terminal IN (0, 1))
|
|
793
813
|
);
|
|
794
814
|
CREATE INDEX IF NOT EXISTS idx_sync_checkpoints_expires_at
|
|
795
815
|
ON sync_checkpoints(expires_at);
|
|
@@ -1023,7 +1043,7 @@ export class DashboardDB {
|
|
|
1023
1043
|
// OFFSET pagination is scoped to an immutable responder row list. Keep
|
|
1024
1044
|
// the opaque responder token beside the verified offset so a requester
|
|
1025
1045
|
// restart can resume that same list instead of discarding durable work.
|
|
1026
|
-
|
|
1046
|
+
ensureSyncCheckpointResumeColumns();
|
|
1027
1047
|
}
|
|
1028
1048
|
if (version < 31) {
|
|
1029
1049
|
this.db.exec(`
|
|
@@ -1043,6 +1063,49 @@ export class DashboardDB {
|
|
|
1043
1063
|
);
|
|
1044
1064
|
`);
|
|
1045
1065
|
}
|
|
1066
|
+
if (version < 32) {
|
|
1067
|
+
this.db.exec(`
|
|
1068
|
+
CREATE TABLE IF NOT EXISTS selected_vm_reconcile_cursors (
|
|
1069
|
+
deployment_id TEXT NOT NULL CHECK (length(trim(deployment_id)) > 0),
|
|
1070
|
+
context_graph_id TEXT NOT NULL,
|
|
1071
|
+
on_chain_context_graph_id TEXT NOT NULL,
|
|
1072
|
+
name_hash TEXT NOT NULL,
|
|
1073
|
+
watermark INTEGER NOT NULL CHECK (watermark >= 0),
|
|
1074
|
+
updated_at INTEGER NOT NULL,
|
|
1075
|
+
PRIMARY KEY (deployment_id, context_graph_id, on_chain_context_graph_id)
|
|
1076
|
+
);
|
|
1077
|
+
CREATE INDEX IF NOT EXISTS idx_selected_vm_reconcile_cursor_cg
|
|
1078
|
+
ON selected_vm_reconcile_cursors(deployment_id, context_graph_id);
|
|
1079
|
+
`);
|
|
1080
|
+
}
|
|
1081
|
+
if (version < 33) {
|
|
1082
|
+
// A durable DATA offset is reusable only with cryptographic proof of the
|
|
1083
|
+
// canonical META generation and verified descriptor prefix that gave it
|
|
1084
|
+
// meaning. Persist both digests beside the offset/session so production
|
|
1085
|
+
// SQLite checkpoints have the same safety contract as the in-memory
|
|
1086
|
+
// requester store and survive daemon restarts.
|
|
1087
|
+
ensureSyncCheckpointResumeColumns();
|
|
1088
|
+
}
|
|
1089
|
+
if (version < 34) {
|
|
1090
|
+
// The V33 manifest-bound continuation must not share a key with V32,
|
|
1091
|
+
// which interpreted OFFSET as an unverified raw responder coordinate.
|
|
1092
|
+
// Drop every pre-versioned durable DATA row before new code starts using
|
|
1093
|
+
// the `|checkpoint:v2` namespace, so a later rollback also fails closed.
|
|
1094
|
+
ensureSyncCheckpointResumeColumns();
|
|
1095
|
+
this.db.prepare(`
|
|
1096
|
+
DELETE FROM sync_checkpoints
|
|
1097
|
+
WHERE key LIKE '%|durable|data%'
|
|
1098
|
+
AND key NOT LIKE '%|durable|data|checkpoint:v2%'
|
|
1099
|
+
`).run();
|
|
1100
|
+
}
|
|
1101
|
+
if (version < 35) {
|
|
1102
|
+
// Retention probes used to find the (cap + 1)th newest routine row via
|
|
1103
|
+
// OFFSET, synchronously scanning the million-row cap on every cleanup
|
|
1104
|
+
// tick. Maintain the exact count transactionally and index only routine
|
|
1105
|
+
// row ids, so overflow checks are O(1) and each prune touches at most one
|
|
1106
|
+
// configured batch.
|
|
1107
|
+
installRoutineLogRetentionSchema(this.db);
|
|
1108
|
+
}
|
|
1046
1109
|
this.db.pragma(`user_version = ${SCHEMA_VERSION}`);
|
|
1047
1110
|
if (upgradedExistingDb && !this.explicitRetentionDays) {
|
|
1048
1111
|
this.retentionDays = LEGACY_IMPLICIT_RETENTION_DAYS;
|
|
@@ -1059,14 +1122,7 @@ export class DashboardDB {
|
|
|
1059
1122
|
}
|
|
1060
1123
|
prune() {
|
|
1061
1124
|
const cutoff = Date.now() - this.retentionDays * 86_400_000;
|
|
1062
|
-
|
|
1063
|
-
// SQLite's per-statement change count is exposed via better-sqlite3's
|
|
1064
|
-
// `Database.run().changes`, but `exec()` returns nothing — for a
|
|
1065
|
-
// proper accounting we'd switch each statement to `prepare/run`. For
|
|
1066
|
-
// the VACUUM gating decision below we only need to know whether
|
|
1067
|
-
// *something* substantial was deleted, so we sample the only table
|
|
1068
|
-
// that actually grows fast in practice: `logs`.
|
|
1069
|
-
const logsDeleted = this.db.prepare(`DELETE FROM logs WHERE ts < ?`).run(cutoff).changes;
|
|
1125
|
+
this.db.prepare(`DELETE FROM logs WHERE ts < ?`).run(cutoff);
|
|
1070
1126
|
this.db.exec(`DELETE FROM metric_snapshots WHERE ts < ${cutoff}`);
|
|
1071
1127
|
this.db.exec(`DELETE FROM operation_phases WHERE started_at < ${cutoff}`);
|
|
1072
1128
|
this.db.exec(`DELETE FROM operations WHERE started_at < ${cutoff}`);
|
|
@@ -1098,32 +1154,32 @@ export class DashboardDB {
|
|
|
1098
1154
|
const messengerCutoff = Date.now() - 24 * 60 * 60 * 1000;
|
|
1099
1155
|
this.db.exec(`DELETE FROM message_idempotency WHERE ts < ${messengerCutoff}`);
|
|
1100
1156
|
// Avoid rebuilding an oversized DB at startup while millions of routine
|
|
1101
|
-
// rows are still live. The
|
|
1102
|
-
// the
|
|
1103
|
-
//
|
|
1157
|
+
// rows are still live. The independent volume pruner trims only rows above
|
|
1158
|
+
// the published count cap, in bounded batches, and performs the final
|
|
1159
|
+
// compaction. Ambiguous rows below the cap remain untouched.
|
|
1104
1160
|
if (!this.hasRoutineLogOverflow()) {
|
|
1105
|
-
this.reclaimFreePagesIfNeeded(
|
|
1161
|
+
this.reclaimFreePagesIfNeeded(false);
|
|
1106
1162
|
}
|
|
1107
1163
|
// Return the WAL file itself to the OS. journal_size_limit bounds it
|
|
1108
1164
|
// in steady state, but a TRUNCATE checkpoint here shrinks it promptly
|
|
1109
|
-
// on the prune cadence (~6h) and
|
|
1110
|
-
//
|
|
1111
|
-
//
|
|
1112
|
-
// an idle node still wants its -wal reclaimed.
|
|
1165
|
+
// on the prune cadence (~6h) and after a cleanup VACUUM, which rewrites
|
|
1166
|
+
// the whole DB through the WAL and momentarily grows it. Runs
|
|
1167
|
+
// unconditionally because an idle node still wants its -wal reclaimed.
|
|
1113
1168
|
this.truncateWal('prune');
|
|
1114
1169
|
}
|
|
1115
1170
|
/**
|
|
1116
|
-
* Remove one bounded batch of the oldest routine (non-warning/error) logs
|
|
1117
|
-
*
|
|
1118
|
-
*
|
|
1171
|
+
* Remove one bounded batch of the oldest routine (non-warning/error) logs
|
|
1172
|
+
* above the public count cap. The cap is the only ownership-neutral rule we
|
|
1173
|
+
* can safely apply to pre-upgrade rows: historical daemon sink records and
|
|
1174
|
+
* published compatibility API records have identical schemas.
|
|
1119
1175
|
*
|
|
1120
1176
|
* Deletion is deliberately incremental so an upgrade does not block node
|
|
1121
1177
|
* startup on a multi-GB transaction. Once the backlog reaches the cap, one
|
|
1122
1178
|
* VACUUM returns the accumulated free pages to the OS and the file shrinks.
|
|
1123
1179
|
*/
|
|
1124
1180
|
pruneLogVolumeBatch() {
|
|
1125
|
-
const
|
|
1126
|
-
if (
|
|
1181
|
+
const batch = this.routineLogRetention.pruneOverflowBatch();
|
|
1182
|
+
if (!batch.hadOverflow) {
|
|
1127
1183
|
const reclaim = this.reclaimFreePagesIfNeeded(false);
|
|
1128
1184
|
if (!reclaim.reclaimPending)
|
|
1129
1185
|
this.truncateWal('log-volume prune');
|
|
@@ -1136,24 +1192,7 @@ export class DashboardDB {
|
|
|
1136
1192
|
: 'done',
|
|
1137
1193
|
};
|
|
1138
1194
|
}
|
|
1139
|
-
const deleted =
|
|
1140
|
-
DELETE FROM logs
|
|
1141
|
-
WHERE id IN (
|
|
1142
|
-
SELECT id
|
|
1143
|
-
FROM logs
|
|
1144
|
-
WHERE id <= @cutoff
|
|
1145
|
-
AND level NOT IN ('warn', 'error')
|
|
1146
|
-
ORDER BY id ASC
|
|
1147
|
-
LIMIT @batchRows
|
|
1148
|
-
)
|
|
1149
|
-
`).run({
|
|
1150
|
-
cutoff: overflowCutoff,
|
|
1151
|
-
batchRows: this.logVolumePruneBatchRows,
|
|
1152
|
-
}).changes;
|
|
1153
|
-
// If the batch filled, conservatively schedule another tick. An exact-size
|
|
1154
|
-
// final batch costs one extra cheap probe before compaction, which is safer
|
|
1155
|
-
// than running a second million-row count after every deletion.
|
|
1156
|
-
const hasMore = deleted === this.logVolumePruneBatchRows;
|
|
1195
|
+
const { deleted, hasMore } = batch;
|
|
1157
1196
|
const reclaim = hasMore
|
|
1158
1197
|
? { compacted: false, reclaimPending: false }
|
|
1159
1198
|
: this.reclaimFreePagesIfNeeded(deleted > LOGS_VACUUM_DELETE_THRESHOLD);
|
|
@@ -1170,18 +1209,8 @@ export class DashboardDB {
|
|
|
1170
1209
|
: 'done',
|
|
1171
1210
|
};
|
|
1172
1211
|
}
|
|
1173
|
-
routineLogOverflowCutoff() {
|
|
1174
|
-
const row = this.db.prepare(`
|
|
1175
|
-
SELECT id
|
|
1176
|
-
FROM logs
|
|
1177
|
-
WHERE level NOT IN ('warn', 'error')
|
|
1178
|
-
ORDER BY id DESC
|
|
1179
|
-
LIMIT 1 OFFSET ?
|
|
1180
|
-
`).get(this.routineLogRowCap);
|
|
1181
|
-
return row?.id ?? null;
|
|
1182
|
-
}
|
|
1183
1212
|
hasRoutineLogOverflow() {
|
|
1184
|
-
return this.
|
|
1213
|
+
return this.routineLogRetention.hasOverflow();
|
|
1185
1214
|
}
|
|
1186
1215
|
reclaimFreePagesIfNeeded(force) {
|
|
1187
1216
|
// DELETE / DROP only marks pages reusable; VACUUM is what returns them to
|
|
@@ -1640,6 +1669,29 @@ export class DashboardDB {
|
|
|
1640
1669
|
deleteVmReconcileNegativesForContextGraph(contextGraphId) {
|
|
1641
1670
|
this.stmt('deleteVmReconcileNegativesForContextGraph', 'DELETE FROM vm_reconcile_negative_cache WHERE context_graph_id = ?').run(contextGraphId);
|
|
1642
1671
|
}
|
|
1672
|
+
upsertSelectedVmReconcileCursor(record) {
|
|
1673
|
+
this.stmt('upsertSelectedVmReconcileCursor', `
|
|
1674
|
+
INSERT INTO selected_vm_reconcile_cursors (
|
|
1675
|
+
deployment_id, context_graph_id, on_chain_context_graph_id,
|
|
1676
|
+
name_hash, watermark, updated_at
|
|
1677
|
+
) VALUES (
|
|
1678
|
+
@deployment_id, @context_graph_id, @on_chain_context_graph_id,
|
|
1679
|
+
@name_hash, @watermark, @updated_at
|
|
1680
|
+
)
|
|
1681
|
+
ON CONFLICT(deployment_id, context_graph_id, on_chain_context_graph_id) DO UPDATE SET
|
|
1682
|
+
name_hash = excluded.name_hash,
|
|
1683
|
+
watermark = excluded.watermark,
|
|
1684
|
+
updated_at = excluded.updated_at
|
|
1685
|
+
`).run(record);
|
|
1686
|
+
}
|
|
1687
|
+
getSelectedVmReconcileCursor(deploymentId, contextGraphId, onChainContextGraphId) {
|
|
1688
|
+
return this.stmt('getSelectedVmReconcileCursor', `
|
|
1689
|
+
SELECT * FROM selected_vm_reconcile_cursors
|
|
1690
|
+
WHERE deployment_id = ?
|
|
1691
|
+
AND context_graph_id = ?
|
|
1692
|
+
AND on_chain_context_graph_id = ?
|
|
1693
|
+
`).get(deploymentId, contextGraphId, onChainContextGraphId);
|
|
1694
|
+
}
|
|
1643
1695
|
// --- Phase F: chain-driven VM reconciliation telemetry ---
|
|
1644
1696
|
/**
|
|
1645
1697
|
* Persist one {@link ReplicationEvent} (best-effort sink target). `ordinal`,
|
|
@@ -2464,6 +2516,7 @@ export class DashboardDB {
|
|
|
2464
2516
|
module: entry.module,
|
|
2465
2517
|
message: entry.message,
|
|
2466
2518
|
});
|
|
2519
|
+
this.routineLogRetention.noteCommittedInsert(entry.level);
|
|
2467
2520
|
}
|
|
2468
2521
|
/**
|
|
2469
2522
|
* Backwards-compatible DB-backed log search. V15 deliberately removed
|
|
@@ -2687,11 +2740,12 @@ export class DashboardDB {
|
|
|
2687
2740
|
return rows.map((r) => r.id);
|
|
2688
2741
|
}
|
|
2689
2742
|
close() {
|
|
2743
|
+
if (!this.db.open)
|
|
2744
|
+
return;
|
|
2690
2745
|
this.db.close();
|
|
2691
2746
|
}
|
|
2692
2747
|
}
|
|
2693
2748
|
// --- Sync requester checkpoints (issue #1138 A3) ---
|
|
2694
|
-
const DEFAULT_SYNC_CHECKPOINT_TTL_MS = 24 * 60 * 60 * 1000;
|
|
2695
2749
|
export class SqliteSyncCheckpointStore {
|
|
2696
2750
|
db;
|
|
2697
2751
|
clock;
|
|
@@ -2701,73 +2755,128 @@ export class SqliteSyncCheckpointStore {
|
|
|
2701
2755
|
this.clock = options.clock ?? (() => Date.now());
|
|
2702
2756
|
this.ttlMs = options.ttlMs ?? DEFAULT_SYNC_CHECKPOINT_TTL_MS;
|
|
2703
2757
|
}
|
|
2704
|
-
|
|
2705
|
-
const now = this.clock();
|
|
2758
|
+
readRow(key) {
|
|
2706
2759
|
const row = this.db.prepare(`SELECT offset, updated_at, expires_at,
|
|
2707
|
-
responder_session_id, responder_session_expires_at
|
|
2760
|
+
responder_session_id, responder_session_expires_at, responder_session_offset,
|
|
2761
|
+
manifest_digest, manifest_prefix_digest, terminal
|
|
2708
2762
|
FROM sync_checkpoints WHERE key = ?`).get(key);
|
|
2709
2763
|
if (!row)
|
|
2710
2764
|
return undefined;
|
|
2711
|
-
if (row.expires_at < now) {
|
|
2712
|
-
this.delete(key);
|
|
2713
|
-
return undefined;
|
|
2714
|
-
}
|
|
2715
|
-
const hasFreshResponderSession = Boolean(row.responder_session_id)
|
|
2716
|
-
&& Number.isSafeInteger(row.responder_session_expires_at)
|
|
2717
|
-
&& (row.responder_session_expires_at ?? 0) > now;
|
|
2718
|
-
if (row.responder_session_id && !hasFreshResponderSession) {
|
|
2719
|
-
this.clearResponderSession(key);
|
|
2720
|
-
}
|
|
2721
2765
|
return {
|
|
2722
2766
|
offset: row.offset,
|
|
2723
2767
|
updatedAtMs: row.updated_at,
|
|
2724
2768
|
expiresAtMs: row.expires_at,
|
|
2725
|
-
...(
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
}
|
|
2769
|
+
...(row.terminal === 1 ? { terminal: true } : {}),
|
|
2770
|
+
...(row.manifest_digest ? { manifestDigest: row.manifest_digest } : {}),
|
|
2771
|
+
...(row.manifest_prefix_digest
|
|
2772
|
+
? { manifestPrefixDigest: row.manifest_prefix_digest }
|
|
2773
|
+
: {}),
|
|
2774
|
+
// Preserve each nullable session column independently so the shared
|
|
2775
|
+
// validator can distinguish an absent session from a torn/malformed
|
|
2776
|
+
// persisted session. Collapsing a partial row to no session fields would
|
|
2777
|
+
// turn corrupt durable state into an apparently valid ordinary offset.
|
|
2778
|
+
...(row.responder_session_id !== null
|
|
2779
|
+
? { responderSessionId: row.responder_session_id }
|
|
2780
|
+
: {}),
|
|
2781
|
+
...(row.responder_session_expires_at !== null
|
|
2782
|
+
? { responderSessionExpiresAtMs: row.responder_session_expires_at }
|
|
2783
|
+
: {}),
|
|
2784
|
+
...(row.responder_session_offset !== null
|
|
2785
|
+
? { responderSessionOffset: row.responder_session_offset }
|
|
2730
2786
|
: {}),
|
|
2731
2787
|
};
|
|
2732
2788
|
}
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2789
|
+
get(key, now = this.clock()) {
|
|
2790
|
+
const entry = this.readRow(key);
|
|
2791
|
+
if (!entry)
|
|
2792
|
+
return undefined;
|
|
2793
|
+
if (!isValidSyncCheckpointEntry(entry) || entry.expiresAtMs < now) {
|
|
2794
|
+
this.delete(key);
|
|
2795
|
+
return undefined;
|
|
2796
|
+
}
|
|
2797
|
+
if (entry.responderSessionId
|
|
2798
|
+
&& (entry.responderSessionExpiresAtMs ?? 0) <= now) {
|
|
2799
|
+
const withoutExpiredSession = withoutSyncCheckpointResponderSession(entry);
|
|
2800
|
+
this.writeEntry(key, withoutExpiredSession);
|
|
2801
|
+
return withoutExpiredSession;
|
|
2736
2802
|
}
|
|
2803
|
+
return entry;
|
|
2804
|
+
}
|
|
2805
|
+
writeEntry(key, entry) {
|
|
2737
2806
|
this.db.prepare(`
|
|
2738
|
-
INSERT INTO sync_checkpoints (
|
|
2739
|
-
|
|
2807
|
+
INSERT INTO sync_checkpoints (
|
|
2808
|
+
key, offset, updated_at, expires_at,
|
|
2809
|
+
responder_session_id, responder_session_expires_at, responder_session_offset,
|
|
2810
|
+
manifest_digest, manifest_prefix_digest, terminal
|
|
2811
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2740
2812
|
ON CONFLICT(key) DO UPDATE SET
|
|
2741
2813
|
offset = excluded.offset,
|
|
2742
2814
|
updated_at = excluded.updated_at,
|
|
2743
|
-
expires_at = excluded.expires_at
|
|
2744
|
-
|
|
2815
|
+
expires_at = excluded.expires_at,
|
|
2816
|
+
responder_session_id = excluded.responder_session_id,
|
|
2817
|
+
responder_session_expires_at = excluded.responder_session_expires_at,
|
|
2818
|
+
responder_session_offset = excluded.responder_session_offset,
|
|
2819
|
+
manifest_digest = excluded.manifest_digest,
|
|
2820
|
+
manifest_prefix_digest = excluded.manifest_prefix_digest,
|
|
2821
|
+
terminal = excluded.terminal
|
|
2822
|
+
`).run(key, entry.offset, entry.updatedAtMs, entry.expiresAtMs, entry.responderSessionId ?? null, entry.responderSessionExpiresAtMs ?? null, entry.responderSessionOffset ?? null, entry.manifestDigest ?? null, entry.manifestPrefixDigest ?? null, entry.terminal ? 1 : 0);
|
|
2823
|
+
}
|
|
2824
|
+
set(key, value, nowMs = this.clock(), responderSessionOffset) {
|
|
2825
|
+
const transition = this.db.transaction(() => {
|
|
2826
|
+
this.writeEntry(key, transitionSyncCheckpointOffset({
|
|
2827
|
+
key,
|
|
2828
|
+
existing: this.get(key, nowMs),
|
|
2829
|
+
value,
|
|
2830
|
+
nowMs,
|
|
2831
|
+
ttlMs: this.ttlMs,
|
|
2832
|
+
responderSessionOffset,
|
|
2833
|
+
}));
|
|
2834
|
+
});
|
|
2835
|
+
transition();
|
|
2745
2836
|
}
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2837
|
+
setManifestBoundOffset(key, value, manifestDigest, nowMs = this.clock(), manifestPrefixDigest, terminal = false, responderSessionOffset) {
|
|
2838
|
+
const transition = this.db.transaction(() => {
|
|
2839
|
+
this.writeEntry(key, transitionSyncCheckpointManifestOffset({
|
|
2840
|
+
key,
|
|
2841
|
+
existing: this.get(key, nowMs),
|
|
2842
|
+
value,
|
|
2843
|
+
manifestDigest,
|
|
2844
|
+
nowMs,
|
|
2845
|
+
ttlMs: this.ttlMs,
|
|
2846
|
+
manifestPrefixDigest,
|
|
2847
|
+
terminal,
|
|
2848
|
+
responderSessionOffset,
|
|
2849
|
+
}));
|
|
2850
|
+
});
|
|
2851
|
+
transition();
|
|
2852
|
+
}
|
|
2853
|
+
setResponderSession(key, sessionId, expiresAtMs, nowMs = this.clock(), manifestDigest, manifestPrefixDigest, responderSessionOffset) {
|
|
2750
2854
|
if (expiresAtMs <= nowMs) {
|
|
2751
2855
|
this.clearResponderSession(key);
|
|
2752
2856
|
return;
|
|
2753
2857
|
}
|
|
2754
|
-
this.db.
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2858
|
+
const transition = this.db.transaction(() => {
|
|
2859
|
+
this.writeEntry(key, transitionSyncCheckpointResponderSession({
|
|
2860
|
+
key,
|
|
2861
|
+
existing: this.get(key, nowMs),
|
|
2862
|
+
sessionId,
|
|
2863
|
+
expiresAtMs,
|
|
2864
|
+
nowMs,
|
|
2865
|
+
ttlMs: this.ttlMs,
|
|
2866
|
+
manifestDigest,
|
|
2867
|
+
manifestPrefixDigest,
|
|
2868
|
+
responderSessionOffset,
|
|
2869
|
+
}));
|
|
2870
|
+
});
|
|
2871
|
+
transition();
|
|
2763
2872
|
}
|
|
2764
2873
|
clearResponderSession(key) {
|
|
2765
|
-
this.db.
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2874
|
+
const transition = this.db.transaction(() => {
|
|
2875
|
+
const existing = this.readRow(key);
|
|
2876
|
+
if (existing)
|
|
2877
|
+
this.writeEntry(key, withoutSyncCheckpointResponderSession(existing));
|
|
2878
|
+
});
|
|
2879
|
+
transition();
|
|
2771
2880
|
}
|
|
2772
2881
|
delete(key) {
|
|
2773
2882
|
this.db.prepare(`DELETE FROM sync_checkpoints WHERE key = ?`).run(key);
|