@origintrail-official/dkg-node-ui 10.0.7 → 10.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -201
- package/README.md +50 -50
- package/dist/chain-cursor-stores.js +6 -6
- package/dist/chat-memory.js +209 -209
- package/dist/db.d.ts +4 -0
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +984 -921
- package/dist/db.js.map +1 -1
- package/dist-ui/assets/{3d-force-graph-oy_vn0Oy.js → 3d-force-graph-BOxtBjdk.js} +1 -1
- package/dist-ui/assets/{AgentHub-BdodC7bc.js → AgentHub-BwBMYiaM.js} +1 -1
- package/dist-ui/assets/{AgentProfilePage-DsjmgCNC.js → AgentProfilePage-Bufz0Md_.js} +1 -1
- package/dist-ui/assets/{ApproveWalletsModal-CoK9PC14.js → ApproveWalletsModal-B8bQTpiR.js} +3 -3
- package/dist-ui/assets/{ConvictionDetailView-DnpWB1CW.js → ConvictionDetailView-CiwcbT2W.js} +1 -1
- package/dist-ui/assets/{Network-B1hFeJc_.js → Network-C8AvmIfL.js} +1 -1
- package/dist-ui/assets/{OnChainProvenanceCard-5HtdkdH8.js → OnChainProvenanceCard-Dga51ZJL.js} +1 -1
- package/dist-ui/assets/{Operations-CD56ktgV.js → Operations-BhRo3Qzg.js} +1 -1
- package/dist-ui/assets/{PublishingConviction-CsGM0rzZ.js → PublishingConviction-DzvXFq9z.js} +1 -1
- package/dist-ui/assets/{Settings-FtB9ej6P.js → Settings-CwO6V6er.js} +1 -1
- package/dist-ui/assets/{ccip-C3eQxfL2.js → ccip-BxPhmAn8.js} +1 -1
- package/dist-ui/assets/{index-zKvsH-2o.js → index-BTD4QwAm.js} +3 -3
- package/dist-ui/assets/{index-B0ozkBOt.js → index-Btc8omeP.js} +944 -944
- package/dist-ui/assets/{jsonld-32FQRO67-Ckj0IC7a.js → jsonld-B67IUHNU-Xa0S85WB.js} +2 -2
- package/dist-ui/assets/{jsonld-D76K907R.js → jsonld-DzfKKFxR.js} +1 -1
- package/dist-ui/assets/{renderer-3d-2EVDZII7-C0_bWCJC.js → renderer-3d-OSESAPET-xkehb8k9.js} +2 -2
- package/dist-ui/assets/{shikiHighlighter-FYa041eg.js → shikiHighlighter-DRJC8bc5.js} +1 -1
- package/dist-ui/index.html +13 -13
- package/package.json +3 -3
- /package/dist-ui/assets/{ntriples-ZWBY2WET-DH7OofPp.js → ntriples-SF2LFNMC-DH7OofPp.js} +0 -0
- /package/dist-ui/assets/{turtle-JJPK7LJ5-BzTu0283.js → turtle-BXN4L2BY-BzTu0283.js} +0 -0
package/dist/db.js
CHANGED
|
@@ -2,7 +2,7 @@ import Database from 'better-sqlite3';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { RESPONSE_CACHE_BYTES, parseContextGraphJoinPolicyRecord, } from '@origintrail-official/dkg-core';
|
|
4
4
|
export { SqliteChainEventCursorStore, SqliteContextGraphRegistryScanCursorStore, } from './chain-cursor-stores.js';
|
|
5
|
-
const SCHEMA_VERSION =
|
|
5
|
+
const SCHEMA_VERSION = 30;
|
|
6
6
|
// Default operator retention. Lowered from 90 → 14 days on V15 (2026-05) after
|
|
7
7
|
// a production incident in which the `logs` table + its FTS5 shadow tables
|
|
8
8
|
// grew to ~9 GB on a 12-day-old node and corrupted the SQLite page (header
|
|
@@ -138,161 +138,178 @@ export class DashboardDB {
|
|
|
138
138
|
migrate() {
|
|
139
139
|
const version = this.db.pragma('user_version', { simple: true });
|
|
140
140
|
const upgradedExistingDb = version > 0 && version < SCHEMA_VERSION;
|
|
141
|
-
const ensureJoinPolicyAuditCapTrigger = () => this.db.exec(`
|
|
142
|
-
CREATE TRIGGER IF NOT EXISTS cap_cg_join_policy_audit_rows
|
|
143
|
-
AFTER INSERT ON context_graph_join_policy_audit
|
|
144
|
-
BEGIN
|
|
145
|
-
DELETE FROM context_graph_join_policy_audit
|
|
146
|
-
WHERE id <= NEW.id - 100000
|
|
147
|
-
AND event_type NOT IN (
|
|
148
|
-
'join_admission_committed',
|
|
149
|
-
'join_policy_changed'
|
|
150
|
-
);
|
|
151
|
-
END;
|
|
141
|
+
const ensureJoinPolicyAuditCapTrigger = () => this.db.exec(`
|
|
142
|
+
CREATE TRIGGER IF NOT EXISTS cap_cg_join_policy_audit_rows
|
|
143
|
+
AFTER INSERT ON context_graph_join_policy_audit
|
|
144
|
+
BEGIN
|
|
145
|
+
DELETE FROM context_graph_join_policy_audit
|
|
146
|
+
WHERE id <= NEW.id - 100000
|
|
147
|
+
AND event_type NOT IN (
|
|
148
|
+
'join_admission_committed',
|
|
149
|
+
'join_policy_changed'
|
|
150
|
+
);
|
|
151
|
+
END;
|
|
152
152
|
`);
|
|
153
153
|
const ensureJoinApprovalRepairMarker = () => {
|
|
154
|
-
const table = this.db.prepare(`
|
|
155
|
-
SELECT 1 AS found FROM sqlite_master
|
|
156
|
-
WHERE type = 'table' AND name = 'context_graph_join_approval_ledger'
|
|
154
|
+
const table = this.db.prepare(`
|
|
155
|
+
SELECT 1 AS found FROM sqlite_master
|
|
156
|
+
WHERE type = 'table' AND name = 'context_graph_join_approval_ledger'
|
|
157
157
|
`).get();
|
|
158
158
|
if (!table)
|
|
159
159
|
return;
|
|
160
160
|
const columns = this.db.pragma('table_info(context_graph_join_approval_ledger)');
|
|
161
161
|
if (!columns.some((column) => column.name === 'repair_pending')) {
|
|
162
|
-
this.db.exec(`
|
|
163
|
-
ALTER TABLE context_graph_join_approval_ledger
|
|
164
|
-
ADD COLUMN repair_pending INTEGER NOT NULL DEFAULT 0
|
|
165
|
-
CHECK (repair_pending IN (0, 1));
|
|
162
|
+
this.db.exec(`
|
|
163
|
+
ALTER TABLE context_graph_join_approval_ledger
|
|
164
|
+
ADD COLUMN repair_pending INTEGER NOT NULL DEFAULT 0
|
|
165
|
+
CHECK (repair_pending IN (0, 1));
|
|
166
166
|
`);
|
|
167
167
|
}
|
|
168
|
-
this.db.exec(`
|
|
169
|
-
CREATE INDEX IF NOT EXISTS idx_cg_join_approval_ledger_repair
|
|
170
|
-
ON context_graph_join_approval_ledger(
|
|
171
|
-
context_graph_id, request_digest, repair_pending, reserved_at DESC
|
|
172
|
-
);
|
|
168
|
+
this.db.exec(`
|
|
169
|
+
CREATE INDEX IF NOT EXISTS idx_cg_join_approval_ledger_repair
|
|
170
|
+
ON context_graph_join_approval_ledger(
|
|
171
|
+
context_graph_id, request_digest, repair_pending, reserved_at DESC
|
|
172
|
+
);
|
|
173
173
|
`);
|
|
174
174
|
};
|
|
175
|
+
const ensureSyncCheckpointResponderSessionColumns = () => {
|
|
176
|
+
const table = this.db.prepare(`
|
|
177
|
+
SELECT 1 AS found FROM sqlite_master
|
|
178
|
+
WHERE type = 'table' AND name = 'sync_checkpoints'
|
|
179
|
+
`).get();
|
|
180
|
+
if (!table)
|
|
181
|
+
return;
|
|
182
|
+
const columns = new Set(this.db.prepare('PRAGMA table_info(sync_checkpoints)').all()
|
|
183
|
+
.map((column) => column.name));
|
|
184
|
+
if (!columns.has('responder_session_id')) {
|
|
185
|
+
this.db.exec(`ALTER TABLE sync_checkpoints ADD COLUMN responder_session_id TEXT;`);
|
|
186
|
+
}
|
|
187
|
+
if (!columns.has('responder_session_expires_at')) {
|
|
188
|
+
this.db.exec(`ALTER TABLE sync_checkpoints ADD COLUMN responder_session_expires_at INTEGER;`);
|
|
189
|
+
}
|
|
190
|
+
};
|
|
175
191
|
if (version > SCHEMA_VERSION)
|
|
176
192
|
return;
|
|
177
193
|
if (version === SCHEMA_VERSION) {
|
|
178
194
|
// Repair restored/development databases that carry the current version
|
|
179
195
|
// but lost an idempotent schema adjunct.
|
|
180
196
|
ensureJoinApprovalRepairMarker();
|
|
197
|
+
ensureSyncCheckpointResponderSessionColumns();
|
|
181
198
|
ensureJoinPolicyAuditCapTrigger();
|
|
182
199
|
return;
|
|
183
200
|
}
|
|
184
201
|
if (version < 1) {
|
|
185
|
-
this.db.exec(`
|
|
186
|
-
CREATE TABLE IF NOT EXISTS metric_snapshots (
|
|
187
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
188
|
-
ts INTEGER NOT NULL,
|
|
189
|
-
cpu_percent REAL,
|
|
190
|
-
mem_used_bytes INTEGER,
|
|
191
|
-
mem_total_bytes INTEGER,
|
|
192
|
-
disk_used_bytes INTEGER,
|
|
193
|
-
disk_total_bytes INTEGER,
|
|
194
|
-
heap_used_bytes INTEGER,
|
|
195
|
-
uptime_seconds INTEGER,
|
|
196
|
-
peer_count INTEGER,
|
|
197
|
-
direct_peers INTEGER,
|
|
198
|
-
relayed_peers INTEGER,
|
|
199
|
-
mesh_peers INTEGER,
|
|
200
|
-
contextGraph_count INTEGER,
|
|
201
|
-
total_triples INTEGER,
|
|
202
|
-
total_kcs INTEGER,
|
|
203
|
-
total_kas INTEGER,
|
|
204
|
-
store_bytes INTEGER,
|
|
205
|
-
confirmed_kcs INTEGER,
|
|
206
|
-
tentative_kcs INTEGER,
|
|
207
|
-
rpc_latency_ms INTEGER,
|
|
208
|
-
rpc_healthy INTEGER,
|
|
209
|
-
relay_capacity INTEGER,
|
|
210
|
-
relay_reservation_count INTEGER,
|
|
211
|
-
relay_active_circuits INTEGER,
|
|
212
|
-
relay_bytes_in INTEGER,
|
|
213
|
-
relay_bytes_out INTEGER
|
|
214
|
-
);
|
|
215
|
-
CREATE INDEX IF NOT EXISTS idx_snapshots_ts ON metric_snapshots(ts);
|
|
216
|
-
|
|
217
|
-
CREATE TABLE IF NOT EXISTS operations (
|
|
218
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
219
|
-
operation_id TEXT NOT NULL,
|
|
220
|
-
operation_name TEXT NOT NULL,
|
|
221
|
-
started_at INTEGER NOT NULL,
|
|
222
|
-
duration_ms INTEGER,
|
|
223
|
-
status TEXT DEFAULT 'in_progress',
|
|
224
|
-
peer_id TEXT,
|
|
225
|
-
contextGraph_id TEXT,
|
|
226
|
-
triple_count INTEGER,
|
|
227
|
-
error_message TEXT,
|
|
228
|
-
details TEXT
|
|
229
|
-
);
|
|
230
|
-
CREATE INDEX IF NOT EXISTS idx_ops_operation_id ON operations(operation_id);
|
|
231
|
-
CREATE INDEX IF NOT EXISTS idx_ops_started_at ON operations(started_at);
|
|
232
|
-
CREATE INDEX IF NOT EXISTS idx_ops_name ON operations(operation_name);
|
|
233
|
-
|
|
234
|
-
CREATE TABLE IF NOT EXISTS logs (
|
|
235
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
236
|
-
ts INTEGER NOT NULL,
|
|
237
|
-
level TEXT NOT NULL,
|
|
238
|
-
operation_name TEXT,
|
|
239
|
-
operation_id TEXT,
|
|
240
|
-
module TEXT,
|
|
241
|
-
message TEXT NOT NULL
|
|
242
|
-
);
|
|
243
|
-
CREATE INDEX IF NOT EXISTS idx_logs_ts ON logs(ts);
|
|
244
|
-
CREATE INDEX IF NOT EXISTS idx_logs_operation_id ON logs(operation_id);
|
|
245
|
-
CREATE INDEX IF NOT EXISTS idx_logs_level ON logs(level);
|
|
246
|
-
|
|
247
|
-
-- NOTE: Earlier schema versions (V1..V14) also created an FTS5
|
|
248
|
-
-- virtual table "logs_fts" plus AFTER INSERT/DELETE triggers to
|
|
249
|
-
-- keep it in sync with "logs". That fed a free-text search path
|
|
250
|
-
-- behind /api/logs?q=... The FTS5 index turned out to dominate
|
|
251
|
-
-- on-disk size (multi-GB shadow tables on long-lived nodes) and
|
|
252
|
-
-- the corresponding HTTP route had no production consumer
|
|
253
|
-
-- (the dashboard log viewer is file-backed via /api/node-log).
|
|
254
|
-
-- V15 drops it for fresh installs; the migration below cleans
|
|
255
|
-
-- it up for in-place upgrades.
|
|
256
|
-
|
|
257
|
-
CREATE TABLE IF NOT EXISTS query_history (
|
|
258
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
259
|
-
ts INTEGER NOT NULL,
|
|
260
|
-
sparql TEXT NOT NULL,
|
|
261
|
-
duration_ms INTEGER,
|
|
262
|
-
result_count INTEGER,
|
|
263
|
-
error TEXT
|
|
264
|
-
);
|
|
265
|
-
CREATE INDEX IF NOT EXISTS idx_qhist_ts ON query_history(ts);
|
|
266
|
-
|
|
267
|
-
CREATE TABLE IF NOT EXISTS saved_queries (
|
|
268
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
269
|
-
name TEXT NOT NULL,
|
|
270
|
-
description TEXT,
|
|
271
|
-
sparql TEXT NOT NULL,
|
|
272
|
-
created_at INTEGER NOT NULL,
|
|
273
|
-
updated_at INTEGER NOT NULL
|
|
274
|
-
);
|
|
202
|
+
this.db.exec(`
|
|
203
|
+
CREATE TABLE IF NOT EXISTS metric_snapshots (
|
|
204
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
205
|
+
ts INTEGER NOT NULL,
|
|
206
|
+
cpu_percent REAL,
|
|
207
|
+
mem_used_bytes INTEGER,
|
|
208
|
+
mem_total_bytes INTEGER,
|
|
209
|
+
disk_used_bytes INTEGER,
|
|
210
|
+
disk_total_bytes INTEGER,
|
|
211
|
+
heap_used_bytes INTEGER,
|
|
212
|
+
uptime_seconds INTEGER,
|
|
213
|
+
peer_count INTEGER,
|
|
214
|
+
direct_peers INTEGER,
|
|
215
|
+
relayed_peers INTEGER,
|
|
216
|
+
mesh_peers INTEGER,
|
|
217
|
+
contextGraph_count INTEGER,
|
|
218
|
+
total_triples INTEGER,
|
|
219
|
+
total_kcs INTEGER,
|
|
220
|
+
total_kas INTEGER,
|
|
221
|
+
store_bytes INTEGER,
|
|
222
|
+
confirmed_kcs INTEGER,
|
|
223
|
+
tentative_kcs INTEGER,
|
|
224
|
+
rpc_latency_ms INTEGER,
|
|
225
|
+
rpc_healthy INTEGER,
|
|
226
|
+
relay_capacity INTEGER,
|
|
227
|
+
relay_reservation_count INTEGER,
|
|
228
|
+
relay_active_circuits INTEGER,
|
|
229
|
+
relay_bytes_in INTEGER,
|
|
230
|
+
relay_bytes_out INTEGER
|
|
231
|
+
);
|
|
232
|
+
CREATE INDEX IF NOT EXISTS idx_snapshots_ts ON metric_snapshots(ts);
|
|
233
|
+
|
|
234
|
+
CREATE TABLE IF NOT EXISTS operations (
|
|
235
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
236
|
+
operation_id TEXT NOT NULL,
|
|
237
|
+
operation_name TEXT NOT NULL,
|
|
238
|
+
started_at INTEGER NOT NULL,
|
|
239
|
+
duration_ms INTEGER,
|
|
240
|
+
status TEXT DEFAULT 'in_progress',
|
|
241
|
+
peer_id TEXT,
|
|
242
|
+
contextGraph_id TEXT,
|
|
243
|
+
triple_count INTEGER,
|
|
244
|
+
error_message TEXT,
|
|
245
|
+
details TEXT
|
|
246
|
+
);
|
|
247
|
+
CREATE INDEX IF NOT EXISTS idx_ops_operation_id ON operations(operation_id);
|
|
248
|
+
CREATE INDEX IF NOT EXISTS idx_ops_started_at ON operations(started_at);
|
|
249
|
+
CREATE INDEX IF NOT EXISTS idx_ops_name ON operations(operation_name);
|
|
250
|
+
|
|
251
|
+
CREATE TABLE IF NOT EXISTS logs (
|
|
252
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
253
|
+
ts INTEGER NOT NULL,
|
|
254
|
+
level TEXT NOT NULL,
|
|
255
|
+
operation_name TEXT,
|
|
256
|
+
operation_id TEXT,
|
|
257
|
+
module TEXT,
|
|
258
|
+
message TEXT NOT NULL
|
|
259
|
+
);
|
|
260
|
+
CREATE INDEX IF NOT EXISTS idx_logs_ts ON logs(ts);
|
|
261
|
+
CREATE INDEX IF NOT EXISTS idx_logs_operation_id ON logs(operation_id);
|
|
262
|
+
CREATE INDEX IF NOT EXISTS idx_logs_level ON logs(level);
|
|
263
|
+
|
|
264
|
+
-- NOTE: Earlier schema versions (V1..V14) also created an FTS5
|
|
265
|
+
-- virtual table "logs_fts" plus AFTER INSERT/DELETE triggers to
|
|
266
|
+
-- keep it in sync with "logs". That fed a free-text search path
|
|
267
|
+
-- behind /api/logs?q=... The FTS5 index turned out to dominate
|
|
268
|
+
-- on-disk size (multi-GB shadow tables on long-lived nodes) and
|
|
269
|
+
-- the corresponding HTTP route had no production consumer
|
|
270
|
+
-- (the dashboard log viewer is file-backed via /api/node-log).
|
|
271
|
+
-- V15 drops it for fresh installs; the migration below cleans
|
|
272
|
+
-- it up for in-place upgrades.
|
|
273
|
+
|
|
274
|
+
CREATE TABLE IF NOT EXISTS query_history (
|
|
275
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
276
|
+
ts INTEGER NOT NULL,
|
|
277
|
+
sparql TEXT NOT NULL,
|
|
278
|
+
duration_ms INTEGER,
|
|
279
|
+
result_count INTEGER,
|
|
280
|
+
error TEXT
|
|
281
|
+
);
|
|
282
|
+
CREATE INDEX IF NOT EXISTS idx_qhist_ts ON query_history(ts);
|
|
283
|
+
|
|
284
|
+
CREATE TABLE IF NOT EXISTS saved_queries (
|
|
285
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
286
|
+
name TEXT NOT NULL,
|
|
287
|
+
description TEXT,
|
|
288
|
+
sparql TEXT NOT NULL,
|
|
289
|
+
created_at INTEGER NOT NULL,
|
|
290
|
+
updated_at INTEGER NOT NULL
|
|
291
|
+
);
|
|
275
292
|
`);
|
|
276
293
|
}
|
|
277
294
|
if (version < 2) {
|
|
278
|
-
this.db.exec(`
|
|
279
|
-
CREATE TABLE IF NOT EXISTS operation_phases (
|
|
280
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
281
|
-
operation_id TEXT NOT NULL,
|
|
282
|
-
phase TEXT NOT NULL,
|
|
283
|
-
started_at INTEGER NOT NULL,
|
|
284
|
-
duration_ms INTEGER,
|
|
285
|
-
status TEXT DEFAULT 'in_progress',
|
|
286
|
-
details TEXT
|
|
287
|
-
);
|
|
288
|
-
CREATE INDEX IF NOT EXISTS idx_phases_op ON operation_phases(operation_id);
|
|
289
|
-
|
|
290
|
-
ALTER TABLE operations ADD COLUMN gas_used INTEGER;
|
|
291
|
-
ALTER TABLE operations ADD COLUMN gas_price_gwei REAL;
|
|
292
|
-
ALTER TABLE operations ADD COLUMN gas_cost_eth REAL;
|
|
293
|
-
ALTER TABLE operations ADD COLUMN trac_cost REAL;
|
|
294
|
-
ALTER TABLE operations ADD COLUMN tx_hash TEXT;
|
|
295
|
-
ALTER TABLE operations ADD COLUMN chain_id INTEGER;
|
|
295
|
+
this.db.exec(`
|
|
296
|
+
CREATE TABLE IF NOT EXISTS operation_phases (
|
|
297
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
298
|
+
operation_id TEXT NOT NULL,
|
|
299
|
+
phase TEXT NOT NULL,
|
|
300
|
+
started_at INTEGER NOT NULL,
|
|
301
|
+
duration_ms INTEGER,
|
|
302
|
+
status TEXT DEFAULT 'in_progress',
|
|
303
|
+
details TEXT
|
|
304
|
+
);
|
|
305
|
+
CREATE INDEX IF NOT EXISTS idx_phases_op ON operation_phases(operation_id);
|
|
306
|
+
|
|
307
|
+
ALTER TABLE operations ADD COLUMN gas_used INTEGER;
|
|
308
|
+
ALTER TABLE operations ADD COLUMN gas_price_gwei REAL;
|
|
309
|
+
ALTER TABLE operations ADD COLUMN gas_cost_eth REAL;
|
|
310
|
+
ALTER TABLE operations ADD COLUMN trac_cost REAL;
|
|
311
|
+
ALTER TABLE operations ADD COLUMN tx_hash TEXT;
|
|
312
|
+
ALTER TABLE operations ADD COLUMN chain_id INTEGER;
|
|
296
313
|
`);
|
|
297
314
|
}
|
|
298
315
|
if (version < 3) {
|
|
@@ -302,42 +319,42 @@ export class DashboardDB {
|
|
|
302
319
|
// column + the partial unique index in one step; the V11 block
|
|
303
320
|
// below idempotently adds the column to nodes that upgraded
|
|
304
321
|
// through versions 3..10 first.
|
|
305
|
-
this.db.exec(`
|
|
306
|
-
CREATE TABLE IF NOT EXISTS chat_messages (
|
|
307
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
308
|
-
ts INTEGER NOT NULL,
|
|
309
|
-
direction TEXT NOT NULL,
|
|
310
|
-
peer TEXT NOT NULL,
|
|
311
|
-
peer_name TEXT,
|
|
312
|
-
text TEXT NOT NULL,
|
|
313
|
-
delivered INTEGER,
|
|
314
|
-
message_id TEXT
|
|
315
|
-
);
|
|
316
|
-
CREATE INDEX IF NOT EXISTS idx_chat_ts ON chat_messages(ts);
|
|
317
|
-
CREATE INDEX IF NOT EXISTS idx_chat_peer ON chat_messages(peer);
|
|
322
|
+
this.db.exec(`
|
|
323
|
+
CREATE TABLE IF NOT EXISTS chat_messages (
|
|
324
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
325
|
+
ts INTEGER NOT NULL,
|
|
326
|
+
direction TEXT NOT NULL,
|
|
327
|
+
peer TEXT NOT NULL,
|
|
328
|
+
peer_name TEXT,
|
|
329
|
+
text TEXT NOT NULL,
|
|
330
|
+
delivered INTEGER,
|
|
331
|
+
message_id TEXT
|
|
332
|
+
);
|
|
333
|
+
CREATE INDEX IF NOT EXISTS idx_chat_ts ON chat_messages(ts);
|
|
334
|
+
CREATE INDEX IF NOT EXISTS idx_chat_peer ON chat_messages(peer);
|
|
318
335
|
`);
|
|
319
336
|
}
|
|
320
337
|
if (version < 4) {
|
|
321
|
-
this.db.exec(`
|
|
322
|
-
CREATE TABLE IF NOT EXISTS chat_persistence_jobs (
|
|
323
|
-
turn_id TEXT PRIMARY KEY,
|
|
324
|
-
session_id TEXT NOT NULL,
|
|
325
|
-
user_message TEXT NOT NULL,
|
|
326
|
-
assistant_reply TEXT NOT NULL,
|
|
327
|
-
tool_calls_json TEXT,
|
|
328
|
-
status TEXT NOT NULL,
|
|
329
|
-
attempts INTEGER NOT NULL DEFAULT 0,
|
|
330
|
-
max_attempts INTEGER NOT NULL DEFAULT 3,
|
|
331
|
-
next_attempt_at INTEGER NOT NULL,
|
|
332
|
-
queued_at INTEGER NOT NULL,
|
|
333
|
-
updated_at INTEGER NOT NULL,
|
|
334
|
-
store_ms INTEGER,
|
|
335
|
-
error_message TEXT
|
|
336
|
-
);
|
|
337
|
-
CREATE INDEX IF NOT EXISTS idx_chat_persist_status_next
|
|
338
|
-
ON chat_persistence_jobs(status, next_attempt_at);
|
|
339
|
-
CREATE INDEX IF NOT EXISTS idx_chat_persist_session
|
|
340
|
-
ON chat_persistence_jobs(session_id);
|
|
338
|
+
this.db.exec(`
|
|
339
|
+
CREATE TABLE IF NOT EXISTS chat_persistence_jobs (
|
|
340
|
+
turn_id TEXT PRIMARY KEY,
|
|
341
|
+
session_id TEXT NOT NULL,
|
|
342
|
+
user_message TEXT NOT NULL,
|
|
343
|
+
assistant_reply TEXT NOT NULL,
|
|
344
|
+
tool_calls_json TEXT,
|
|
345
|
+
status TEXT NOT NULL,
|
|
346
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
347
|
+
max_attempts INTEGER NOT NULL DEFAULT 3,
|
|
348
|
+
next_attempt_at INTEGER NOT NULL,
|
|
349
|
+
queued_at INTEGER NOT NULL,
|
|
350
|
+
updated_at INTEGER NOT NULL,
|
|
351
|
+
store_ms INTEGER,
|
|
352
|
+
error_message TEXT
|
|
353
|
+
);
|
|
354
|
+
CREATE INDEX IF NOT EXISTS idx_chat_persist_status_next
|
|
355
|
+
ON chat_persistence_jobs(status, next_attempt_at);
|
|
356
|
+
CREATE INDEX IF NOT EXISTS idx_chat_persist_session
|
|
357
|
+
ON chat_persistence_jobs(session_id);
|
|
341
358
|
`);
|
|
342
359
|
}
|
|
343
360
|
if (version < 5) {
|
|
@@ -346,77 +363,77 @@ export class DashboardDB {
|
|
|
346
363
|
// fresh-install CREATE so new nodes get the column + its index in one
|
|
347
364
|
// step; the V16 block below idempotently adds it to nodes that upgraded
|
|
348
365
|
// through versions 5..15 first (same pattern as `message_id` in V3/V11).
|
|
349
|
-
this.db.exec(`
|
|
350
|
-
CREATE TABLE IF NOT EXISTS notifications (
|
|
351
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
352
|
-
ts INTEGER NOT NULL,
|
|
353
|
-
type TEXT NOT NULL,
|
|
354
|
-
title TEXT NOT NULL,
|
|
355
|
-
message TEXT NOT NULL,
|
|
356
|
-
source TEXT,
|
|
357
|
-
peer TEXT,
|
|
358
|
-
read INTEGER NOT NULL DEFAULT 0,
|
|
359
|
-
meta TEXT,
|
|
360
|
-
context_graph_id TEXT
|
|
361
|
-
);
|
|
362
|
-
CREATE INDEX IF NOT EXISTS idx_notif_ts ON notifications(ts);
|
|
363
|
-
CREATE INDEX IF NOT EXISTS idx_notif_read ON notifications(read);
|
|
364
|
-
CREATE INDEX IF NOT EXISTS idx_notif_cg ON notifications(context_graph_id);
|
|
366
|
+
this.db.exec(`
|
|
367
|
+
CREATE TABLE IF NOT EXISTS notifications (
|
|
368
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
369
|
+
ts INTEGER NOT NULL,
|
|
370
|
+
type TEXT NOT NULL,
|
|
371
|
+
title TEXT NOT NULL,
|
|
372
|
+
message TEXT NOT NULL,
|
|
373
|
+
source TEXT,
|
|
374
|
+
peer TEXT,
|
|
375
|
+
read INTEGER NOT NULL DEFAULT 0,
|
|
376
|
+
meta TEXT,
|
|
377
|
+
context_graph_id TEXT
|
|
378
|
+
);
|
|
379
|
+
CREATE INDEX IF NOT EXISTS idx_notif_ts ON notifications(ts);
|
|
380
|
+
CREATE INDEX IF NOT EXISTS idx_notif_read ON notifications(read);
|
|
381
|
+
CREATE INDEX IF NOT EXISTS idx_notif_cg ON notifications(context_graph_id);
|
|
365
382
|
`);
|
|
366
383
|
}
|
|
367
384
|
if (version < 6) {
|
|
368
|
-
this.db.exec(`
|
|
369
|
-
CREATE TABLE IF NOT EXISTS settings (
|
|
370
|
-
key TEXT PRIMARY KEY,
|
|
371
|
-
value TEXT NOT NULL
|
|
372
|
-
);
|
|
385
|
+
this.db.exec(`
|
|
386
|
+
CREATE TABLE IF NOT EXISTS settings (
|
|
387
|
+
key TEXT PRIMARY KEY,
|
|
388
|
+
value TEXT NOT NULL
|
|
389
|
+
);
|
|
373
390
|
`);
|
|
374
391
|
}
|
|
375
392
|
if (version < 7) {
|
|
376
|
-
this.db.exec(`
|
|
377
|
-
CREATE TABLE IF NOT EXISTS context_graph_subscriptions (
|
|
378
|
-
context_graph_id TEXT PRIMARY KEY,
|
|
379
|
-
name TEXT,
|
|
380
|
-
subscribed INTEGER NOT NULL,
|
|
381
|
-
synced INTEGER NOT NULL,
|
|
382
|
-
meta_synced INTEGER,
|
|
383
|
-
on_chain_id TEXT,
|
|
384
|
-
sync_scoped INTEGER NOT NULL DEFAULT 1,
|
|
385
|
-
updated_at INTEGER NOT NULL
|
|
386
|
-
);
|
|
387
|
-
CREATE INDEX IF NOT EXISTS idx_cg_subs_sync_scoped
|
|
388
|
-
ON context_graph_subscriptions(sync_scoped);
|
|
393
|
+
this.db.exec(`
|
|
394
|
+
CREATE TABLE IF NOT EXISTS context_graph_subscriptions (
|
|
395
|
+
context_graph_id TEXT PRIMARY KEY,
|
|
396
|
+
name TEXT,
|
|
397
|
+
subscribed INTEGER NOT NULL,
|
|
398
|
+
synced INTEGER NOT NULL,
|
|
399
|
+
meta_synced INTEGER,
|
|
400
|
+
on_chain_id TEXT,
|
|
401
|
+
sync_scoped INTEGER NOT NULL DEFAULT 1,
|
|
402
|
+
updated_at INTEGER NOT NULL
|
|
403
|
+
);
|
|
404
|
+
CREATE INDEX IF NOT EXISTS idx_cg_subs_sync_scoped
|
|
405
|
+
ON context_graph_subscriptions(sync_scoped);
|
|
389
406
|
`);
|
|
390
407
|
}
|
|
391
408
|
if (version < 8) {
|
|
392
|
-
this.db.exec(`
|
|
393
|
-
CREATE TABLE IF NOT EXISTS context_graph_memberships (
|
|
394
|
-
context_graph_id TEXT NOT NULL,
|
|
395
|
-
principal_type TEXT NOT NULL,
|
|
396
|
-
principal_id TEXT NOT NULL,
|
|
397
|
-
role TEXT,
|
|
398
|
-
status TEXT NOT NULL,
|
|
399
|
-
source TEXT,
|
|
400
|
-
display_name TEXT,
|
|
401
|
-
metadata TEXT,
|
|
402
|
-
first_seen_at INTEGER NOT NULL,
|
|
403
|
-
updated_at INTEGER NOT NULL,
|
|
404
|
-
PRIMARY KEY (context_graph_id, principal_type, principal_id)
|
|
405
|
-
);
|
|
406
|
-
CREATE INDEX IF NOT EXISTS idx_cg_members_context
|
|
407
|
-
ON context_graph_memberships(context_graph_id);
|
|
408
|
-
CREATE INDEX IF NOT EXISTS idx_cg_members_principal
|
|
409
|
-
ON context_graph_memberships(principal_type, principal_id);
|
|
410
|
-
CREATE INDEX IF NOT EXISTS idx_cg_members_status
|
|
411
|
-
ON context_graph_memberships(status);
|
|
409
|
+
this.db.exec(`
|
|
410
|
+
CREATE TABLE IF NOT EXISTS context_graph_memberships (
|
|
411
|
+
context_graph_id TEXT NOT NULL,
|
|
412
|
+
principal_type TEXT NOT NULL,
|
|
413
|
+
principal_id TEXT NOT NULL,
|
|
414
|
+
role TEXT,
|
|
415
|
+
status TEXT NOT NULL,
|
|
416
|
+
source TEXT,
|
|
417
|
+
display_name TEXT,
|
|
418
|
+
metadata TEXT,
|
|
419
|
+
first_seen_at INTEGER NOT NULL,
|
|
420
|
+
updated_at INTEGER NOT NULL,
|
|
421
|
+
PRIMARY KEY (context_graph_id, principal_type, principal_id)
|
|
422
|
+
);
|
|
423
|
+
CREATE INDEX IF NOT EXISTS idx_cg_members_context
|
|
424
|
+
ON context_graph_memberships(context_graph_id);
|
|
425
|
+
CREATE INDEX IF NOT EXISTS idx_cg_members_principal
|
|
426
|
+
ON context_graph_memberships(principal_type, principal_id);
|
|
427
|
+
CREATE INDEX IF NOT EXISTS idx_cg_members_status
|
|
428
|
+
ON context_graph_memberships(status);
|
|
412
429
|
`);
|
|
413
430
|
}
|
|
414
431
|
if (version < 9) {
|
|
415
432
|
const columns = this.db.prepare('PRAGMA table_info(context_graph_subscriptions)').all();
|
|
416
433
|
if (!columns.some((column) => column.name === 'shared_memory_synced')) {
|
|
417
|
-
this.db.exec(`
|
|
418
|
-
ALTER TABLE context_graph_subscriptions
|
|
419
|
-
ADD COLUMN shared_memory_synced INTEGER;
|
|
434
|
+
this.db.exec(`
|
|
435
|
+
ALTER TABLE context_graph_subscriptions
|
|
436
|
+
ADD COLUMN shared_memory_synced INTEGER;
|
|
420
437
|
`);
|
|
421
438
|
}
|
|
422
439
|
}
|
|
@@ -485,10 +502,10 @@ export class DashboardDB {
|
|
|
485
502
|
if (!chatCols.has('message_id')) {
|
|
486
503
|
this.db.exec(`ALTER TABLE chat_messages ADD COLUMN message_id TEXT;`);
|
|
487
504
|
}
|
|
488
|
-
this.db.exec(`
|
|
489
|
-
CREATE UNIQUE INDEX IF NOT EXISTS idx_chat_msgid
|
|
490
|
-
ON chat_messages(peer, direction, message_id)
|
|
491
|
-
WHERE message_id IS NOT NULL;
|
|
505
|
+
this.db.exec(`
|
|
506
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_chat_msgid
|
|
507
|
+
ON chat_messages(peer, direction, message_id)
|
|
508
|
+
WHERE message_id IS NOT NULL;
|
|
492
509
|
`);
|
|
493
510
|
}
|
|
494
511
|
if (version < 12) {
|
|
@@ -544,33 +561,33 @@ export class DashboardDB {
|
|
|
544
561
|
// `message_id` is enforced via `message_idempotency` instead.
|
|
545
562
|
// V13 will also preserve `chat_messages.message_id` as
|
|
546
563
|
// nullable + unwritten for rollback safety.
|
|
547
|
-
this.db.exec(`
|
|
548
|
-
CREATE TABLE IF NOT EXISTS message_idempotency (
|
|
549
|
-
peer_id TEXT NOT NULL,
|
|
550
|
-
protocol TEXT NOT NULL,
|
|
551
|
-
message_id TEXT NOT NULL,
|
|
552
|
-
direction TEXT NOT NULL CHECK (direction IN ('in', 'out')),
|
|
553
|
-
response_blob BLOB,
|
|
554
|
-
response_size INTEGER NOT NULL DEFAULT 0,
|
|
555
|
-
ts INTEGER NOT NULL,
|
|
556
|
-
PRIMARY KEY (peer_id, protocol, message_id, direction)
|
|
557
|
-
);
|
|
558
|
-
CREATE INDEX IF NOT EXISTS idx_idem_ts ON message_idempotency(ts);
|
|
559
|
-
|
|
560
|
-
CREATE TABLE IF NOT EXISTS protocol_outbox (
|
|
561
|
-
peer_id TEXT NOT NULL,
|
|
562
|
-
protocol TEXT NOT NULL,
|
|
563
|
-
message_id TEXT NOT NULL,
|
|
564
|
-
payload BLOB NOT NULL,
|
|
565
|
-
attempts INTEGER NOT NULL DEFAULT 0,
|
|
566
|
-
first_failure_at INTEGER NOT NULL,
|
|
567
|
-
last_attempt_at INTEGER NOT NULL,
|
|
568
|
-
next_attempt_at INTEGER NOT NULL,
|
|
569
|
-
last_error TEXT,
|
|
570
|
-
PRIMARY KEY (peer_id, protocol, message_id)
|
|
571
|
-
);
|
|
572
|
-
CREATE INDEX IF NOT EXISTS idx_outbox_next_attempt
|
|
573
|
-
ON protocol_outbox(next_attempt_at);
|
|
564
|
+
this.db.exec(`
|
|
565
|
+
CREATE TABLE IF NOT EXISTS message_idempotency (
|
|
566
|
+
peer_id TEXT NOT NULL,
|
|
567
|
+
protocol TEXT NOT NULL,
|
|
568
|
+
message_id TEXT NOT NULL,
|
|
569
|
+
direction TEXT NOT NULL CHECK (direction IN ('in', 'out')),
|
|
570
|
+
response_blob BLOB,
|
|
571
|
+
response_size INTEGER NOT NULL DEFAULT 0,
|
|
572
|
+
ts INTEGER NOT NULL,
|
|
573
|
+
PRIMARY KEY (peer_id, protocol, message_id, direction)
|
|
574
|
+
);
|
|
575
|
+
CREATE INDEX IF NOT EXISTS idx_idem_ts ON message_idempotency(ts);
|
|
576
|
+
|
|
577
|
+
CREATE TABLE IF NOT EXISTS protocol_outbox (
|
|
578
|
+
peer_id TEXT NOT NULL,
|
|
579
|
+
protocol TEXT NOT NULL,
|
|
580
|
+
message_id TEXT NOT NULL,
|
|
581
|
+
payload BLOB NOT NULL,
|
|
582
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
583
|
+
first_failure_at INTEGER NOT NULL,
|
|
584
|
+
last_attempt_at INTEGER NOT NULL,
|
|
585
|
+
next_attempt_at INTEGER NOT NULL,
|
|
586
|
+
last_error TEXT,
|
|
587
|
+
PRIMARY KEY (peer_id, protocol, message_id)
|
|
588
|
+
);
|
|
589
|
+
CREATE INDEX IF NOT EXISTS idx_outbox_next_attempt
|
|
590
|
+
ON protocol_outbox(next_attempt_at);
|
|
574
591
|
`);
|
|
575
592
|
}
|
|
576
593
|
if (version < 13) {
|
|
@@ -595,8 +612,8 @@ export class DashboardDB {
|
|
|
595
612
|
// table rebuild on downgrade.
|
|
596
613
|
//
|
|
597
614
|
// No data migration. No new tables. Pure DROP INDEX.
|
|
598
|
-
this.db.exec(`
|
|
599
|
-
DROP INDEX IF EXISTS idx_chat_msgid;
|
|
615
|
+
this.db.exec(`
|
|
616
|
+
DROP INDEX IF EXISTS idx_chat_msgid;
|
|
600
617
|
`);
|
|
601
618
|
}
|
|
602
619
|
if (version < 14) {
|
|
@@ -637,10 +654,10 @@ export class DashboardDB {
|
|
|
637
654
|
// operation-correlated log views (`getOperation`,
|
|
638
655
|
// `getFailedOperations`). Substring/text search moves to
|
|
639
656
|
// /api/node-log, which already supports `?q=`.
|
|
640
|
-
this.db.exec(`
|
|
641
|
-
DROP TRIGGER IF EXISTS logs_ai;
|
|
642
|
-
DROP TRIGGER IF EXISTS logs_ad;
|
|
643
|
-
DROP TABLE IF EXISTS logs_fts;
|
|
657
|
+
this.db.exec(`
|
|
658
|
+
DROP TRIGGER IF EXISTS logs_ai;
|
|
659
|
+
DROP TRIGGER IF EXISTS logs_ad;
|
|
660
|
+
DROP TABLE IF EXISTS logs_fts;
|
|
644
661
|
`);
|
|
645
662
|
// VACUUM cannot run inside a transaction; better-sqlite3 wraps
|
|
646
663
|
// multi-statement exec() in implicit BEGIN/COMMIT, so we issue
|
|
@@ -704,23 +721,23 @@ export class DashboardDB {
|
|
|
704
721
|
// re-parsing the daemon log. One row per decision point (sweep / fetch /
|
|
705
722
|
// promote / already / defer / cursor-advance / core-fill). Pruned by `ts`
|
|
706
723
|
// like the other event tables.
|
|
707
|
-
this.db.exec(`
|
|
708
|
-
CREATE TABLE IF NOT EXISTS replication_events (
|
|
709
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
710
|
-
ts INTEGER NOT NULL,
|
|
711
|
-
context_graph_id TEXT NOT NULL,
|
|
712
|
-
on_chain_cg_id TEXT,
|
|
713
|
-
action TEXT NOT NULL,
|
|
714
|
-
ual TEXT,
|
|
715
|
-
ordinal INTEGER,
|
|
716
|
-
ka_id TEXT,
|
|
717
|
-
from_watermark INTEGER,
|
|
718
|
-
to_watermark INTEGER,
|
|
719
|
-
head INTEGER,
|
|
720
|
-
reconciled INTEGER,
|
|
721
|
-
pending INTEGER,
|
|
722
|
-
detail TEXT
|
|
723
|
-
);
|
|
724
|
+
this.db.exec(`
|
|
725
|
+
CREATE TABLE IF NOT EXISTS replication_events (
|
|
726
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
727
|
+
ts INTEGER NOT NULL,
|
|
728
|
+
context_graph_id TEXT NOT NULL,
|
|
729
|
+
on_chain_cg_id TEXT,
|
|
730
|
+
action TEXT NOT NULL,
|
|
731
|
+
ual TEXT,
|
|
732
|
+
ordinal INTEGER,
|
|
733
|
+
ka_id TEXT,
|
|
734
|
+
from_watermark INTEGER,
|
|
735
|
+
to_watermark INTEGER,
|
|
736
|
+
head INTEGER,
|
|
737
|
+
reconciled INTEGER,
|
|
738
|
+
pending INTEGER,
|
|
739
|
+
detail TEXT
|
|
740
|
+
);
|
|
724
741
|
`);
|
|
725
742
|
this.db.exec(`CREATE INDEX IF NOT EXISTS idx_repl_ts ON replication_events(ts);`);
|
|
726
743
|
this.db.exec(`CREATE INDEX IF NOT EXISTS idx_repl_cg ON replication_events(context_graph_id, ts);`);
|
|
@@ -759,54 +776,54 @@ export class DashboardDB {
|
|
|
759
776
|
// the 2^63 INTEGER max and under the uint96 on-chain field. Like
|
|
760
777
|
// `protocol_outbox`, this state is durable and is NEVER pruned —
|
|
761
778
|
// reclaiming a number could mint a duplicate kaId.
|
|
762
|
-
this.db.exec(`
|
|
763
|
-
CREATE TABLE IF NOT EXISTS ka_numbers (
|
|
764
|
-
author_address TEXT PRIMARY KEY,
|
|
765
|
-
next_number INTEGER NOT NULL
|
|
766
|
-
);
|
|
779
|
+
this.db.exec(`
|
|
780
|
+
CREATE TABLE IF NOT EXISTS ka_numbers (
|
|
781
|
+
author_address TEXT PRIMARY KEY,
|
|
782
|
+
next_number INTEGER NOT NULL
|
|
783
|
+
);
|
|
767
784
|
`);
|
|
768
785
|
}
|
|
769
786
|
if (version < 21) {
|
|
770
|
-
this.db.exec(`
|
|
771
|
-
CREATE TABLE IF NOT EXISTS sync_checkpoints (
|
|
772
|
-
key TEXT PRIMARY KEY,
|
|
773
|
-
offset INTEGER NOT NULL CHECK (offset >= 0),
|
|
774
|
-
updated_at INTEGER NOT NULL,
|
|
775
|
-
expires_at INTEGER NOT NULL
|
|
776
|
-
);
|
|
777
|
-
CREATE INDEX IF NOT EXISTS idx_sync_checkpoints_expires_at
|
|
778
|
-
ON sync_checkpoints(expires_at);
|
|
787
|
+
this.db.exec(`
|
|
788
|
+
CREATE TABLE IF NOT EXISTS sync_checkpoints (
|
|
789
|
+
key TEXT PRIMARY KEY,
|
|
790
|
+
offset INTEGER NOT NULL CHECK (offset >= 0),
|
|
791
|
+
updated_at INTEGER NOT NULL,
|
|
792
|
+
expires_at INTEGER NOT NULL
|
|
793
|
+
);
|
|
794
|
+
CREATE INDEX IF NOT EXISTS idx_sync_checkpoints_expires_at
|
|
795
|
+
ON sync_checkpoints(expires_at);
|
|
779
796
|
`);
|
|
780
797
|
}
|
|
781
798
|
if (version < 22) {
|
|
782
|
-
this.db.exec(`
|
|
783
|
-
CREATE TABLE IF NOT EXISTS runtime_cursors (
|
|
784
|
-
namespace TEXT NOT NULL,
|
|
785
|
-
scope TEXT NOT NULL,
|
|
786
|
-
key TEXT NOT NULL,
|
|
787
|
-
value INTEGER NOT NULL CHECK (value > 0),
|
|
788
|
-
updated_at INTEGER NOT NULL,
|
|
789
|
-
PRIMARY KEY (namespace, scope, key)
|
|
790
|
-
);
|
|
799
|
+
this.db.exec(`
|
|
800
|
+
CREATE TABLE IF NOT EXISTS runtime_cursors (
|
|
801
|
+
namespace TEXT NOT NULL,
|
|
802
|
+
scope TEXT NOT NULL,
|
|
803
|
+
key TEXT NOT NULL,
|
|
804
|
+
value INTEGER NOT NULL CHECK (value > 0),
|
|
805
|
+
updated_at INTEGER NOT NULL,
|
|
806
|
+
PRIMARY KEY (namespace, scope, key)
|
|
807
|
+
);
|
|
791
808
|
`);
|
|
792
|
-
this.db.exec(`
|
|
793
|
-
CREATE INDEX IF NOT EXISTS idx_runtime_cursors_namespace_scope
|
|
794
|
-
ON runtime_cursors(namespace, scope);
|
|
809
|
+
this.db.exec(`
|
|
810
|
+
CREATE INDEX IF NOT EXISTS idx_runtime_cursors_namespace_scope
|
|
811
|
+
ON runtime_cursors(namespace, scope);
|
|
795
812
|
`);
|
|
796
813
|
}
|
|
797
814
|
if (version < 23) {
|
|
798
815
|
// OT-RFC-59 SC5: durable per-(peer, contextGraph) changelog cursor. NEVER
|
|
799
816
|
// pruned (see SqliteChangelogCursorStore) — a TTL would defeat the O(delta)
|
|
800
817
|
// cross-restart catch-up this table exists for.
|
|
801
|
-
this.db.exec(`
|
|
802
|
-
CREATE TABLE IF NOT EXISTS changelog_cursors (
|
|
803
|
-
peer_id TEXT NOT NULL,
|
|
804
|
-
context_graph_id TEXT NOT NULL,
|
|
805
|
-
era TEXT NOT NULL,
|
|
806
|
-
seq INTEGER NOT NULL CHECK (seq >= 0),
|
|
807
|
-
updated_at INTEGER NOT NULL,
|
|
808
|
-
PRIMARY KEY (peer_id, context_graph_id)
|
|
809
|
-
);
|
|
818
|
+
this.db.exec(`
|
|
819
|
+
CREATE TABLE IF NOT EXISTS changelog_cursors (
|
|
820
|
+
peer_id TEXT NOT NULL,
|
|
821
|
+
context_graph_id TEXT NOT NULL,
|
|
822
|
+
era TEXT NOT NULL,
|
|
823
|
+
seq INTEGER NOT NULL CHECK (seq >= 0),
|
|
824
|
+
updated_at INTEGER NOT NULL,
|
|
825
|
+
PRIMARY KEY (peer_id, context_graph_id)
|
|
826
|
+
);
|
|
810
827
|
`);
|
|
811
828
|
}
|
|
812
829
|
if (version < 24) {
|
|
@@ -815,39 +832,39 @@ export class DashboardDB {
|
|
|
815
832
|
// `store.nq` restore does NOT roll back with the RDF store. node-ui.db
|
|
816
833
|
// survives such a restore, so a rollback under the same era rotates the era
|
|
817
834
|
// (forcing peers to full-resync instead of silently skipping). Single row.
|
|
818
|
-
this.db.exec(`
|
|
819
|
-
CREATE TABLE IF NOT EXISTS changelog_era (
|
|
820
|
-
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
821
|
-
era TEXT NOT NULL,
|
|
822
|
-
high_seq INTEGER NOT NULL CHECK (high_seq >= 0),
|
|
823
|
-
updated_at INTEGER NOT NULL
|
|
824
|
-
);
|
|
835
|
+
this.db.exec(`
|
|
836
|
+
CREATE TABLE IF NOT EXISTS changelog_era (
|
|
837
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
838
|
+
era TEXT NOT NULL,
|
|
839
|
+
high_seq INTEGER NOT NULL CHECK (high_seq >= 0),
|
|
840
|
+
updated_at INTEGER NOT NULL
|
|
841
|
+
);
|
|
825
842
|
`);
|
|
826
843
|
}
|
|
827
844
|
if (version < 25) {
|
|
828
845
|
// Durable security audit for private-CG open enrollment. V25/V26 also
|
|
829
846
|
// used this table as admission state; V27 projects those legacy rows into
|
|
830
847
|
// the typed operational ledger below.
|
|
831
|
-
this.db.exec(`
|
|
832
|
-
CREATE TABLE IF NOT EXISTS context_graph_join_policy_audit (
|
|
833
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
834
|
-
ts INTEGER NOT NULL,
|
|
835
|
-
context_graph_id TEXT NOT NULL,
|
|
836
|
-
event_type TEXT NOT NULL,
|
|
837
|
-
actor TEXT,
|
|
838
|
-
agent_address TEXT,
|
|
839
|
-
outcome TEXT NOT NULL,
|
|
840
|
-
reason TEXT,
|
|
841
|
-
request_digest TEXT,
|
|
842
|
-
policy_version INTEGER,
|
|
843
|
-
details TEXT
|
|
844
|
-
);
|
|
845
|
-
CREATE INDEX IF NOT EXISTS idx_cg_join_policy_audit_ts
|
|
846
|
-
ON context_graph_join_policy_audit(ts);
|
|
847
|
-
CREATE INDEX IF NOT EXISTS idx_cg_join_policy_audit_cg_ts
|
|
848
|
-
ON context_graph_join_policy_audit(context_graph_id, ts);
|
|
849
|
-
CREATE INDEX IF NOT EXISTS idx_cg_join_policy_audit_event_ts
|
|
850
|
-
ON context_graph_join_policy_audit(event_type, ts);
|
|
848
|
+
this.db.exec(`
|
|
849
|
+
CREATE TABLE IF NOT EXISTS context_graph_join_policy_audit (
|
|
850
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
851
|
+
ts INTEGER NOT NULL,
|
|
852
|
+
context_graph_id TEXT NOT NULL,
|
|
853
|
+
event_type TEXT NOT NULL,
|
|
854
|
+
actor TEXT,
|
|
855
|
+
agent_address TEXT,
|
|
856
|
+
outcome TEXT NOT NULL,
|
|
857
|
+
reason TEXT,
|
|
858
|
+
request_digest TEXT,
|
|
859
|
+
policy_version INTEGER,
|
|
860
|
+
details TEXT
|
|
861
|
+
);
|
|
862
|
+
CREATE INDEX IF NOT EXISTS idx_cg_join_policy_audit_ts
|
|
863
|
+
ON context_graph_join_policy_audit(ts);
|
|
864
|
+
CREATE INDEX IF NOT EXISTS idx_cg_join_policy_audit_cg_ts
|
|
865
|
+
ON context_graph_join_policy_audit(context_graph_id, ts);
|
|
866
|
+
CREATE INDEX IF NOT EXISTS idx_cg_join_policy_audit_event_ts
|
|
867
|
+
ON context_graph_join_policy_audit(event_type, ts);
|
|
851
868
|
`);
|
|
852
869
|
}
|
|
853
870
|
if (version < 26) {
|
|
@@ -866,108 +883,108 @@ export class DashboardDB {
|
|
|
866
883
|
// Operational admission state is deliberately separate from the audit
|
|
867
884
|
// stream. Audit retention and event naming must never alter rate-limit,
|
|
868
885
|
// epoch, or commit-idempotency behaviour.
|
|
869
|
-
this.db.exec(`
|
|
870
|
-
CREATE TABLE IF NOT EXISTS context_graph_join_approval_ledger (
|
|
871
|
-
context_graph_id TEXT NOT NULL,
|
|
872
|
-
request_digest TEXT NOT NULL,
|
|
873
|
-
policy_epoch INTEGER NOT NULL,
|
|
874
|
-
reserved_at INTEGER NOT NULL,
|
|
875
|
-
state TEXT NOT NULL CHECK (state IN ('reserved', 'committed')),
|
|
876
|
-
committed_at INTEGER,
|
|
877
|
-
actor TEXT NOT NULL,
|
|
878
|
-
agent_address TEXT NOT NULL,
|
|
879
|
-
policy_version INTEGER NOT NULL,
|
|
880
|
-
PRIMARY KEY (context_graph_id, request_digest, policy_epoch),
|
|
881
|
-
CHECK (length(context_graph_id) > 0),
|
|
882
|
-
CHECK (length(request_digest) > 0),
|
|
883
|
-
CHECK (typeof(policy_epoch) = 'integer'),
|
|
884
|
-
CHECK (typeof(reserved_at) = 'integer'),
|
|
885
|
-
CHECK (committed_at IS NULL OR typeof(committed_at) = 'integer'),
|
|
886
|
-
CHECK (typeof(policy_version) = 'integer'),
|
|
887
|
-
CHECK (
|
|
888
|
-
(state = 'reserved' AND committed_at IS NULL)
|
|
889
|
-
OR (state = 'committed' AND committed_at IS NOT NULL)
|
|
890
|
-
)
|
|
891
|
-
);
|
|
892
|
-
CREATE INDEX IF NOT EXISTS idx_cg_join_approval_ledger_reserved_at
|
|
893
|
-
ON context_graph_join_approval_ledger(reserved_at);
|
|
894
|
-
CREATE INDEX IF NOT EXISTS idx_cg_join_approval_ledger_cg_reserved_at
|
|
895
|
-
ON context_graph_join_approval_ledger(context_graph_id, reserved_at);
|
|
896
|
-
CREATE INDEX IF NOT EXISTS idx_cg_join_approval_ledger_digest_reserved_at
|
|
897
|
-
ON context_graph_join_approval_ledger(context_graph_id, request_digest, reserved_at DESC);
|
|
898
|
-
CREATE INDEX IF NOT EXISTS idx_cg_join_approval_ledger_committed_at
|
|
899
|
-
ON context_graph_join_approval_ledger(committed_at);
|
|
886
|
+
this.db.exec(`
|
|
887
|
+
CREATE TABLE IF NOT EXISTS context_graph_join_approval_ledger (
|
|
888
|
+
context_graph_id TEXT NOT NULL,
|
|
889
|
+
request_digest TEXT NOT NULL,
|
|
890
|
+
policy_epoch INTEGER NOT NULL,
|
|
891
|
+
reserved_at INTEGER NOT NULL,
|
|
892
|
+
state TEXT NOT NULL CHECK (state IN ('reserved', 'committed')),
|
|
893
|
+
committed_at INTEGER,
|
|
894
|
+
actor TEXT NOT NULL,
|
|
895
|
+
agent_address TEXT NOT NULL,
|
|
896
|
+
policy_version INTEGER NOT NULL,
|
|
897
|
+
PRIMARY KEY (context_graph_id, request_digest, policy_epoch),
|
|
898
|
+
CHECK (length(context_graph_id) > 0),
|
|
899
|
+
CHECK (length(request_digest) > 0),
|
|
900
|
+
CHECK (typeof(policy_epoch) = 'integer'),
|
|
901
|
+
CHECK (typeof(reserved_at) = 'integer'),
|
|
902
|
+
CHECK (committed_at IS NULL OR typeof(committed_at) = 'integer'),
|
|
903
|
+
CHECK (typeof(policy_version) = 'integer'),
|
|
904
|
+
CHECK (
|
|
905
|
+
(state = 'reserved' AND committed_at IS NULL)
|
|
906
|
+
OR (state = 'committed' AND committed_at IS NOT NULL)
|
|
907
|
+
)
|
|
908
|
+
);
|
|
909
|
+
CREATE INDEX IF NOT EXISTS idx_cg_join_approval_ledger_reserved_at
|
|
910
|
+
ON context_graph_join_approval_ledger(reserved_at);
|
|
911
|
+
CREATE INDEX IF NOT EXISTS idx_cg_join_approval_ledger_cg_reserved_at
|
|
912
|
+
ON context_graph_join_approval_ledger(context_graph_id, reserved_at);
|
|
913
|
+
CREATE INDEX IF NOT EXISTS idx_cg_join_approval_ledger_digest_reserved_at
|
|
914
|
+
ON context_graph_join_approval_ledger(context_graph_id, request_digest, reserved_at DESC);
|
|
915
|
+
CREATE INDEX IF NOT EXISTS idx_cg_join_approval_ledger_committed_at
|
|
916
|
+
ON context_graph_join_approval_ledger(committed_at);
|
|
900
917
|
`);
|
|
901
918
|
// Best-effort one-time projection for V25/V26 databases. Invalid legacy
|
|
902
919
|
// JSON cannot abort startup; only rows with a typed numeric epoch can be
|
|
903
920
|
// authoritative operational state. For duplicate audit reservations,
|
|
904
921
|
// retain the newest row for each (CG, digest, epoch).
|
|
905
|
-
this.db.exec(`
|
|
906
|
-
WITH raw_reservations AS (
|
|
907
|
-
SELECT
|
|
908
|
-
id,
|
|
909
|
-
context_graph_id,
|
|
910
|
-
request_digest,
|
|
911
|
-
CASE
|
|
912
|
-
WHEN json_valid(details)
|
|
913
|
-
AND json_type(details, '$.policyEpoch') IN ('integer', 'real')
|
|
914
|
-
THEN CAST(json_extract(details, '$.policyEpoch') AS INTEGER)
|
|
915
|
-
ELSE NULL
|
|
916
|
-
END AS policy_epoch,
|
|
917
|
-
ts AS reserved_at,
|
|
918
|
-
COALESCE(actor, '') AS actor,
|
|
919
|
-
COALESCE(agent_address, '') AS agent_address,
|
|
920
|
-
COALESCE(policy_version, 1) AS policy_version
|
|
921
|
-
FROM context_graph_join_policy_audit
|
|
922
|
-
WHERE event_type = 'join_auto_reservation'
|
|
923
|
-
AND outcome = 'reserved'
|
|
924
|
-
AND request_digest IS NOT NULL
|
|
925
|
-
),
|
|
926
|
-
ranked_reservations AS (
|
|
927
|
-
SELECT *, ROW_NUMBER() OVER (
|
|
928
|
-
PARTITION BY context_graph_id, request_digest, policy_epoch
|
|
929
|
-
ORDER BY reserved_at DESC, id DESC
|
|
930
|
-
) AS rank
|
|
931
|
-
FROM raw_reservations
|
|
932
|
-
WHERE policy_epoch IS NOT NULL
|
|
933
|
-
),
|
|
934
|
-
raw_commits AS (
|
|
935
|
-
SELECT
|
|
936
|
-
context_graph_id,
|
|
937
|
-
request_digest,
|
|
938
|
-
CASE
|
|
939
|
-
WHEN json_valid(details)
|
|
940
|
-
AND json_type(details, '$.policyEpoch') IN ('integer', 'real')
|
|
941
|
-
THEN CAST(json_extract(details, '$.policyEpoch') AS INTEGER)
|
|
942
|
-
ELSE NULL
|
|
943
|
-
END AS policy_epoch,
|
|
944
|
-
MIN(ts) AS committed_at
|
|
945
|
-
FROM context_graph_join_policy_audit
|
|
946
|
-
WHERE event_type = 'join_admission_committed'
|
|
947
|
-
AND outcome = 'approved'
|
|
948
|
-
AND request_digest IS NOT NULL
|
|
949
|
-
GROUP BY context_graph_id, request_digest, policy_epoch
|
|
950
|
-
)
|
|
951
|
-
INSERT OR IGNORE INTO context_graph_join_approval_ledger (
|
|
952
|
-
context_graph_id, request_digest, policy_epoch, reserved_at,
|
|
953
|
-
state, committed_at, actor, agent_address, policy_version
|
|
954
|
-
)
|
|
955
|
-
SELECT
|
|
956
|
-
reservation.context_graph_id,
|
|
957
|
-
reservation.request_digest,
|
|
958
|
-
reservation.policy_epoch,
|
|
959
|
-
reservation.reserved_at,
|
|
960
|
-
CASE WHEN commit_row.committed_at IS NULL THEN 'reserved' ELSE 'committed' END,
|
|
961
|
-
commit_row.committed_at,
|
|
962
|
-
reservation.actor,
|
|
963
|
-
reservation.agent_address,
|
|
964
|
-
reservation.policy_version
|
|
965
|
-
FROM ranked_reservations AS reservation
|
|
966
|
-
LEFT JOIN raw_commits AS commit_row
|
|
967
|
-
ON commit_row.context_graph_id = reservation.context_graph_id
|
|
968
|
-
AND commit_row.request_digest = reservation.request_digest
|
|
969
|
-
AND commit_row.policy_epoch = reservation.policy_epoch
|
|
970
|
-
WHERE reservation.rank = 1;
|
|
922
|
+
this.db.exec(`
|
|
923
|
+
WITH raw_reservations AS (
|
|
924
|
+
SELECT
|
|
925
|
+
id,
|
|
926
|
+
context_graph_id,
|
|
927
|
+
request_digest,
|
|
928
|
+
CASE
|
|
929
|
+
WHEN json_valid(details)
|
|
930
|
+
AND json_type(details, '$.policyEpoch') IN ('integer', 'real')
|
|
931
|
+
THEN CAST(json_extract(details, '$.policyEpoch') AS INTEGER)
|
|
932
|
+
ELSE NULL
|
|
933
|
+
END AS policy_epoch,
|
|
934
|
+
ts AS reserved_at,
|
|
935
|
+
COALESCE(actor, '') AS actor,
|
|
936
|
+
COALESCE(agent_address, '') AS agent_address,
|
|
937
|
+
COALESCE(policy_version, 1) AS policy_version
|
|
938
|
+
FROM context_graph_join_policy_audit
|
|
939
|
+
WHERE event_type = 'join_auto_reservation'
|
|
940
|
+
AND outcome = 'reserved'
|
|
941
|
+
AND request_digest IS NOT NULL
|
|
942
|
+
),
|
|
943
|
+
ranked_reservations AS (
|
|
944
|
+
SELECT *, ROW_NUMBER() OVER (
|
|
945
|
+
PARTITION BY context_graph_id, request_digest, policy_epoch
|
|
946
|
+
ORDER BY reserved_at DESC, id DESC
|
|
947
|
+
) AS rank
|
|
948
|
+
FROM raw_reservations
|
|
949
|
+
WHERE policy_epoch IS NOT NULL
|
|
950
|
+
),
|
|
951
|
+
raw_commits AS (
|
|
952
|
+
SELECT
|
|
953
|
+
context_graph_id,
|
|
954
|
+
request_digest,
|
|
955
|
+
CASE
|
|
956
|
+
WHEN json_valid(details)
|
|
957
|
+
AND json_type(details, '$.policyEpoch') IN ('integer', 'real')
|
|
958
|
+
THEN CAST(json_extract(details, '$.policyEpoch') AS INTEGER)
|
|
959
|
+
ELSE NULL
|
|
960
|
+
END AS policy_epoch,
|
|
961
|
+
MIN(ts) AS committed_at
|
|
962
|
+
FROM context_graph_join_policy_audit
|
|
963
|
+
WHERE event_type = 'join_admission_committed'
|
|
964
|
+
AND outcome = 'approved'
|
|
965
|
+
AND request_digest IS NOT NULL
|
|
966
|
+
GROUP BY context_graph_id, request_digest, policy_epoch
|
|
967
|
+
)
|
|
968
|
+
INSERT OR IGNORE INTO context_graph_join_approval_ledger (
|
|
969
|
+
context_graph_id, request_digest, policy_epoch, reserved_at,
|
|
970
|
+
state, committed_at, actor, agent_address, policy_version
|
|
971
|
+
)
|
|
972
|
+
SELECT
|
|
973
|
+
reservation.context_graph_id,
|
|
974
|
+
reservation.request_digest,
|
|
975
|
+
reservation.policy_epoch,
|
|
976
|
+
reservation.reserved_at,
|
|
977
|
+
CASE WHEN commit_row.committed_at IS NULL THEN 'reserved' ELSE 'committed' END,
|
|
978
|
+
commit_row.committed_at,
|
|
979
|
+
reservation.actor,
|
|
980
|
+
reservation.agent_address,
|
|
981
|
+
reservation.policy_version
|
|
982
|
+
FROM ranked_reservations AS reservation
|
|
983
|
+
LEFT JOIN raw_commits AS commit_row
|
|
984
|
+
ON commit_row.context_graph_id = reservation.context_graph_id
|
|
985
|
+
AND commit_row.request_digest = reservation.request_digest
|
|
986
|
+
AND commit_row.policy_epoch = reservation.policy_epoch
|
|
987
|
+
WHERE reservation.rank = 1;
|
|
971
988
|
`);
|
|
972
989
|
// V26's trigger protected live reservation audit rows because they were
|
|
973
990
|
// operational state. The ledger makes that coupling obsolete, so replace
|
|
@@ -985,23 +1002,29 @@ export class DashboardDB {
|
|
|
985
1002
|
// idempotent and keeps the audit bound fail-closed on every open.
|
|
986
1003
|
ensureJoinPolicyAuditCapTrigger();
|
|
987
1004
|
if (version < 29) {
|
|
988
|
-
this.db.exec(`
|
|
989
|
-
CREATE TABLE IF NOT EXISTS vm_reconcile_negative_cache (
|
|
990
|
-
cache_key TEXT PRIMARY KEY,
|
|
991
|
-
context_graph_id TEXT NOT NULL,
|
|
992
|
-
failures INTEGER NOT NULL,
|
|
993
|
-
next_retry_at INTEGER NOT NULL,
|
|
994
|
-
swm_gen TEXT NOT NULL,
|
|
995
|
-
candidate_namespaces TEXT NOT NULL,
|
|
996
|
-
peer_topology_key TEXT NOT NULL,
|
|
997
|
-
updated_at INTEGER NOT NULL
|
|
998
|
-
);
|
|
999
|
-
CREATE INDEX IF NOT EXISTS idx_vm_reconcile_negative_cg
|
|
1000
|
-
ON vm_reconcile_negative_cache(context_graph_id);
|
|
1001
|
-
CREATE INDEX IF NOT EXISTS idx_vm_reconcile_negative_retry
|
|
1002
|
-
ON vm_reconcile_negative_cache(next_retry_at);
|
|
1005
|
+
this.db.exec(`
|
|
1006
|
+
CREATE TABLE IF NOT EXISTS vm_reconcile_negative_cache (
|
|
1007
|
+
cache_key TEXT PRIMARY KEY,
|
|
1008
|
+
context_graph_id TEXT NOT NULL,
|
|
1009
|
+
failures INTEGER NOT NULL,
|
|
1010
|
+
next_retry_at INTEGER NOT NULL,
|
|
1011
|
+
swm_gen TEXT NOT NULL,
|
|
1012
|
+
candidate_namespaces TEXT NOT NULL,
|
|
1013
|
+
peer_topology_key TEXT NOT NULL,
|
|
1014
|
+
updated_at INTEGER NOT NULL
|
|
1015
|
+
);
|
|
1016
|
+
CREATE INDEX IF NOT EXISTS idx_vm_reconcile_negative_cg
|
|
1017
|
+
ON vm_reconcile_negative_cache(context_graph_id);
|
|
1018
|
+
CREATE INDEX IF NOT EXISTS idx_vm_reconcile_negative_retry
|
|
1019
|
+
ON vm_reconcile_negative_cache(next_retry_at);
|
|
1003
1020
|
`);
|
|
1004
1021
|
}
|
|
1022
|
+
if (version < 30) {
|
|
1023
|
+
// OFFSET pagination is scoped to an immutable responder row list. Keep
|
|
1024
|
+
// the opaque responder token beside the verified offset so a requester
|
|
1025
|
+
// restart can resume that same list instead of discarding durable work.
|
|
1026
|
+
ensureSyncCheckpointResponderSessionColumns();
|
|
1027
|
+
}
|
|
1005
1028
|
this.db.pragma(`user_version = ${SCHEMA_VERSION}`);
|
|
1006
1029
|
if (upgradedExistingDb && !this.explicitRetentionDays) {
|
|
1007
1030
|
this.retentionDays = LEGACY_IMPLICIT_RETENTION_DAYS;
|
|
@@ -1039,10 +1062,10 @@ export class DashboardDB {
|
|
|
1039
1062
|
// not depend on whether the corresponding audit rows still exist. Preserve
|
|
1040
1063
|
// the historical retention envelope for durable commit idempotency while
|
|
1041
1064
|
// allowing fully expired reservation/commit state to age out.
|
|
1042
|
-
this.db.prepare(`
|
|
1043
|
-
DELETE FROM context_graph_join_approval_ledger
|
|
1044
|
-
WHERE reserved_at < ?
|
|
1045
|
-
AND (committed_at IS NULL OR committed_at < ?)
|
|
1065
|
+
this.db.prepare(`
|
|
1066
|
+
DELETE FROM context_graph_join_approval_ledger
|
|
1067
|
+
WHERE reserved_at < ?
|
|
1068
|
+
AND (committed_at IS NULL OR committed_at < ?)
|
|
1046
1069
|
`).run(cutoff, cutoff);
|
|
1047
1070
|
this.db.prepare(`DELETE FROM sync_checkpoints WHERE expires_at < ?`).run(Date.now());
|
|
1048
1071
|
// Reconcile negatives are accelerators, never historical records. Once the
|
|
@@ -1095,16 +1118,16 @@ export class DashboardDB {
|
|
|
1095
1118
|
: 'done',
|
|
1096
1119
|
};
|
|
1097
1120
|
}
|
|
1098
|
-
const deleted = this.db.prepare(`
|
|
1099
|
-
DELETE FROM logs
|
|
1100
|
-
WHERE id IN (
|
|
1101
|
-
SELECT id
|
|
1102
|
-
FROM logs
|
|
1103
|
-
WHERE id <= @cutoff
|
|
1104
|
-
AND level NOT IN ('warn', 'error')
|
|
1105
|
-
ORDER BY id ASC
|
|
1106
|
-
LIMIT @batchRows
|
|
1107
|
-
)
|
|
1121
|
+
const deleted = this.db.prepare(`
|
|
1122
|
+
DELETE FROM logs
|
|
1123
|
+
WHERE id IN (
|
|
1124
|
+
SELECT id
|
|
1125
|
+
FROM logs
|
|
1126
|
+
WHERE id <= @cutoff
|
|
1127
|
+
AND level NOT IN ('warn', 'error')
|
|
1128
|
+
ORDER BY id ASC
|
|
1129
|
+
LIMIT @batchRows
|
|
1130
|
+
)
|
|
1108
1131
|
`).run({
|
|
1109
1132
|
cutoff: overflowCutoff,
|
|
1110
1133
|
batchRows: this.logVolumePruneBatchRows,
|
|
@@ -1130,12 +1153,12 @@ export class DashboardDB {
|
|
|
1130
1153
|
};
|
|
1131
1154
|
}
|
|
1132
1155
|
routineLogOverflowCutoff() {
|
|
1133
|
-
const row = this.db.prepare(`
|
|
1134
|
-
SELECT id
|
|
1135
|
-
FROM logs
|
|
1136
|
-
WHERE level NOT IN ('warn', 'error')
|
|
1137
|
-
ORDER BY id DESC
|
|
1138
|
-
LIMIT 1 OFFSET ?
|
|
1156
|
+
const row = this.db.prepare(`
|
|
1157
|
+
SELECT id
|
|
1158
|
+
FROM logs
|
|
1159
|
+
WHERE level NOT IN ('warn', 'error')
|
|
1160
|
+
ORDER BY id DESC
|
|
1161
|
+
LIMIT 1 OFFSET ?
|
|
1139
1162
|
`).get(this.routineLogRowCap);
|
|
1140
1163
|
return row?.id ?? null;
|
|
1141
1164
|
}
|
|
@@ -1236,50 +1259,50 @@ export class DashboardDB {
|
|
|
1236
1259
|
}
|
|
1237
1260
|
// --- Metric snapshots ---
|
|
1238
1261
|
insertSnapshot(snap) {
|
|
1239
|
-
this.stmt('insertSnapshot', `
|
|
1240
|
-
INSERT INTO metric_snapshots (
|
|
1241
|
-
ts, cpu_percent, mem_used_bytes, mem_total_bytes,
|
|
1242
|
-
disk_used_bytes, disk_total_bytes, heap_used_bytes, uptime_seconds,
|
|
1243
|
-
peer_count, direct_peers, relayed_peers, mesh_peers, contextGraph_count,
|
|
1244
|
-
total_triples, total_kcs, total_kas, store_bytes,
|
|
1245
|
-
confirmed_kcs, tentative_kcs, rpc_latency_ms, rpc_healthy,
|
|
1246
|
-
relay_capacity, relay_reservation_count, relay_active_circuits,
|
|
1247
|
-
relay_bytes_in, relay_bytes_out
|
|
1248
|
-
) VALUES (
|
|
1249
|
-
@ts, @cpu_percent, @mem_used_bytes, @mem_total_bytes,
|
|
1250
|
-
@disk_used_bytes, @disk_total_bytes, @heap_used_bytes, @uptime_seconds,
|
|
1251
|
-
@peer_count, @direct_peers, @relayed_peers, @mesh_peers, @contextGraph_count,
|
|
1252
|
-
@total_triples, @total_kcs, @total_kas, @store_bytes,
|
|
1253
|
-
@confirmed_kcs, @tentative_kcs, @rpc_latency_ms, @rpc_healthy,
|
|
1254
|
-
@relay_capacity, @relay_reservation_count, @relay_active_circuits,
|
|
1255
|
-
@relay_bytes_in, @relay_bytes_out
|
|
1256
|
-
)
|
|
1262
|
+
this.stmt('insertSnapshot', `
|
|
1263
|
+
INSERT INTO metric_snapshots (
|
|
1264
|
+
ts, cpu_percent, mem_used_bytes, mem_total_bytes,
|
|
1265
|
+
disk_used_bytes, disk_total_bytes, heap_used_bytes, uptime_seconds,
|
|
1266
|
+
peer_count, direct_peers, relayed_peers, mesh_peers, contextGraph_count,
|
|
1267
|
+
total_triples, total_kcs, total_kas, store_bytes,
|
|
1268
|
+
confirmed_kcs, tentative_kcs, rpc_latency_ms, rpc_healthy,
|
|
1269
|
+
relay_capacity, relay_reservation_count, relay_active_circuits,
|
|
1270
|
+
relay_bytes_in, relay_bytes_out
|
|
1271
|
+
) VALUES (
|
|
1272
|
+
@ts, @cpu_percent, @mem_used_bytes, @mem_total_bytes,
|
|
1273
|
+
@disk_used_bytes, @disk_total_bytes, @heap_used_bytes, @uptime_seconds,
|
|
1274
|
+
@peer_count, @direct_peers, @relayed_peers, @mesh_peers, @contextGraph_count,
|
|
1275
|
+
@total_triples, @total_kcs, @total_kas, @store_bytes,
|
|
1276
|
+
@confirmed_kcs, @tentative_kcs, @rpc_latency_ms, @rpc_healthy,
|
|
1277
|
+
@relay_capacity, @relay_reservation_count, @relay_active_circuits,
|
|
1278
|
+
@relay_bytes_in, @relay_bytes_out
|
|
1279
|
+
)
|
|
1257
1280
|
`).run(snap);
|
|
1258
1281
|
}
|
|
1259
1282
|
getLatestSnapshot() {
|
|
1260
1283
|
return this.db.prepare('SELECT * FROM metric_snapshots ORDER BY ts DESC LIMIT 1').get();
|
|
1261
1284
|
}
|
|
1262
1285
|
upsertContextGraphSubscription(record) {
|
|
1263
|
-
this.stmt('upsertContextGraphSubscription', `
|
|
1264
|
-
INSERT INTO context_graph_subscriptions (
|
|
1265
|
-
context_graph_id, name, subscribed, synced, shared_memory_synced, meta_synced,
|
|
1266
|
-
on_chain_id, on_chain_hash, last_reconciled_ordinal, core_hosted, sync_scoped, updated_at
|
|
1267
|
-
) VALUES (
|
|
1268
|
-
@context_graph_id, @name, @subscribed, @synced, @shared_memory_synced, @meta_synced,
|
|
1269
|
-
@on_chain_id, @on_chain_hash, @last_reconciled_ordinal, @core_hosted, @sync_scoped, @updated_at
|
|
1270
|
-
)
|
|
1271
|
-
ON CONFLICT(context_graph_id) DO UPDATE SET
|
|
1272
|
-
name = excluded.name,
|
|
1273
|
-
subscribed = excluded.subscribed,
|
|
1274
|
-
synced = excluded.synced,
|
|
1275
|
-
shared_memory_synced = excluded.shared_memory_synced,
|
|
1276
|
-
meta_synced = excluded.meta_synced,
|
|
1277
|
-
on_chain_id = excluded.on_chain_id,
|
|
1278
|
-
on_chain_hash = excluded.on_chain_hash,
|
|
1279
|
-
last_reconciled_ordinal = excluded.last_reconciled_ordinal,
|
|
1280
|
-
core_hosted = excluded.core_hosted,
|
|
1281
|
-
sync_scoped = excluded.sync_scoped,
|
|
1282
|
-
updated_at = excluded.updated_at
|
|
1286
|
+
this.stmt('upsertContextGraphSubscription', `
|
|
1287
|
+
INSERT INTO context_graph_subscriptions (
|
|
1288
|
+
context_graph_id, name, subscribed, synced, shared_memory_synced, meta_synced,
|
|
1289
|
+
on_chain_id, on_chain_hash, last_reconciled_ordinal, core_hosted, sync_scoped, updated_at
|
|
1290
|
+
) VALUES (
|
|
1291
|
+
@context_graph_id, @name, @subscribed, @synced, @shared_memory_synced, @meta_synced,
|
|
1292
|
+
@on_chain_id, @on_chain_hash, @last_reconciled_ordinal, @core_hosted, @sync_scoped, @updated_at
|
|
1293
|
+
)
|
|
1294
|
+
ON CONFLICT(context_graph_id) DO UPDATE SET
|
|
1295
|
+
name = excluded.name,
|
|
1296
|
+
subscribed = excluded.subscribed,
|
|
1297
|
+
synced = excluded.synced,
|
|
1298
|
+
shared_memory_synced = excluded.shared_memory_synced,
|
|
1299
|
+
meta_synced = excluded.meta_synced,
|
|
1300
|
+
on_chain_id = excluded.on_chain_id,
|
|
1301
|
+
on_chain_hash = excluded.on_chain_hash,
|
|
1302
|
+
last_reconciled_ordinal = excluded.last_reconciled_ordinal,
|
|
1303
|
+
core_hosted = excluded.core_hosted,
|
|
1304
|
+
sync_scoped = excluded.sync_scoped,
|
|
1305
|
+
updated_at = excluded.updated_at
|
|
1283
1306
|
`).run({
|
|
1284
1307
|
context_graph_id: record.context_graph_id,
|
|
1285
1308
|
name: record.name ?? null,
|
|
@@ -1377,37 +1400,37 @@ export class DashboardDB {
|
|
|
1377
1400
|
transition();
|
|
1378
1401
|
}
|
|
1379
1402
|
appendContextGraphJoinPolicyAudit(event) {
|
|
1380
|
-
this.stmt('appendContextGraphJoinPolicyAudit', `
|
|
1381
|
-
INSERT INTO context_graph_join_policy_audit (
|
|
1382
|
-
ts, context_graph_id, event_type, actor, agent_address, outcome,
|
|
1383
|
-
reason, request_digest, policy_version, details
|
|
1384
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1403
|
+
this.stmt('appendContextGraphJoinPolicyAudit', `
|
|
1404
|
+
INSERT INTO context_graph_join_policy_audit (
|
|
1405
|
+
ts, context_graph_id, event_type, actor, agent_address, outcome,
|
|
1406
|
+
reason, request_digest, policy_version, details
|
|
1407
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1385
1408
|
`).run(event.timestamp, event.contextGraphId, event.eventType, event.actor ?? null, event.agentAddress ?? null, event.outcome, event.reason ?? null, event.requestDigest ?? null, event.policyVersion ?? null, event.details ? JSON.stringify(event.details) : null);
|
|
1386
1409
|
}
|
|
1387
1410
|
reserveContextGraphAutomaticApproval(input) {
|
|
1388
1411
|
const reserve = this.db.transaction(() => {
|
|
1389
1412
|
const since = input.timestamp - 60 * 60 * 1000;
|
|
1390
|
-
const cgRow = this.stmt('countContextGraphAutomaticApprovalReservations', `
|
|
1391
|
-
SELECT COUNT(*) AS count
|
|
1392
|
-
FROM context_graph_join_approval_ledger
|
|
1393
|
-
WHERE context_graph_id = ?
|
|
1394
|
-
AND reserved_at >= ?
|
|
1413
|
+
const cgRow = this.stmt('countContextGraphAutomaticApprovalReservations', `
|
|
1414
|
+
SELECT COUNT(*) AS count
|
|
1415
|
+
FROM context_graph_join_approval_ledger
|
|
1416
|
+
WHERE context_graph_id = ?
|
|
1417
|
+
AND reserved_at >= ?
|
|
1395
1418
|
`).get(input.contextGraphId, since);
|
|
1396
|
-
const nodeRow = this.stmt('countNodeAutomaticApprovalReservations', `
|
|
1397
|
-
SELECT COUNT(*) AS count
|
|
1398
|
-
FROM context_graph_join_approval_ledger
|
|
1399
|
-
WHERE reserved_at >= ?
|
|
1419
|
+
const nodeRow = this.stmt('countNodeAutomaticApprovalReservations', `
|
|
1420
|
+
SELECT COUNT(*) AS count
|
|
1421
|
+
FROM context_graph_join_approval_ledger
|
|
1422
|
+
WHERE reserved_at >= ?
|
|
1400
1423
|
`).get(since);
|
|
1401
1424
|
const contextGraphCount = Number(cgRow?.count ?? 0);
|
|
1402
1425
|
const nodeCount = Number(nodeRow?.count ?? 0);
|
|
1403
|
-
const existing = this.stmt('findContextGraphAutomaticApprovalReservation', `
|
|
1404
|
-
SELECT 1 AS found
|
|
1405
|
-
FROM context_graph_join_approval_ledger
|
|
1406
|
-
WHERE context_graph_id = ?
|
|
1407
|
-
AND request_digest = ?
|
|
1408
|
-
AND policy_epoch = ?
|
|
1409
|
-
AND reserved_at >= ?
|
|
1410
|
-
LIMIT 1
|
|
1426
|
+
const existing = this.stmt('findContextGraphAutomaticApprovalReservation', `
|
|
1427
|
+
SELECT 1 AS found
|
|
1428
|
+
FROM context_graph_join_approval_ledger
|
|
1429
|
+
WHERE context_graph_id = ?
|
|
1430
|
+
AND request_digest = ?
|
|
1431
|
+
AND policy_epoch = ?
|
|
1432
|
+
AND reserved_at >= ?
|
|
1433
|
+
LIMIT 1
|
|
1411
1434
|
`).get(input.contextGraphId, input.requestDigest, input.policyEpoch, since);
|
|
1412
1435
|
if (existing) {
|
|
1413
1436
|
return {
|
|
@@ -1437,16 +1460,16 @@ export class DashboardDB {
|
|
|
1437
1460
|
// reservation without durable operational state. An expired replay of
|
|
1438
1461
|
// the same digest+epoch refreshes its reservation timestamp and consumes
|
|
1439
1462
|
// the rolling-window quota again, matching the former append-only model.
|
|
1440
|
-
this.stmt('upsertContextGraphAutomaticApprovalReservation', `
|
|
1441
|
-
INSERT INTO context_graph_join_approval_ledger (
|
|
1442
|
-
context_graph_id, request_digest, policy_epoch, reserved_at,
|
|
1443
|
-
state, committed_at, actor, agent_address, policy_version
|
|
1444
|
-
) VALUES (?, ?, ?, ?, 'reserved', NULL, ?, ?, ?)
|
|
1445
|
-
ON CONFLICT(context_graph_id, request_digest, policy_epoch) DO UPDATE SET
|
|
1446
|
-
reserved_at = excluded.reserved_at,
|
|
1447
|
-
actor = excluded.actor,
|
|
1448
|
-
agent_address = excluded.agent_address,
|
|
1449
|
-
policy_version = excluded.policy_version
|
|
1463
|
+
this.stmt('upsertContextGraphAutomaticApprovalReservation', `
|
|
1464
|
+
INSERT INTO context_graph_join_approval_ledger (
|
|
1465
|
+
context_graph_id, request_digest, policy_epoch, reserved_at,
|
|
1466
|
+
state, committed_at, actor, agent_address, policy_version
|
|
1467
|
+
) VALUES (?, ?, ?, ?, 'reserved', NULL, ?, ?, ?)
|
|
1468
|
+
ON CONFLICT(context_graph_id, request_digest, policy_epoch) DO UPDATE SET
|
|
1469
|
+
reserved_at = excluded.reserved_at,
|
|
1470
|
+
actor = excluded.actor,
|
|
1471
|
+
agent_address = excluded.agent_address,
|
|
1472
|
+
policy_version = excluded.policy_version
|
|
1450
1473
|
`).run(input.contextGraphId, input.requestDigest, input.policyEpoch, input.timestamp, input.actor, input.agentAddress, input.policyVersion);
|
|
1451
1474
|
this.appendContextGraphJoinPolicyAudit({
|
|
1452
1475
|
timestamp: input.timestamp,
|
|
@@ -1472,26 +1495,26 @@ export class DashboardDB {
|
|
|
1472
1495
|
return reserve();
|
|
1473
1496
|
}
|
|
1474
1497
|
markContextGraphAutomaticApprovalRepairPending(input) {
|
|
1475
|
-
const marked = this.stmt('markContextGraphAutomaticApprovalRepairPending', `
|
|
1476
|
-
UPDATE context_graph_join_approval_ledger
|
|
1477
|
-
SET repair_pending = 1
|
|
1478
|
-
WHERE context_graph_id = ?
|
|
1479
|
-
AND request_digest = ?
|
|
1480
|
-
AND policy_epoch = ?
|
|
1481
|
-
AND state = 'reserved'
|
|
1498
|
+
const marked = this.stmt('markContextGraphAutomaticApprovalRepairPending', `
|
|
1499
|
+
UPDATE context_graph_join_approval_ledger
|
|
1500
|
+
SET repair_pending = 1
|
|
1501
|
+
WHERE context_graph_id = ?
|
|
1502
|
+
AND request_digest = ?
|
|
1503
|
+
AND policy_epoch = ?
|
|
1504
|
+
AND state = 'reserved'
|
|
1482
1505
|
`).run(input.contextGraphId, input.requestDigest, input.policyEpoch);
|
|
1483
1506
|
return marked.changes === 1;
|
|
1484
1507
|
}
|
|
1485
1508
|
getContextGraphAutomaticApprovalRepair(contextGraphId, requestDigest) {
|
|
1486
|
-
const repair = this.stmt('getContextGraphAutomaticApprovalRepair', `
|
|
1487
|
-
SELECT policy_epoch, actor, agent_address
|
|
1488
|
-
FROM context_graph_join_approval_ledger
|
|
1489
|
-
WHERE context_graph_id = ?
|
|
1490
|
-
AND request_digest = ?
|
|
1491
|
-
AND state = 'reserved'
|
|
1492
|
-
AND repair_pending = 1
|
|
1493
|
-
ORDER BY reserved_at DESC
|
|
1494
|
-
LIMIT 1
|
|
1509
|
+
const repair = this.stmt('getContextGraphAutomaticApprovalRepair', `
|
|
1510
|
+
SELECT policy_epoch, actor, agent_address
|
|
1511
|
+
FROM context_graph_join_approval_ledger
|
|
1512
|
+
WHERE context_graph_id = ?
|
|
1513
|
+
AND request_digest = ?
|
|
1514
|
+
AND state = 'reserved'
|
|
1515
|
+
AND repair_pending = 1
|
|
1516
|
+
ORDER BY reserved_at DESC
|
|
1517
|
+
LIMIT 1
|
|
1495
1518
|
`).get(contextGraphId, requestDigest);
|
|
1496
1519
|
return repair
|
|
1497
1520
|
? {
|
|
@@ -1503,26 +1526,26 @@ export class DashboardDB {
|
|
|
1503
1526
|
}
|
|
1504
1527
|
commitContextGraphAutomaticApproval(input) {
|
|
1505
1528
|
const commit = this.db.transaction(() => {
|
|
1506
|
-
const reservation = this.stmt('findExactContextGraphAutomaticApprovalReservation', `
|
|
1507
|
-
SELECT context_graph_id, request_digest, policy_epoch, reserved_at,
|
|
1508
|
-
state, committed_at, actor, agent_address, policy_version
|
|
1509
|
-
FROM context_graph_join_approval_ledger
|
|
1510
|
-
WHERE context_graph_id = ?
|
|
1511
|
-
AND request_digest = ?
|
|
1512
|
-
AND policy_epoch = ?
|
|
1513
|
-
LIMIT 1
|
|
1529
|
+
const reservation = this.stmt('findExactContextGraphAutomaticApprovalReservation', `
|
|
1530
|
+
SELECT context_graph_id, request_digest, policy_epoch, reserved_at,
|
|
1531
|
+
state, committed_at, actor, agent_address, policy_version
|
|
1532
|
+
FROM context_graph_join_approval_ledger
|
|
1533
|
+
WHERE context_graph_id = ?
|
|
1534
|
+
AND request_digest = ?
|
|
1535
|
+
AND policy_epoch = ?
|
|
1536
|
+
LIMIT 1
|
|
1514
1537
|
`).get(input.contextGraphId, input.requestDigest, input.policyEpoch);
|
|
1515
1538
|
if (!reservation)
|
|
1516
1539
|
return false;
|
|
1517
1540
|
if (reservation.state === 'committed')
|
|
1518
1541
|
return true;
|
|
1519
|
-
const updated = this.stmt('commitContextGraphAutomaticApprovalReservation', `
|
|
1520
|
-
UPDATE context_graph_join_approval_ledger
|
|
1521
|
-
SET state = 'committed', committed_at = ?, repair_pending = 0
|
|
1522
|
-
WHERE context_graph_id = ?
|
|
1523
|
-
AND request_digest = ?
|
|
1524
|
-
AND policy_epoch = ?
|
|
1525
|
-
AND state = 'reserved'
|
|
1542
|
+
const updated = this.stmt('commitContextGraphAutomaticApprovalReservation', `
|
|
1543
|
+
UPDATE context_graph_join_approval_ledger
|
|
1544
|
+
SET state = 'committed', committed_at = ?, repair_pending = 0
|
|
1545
|
+
WHERE context_graph_id = ?
|
|
1546
|
+
AND request_digest = ?
|
|
1547
|
+
AND policy_epoch = ?
|
|
1548
|
+
AND state = 'reserved'
|
|
1526
1549
|
`).run(input.timestamp, reservation.context_graph_id, reservation.request_digest, reservation.policy_epoch);
|
|
1527
1550
|
if (updated.changes !== 1)
|
|
1528
1551
|
return false;
|
|
@@ -1546,48 +1569,48 @@ export class DashboardDB {
|
|
|
1546
1569
|
}
|
|
1547
1570
|
getContextGraphAutomaticApprovalUsage(contextGraphId, timestamp) {
|
|
1548
1571
|
const since = timestamp - 60 * 60 * 1000;
|
|
1549
|
-
const contextGraphApprovalsLastHour = Number(this.stmt('getContextGraphAutomaticApprovalUsage', `
|
|
1550
|
-
SELECT COUNT(*) AS count
|
|
1551
|
-
FROM context_graph_join_approval_ledger
|
|
1552
|
-
WHERE context_graph_id = ?
|
|
1553
|
-
AND reserved_at >= ?
|
|
1572
|
+
const contextGraphApprovalsLastHour = Number(this.stmt('getContextGraphAutomaticApprovalUsage', `
|
|
1573
|
+
SELECT COUNT(*) AS count
|
|
1574
|
+
FROM context_graph_join_approval_ledger
|
|
1575
|
+
WHERE context_graph_id = ?
|
|
1576
|
+
AND reserved_at >= ?
|
|
1554
1577
|
`).get(contextGraphId, since)?.count ?? 0);
|
|
1555
|
-
const nodeApprovalsLastHour = Number(this.stmt('getNodeAutomaticApprovalUsage', `
|
|
1556
|
-
SELECT COUNT(*) AS count
|
|
1557
|
-
FROM context_graph_join_approval_ledger
|
|
1558
|
-
WHERE reserved_at >= ?
|
|
1578
|
+
const nodeApprovalsLastHour = Number(this.stmt('getNodeAutomaticApprovalUsage', `
|
|
1579
|
+
SELECT COUNT(*) AS count
|
|
1580
|
+
FROM context_graph_join_approval_ledger
|
|
1581
|
+
WHERE reserved_at >= ?
|
|
1559
1582
|
`).get(since)?.count ?? 0);
|
|
1560
1583
|
return { contextGraphApprovalsLastHour, nodeApprovalsLastHour };
|
|
1561
1584
|
}
|
|
1562
1585
|
listContextGraphJoinPolicyAudit(contextGraphId) {
|
|
1563
|
-
return this.stmt('listContextGraphJoinPolicyAudit', `
|
|
1564
|
-
SELECT id, ts, context_graph_id, event_type, actor, agent_address,
|
|
1565
|
-
outcome, reason, request_digest, policy_version, details
|
|
1566
|
-
FROM context_graph_join_policy_audit
|
|
1567
|
-
WHERE context_graph_id = ?
|
|
1568
|
-
ORDER BY id ASC
|
|
1586
|
+
return this.stmt('listContextGraphJoinPolicyAudit', `
|
|
1587
|
+
SELECT id, ts, context_graph_id, event_type, actor, agent_address,
|
|
1588
|
+
outcome, reason, request_digest, policy_version, details
|
|
1589
|
+
FROM context_graph_join_policy_audit
|
|
1590
|
+
WHERE context_graph_id = ?
|
|
1591
|
+
ORDER BY id ASC
|
|
1569
1592
|
`).all(contextGraphId);
|
|
1570
1593
|
}
|
|
1571
1594
|
contextGraphJoinPolicyKey(contextGraphId) {
|
|
1572
1595
|
return `contextGraphJoinPolicy:${contextGraphId}`;
|
|
1573
1596
|
}
|
|
1574
1597
|
upsertVmReconcileNegative(record) {
|
|
1575
|
-
this.stmt('upsertVmReconcileNegative', `
|
|
1576
|
-
INSERT INTO vm_reconcile_negative_cache (
|
|
1577
|
-
cache_key, context_graph_id, failures, next_retry_at, swm_gen,
|
|
1578
|
-
candidate_namespaces, peer_topology_key, updated_at
|
|
1579
|
-
) VALUES (
|
|
1580
|
-
@cache_key, @context_graph_id, @failures, @next_retry_at, @swm_gen,
|
|
1581
|
-
@candidate_namespaces, @peer_topology_key, @updated_at
|
|
1582
|
-
)
|
|
1583
|
-
ON CONFLICT(cache_key) DO UPDATE SET
|
|
1584
|
-
context_graph_id = excluded.context_graph_id,
|
|
1585
|
-
failures = excluded.failures,
|
|
1586
|
-
next_retry_at = excluded.next_retry_at,
|
|
1587
|
-
swm_gen = excluded.swm_gen,
|
|
1588
|
-
candidate_namespaces = excluded.candidate_namespaces,
|
|
1589
|
-
peer_topology_key = excluded.peer_topology_key,
|
|
1590
|
-
updated_at = excluded.updated_at
|
|
1598
|
+
this.stmt('upsertVmReconcileNegative', `
|
|
1599
|
+
INSERT INTO vm_reconcile_negative_cache (
|
|
1600
|
+
cache_key, context_graph_id, failures, next_retry_at, swm_gen,
|
|
1601
|
+
candidate_namespaces, peer_topology_key, updated_at
|
|
1602
|
+
) VALUES (
|
|
1603
|
+
@cache_key, @context_graph_id, @failures, @next_retry_at, @swm_gen,
|
|
1604
|
+
@candidate_namespaces, @peer_topology_key, @updated_at
|
|
1605
|
+
)
|
|
1606
|
+
ON CONFLICT(cache_key) DO UPDATE SET
|
|
1607
|
+
context_graph_id = excluded.context_graph_id,
|
|
1608
|
+
failures = excluded.failures,
|
|
1609
|
+
next_retry_at = excluded.next_retry_at,
|
|
1610
|
+
swm_gen = excluded.swm_gen,
|
|
1611
|
+
candidate_namespaces = excluded.candidate_namespaces,
|
|
1612
|
+
peer_topology_key = excluded.peer_topology_key,
|
|
1613
|
+
updated_at = excluded.updated_at
|
|
1591
1614
|
`).run(record);
|
|
1592
1615
|
}
|
|
1593
1616
|
getVmReconcileNegative(cacheKey) {
|
|
@@ -1606,14 +1629,14 @@ export class DashboardDB {
|
|
|
1606
1629
|
* coerced defensively so a malformed event can never abort the insert.
|
|
1607
1630
|
*/
|
|
1608
1631
|
insertReplicationEvent(ev) {
|
|
1609
|
-
this.stmt('insertReplicationEvent', `
|
|
1610
|
-
INSERT INTO replication_events (
|
|
1611
|
-
ts, context_graph_id, on_chain_cg_id, action, ual, ordinal, ka_id,
|
|
1612
|
-
from_watermark, to_watermark, head, reconciled, pending, detail
|
|
1613
|
-
) VALUES (
|
|
1614
|
-
@ts, @context_graph_id, @on_chain_cg_id, @action, @ual, @ordinal, @ka_id,
|
|
1615
|
-
@from_watermark, @to_watermark, @head, @reconciled, @pending, @detail
|
|
1616
|
-
)
|
|
1632
|
+
this.stmt('insertReplicationEvent', `
|
|
1633
|
+
INSERT INTO replication_events (
|
|
1634
|
+
ts, context_graph_id, on_chain_cg_id, action, ual, ordinal, ka_id,
|
|
1635
|
+
from_watermark, to_watermark, head, reconciled, pending, detail
|
|
1636
|
+
) VALUES (
|
|
1637
|
+
@ts, @context_graph_id, @on_chain_cg_id, @action, @ual, @ordinal, @ka_id,
|
|
1638
|
+
@from_watermark, @to_watermark, @head, @reconciled, @pending, @detail
|
|
1639
|
+
)
|
|
1617
1640
|
`).run({
|
|
1618
1641
|
ts: ev.ts,
|
|
1619
1642
|
context_graph_id: ev.context_graph_id,
|
|
@@ -1660,15 +1683,15 @@ export class DashboardDB {
|
|
|
1660
1683
|
// and inflate the reported latency. fetch/promote for one version always
|
|
1661
1684
|
// share the same ordinal, so this pairs them exactly; a fast-path promote
|
|
1662
1685
|
// with no fetch for its ordinal correctly contributes a 0ms latency.
|
|
1663
|
-
const latencyRows = this.db.prepare(`
|
|
1664
|
-
SELECT p.ts AS promote_ts,
|
|
1665
|
-
(SELECT MAX(f.ts) FROM replication_events f
|
|
1666
|
-
WHERE f.action = 'fetch'
|
|
1667
|
-
AND f.context_graph_id = p.context_graph_id
|
|
1668
|
-
AND f.ordinal = p.ordinal
|
|
1669
|
-
AND f.ts <= p.ts AND f.ts >= ?) AS fetch_ts
|
|
1670
|
-
FROM replication_events p
|
|
1671
|
-
WHERE p.action = 'promote' AND p.ts >= ? AND p.ordinal IS NOT NULL
|
|
1686
|
+
const latencyRows = this.db.prepare(`
|
|
1687
|
+
SELECT p.ts AS promote_ts,
|
|
1688
|
+
(SELECT MAX(f.ts) FROM replication_events f
|
|
1689
|
+
WHERE f.action = 'fetch'
|
|
1690
|
+
AND f.context_graph_id = p.context_graph_id
|
|
1691
|
+
AND f.ordinal = p.ordinal
|
|
1692
|
+
AND f.ts <= p.ts AND f.ts >= ?) AS fetch_ts
|
|
1693
|
+
FROM replication_events p
|
|
1694
|
+
WHERE p.action = 'promote' AND p.ts >= ? AND p.ordinal IS NOT NULL
|
|
1672
1695
|
`).all(cutoff, cutoff);
|
|
1673
1696
|
const latencies = latencyRows
|
|
1674
1697
|
.map((r) => (r.fetch_ts != null ? r.promote_ts - r.fetch_ts : 0))
|
|
@@ -1698,20 +1721,20 @@ export class DashboardDB {
|
|
|
1698
1721
|
}
|
|
1699
1722
|
_computeReplicationPerCg(periodMs) {
|
|
1700
1723
|
const cutoff = Date.now() - periodMs;
|
|
1701
|
-
return this.db.prepare(`
|
|
1702
|
-
SELECT context_graph_id,
|
|
1703
|
-
on_chain_cg_id,
|
|
1704
|
-
SUM(CASE WHEN action = 'promote' THEN 1 ELSE 0 END) AS promotes,
|
|
1705
|
-
SUM(CASE WHEN action = 'fetch' THEN 1 ELSE 0 END) AS fetches,
|
|
1706
|
-
SUM(CASE WHEN action = 'defer' THEN 1 ELSE 0 END) AS defers,
|
|
1707
|
-
SUM(CASE WHEN action = 'cursor-advance' THEN 1 ELSE 0 END) AS cursor_advances,
|
|
1708
|
-
MAX(to_watermark) AS last_watermark,
|
|
1709
|
-
MAX(head) AS last_head,
|
|
1710
|
-
MAX(ts) AS last_event_ts
|
|
1711
|
-
FROM replication_events
|
|
1712
|
-
WHERE ts >= ?
|
|
1713
|
-
GROUP BY context_graph_id, on_chain_cg_id
|
|
1714
|
-
ORDER BY last_event_ts DESC
|
|
1724
|
+
return this.db.prepare(`
|
|
1725
|
+
SELECT context_graph_id,
|
|
1726
|
+
on_chain_cg_id,
|
|
1727
|
+
SUM(CASE WHEN action = 'promote' THEN 1 ELSE 0 END) AS promotes,
|
|
1728
|
+
SUM(CASE WHEN action = 'fetch' THEN 1 ELSE 0 END) AS fetches,
|
|
1729
|
+
SUM(CASE WHEN action = 'defer' THEN 1 ELSE 0 END) AS defers,
|
|
1730
|
+
SUM(CASE WHEN action = 'cursor-advance' THEN 1 ELSE 0 END) AS cursor_advances,
|
|
1731
|
+
MAX(to_watermark) AS last_watermark,
|
|
1732
|
+
MAX(head) AS last_head,
|
|
1733
|
+
MAX(ts) AS last_event_ts
|
|
1734
|
+
FROM replication_events
|
|
1735
|
+
WHERE ts >= ?
|
|
1736
|
+
GROUP BY context_graph_id, on_chain_cg_id
|
|
1737
|
+
ORDER BY last_event_ts DESC
|
|
1715
1738
|
`).all(cutoff);
|
|
1716
1739
|
}
|
|
1717
1740
|
/**
|
|
@@ -1730,16 +1753,16 @@ export class DashboardDB {
|
|
|
1730
1753
|
const cutoff = Date.now() - opts.periodMs;
|
|
1731
1754
|
const bucket = Math.max(1, Math.floor(opts.bucketMs));
|
|
1732
1755
|
const where = opts.contextGraphId ? 'AND context_graph_id = @cg' : '';
|
|
1733
|
-
return this.db.prepare(`
|
|
1734
|
-
SELECT (ts / @bucket) * @bucket AS bucket,
|
|
1735
|
-
SUM(CASE WHEN action = 'promote' THEN 1 ELSE 0 END) AS promotes,
|
|
1736
|
-
SUM(CASE WHEN action = 'fetch' THEN 1 ELSE 0 END) AS fetches,
|
|
1737
|
-
SUM(CASE WHEN action = 'defer' THEN 1 ELSE 0 END) AS defers,
|
|
1738
|
-
COUNT(*) AS total
|
|
1739
|
-
FROM replication_events
|
|
1740
|
-
WHERE ts >= @cutoff ${where}
|
|
1741
|
-
GROUP BY bucket
|
|
1742
|
-
ORDER BY bucket ASC
|
|
1756
|
+
return this.db.prepare(`
|
|
1757
|
+
SELECT (ts / @bucket) * @bucket AS bucket,
|
|
1758
|
+
SUM(CASE WHEN action = 'promote' THEN 1 ELSE 0 END) AS promotes,
|
|
1759
|
+
SUM(CASE WHEN action = 'fetch' THEN 1 ELSE 0 END) AS fetches,
|
|
1760
|
+
SUM(CASE WHEN action = 'defer' THEN 1 ELSE 0 END) AS defers,
|
|
1761
|
+
COUNT(*) AS total
|
|
1762
|
+
FROM replication_events
|
|
1763
|
+
WHERE ts >= @cutoff ${where}
|
|
1764
|
+
GROUP BY bucket
|
|
1765
|
+
ORDER BY bucket ASC
|
|
1743
1766
|
`).all({ cutoff, bucket, cg: opts.contextGraphId ?? null });
|
|
1744
1767
|
}
|
|
1745
1768
|
/**
|
|
@@ -1751,13 +1774,13 @@ export class DashboardDB {
|
|
|
1751
1774
|
const safeLimit = Number.isFinite(limit)
|
|
1752
1775
|
? Math.max(1, Math.min(1000, Math.trunc(limit)))
|
|
1753
1776
|
: 100;
|
|
1754
|
-
return this.db.prepare(`
|
|
1755
|
-
SELECT ts, context_graph_id, on_chain_cg_id, action, ual, ordinal, ka_id,
|
|
1756
|
-
from_watermark, to_watermark, head, reconciled, pending, detail
|
|
1757
|
-
FROM replication_events
|
|
1758
|
-
WHERE context_graph_id = ?
|
|
1759
|
-
ORDER BY ts DESC
|
|
1760
|
-
LIMIT ?
|
|
1777
|
+
return this.db.prepare(`
|
|
1778
|
+
SELECT ts, context_graph_id, on_chain_cg_id, action, ual, ordinal, ka_id,
|
|
1779
|
+
from_watermark, to_watermark, head, reconciled, pending, detail
|
|
1780
|
+
FROM replication_events
|
|
1781
|
+
WHERE context_graph_id = ?
|
|
1782
|
+
ORDER BY ts DESC
|
|
1783
|
+
LIMIT ?
|
|
1761
1784
|
`).all(contextGraphId, safeLimit);
|
|
1762
1785
|
}
|
|
1763
1786
|
/**
|
|
@@ -1766,38 +1789,38 @@ export class DashboardDB {
|
|
|
1766
1789
|
* on-disk truth the Replication tab's cursor inspector renders.
|
|
1767
1790
|
*/
|
|
1768
1791
|
getReplicationCursors() {
|
|
1769
|
-
return this.db.prepare(`
|
|
1770
|
-
SELECT s.context_graph_id AS context_graph_id,
|
|
1771
|
-
s.on_chain_id AS on_chain_id,
|
|
1772
|
-
s.last_reconciled_ordinal AS last_reconciled_ordinal,
|
|
1773
|
-
s.core_hosted AS core_hosted,
|
|
1774
|
-
s.subscribed AS subscribed,
|
|
1775
|
-
(SELECT MAX(head) FROM replication_events e
|
|
1776
|
-
WHERE e.context_graph_id = s.context_graph_id) AS last_head,
|
|
1777
|
-
(SELECT MAX(ts) FROM replication_events e
|
|
1778
|
-
WHERE e.context_graph_id = s.context_graph_id) AS last_event_ts
|
|
1779
|
-
FROM context_graph_subscriptions s
|
|
1780
|
-
ORDER BY s.context_graph_id ASC
|
|
1792
|
+
return this.db.prepare(`
|
|
1793
|
+
SELECT s.context_graph_id AS context_graph_id,
|
|
1794
|
+
s.on_chain_id AS on_chain_id,
|
|
1795
|
+
s.last_reconciled_ordinal AS last_reconciled_ordinal,
|
|
1796
|
+
s.core_hosted AS core_hosted,
|
|
1797
|
+
s.subscribed AS subscribed,
|
|
1798
|
+
(SELECT MAX(head) FROM replication_events e
|
|
1799
|
+
WHERE e.context_graph_id = s.context_graph_id) AS last_head,
|
|
1800
|
+
(SELECT MAX(ts) FROM replication_events e
|
|
1801
|
+
WHERE e.context_graph_id = s.context_graph_id) AS last_event_ts
|
|
1802
|
+
FROM context_graph_subscriptions s
|
|
1803
|
+
ORDER BY s.context_graph_id ASC
|
|
1781
1804
|
`).all();
|
|
1782
1805
|
}
|
|
1783
1806
|
upsertContextGraphMember(record) {
|
|
1784
1807
|
const firstSeenAt = record.first_seen_at ?? record.updated_at;
|
|
1785
|
-
this.stmt('upsertContextGraphMember', `
|
|
1786
|
-
INSERT INTO context_graph_memberships (
|
|
1787
|
-
context_graph_id, principal_type, principal_id, role, status, source,
|
|
1788
|
-
display_name, metadata, first_seen_at, updated_at
|
|
1789
|
-
) VALUES (
|
|
1790
|
-
@context_graph_id, @principal_type, @principal_id, @role, @status, @source,
|
|
1791
|
-
@display_name, @metadata, @first_seen_at, @updated_at
|
|
1792
|
-
)
|
|
1793
|
-
ON CONFLICT(context_graph_id, principal_type, principal_id) DO UPDATE SET
|
|
1794
|
-
role = excluded.role,
|
|
1795
|
-
status = excluded.status,
|
|
1796
|
-
source = excluded.source,
|
|
1797
|
-
display_name = excluded.display_name,
|
|
1798
|
-
metadata = excluded.metadata,
|
|
1799
|
-
first_seen_at = context_graph_memberships.first_seen_at,
|
|
1800
|
-
updated_at = excluded.updated_at
|
|
1808
|
+
this.stmt('upsertContextGraphMember', `
|
|
1809
|
+
INSERT INTO context_graph_memberships (
|
|
1810
|
+
context_graph_id, principal_type, principal_id, role, status, source,
|
|
1811
|
+
display_name, metadata, first_seen_at, updated_at
|
|
1812
|
+
) VALUES (
|
|
1813
|
+
@context_graph_id, @principal_type, @principal_id, @role, @status, @source,
|
|
1814
|
+
@display_name, @metadata, @first_seen_at, @updated_at
|
|
1815
|
+
)
|
|
1816
|
+
ON CONFLICT(context_graph_id, principal_type, principal_id) DO UPDATE SET
|
|
1817
|
+
role = excluded.role,
|
|
1818
|
+
status = excluded.status,
|
|
1819
|
+
source = excluded.source,
|
|
1820
|
+
display_name = excluded.display_name,
|
|
1821
|
+
metadata = excluded.metadata,
|
|
1822
|
+
first_seen_at = context_graph_memberships.first_seen_at,
|
|
1823
|
+
updated_at = excluded.updated_at
|
|
1801
1824
|
`).run({
|
|
1802
1825
|
context_graph_id: record.context_graph_id,
|
|
1803
1826
|
principal_type: record.principal_type,
|
|
@@ -1813,15 +1836,15 @@ export class DashboardDB {
|
|
|
1813
1836
|
}
|
|
1814
1837
|
listContextGraphMembers(contextGraphId) {
|
|
1815
1838
|
if (contextGraphId) {
|
|
1816
|
-
return this.db.prepare(`
|
|
1817
|
-
SELECT * FROM context_graph_memberships
|
|
1818
|
-
WHERE context_graph_id = ?
|
|
1819
|
-
ORDER BY principal_type ASC, principal_id ASC
|
|
1839
|
+
return this.db.prepare(`
|
|
1840
|
+
SELECT * FROM context_graph_memberships
|
|
1841
|
+
WHERE context_graph_id = ?
|
|
1842
|
+
ORDER BY principal_type ASC, principal_id ASC
|
|
1820
1843
|
`).all(contextGraphId);
|
|
1821
1844
|
}
|
|
1822
|
-
return this.db.prepare(`
|
|
1823
|
-
SELECT * FROM context_graph_memberships
|
|
1824
|
-
ORDER BY context_graph_id ASC, principal_type ASC, principal_id ASC
|
|
1845
|
+
return this.db.prepare(`
|
|
1846
|
+
SELECT * FROM context_graph_memberships
|
|
1847
|
+
ORDER BY context_graph_id ASC, principal_type ASC, principal_id ASC
|
|
1825
1848
|
`).all();
|
|
1826
1849
|
}
|
|
1827
1850
|
deleteContextGraphMember(contextGraphId, principalType, principalId) {
|
|
@@ -1833,18 +1856,18 @@ export class DashboardDB {
|
|
|
1833
1856
|
return this.db.prepare('SELECT * FROM metric_snapshots WHERE ts >= ? AND ts <= ? ORDER BY ts').all(from, to);
|
|
1834
1857
|
}
|
|
1835
1858
|
const step = Math.ceil(total.c / maxPoints);
|
|
1836
|
-
return this.db.prepare(`
|
|
1837
|
-
SELECT * FROM (
|
|
1838
|
-
SELECT *, ROW_NUMBER() OVER (ORDER BY ts) as rn
|
|
1839
|
-
FROM metric_snapshots WHERE ts >= ? AND ts <= ?
|
|
1840
|
-
) WHERE rn % ? = 0 ORDER BY ts
|
|
1859
|
+
return this.db.prepare(`
|
|
1860
|
+
SELECT * FROM (
|
|
1861
|
+
SELECT *, ROW_NUMBER() OVER (ORDER BY ts) as rn
|
|
1862
|
+
FROM metric_snapshots WHERE ts >= ? AND ts <= ?
|
|
1863
|
+
) WHERE rn % ? = 0 ORDER BY ts
|
|
1841
1864
|
`).all(from, to, step);
|
|
1842
1865
|
}
|
|
1843
1866
|
// --- Operations ---
|
|
1844
1867
|
insertOperation(op) {
|
|
1845
|
-
this.stmt('insertOp', `
|
|
1846
|
-
INSERT INTO operations (operation_id, operation_name, started_at, status, peer_id, contextGraph_id, details)
|
|
1847
|
-
VALUES (@operation_id, @operation_name, @started_at, 'in_progress', @peer_id, @contextGraph_id, @details)
|
|
1868
|
+
this.stmt('insertOp', `
|
|
1869
|
+
INSERT INTO operations (operation_id, operation_name, started_at, status, peer_id, contextGraph_id, details)
|
|
1870
|
+
VALUES (@operation_id, @operation_name, @started_at, 'in_progress', @peer_id, @contextGraph_id, @details)
|
|
1848
1871
|
`).run({
|
|
1849
1872
|
operation_id: op.operation_id,
|
|
1850
1873
|
operation_name: op.operation_name,
|
|
@@ -1855,10 +1878,10 @@ export class DashboardDB {
|
|
|
1855
1878
|
});
|
|
1856
1879
|
}
|
|
1857
1880
|
completeOperation(op) {
|
|
1858
|
-
this.stmt('completeOp', `
|
|
1859
|
-
UPDATE operations SET status = 'success', duration_ms = @duration_ms,
|
|
1860
|
-
triple_count = @triple_count, details = COALESCE(@details, details)
|
|
1861
|
-
WHERE operation_id = @operation_id AND status = 'in_progress'
|
|
1881
|
+
this.stmt('completeOp', `
|
|
1882
|
+
UPDATE operations SET status = 'success', duration_ms = @duration_ms,
|
|
1883
|
+
triple_count = @triple_count, details = COALESCE(@details, details)
|
|
1884
|
+
WHERE operation_id = @operation_id AND status = 'in_progress'
|
|
1862
1885
|
`).run({
|
|
1863
1886
|
operation_id: op.operation_id,
|
|
1864
1887
|
duration_ms: op.duration_ms,
|
|
@@ -1867,10 +1890,10 @@ export class DashboardDB {
|
|
|
1867
1890
|
});
|
|
1868
1891
|
}
|
|
1869
1892
|
failOperation(op) {
|
|
1870
|
-
this.stmt('failOp', `
|
|
1871
|
-
UPDATE operations SET status = 'error', duration_ms = @duration_ms,
|
|
1872
|
-
error_message = @error_message
|
|
1873
|
-
WHERE operation_id = @operation_id AND status = 'in_progress'
|
|
1893
|
+
this.stmt('failOp', `
|
|
1894
|
+
UPDATE operations SET status = 'error', duration_ms = @duration_ms,
|
|
1895
|
+
error_message = @error_message
|
|
1896
|
+
WHERE operation_id = @operation_id AND status = 'in_progress'
|
|
1874
1897
|
`).run(op);
|
|
1875
1898
|
}
|
|
1876
1899
|
getOperations(opts = {}) {
|
|
@@ -1927,22 +1950,22 @@ export class DashboardDB {
|
|
|
1927
1950
|
}
|
|
1928
1951
|
getErrorHotspots(periodMs = 7 * 86_400_000) {
|
|
1929
1952
|
const cutoff = Date.now() - periodMs;
|
|
1930
|
-
return this.db.prepare(`
|
|
1931
|
-
SELECT
|
|
1932
|
-
p.phase,
|
|
1933
|
-
o.operation_name,
|
|
1934
|
-
COUNT(*) as error_count,
|
|
1935
|
-
(SELECT p2.details FROM operation_phases p2
|
|
1936
|
-
JOIN operations o2 ON o2.operation_id = p2.operation_id
|
|
1937
|
-
WHERE p2.phase = p.phase AND o2.operation_name = o.operation_name
|
|
1938
|
-
AND p2.status = 'error' AND p2.started_at >= ?
|
|
1939
|
-
ORDER BY p2.started_at DESC LIMIT 1) as last_error,
|
|
1940
|
-
MAX(p.started_at) as last_occurred
|
|
1941
|
-
FROM operation_phases p
|
|
1942
|
-
JOIN operations o ON o.operation_id = p.operation_id
|
|
1943
|
-
WHERE p.status = 'error' AND p.started_at >= ?
|
|
1944
|
-
GROUP BY p.phase, o.operation_name
|
|
1945
|
-
ORDER BY error_count DESC
|
|
1953
|
+
return this.db.prepare(`
|
|
1954
|
+
SELECT
|
|
1955
|
+
p.phase,
|
|
1956
|
+
o.operation_name,
|
|
1957
|
+
COUNT(*) as error_count,
|
|
1958
|
+
(SELECT p2.details FROM operation_phases p2
|
|
1959
|
+
JOIN operations o2 ON o2.operation_id = p2.operation_id
|
|
1960
|
+
WHERE p2.phase = p.phase AND o2.operation_name = o.operation_name
|
|
1961
|
+
AND p2.status = 'error' AND p2.started_at >= ?
|
|
1962
|
+
ORDER BY p2.started_at DESC LIMIT 1) as last_error,
|
|
1963
|
+
MAX(p.started_at) as last_occurred
|
|
1964
|
+
FROM operation_phases p
|
|
1965
|
+
JOIN operations o ON o.operation_id = p.operation_id
|
|
1966
|
+
WHERE p.status = 'error' AND p.started_at >= ?
|
|
1967
|
+
GROUP BY p.phase, o.operation_name
|
|
1968
|
+
ORDER BY error_count DESC
|
|
1946
1969
|
`).all(cutoff, cutoff);
|
|
1947
1970
|
}
|
|
1948
1971
|
getFailedOperations(opts = {}) {
|
|
@@ -1964,17 +1987,17 @@ export class DashboardDB {
|
|
|
1964
1987
|
params.push(like, like, like);
|
|
1965
1988
|
}
|
|
1966
1989
|
params.push(limit);
|
|
1967
|
-
const rows = this.db.prepare(`
|
|
1968
|
-
SELECT
|
|
1969
|
-
o.*,
|
|
1970
|
-
p.phase AS phase,
|
|
1971
|
-
p.details AS phase_error,
|
|
1972
|
-
p.started_at AS phase_started_at
|
|
1973
|
-
FROM operation_phases p
|
|
1974
|
-
JOIN operations o ON o.operation_id = p.operation_id
|
|
1975
|
-
WHERE ${where}
|
|
1976
|
-
ORDER BY p.started_at DESC
|
|
1977
|
-
LIMIT ?
|
|
1990
|
+
const rows = this.db.prepare(`
|
|
1991
|
+
SELECT
|
|
1992
|
+
o.*,
|
|
1993
|
+
p.phase AS phase,
|
|
1994
|
+
p.details AS phase_error,
|
|
1995
|
+
p.started_at AS phase_started_at
|
|
1996
|
+
FROM operation_phases p
|
|
1997
|
+
JOIN operations o ON o.operation_id = p.operation_id
|
|
1998
|
+
WHERE ${where}
|
|
1999
|
+
ORDER BY p.started_at DESC
|
|
2000
|
+
LIMIT ?
|
|
1978
2001
|
`).all(...params);
|
|
1979
2002
|
const operations = rows.map(row => {
|
|
1980
2003
|
const logs = this.db.prepare('SELECT * FROM logs WHERE operation_id = ? ORDER BY ts DESC LIMIT 20').all(row.operation_id);
|
|
@@ -1991,42 +2014,42 @@ export class DashboardDB {
|
|
|
1991
2014
|
}
|
|
1992
2015
|
// --- Operation phases ---
|
|
1993
2016
|
insertPhase(op) {
|
|
1994
|
-
this.stmt('insertPhase', `
|
|
1995
|
-
INSERT INTO operation_phases (operation_id, phase, started_at, status)
|
|
1996
|
-
VALUES (@operation_id, @phase, @started_at, 'in_progress')
|
|
2017
|
+
this.stmt('insertPhase', `
|
|
2018
|
+
INSERT INTO operation_phases (operation_id, phase, started_at, status)
|
|
2019
|
+
VALUES (@operation_id, @phase, @started_at, 'in_progress')
|
|
1997
2020
|
`).run(op);
|
|
1998
2021
|
}
|
|
1999
2022
|
completePhase(op) {
|
|
2000
|
-
this.stmt('completePhase', `
|
|
2001
|
-
UPDATE operation_phases SET status = 'success', duration_ms = @duration_ms
|
|
2002
|
-
WHERE operation_id = @operation_id AND phase = @phase AND status = 'in_progress'
|
|
2023
|
+
this.stmt('completePhase', `
|
|
2024
|
+
UPDATE operation_phases SET status = 'success', duration_ms = @duration_ms
|
|
2025
|
+
WHERE operation_id = @operation_id AND phase = @phase AND status = 'in_progress'
|
|
2003
2026
|
`).run(op);
|
|
2004
2027
|
}
|
|
2005
2028
|
failPhase(op) {
|
|
2006
|
-
this.stmt('failPhase', `
|
|
2007
|
-
UPDATE operation_phases SET status = 'error', duration_ms = @duration_ms,
|
|
2008
|
-
details = @error_message
|
|
2009
|
-
WHERE operation_id = @operation_id AND phase = @phase AND status = 'in_progress'
|
|
2029
|
+
this.stmt('failPhase', `
|
|
2030
|
+
UPDATE operation_phases SET status = 'error', duration_ms = @duration_ms,
|
|
2031
|
+
details = @error_message
|
|
2032
|
+
WHERE operation_id = @operation_id AND phase = @phase AND status = 'in_progress'
|
|
2010
2033
|
`).run(op);
|
|
2011
2034
|
}
|
|
2012
2035
|
failAllPhases(op) {
|
|
2013
|
-
this.stmt('failAllPhases', `
|
|
2014
|
-
UPDATE operation_phases SET status = 'error', duration_ms = @duration_ms,
|
|
2015
|
-
details = @error_message
|
|
2016
|
-
WHERE operation_id = @operation_id AND status = 'in_progress'
|
|
2036
|
+
this.stmt('failAllPhases', `
|
|
2037
|
+
UPDATE operation_phases SET status = 'error', duration_ms = @duration_ms,
|
|
2038
|
+
details = @error_message
|
|
2039
|
+
WHERE operation_id = @operation_id AND status = 'in_progress'
|
|
2017
2040
|
`).run(op);
|
|
2018
2041
|
}
|
|
2019
2042
|
// --- Operation cost & tx ---
|
|
2020
2043
|
setOperationCost(op) {
|
|
2021
|
-
this.stmt('setCost', `
|
|
2022
|
-
UPDATE operations SET
|
|
2023
|
-
gas_used = COALESCE(@gas_used, gas_used),
|
|
2024
|
-
gas_price_gwei = COALESCE(@gas_price_gwei, gas_price_gwei),
|
|
2025
|
-
gas_cost_eth = COALESCE(@gas_cost_eth, gas_cost_eth),
|
|
2026
|
-
trac_cost = COALESCE(@trac_cost, trac_cost),
|
|
2027
|
-
tx_hash = COALESCE(@tx_hash, tx_hash),
|
|
2028
|
-
chain_id = COALESCE(@chain_id, chain_id)
|
|
2029
|
-
WHERE operation_id = @operation_id
|
|
2044
|
+
this.stmt('setCost', `
|
|
2045
|
+
UPDATE operations SET
|
|
2046
|
+
gas_used = COALESCE(@gas_used, gas_used),
|
|
2047
|
+
gas_price_gwei = COALESCE(@gas_price_gwei, gas_price_gwei),
|
|
2048
|
+
gas_cost_eth = COALESCE(@gas_cost_eth, gas_cost_eth),
|
|
2049
|
+
trac_cost = COALESCE(@trac_cost, trac_cost),
|
|
2050
|
+
tx_hash = COALESCE(@tx_hash, tx_hash),
|
|
2051
|
+
chain_id = COALESCE(@chain_id, chain_id)
|
|
2052
|
+
WHERE operation_id = @operation_id
|
|
2030
2053
|
`).run({
|
|
2031
2054
|
operation_id: op.operation_id,
|
|
2032
2055
|
gas_used: op.gas_used ?? null,
|
|
@@ -2044,17 +2067,17 @@ export class DashboardDB {
|
|
|
2044
2067
|
const params = [cutoff];
|
|
2045
2068
|
if (opts.name)
|
|
2046
2069
|
params.push(opts.name);
|
|
2047
|
-
const summaryRow = this.db.prepare(`
|
|
2048
|
-
SELECT
|
|
2049
|
-
COUNT(*) as totalCount,
|
|
2050
|
-
SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END) as successCount,
|
|
2051
|
-
SUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END) as errorCount,
|
|
2052
|
-
AVG(CASE WHEN status = 'success' THEN duration_ms END) as avgDurationMs,
|
|
2053
|
-
AVG(gas_cost_eth) as avgGasCostEth,
|
|
2054
|
-
SUM(gas_cost_eth) as totalGasCostEth,
|
|
2055
|
-
AVG(trac_cost) as avgTracCost,
|
|
2056
|
-
SUM(trac_cost) as totalTracCost
|
|
2057
|
-
FROM operations WHERE started_at >= ? ${nameFilter}
|
|
2070
|
+
const summaryRow = this.db.prepare(`
|
|
2071
|
+
SELECT
|
|
2072
|
+
COUNT(*) as totalCount,
|
|
2073
|
+
SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END) as successCount,
|
|
2074
|
+
SUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END) as errorCount,
|
|
2075
|
+
AVG(CASE WHEN status = 'success' THEN duration_ms END) as avgDurationMs,
|
|
2076
|
+
AVG(gas_cost_eth) as avgGasCostEth,
|
|
2077
|
+
SUM(gas_cost_eth) as totalGasCostEth,
|
|
2078
|
+
AVG(trac_cost) as avgTracCost,
|
|
2079
|
+
SUM(trac_cost) as totalTracCost
|
|
2080
|
+
FROM operations WHERE started_at >= ? ${nameFilter}
|
|
2058
2081
|
`).get(...params);
|
|
2059
2082
|
const summary = {
|
|
2060
2083
|
totalCount: summaryRow.totalCount ?? 0,
|
|
@@ -2071,17 +2094,17 @@ export class DashboardDB {
|
|
|
2071
2094
|
const tsParams = [bucketSize, cutoff];
|
|
2072
2095
|
if (opts.name)
|
|
2073
2096
|
tsParams.push(opts.name);
|
|
2074
|
-
const timeSeries = this.db.prepare(`
|
|
2075
|
-
SELECT
|
|
2076
|
-
(CAST(started_at / ? AS INTEGER) * ?) as bucket,
|
|
2077
|
-
COUNT(*) as count,
|
|
2078
|
-
SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END) as successCount,
|
|
2079
|
-
AVG(CASE WHEN status = 'success' THEN duration_ms END) as avgDurationMs,
|
|
2080
|
-
AVG(gas_cost_eth) as avgGasCostEth,
|
|
2081
|
-
SUM(gas_cost_eth) as totalGasCostEth
|
|
2082
|
-
FROM operations
|
|
2083
|
-
WHERE started_at >= ? ${nameFilter}
|
|
2084
|
-
GROUP BY bucket ORDER BY bucket
|
|
2097
|
+
const timeSeries = this.db.prepare(`
|
|
2098
|
+
SELECT
|
|
2099
|
+
(CAST(started_at / ? AS INTEGER) * ?) as bucket,
|
|
2100
|
+
COUNT(*) as count,
|
|
2101
|
+
SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END) as successCount,
|
|
2102
|
+
AVG(CASE WHEN status = 'success' THEN duration_ms END) as avgDurationMs,
|
|
2103
|
+
AVG(gas_cost_eth) as avgGasCostEth,
|
|
2104
|
+
SUM(gas_cost_eth) as totalGasCostEth
|
|
2105
|
+
FROM operations
|
|
2106
|
+
WHERE started_at >= ? ${nameFilter}
|
|
2107
|
+
GROUP BY bucket ORDER BY bucket
|
|
2085
2108
|
`).all(bucketSize, bucketSize, ...params);
|
|
2086
2109
|
return {
|
|
2087
2110
|
summary,
|
|
@@ -2098,16 +2121,16 @@ export class DashboardDB {
|
|
|
2098
2121
|
// --- Per-type time series ---
|
|
2099
2122
|
getPerTypeTimeSeries(opts) {
|
|
2100
2123
|
const cutoff = Date.now() - opts.periodMs;
|
|
2101
|
-
const rows = this.db.prepare(`
|
|
2102
|
-
SELECT
|
|
2103
|
-
(CAST(started_at / ? AS INTEGER) * ?) as bucket,
|
|
2104
|
-
operation_name as type,
|
|
2105
|
-
COUNT(*) as count,
|
|
2106
|
-
AVG(CASE WHEN status = 'success' THEN duration_ms END) as avgMs,
|
|
2107
|
-
SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END) as successCount,
|
|
2108
|
-
SUM(gas_cost_eth) as gasCostEth
|
|
2109
|
-
FROM operations WHERE started_at >= ?
|
|
2110
|
-
GROUP BY bucket, operation_name ORDER BY bucket
|
|
2124
|
+
const rows = this.db.prepare(`
|
|
2125
|
+
SELECT
|
|
2126
|
+
(CAST(started_at / ? AS INTEGER) * ?) as bucket,
|
|
2127
|
+
operation_name as type,
|
|
2128
|
+
COUNT(*) as count,
|
|
2129
|
+
AVG(CASE WHEN status = 'success' THEN duration_ms END) as avgMs,
|
|
2130
|
+
SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END) as successCount,
|
|
2131
|
+
SUM(gas_cost_eth) as gasCostEth
|
|
2132
|
+
FROM operations WHERE started_at >= ?
|
|
2133
|
+
GROUP BY bucket, operation_name ORDER BY bucket
|
|
2111
2134
|
`).all(opts.bucketMs, opts.bucketMs, cutoff);
|
|
2112
2135
|
const bucketSet = new Set();
|
|
2113
2136
|
const typeSet = new Set();
|
|
@@ -2137,15 +2160,15 @@ export class DashboardDB {
|
|
|
2137
2160
|
// --- Success rates by operation type ---
|
|
2138
2161
|
getSuccessRatesByType(periodMs) {
|
|
2139
2162
|
const cutoff = Date.now() - periodMs;
|
|
2140
|
-
return this.db.prepare(`
|
|
2141
|
-
SELECT
|
|
2142
|
-
operation_name as type,
|
|
2143
|
-
COUNT(*) as total,
|
|
2144
|
-
SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END) as success,
|
|
2145
|
-
SUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END) as error,
|
|
2146
|
-
AVG(CASE WHEN status = 'success' THEN duration_ms END) as avgMs
|
|
2147
|
-
FROM operations WHERE started_at >= ?
|
|
2148
|
-
GROUP BY operation_name ORDER BY total DESC
|
|
2163
|
+
return this.db.prepare(`
|
|
2164
|
+
SELECT
|
|
2165
|
+
operation_name as type,
|
|
2166
|
+
COUNT(*) as total,
|
|
2167
|
+
SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END) as success,
|
|
2168
|
+
SUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END) as error,
|
|
2169
|
+
AVG(CASE WHEN status = 'success' THEN duration_ms END) as avgMs
|
|
2170
|
+
FROM operations WHERE started_at >= ?
|
|
2171
|
+
GROUP BY operation_name ORDER BY total DESC
|
|
2149
2172
|
`).all(cutoff).map(r => ({
|
|
2150
2173
|
...r,
|
|
2151
2174
|
rate: r.total > 0 ? r.success / r.total : 0,
|
|
@@ -2168,16 +2191,16 @@ export class DashboardDB {
|
|
|
2168
2191
|
// on-chain (Verifiable Memory commits) are counted, so the publish
|
|
2169
2192
|
// count and the TRAC total are consistent. Free SWM/local/testnet
|
|
2170
2193
|
// publishes record trac_cost = 0 and are intentionally excluded.
|
|
2171
|
-
const row = this.db.prepare(`
|
|
2172
|
-
SELECT
|
|
2173
|
-
SUM(CASE WHEN trac_cost > 0 THEN 1 ELSE 0 END) as publishCount,
|
|
2174
|
-
SUM(CASE WHEN status = 'success' AND trac_cost > 0 THEN 1 ELSE 0 END) as successCount,
|
|
2175
|
-
COALESCE(SUM(gas_cost_eth), 0) as totalGasEth,
|
|
2176
|
-
COALESCE(SUM(trac_cost), 0) as totalTrac,
|
|
2177
|
-
COALESCE(AVG(CASE WHEN trac_cost > 0 THEN gas_cost_eth END), 0) as avgGasEth,
|
|
2178
|
-
COALESCE(AVG(CASE WHEN trac_cost > 0 THEN trac_cost END), 0) as avgTrac
|
|
2179
|
-
FROM operations
|
|
2180
|
-
WHERE operation_name = 'publish' AND trac_cost > 0 AND started_at >= ?
|
|
2194
|
+
const row = this.db.prepare(`
|
|
2195
|
+
SELECT
|
|
2196
|
+
SUM(CASE WHEN trac_cost > 0 THEN 1 ELSE 0 END) as publishCount,
|
|
2197
|
+
SUM(CASE WHEN status = 'success' AND trac_cost > 0 THEN 1 ELSE 0 END) as successCount,
|
|
2198
|
+
COALESCE(SUM(gas_cost_eth), 0) as totalGasEth,
|
|
2199
|
+
COALESCE(SUM(trac_cost), 0) as totalTrac,
|
|
2200
|
+
COALESCE(AVG(CASE WHEN trac_cost > 0 THEN gas_cost_eth END), 0) as avgGasEth,
|
|
2201
|
+
COALESCE(AVG(CASE WHEN trac_cost > 0 THEN trac_cost END), 0) as avgTrac
|
|
2202
|
+
FROM operations
|
|
2203
|
+
WHERE operation_name = 'publish' AND trac_cost > 0 AND started_at >= ?
|
|
2181
2204
|
`).get(cutoff);
|
|
2182
2205
|
results.periods.push({
|
|
2183
2206
|
label: p.label,
|
|
@@ -2212,9 +2235,9 @@ export class DashboardDB {
|
|
|
2212
2235
|
* still throw — pinned by db.test.ts.
|
|
2213
2236
|
*/
|
|
2214
2237
|
insertChatMessage(msg) {
|
|
2215
|
-
const info = this.stmt('insertChat', `
|
|
2216
|
-
INSERT INTO chat_messages (ts, direction, peer, peer_name, text, delivered, message_id)
|
|
2217
|
-
VALUES (@ts, @direction, @peer, @peer_name, @text, @delivered, @message_id)
|
|
2238
|
+
const info = this.stmt('insertChat', `
|
|
2239
|
+
INSERT INTO chat_messages (ts, direction, peer, peer_name, text, delivered, message_id)
|
|
2240
|
+
VALUES (@ts, @direction, @peer, @peer_name, @text, @delivered, @message_id)
|
|
2218
2241
|
`).run({
|
|
2219
2242
|
ts: msg.ts,
|
|
2220
2243
|
direction: msg.direction,
|
|
@@ -2305,16 +2328,16 @@ export class DashboardDB {
|
|
|
2305
2328
|
return this.db.prepare('SELECT * FROM chat_persistence_jobs WHERE turn_id = ?').get(turnId);
|
|
2306
2329
|
}
|
|
2307
2330
|
insertChatPersistenceJob(job) {
|
|
2308
|
-
this.stmt('insertChatPersistenceJob', `
|
|
2309
|
-
INSERT INTO chat_persistence_jobs (
|
|
2310
|
-
turn_id, session_id, user_message, assistant_reply, tool_calls_json,
|
|
2311
|
-
status, attempts, max_attempts, next_attempt_at, queued_at, updated_at,
|
|
2312
|
-
store_ms, error_message
|
|
2313
|
-
) VALUES (
|
|
2314
|
-
@turn_id, @session_id, @user_message, @assistant_reply, @tool_calls_json,
|
|
2315
|
-
@status, @attempts, @max_attempts, @next_attempt_at, @queued_at, @updated_at,
|
|
2316
|
-
@store_ms, @error_message
|
|
2317
|
-
)
|
|
2331
|
+
this.stmt('insertChatPersistenceJob', `
|
|
2332
|
+
INSERT INTO chat_persistence_jobs (
|
|
2333
|
+
turn_id, session_id, user_message, assistant_reply, tool_calls_json,
|
|
2334
|
+
status, attempts, max_attempts, next_attempt_at, queued_at, updated_at,
|
|
2335
|
+
store_ms, error_message
|
|
2336
|
+
) VALUES (
|
|
2337
|
+
@turn_id, @session_id, @user_message, @assistant_reply, @tool_calls_json,
|
|
2338
|
+
@status, @attempts, @max_attempts, @next_attempt_at, @queued_at, @updated_at,
|
|
2339
|
+
@store_ms, @error_message
|
|
2340
|
+
)
|
|
2318
2341
|
`).run({
|
|
2319
2342
|
...job,
|
|
2320
2343
|
tool_calls_json: job.tool_calls_json ?? null,
|
|
@@ -2323,46 +2346,46 @@ export class DashboardDB {
|
|
|
2323
2346
|
});
|
|
2324
2347
|
}
|
|
2325
2348
|
markChatPersistenceInProgress(turnId, attempts, updatedAt) {
|
|
2326
|
-
this.stmt('markChatPersistenceInProgress', `
|
|
2327
|
-
UPDATE chat_persistence_jobs
|
|
2328
|
-
SET status = 'in_progress', attempts = ?, updated_at = ?, error_message = NULL
|
|
2329
|
-
WHERE turn_id = ?
|
|
2349
|
+
this.stmt('markChatPersistenceInProgress', `
|
|
2350
|
+
UPDATE chat_persistence_jobs
|
|
2351
|
+
SET status = 'in_progress', attempts = ?, updated_at = ?, error_message = NULL
|
|
2352
|
+
WHERE turn_id = ?
|
|
2330
2353
|
`).run(attempts, updatedAt, turnId);
|
|
2331
2354
|
}
|
|
2332
2355
|
markChatPersistenceStored(turnId, storeMs, updatedAt) {
|
|
2333
|
-
this.stmt('markChatPersistenceStored', `
|
|
2334
|
-
UPDATE chat_persistence_jobs
|
|
2335
|
-
SET status = 'stored', store_ms = ?, updated_at = ?, error_message = NULL
|
|
2336
|
-
WHERE turn_id = ?
|
|
2356
|
+
this.stmt('markChatPersistenceStored', `
|
|
2357
|
+
UPDATE chat_persistence_jobs
|
|
2358
|
+
SET status = 'stored', store_ms = ?, updated_at = ?, error_message = NULL
|
|
2359
|
+
WHERE turn_id = ?
|
|
2337
2360
|
`).run(storeMs, updatedAt, turnId);
|
|
2338
2361
|
}
|
|
2339
2362
|
markChatPersistencePendingRetry(turnId, attempts, nextAttemptAt, updatedAt, errorMessage) {
|
|
2340
|
-
this.stmt('markChatPersistencePendingRetry', `
|
|
2341
|
-
UPDATE chat_persistence_jobs
|
|
2342
|
-
SET status = 'pending', attempts = ?, next_attempt_at = ?, updated_at = ?, error_message = ?
|
|
2343
|
-
WHERE turn_id = ?
|
|
2363
|
+
this.stmt('markChatPersistencePendingRetry', `
|
|
2364
|
+
UPDATE chat_persistence_jobs
|
|
2365
|
+
SET status = 'pending', attempts = ?, next_attempt_at = ?, updated_at = ?, error_message = ?
|
|
2366
|
+
WHERE turn_id = ?
|
|
2344
2367
|
`).run(attempts, nextAttemptAt, updatedAt, errorMessage, turnId);
|
|
2345
2368
|
}
|
|
2346
2369
|
markChatPersistenceFailed(turnId, attempts, updatedAt, errorMessage) {
|
|
2347
|
-
this.stmt('markChatPersistenceFailed', `
|
|
2348
|
-
UPDATE chat_persistence_jobs
|
|
2349
|
-
SET status = 'failed', attempts = ?, updated_at = ?, error_message = ?
|
|
2350
|
-
WHERE turn_id = ?
|
|
2370
|
+
this.stmt('markChatPersistenceFailed', `
|
|
2371
|
+
UPDATE chat_persistence_jobs
|
|
2372
|
+
SET status = 'failed', attempts = ?, updated_at = ?, error_message = ?
|
|
2373
|
+
WHERE turn_id = ?
|
|
2351
2374
|
`).run(attempts, updatedAt, errorMessage, turnId);
|
|
2352
2375
|
}
|
|
2353
2376
|
recoverInProgressChatPersistenceJobs(now) {
|
|
2354
|
-
this.stmt('recoverInProgressChatPersistenceJobs', `
|
|
2355
|
-
UPDATE chat_persistence_jobs
|
|
2356
|
-
SET status = 'pending', next_attempt_at = ?, updated_at = ?
|
|
2357
|
-
WHERE status = 'in_progress'
|
|
2377
|
+
this.stmt('recoverInProgressChatPersistenceJobs', `
|
|
2378
|
+
UPDATE chat_persistence_jobs
|
|
2379
|
+
SET status = 'pending', next_attempt_at = ?, updated_at = ?
|
|
2380
|
+
WHERE status = 'in_progress'
|
|
2358
2381
|
`).run(now, now);
|
|
2359
2382
|
}
|
|
2360
2383
|
getRunnableChatPersistenceJobs(now, limit = 10) {
|
|
2361
|
-
return this.db.prepare(`
|
|
2362
|
-
SELECT * FROM chat_persistence_jobs
|
|
2363
|
-
WHERE status = 'pending' AND next_attempt_at <= ?
|
|
2364
|
-
ORDER BY next_attempt_at ASC, queued_at ASC
|
|
2365
|
-
LIMIT ?
|
|
2384
|
+
return this.db.prepare(`
|
|
2385
|
+
SELECT * FROM chat_persistence_jobs
|
|
2386
|
+
WHERE status = 'pending' AND next_attempt_at <= ?
|
|
2387
|
+
ORDER BY next_attempt_at ASC, queued_at ASC
|
|
2388
|
+
LIMIT ?
|
|
2366
2389
|
`).all(now, limit);
|
|
2367
2390
|
}
|
|
2368
2391
|
getNextPendingChatPersistenceAt() {
|
|
@@ -2370,19 +2393,19 @@ export class DashboardDB {
|
|
|
2370
2393
|
return row?.next_at ?? null;
|
|
2371
2394
|
}
|
|
2372
2395
|
getChatPersistenceHealth(now) {
|
|
2373
|
-
const counts = this.db.prepare(`
|
|
2374
|
-
SELECT
|
|
2375
|
-
SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) AS pending_count,
|
|
2376
|
-
SUM(CASE WHEN status = 'in_progress' THEN 1 ELSE 0 END) AS in_progress_count,
|
|
2377
|
-
SUM(CASE WHEN status = 'stored' THEN 1 ELSE 0 END) AS stored_count,
|
|
2378
|
-
SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) AS failed_count,
|
|
2379
|
-
SUM(CASE WHEN status = 'pending' AND next_attempt_at < ? THEN 1 ELSE 0 END) AS overdue_pending_count
|
|
2380
|
-
FROM chat_persistence_jobs
|
|
2396
|
+
const counts = this.db.prepare(`
|
|
2397
|
+
SELECT
|
|
2398
|
+
SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) AS pending_count,
|
|
2399
|
+
SUM(CASE WHEN status = 'in_progress' THEN 1 ELSE 0 END) AS in_progress_count,
|
|
2400
|
+
SUM(CASE WHEN status = 'stored' THEN 1 ELSE 0 END) AS stored_count,
|
|
2401
|
+
SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) AS failed_count,
|
|
2402
|
+
SUM(CASE WHEN status = 'pending' AND next_attempt_at < ? THEN 1 ELSE 0 END) AS overdue_pending_count
|
|
2403
|
+
FROM chat_persistence_jobs
|
|
2381
2404
|
`).get(now);
|
|
2382
|
-
const oldest = this.db.prepare(`
|
|
2383
|
-
SELECT MIN(queued_at) AS oldest_pending_queued_at
|
|
2384
|
-
FROM chat_persistence_jobs
|
|
2385
|
-
WHERE status = 'pending'
|
|
2405
|
+
const oldest = this.db.prepare(`
|
|
2406
|
+
SELECT MIN(queued_at) AS oldest_pending_queued_at
|
|
2407
|
+
FROM chat_persistence_jobs
|
|
2408
|
+
WHERE status = 'pending'
|
|
2386
2409
|
`).get();
|
|
2387
2410
|
return {
|
|
2388
2411
|
pending_count: counts?.pending_count ?? 0,
|
|
@@ -2395,9 +2418,9 @@ export class DashboardDB {
|
|
|
2395
2418
|
}
|
|
2396
2419
|
// --- Logs ---
|
|
2397
2420
|
insertLog(entry) {
|
|
2398
|
-
this.stmt('insertLog', `
|
|
2399
|
-
INSERT INTO logs (ts, level, operation_name, operation_id, module, message)
|
|
2400
|
-
VALUES (@ts, @level, @operation_name, @operation_id, @module, @message)
|
|
2421
|
+
this.stmt('insertLog', `
|
|
2422
|
+
INSERT INTO logs (ts, level, operation_name, operation_id, module, message)
|
|
2423
|
+
VALUES (@ts, @level, @operation_name, @operation_id, @module, @message)
|
|
2401
2424
|
`).run({
|
|
2402
2425
|
ts: entry.ts,
|
|
2403
2426
|
level: entry.level,
|
|
@@ -2452,9 +2475,9 @@ export class DashboardDB {
|
|
|
2452
2475
|
}
|
|
2453
2476
|
// --- Query history ---
|
|
2454
2477
|
insertQueryHistory(entry) {
|
|
2455
|
-
this.stmt('insertQueryHistory', `
|
|
2456
|
-
INSERT INTO query_history (ts, sparql, duration_ms, result_count, error)
|
|
2457
|
-
VALUES (@ts, @sparql, @duration_ms, @result_count, @error)
|
|
2478
|
+
this.stmt('insertQueryHistory', `
|
|
2479
|
+
INSERT INTO query_history (ts, sparql, duration_ms, result_count, error)
|
|
2480
|
+
VALUES (@ts, @sparql, @duration_ms, @result_count, @error)
|
|
2458
2481
|
`).run({
|
|
2459
2482
|
ts: Date.now(),
|
|
2460
2483
|
sparql: entry.sparql,
|
|
@@ -2498,9 +2521,9 @@ export class DashboardDB {
|
|
|
2498
2521
|
}
|
|
2499
2522
|
// --- Notifications ---
|
|
2500
2523
|
insertNotification(n) {
|
|
2501
|
-
const result = this.stmt('insertNotif', `
|
|
2502
|
-
INSERT INTO notifications (ts, type, title, message, source, peer, read, meta, context_graph_id)
|
|
2503
|
-
VALUES (@ts, @type, @title, @message, @source, @peer, 0, @meta, @contextGraphId)
|
|
2524
|
+
const result = this.stmt('insertNotif', `
|
|
2525
|
+
INSERT INTO notifications (ts, type, title, message, source, peer, read, meta, context_graph_id)
|
|
2526
|
+
VALUES (@ts, @type, @title, @message, @source, @peer, 0, @meta, @contextGraphId)
|
|
2504
2527
|
`).run({
|
|
2505
2528
|
ts: n.ts,
|
|
2506
2529
|
type: n.type,
|
|
@@ -2536,8 +2559,8 @@ export class DashboardDB {
|
|
|
2536
2559
|
if (cgIds.length === 0)
|
|
2537
2560
|
return [];
|
|
2538
2561
|
const placeholders = cgIds.map(() => '?').join(',');
|
|
2539
|
-
return this.db.prepare(`SELECT * FROM notifications
|
|
2540
|
-
WHERE context_graph_id IN (${placeholders})
|
|
2562
|
+
return this.db.prepare(`SELECT * FROM notifications
|
|
2563
|
+
WHERE context_graph_id IN (${placeholders})
|
|
2541
2564
|
ORDER BY ts DESC LIMIT ?`).all(...cgIds, limit);
|
|
2542
2565
|
}
|
|
2543
2566
|
/**
|
|
@@ -2568,8 +2591,8 @@ export class DashboardDB {
|
|
|
2568
2591
|
if (types.length === 0)
|
|
2569
2592
|
return [];
|
|
2570
2593
|
const placeholders = types.map(() => '?').join(',');
|
|
2571
|
-
return this.db.prepare(`SELECT * FROM notifications
|
|
2572
|
-
WHERE type IN (${placeholders})
|
|
2594
|
+
return this.db.prepare(`SELECT * FROM notifications
|
|
2595
|
+
WHERE type IN (${placeholders})
|
|
2573
2596
|
ORDER BY ts DESC LIMIT ?`).all(...types, limit);
|
|
2574
2597
|
}
|
|
2575
2598
|
/**
|
|
@@ -2589,9 +2612,9 @@ export class DashboardDB {
|
|
|
2589
2612
|
const wallet = walletAddress.trim().toLowerCase();
|
|
2590
2613
|
if (!wallet)
|
|
2591
2614
|
return [];
|
|
2592
|
-
return this.db.prepare(`SELECT * FROM notifications
|
|
2593
|
-
WHERE type = 'pca_cost_covered'
|
|
2594
|
-
AND lower(json_extract(meta, '$.publisherAddress')) = ?
|
|
2615
|
+
return this.db.prepare(`SELECT * FROM notifications
|
|
2616
|
+
WHERE type = 'pca_cost_covered'
|
|
2617
|
+
AND lower(json_extract(meta, '$.publisherAddress')) = ?
|
|
2595
2618
|
ORDER BY ts DESC LIMIT ?`).all(wallet, limit);
|
|
2596
2619
|
}
|
|
2597
2620
|
markNotificationsRead(ids) {
|
|
@@ -2621,10 +2644,10 @@ export class DashboardDB {
|
|
|
2621
2644
|
return [];
|
|
2622
2645
|
const windowStart = parsed.windowBucket * ACTIVITY_DIGEST_WINDOW_MS;
|
|
2623
2646
|
const windowEnd = windowStart + ACTIVITY_DIGEST_WINDOW_MS;
|
|
2624
|
-
const rows = this.db.prepare(`SELECT id FROM notifications
|
|
2625
|
-
WHERE context_graph_id = ?
|
|
2626
|
-
AND type = ?
|
|
2627
|
-
AND ts >= ? AND ts < ?
|
|
2647
|
+
const rows = this.db.prepare(`SELECT id FROM notifications
|
|
2648
|
+
WHERE context_graph_id = ?
|
|
2649
|
+
AND type = ?
|
|
2650
|
+
AND ts >= ? AND ts < ?
|
|
2628
2651
|
AND json_extract(meta, '$.kind') = ?`).all(parsed.contextGraphId, ASSERTION_ACTIVITY_TYPE, windowStart, windowEnd, parsed.kind);
|
|
2629
2652
|
return rows.map((r) => r.id);
|
|
2630
2653
|
}
|
|
@@ -2645,32 +2668,72 @@ export class SqliteSyncCheckpointStore {
|
|
|
2645
2668
|
}
|
|
2646
2669
|
get(key) {
|
|
2647
2670
|
const now = this.clock();
|
|
2648
|
-
const row = this.db.prepare(`SELECT offset, updated_at, expires_at
|
|
2671
|
+
const row = this.db.prepare(`SELECT offset, updated_at, expires_at,
|
|
2672
|
+
responder_session_id, responder_session_expires_at
|
|
2673
|
+
FROM sync_checkpoints WHERE key = ?`).get(key);
|
|
2649
2674
|
if (!row)
|
|
2650
2675
|
return undefined;
|
|
2651
2676
|
if (row.expires_at < now) {
|
|
2652
2677
|
this.delete(key);
|
|
2653
2678
|
return undefined;
|
|
2654
2679
|
}
|
|
2680
|
+
const hasFreshResponderSession = Boolean(row.responder_session_id)
|
|
2681
|
+
&& Number.isSafeInteger(row.responder_session_expires_at)
|
|
2682
|
+
&& (row.responder_session_expires_at ?? 0) > now;
|
|
2683
|
+
if (row.responder_session_id && !hasFreshResponderSession) {
|
|
2684
|
+
this.clearResponderSession(key);
|
|
2685
|
+
}
|
|
2655
2686
|
return {
|
|
2656
2687
|
offset: row.offset,
|
|
2657
2688
|
updatedAtMs: row.updated_at,
|
|
2658
2689
|
expiresAtMs: row.expires_at,
|
|
2690
|
+
...(hasFreshResponderSession
|
|
2691
|
+
? {
|
|
2692
|
+
responderSessionId: row.responder_session_id,
|
|
2693
|
+
responderSessionExpiresAtMs: row.responder_session_expires_at,
|
|
2694
|
+
}
|
|
2695
|
+
: {}),
|
|
2659
2696
|
};
|
|
2660
2697
|
}
|
|
2661
2698
|
set(key, value, nowMs = this.clock()) {
|
|
2662
2699
|
if (!Number.isSafeInteger(value) || value < 0) {
|
|
2663
2700
|
throw new Error(`Invalid sync checkpoint offset for ${key}: ${value}`);
|
|
2664
2701
|
}
|
|
2665
|
-
this.db.prepare(`
|
|
2666
|
-
INSERT INTO sync_checkpoints (key, offset, updated_at, expires_at)
|
|
2667
|
-
VALUES (?, ?, ?, ?)
|
|
2668
|
-
ON CONFLICT(key) DO UPDATE SET
|
|
2669
|
-
offset = excluded.offset,
|
|
2670
|
-
updated_at = excluded.updated_at,
|
|
2671
|
-
expires_at = excluded.expires_at
|
|
2702
|
+
this.db.prepare(`
|
|
2703
|
+
INSERT INTO sync_checkpoints (key, offset, updated_at, expires_at)
|
|
2704
|
+
VALUES (?, ?, ?, ?)
|
|
2705
|
+
ON CONFLICT(key) DO UPDATE SET
|
|
2706
|
+
offset = excluded.offset,
|
|
2707
|
+
updated_at = excluded.updated_at,
|
|
2708
|
+
expires_at = excluded.expires_at
|
|
2672
2709
|
`).run(key, value, nowMs, nowMs + this.ttlMs);
|
|
2673
2710
|
}
|
|
2711
|
+
setResponderSession(key, sessionId, expiresAtMs, nowMs = this.clock()) {
|
|
2712
|
+
if (!sessionId || !Number.isSafeInteger(expiresAtMs)) {
|
|
2713
|
+
throw new Error(`Invalid sync responder session for ${key}`);
|
|
2714
|
+
}
|
|
2715
|
+
if (expiresAtMs <= nowMs) {
|
|
2716
|
+
this.clearResponderSession(key);
|
|
2717
|
+
return;
|
|
2718
|
+
}
|
|
2719
|
+
this.db.prepare(`
|
|
2720
|
+
INSERT INTO sync_checkpoints (
|
|
2721
|
+
key, offset, updated_at, expires_at,
|
|
2722
|
+
responder_session_id, responder_session_expires_at
|
|
2723
|
+
) VALUES (?, 0, ?, ?, ?, ?)
|
|
2724
|
+
ON CONFLICT(key) DO UPDATE SET
|
|
2725
|
+
responder_session_id = excluded.responder_session_id,
|
|
2726
|
+
responder_session_expires_at = excluded.responder_session_expires_at
|
|
2727
|
+
`).run(key, nowMs, nowMs + this.ttlMs, sessionId, expiresAtMs);
|
|
2728
|
+
}
|
|
2729
|
+
clearResponderSession(key) {
|
|
2730
|
+
this.db.prepare(`
|
|
2731
|
+
UPDATE sync_checkpoints
|
|
2732
|
+
SET responder_session_id = NULL,
|
|
2733
|
+
responder_session_expires_at = NULL
|
|
2734
|
+
WHERE key = ?
|
|
2735
|
+
`).run(key);
|
|
2736
|
+
}
|
|
2674
2737
|
delete(key) {
|
|
2675
2738
|
this.db.prepare(`DELETE FROM sync_checkpoints WHERE key = ?`).run(key);
|
|
2676
2739
|
}
|
|
@@ -2702,13 +2765,13 @@ export class SqliteChangelogCursorStore {
|
|
|
2702
2765
|
if (!Number.isSafeInteger(seq) || seq < 0) {
|
|
2703
2766
|
throw new Error(`Invalid changelog cursor seq for ${peerId}/${contextGraphId}: ${seq}`);
|
|
2704
2767
|
}
|
|
2705
|
-
this.db.prepare(`
|
|
2706
|
-
INSERT INTO changelog_cursors (peer_id, context_graph_id, era, seq, updated_at)
|
|
2707
|
-
VALUES (?, ?, ?, ?, ?)
|
|
2708
|
-
ON CONFLICT(peer_id, context_graph_id) DO UPDATE SET
|
|
2709
|
-
era = excluded.era,
|
|
2710
|
-
seq = excluded.seq,
|
|
2711
|
-
updated_at = excluded.updated_at
|
|
2768
|
+
this.db.prepare(`
|
|
2769
|
+
INSERT INTO changelog_cursors (peer_id, context_graph_id, era, seq, updated_at)
|
|
2770
|
+
VALUES (?, ?, ?, ?, ?)
|
|
2771
|
+
ON CONFLICT(peer_id, context_graph_id) DO UPDATE SET
|
|
2772
|
+
era = excluded.era,
|
|
2773
|
+
seq = excluded.seq,
|
|
2774
|
+
updated_at = excluded.updated_at
|
|
2712
2775
|
`).run(peerId, contextGraphId, era, seq, nowMs);
|
|
2713
2776
|
}
|
|
2714
2777
|
}
|
|
@@ -2736,13 +2799,13 @@ export class SqliteChangelogEraGuard {
|
|
|
2736
2799
|
if (!Number.isSafeInteger(highSeq) || highSeq < 0) {
|
|
2737
2800
|
throw new Error(`Invalid changelog era high_seq: ${highSeq}`);
|
|
2738
2801
|
}
|
|
2739
|
-
this.db.prepare(`
|
|
2740
|
-
INSERT INTO changelog_era (id, era, high_seq, updated_at)
|
|
2741
|
-
VALUES (1, ?, ?, ?)
|
|
2742
|
-
ON CONFLICT(id) DO UPDATE SET
|
|
2743
|
-
era = excluded.era,
|
|
2744
|
-
high_seq = excluded.high_seq,
|
|
2745
|
-
updated_at = excluded.updated_at
|
|
2802
|
+
this.db.prepare(`
|
|
2803
|
+
INSERT INTO changelog_era (id, era, high_seq, updated_at)
|
|
2804
|
+
VALUES (1, ?, ?, ?)
|
|
2805
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
2806
|
+
era = excluded.era,
|
|
2807
|
+
high_seq = excluded.high_seq,
|
|
2808
|
+
updated_at = excluded.updated_at
|
|
2746
2809
|
`).run(era, highSeq, this.clock());
|
|
2747
2810
|
}
|
|
2748
2811
|
}
|
|
@@ -2776,7 +2839,7 @@ export class SqliteMessageIdempotencyStore {
|
|
|
2776
2839
|
}
|
|
2777
2840
|
check(peer, protocol, messageId, direction) {
|
|
2778
2841
|
const row = this.db
|
|
2779
|
-
.prepare(`SELECT response_blob FROM message_idempotency
|
|
2842
|
+
.prepare(`SELECT response_blob FROM message_idempotency
|
|
2780
2843
|
WHERE peer_id = ? AND protocol = ? AND message_id = ? AND direction = ?`)
|
|
2781
2844
|
.get(peer, protocol, messageId, direction);
|
|
2782
2845
|
if (!row)
|
|
@@ -2806,9 +2869,9 @@ export class SqliteMessageIdempotencyStore {
|
|
|
2806
2869
|
// SqliteError so the substrate's bug doesn't disguise itself as
|
|
2807
2870
|
// a normal duplicate.
|
|
2808
2871
|
this.db
|
|
2809
|
-
.prepare(`INSERT INTO message_idempotency
|
|
2810
|
-
(peer_id, protocol, message_id, direction, response_blob, response_size, ts)
|
|
2811
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
2872
|
+
.prepare(`INSERT INTO message_idempotency
|
|
2873
|
+
(peer_id, protocol, message_id, direction, response_blob, response_size, ts)
|
|
2874
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
2812
2875
|
ON CONFLICT (peer_id, protocol, message_id, direction) DO NOTHING`)
|
|
2813
2876
|
.run(peer, protocol, messageId, direction, blob, responseSize, this.clock());
|
|
2814
2877
|
}
|
|
@@ -2833,15 +2896,15 @@ export class SqliteProtocolOutboxStore {
|
|
|
2833
2896
|
}
|
|
2834
2897
|
enqueue(peer, protocol, messageId, payload, error, now) {
|
|
2835
2898
|
const existing = this.db
|
|
2836
|
-
.prepare(`SELECT * FROM protocol_outbox
|
|
2899
|
+
.prepare(`SELECT * FROM protocol_outbox
|
|
2837
2900
|
WHERE peer_id = ? AND protocol = ? AND message_id = ?`)
|
|
2838
2901
|
.get(peer, protocol, messageId);
|
|
2839
2902
|
if (existing) {
|
|
2840
2903
|
const newAttempts = existing.attempts + 1;
|
|
2841
2904
|
const nextAttemptAt = now + this.backoffFor(newAttempts);
|
|
2842
2905
|
this.db
|
|
2843
|
-
.prepare(`UPDATE protocol_outbox
|
|
2844
|
-
SET attempts = ?, last_attempt_at = ?, next_attempt_at = ?, last_error = ?
|
|
2906
|
+
.prepare(`UPDATE protocol_outbox
|
|
2907
|
+
SET attempts = ?, last_attempt_at = ?, next_attempt_at = ?, last_error = ?
|
|
2845
2908
|
WHERE peer_id = ? AND protocol = ? AND message_id = ?`)
|
|
2846
2909
|
.run(newAttempts, now, nextAttemptAt, error, peer, protocol, messageId);
|
|
2847
2910
|
return {
|
|
@@ -2860,9 +2923,9 @@ export class SqliteProtocolOutboxStore {
|
|
|
2860
2923
|
const nextAttemptAt = now + this.backoffFor(attempts);
|
|
2861
2924
|
const blob = Buffer.from(payload);
|
|
2862
2925
|
this.db
|
|
2863
|
-
.prepare(`INSERT INTO protocol_outbox
|
|
2864
|
-
(peer_id, protocol, message_id, payload, attempts,
|
|
2865
|
-
first_failure_at, last_attempt_at, next_attempt_at, last_error)
|
|
2926
|
+
.prepare(`INSERT INTO protocol_outbox
|
|
2927
|
+
(peer_id, protocol, message_id, payload, attempts,
|
|
2928
|
+
first_failure_at, last_attempt_at, next_attempt_at, last_error)
|
|
2866
2929
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
2867
2930
|
.run(peer, protocol, messageId, blob, attempts, now, now, nextAttemptAt, error);
|
|
2868
2931
|
return {
|
|
@@ -2879,14 +2942,14 @@ export class SqliteProtocolOutboxStore {
|
|
|
2879
2942
|
}
|
|
2880
2943
|
markDelivered(peer, protocol, messageId) {
|
|
2881
2944
|
const result = this.db
|
|
2882
|
-
.prepare(`DELETE FROM protocol_outbox
|
|
2945
|
+
.prepare(`DELETE FROM protocol_outbox
|
|
2883
2946
|
WHERE peer_id = ? AND protocol = ? AND message_id = ?`)
|
|
2884
2947
|
.run(peer, protocol, messageId);
|
|
2885
2948
|
return result.changes > 0;
|
|
2886
2949
|
}
|
|
2887
2950
|
hasEntry(peer, protocol, messageId) {
|
|
2888
2951
|
const row = this.db
|
|
2889
|
-
.prepare(`SELECT 1 FROM protocol_outbox
|
|
2952
|
+
.prepare(`SELECT 1 FROM protocol_outbox
|
|
2890
2953
|
WHERE peer_id = ? AND protocol = ? AND message_id = ? LIMIT 1`)
|
|
2891
2954
|
.get(peer, protocol, messageId);
|
|
2892
2955
|
return row !== undefined;
|
|
@@ -2896,27 +2959,27 @@ export class SqliteProtocolOutboxStore {
|
|
|
2896
2959
|
}
|
|
2897
2960
|
pendingFor(peer) {
|
|
2898
2961
|
const rows = this.db
|
|
2899
|
-
.prepare(`SELECT * FROM protocol_outbox
|
|
2900
|
-
WHERE peer_id = ?
|
|
2962
|
+
.prepare(`SELECT * FROM protocol_outbox
|
|
2963
|
+
WHERE peer_id = ?
|
|
2901
2964
|
ORDER BY first_failure_at ASC, protocol ASC, message_id ASC`)
|
|
2902
2965
|
.all(peer);
|
|
2903
2966
|
return rows.map(SqliteProtocolOutboxStore.rowToEntry);
|
|
2904
2967
|
}
|
|
2905
2968
|
due(now) {
|
|
2906
2969
|
const rows = this.db
|
|
2907
|
-
.prepare(`SELECT * FROM protocol_outbox
|
|
2908
|
-
WHERE next_attempt_at <= ?
|
|
2909
|
-
ORDER BY next_attempt_at ASC, first_failure_at ASC,
|
|
2970
|
+
.prepare(`SELECT * FROM protocol_outbox
|
|
2971
|
+
WHERE next_attempt_at <= ?
|
|
2972
|
+
ORDER BY next_attempt_at ASC, first_failure_at ASC,
|
|
2910
2973
|
peer_id ASC, protocol ASC, message_id ASC`)
|
|
2911
2974
|
.all(now);
|
|
2912
2975
|
return rows.map(SqliteProtocolOutboxStore.rowToEntry);
|
|
2913
2976
|
}
|
|
2914
2977
|
duePage(now, limit) {
|
|
2915
2978
|
const rows = this.db
|
|
2916
|
-
.prepare(`SELECT * FROM protocol_outbox
|
|
2917
|
-
WHERE next_attempt_at <= ?
|
|
2918
|
-
ORDER BY next_attempt_at ASC, first_failure_at ASC,
|
|
2919
|
-
peer_id ASC, protocol ASC, message_id ASC
|
|
2979
|
+
.prepare(`SELECT * FROM protocol_outbox
|
|
2980
|
+
WHERE next_attempt_at <= ?
|
|
2981
|
+
ORDER BY next_attempt_at ASC, first_failure_at ASC,
|
|
2982
|
+
peer_id ASC, protocol ASC, message_id ASC
|
|
2920
2983
|
LIMIT ?`)
|
|
2921
2984
|
.all(now, limit);
|
|
2922
2985
|
return rows.map(SqliteProtocolOutboxStore.rowToEntry);
|
|
@@ -3006,9 +3069,9 @@ export class SqliteKaNumberStore {
|
|
|
3006
3069
|
// `safeIntegers(true)` opts INTO bigint returns so the counter is
|
|
3007
3070
|
// exact past `Number.MAX_SAFE_INTEGER` (codex PR #976 F6).
|
|
3008
3071
|
const row = this.db
|
|
3009
|
-
.prepare(`INSERT INTO ka_numbers (author_address, next_number)
|
|
3010
|
-
VALUES (?, 1)
|
|
3011
|
-
ON CONFLICT(author_address) DO UPDATE SET next_number = next_number + 1
|
|
3072
|
+
.prepare(`INSERT INTO ka_numbers (author_address, next_number)
|
|
3073
|
+
VALUES (?, 1)
|
|
3074
|
+
ON CONFLICT(author_address) DO UPDATE SET next_number = next_number + 1
|
|
3012
3075
|
RETURNING next_number - 1 AS number`)
|
|
3013
3076
|
.safeIntegers(true)
|
|
3014
3077
|
.get(author);
|
|
@@ -3020,9 +3083,9 @@ export class SqliteKaNumberStore {
|
|
|
3020
3083
|
// `excluded.next_number` is the proposed floor from the VALUES row.
|
|
3021
3084
|
// `better-sqlite3` accepts bigint statement parameters natively.
|
|
3022
3085
|
this.db
|
|
3023
|
-
.prepare(`INSERT INTO ka_numbers (author_address, next_number)
|
|
3024
|
-
VALUES (?, ?)
|
|
3025
|
-
ON CONFLICT(author_address) DO UPDATE SET
|
|
3086
|
+
.prepare(`INSERT INTO ka_numbers (author_address, next_number)
|
|
3087
|
+
VALUES (?, ?)
|
|
3088
|
+
ON CONFLICT(author_address) DO UPDATE SET
|
|
3026
3089
|
next_number = MAX(next_number, excluded.next_number)`)
|
|
3027
3090
|
.run(author, nextNumberFloor);
|
|
3028
3091
|
}
|