@origintrail-official/dkg-node-ui 10.0.9 → 10.0.11
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 +24 -0
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +977 -942
- package/dist/db.js.map +1 -1
- package/dist/metrics-collector.js +1 -1
- package/dist/metrics-collector.js.map +1 -1
- package/dist/operation-tracker.d.ts +2 -0
- package/dist/operation-tracker.d.ts.map +1 -1
- package/dist/operation-tracker.js +11 -3
- package/dist/operation-tracker.js.map +1 -1
- package/dist-ui/assets/{3d-force-graph-32vYp3Gt.js → 3d-force-graph-Bl6v-uII.js} +1 -1
- package/dist-ui/assets/{AgentHub-B9W-efVf.js → AgentHub-D2vaQxlw.js} +1 -1
- package/dist-ui/assets/{AgentProfilePage-DxS9zMTU.js → AgentProfilePage-BgSTdsjD.js} +1 -1
- package/dist-ui/assets/{ApproveWalletsModal-DIwmlqqm.js → ApproveWalletsModal-D0Ub_eiD.js} +3 -3
- package/dist-ui/assets/{ConvictionDetailView-BozGXkJn.js → ConvictionDetailView-CieVypna.js} +1 -1
- package/dist-ui/assets/{Network-DSwIFRGu.js → Network-DKw_XEGi.js} +1 -1
- package/dist-ui/assets/{OnChainProvenanceCard-C73dR0ZK.js → OnChainProvenanceCard-Dn_IEP7Q.js} +1 -1
- package/dist-ui/assets/{Operations-CpTCodwd.js → Operations-BO2C3UWB.js} +34 -34
- package/dist-ui/assets/{PublishingConviction-BKppjqUt.js → PublishingConviction-xKRNHbzk.js} +1 -1
- package/dist-ui/assets/{Settings-Cgn410CQ.js → Settings-DC_d9STV.js} +1 -1
- package/dist-ui/assets/{ccip-DYNah0HH.js → ccip-zWfeQL8Q.js} +1 -1
- package/dist-ui/assets/{index-I_u_n2I5.js → index-CwRQnNYh.js} +3 -3
- package/dist-ui/assets/index-DEtfq9qv.css +1 -0
- package/dist-ui/assets/{index-91k1Z-EI.js → index-hrI-Ir4l.js} +943 -943
- package/dist-ui/assets/{jsonld-32FQRO67-DoKvywR8.js → jsonld-B67IUHNU-C3ydusi_.js} +2 -2
- package/dist-ui/assets/{jsonld-DLUtuDjD.js → jsonld-G-CM-8JR.js} +1 -1
- package/dist-ui/assets/{renderer-3d-2EVDZII7-DmIioW4I.js → renderer-3d-OSESAPET-ConXqyK6.js} +2 -2
- package/dist-ui/assets/{shikiHighlighter-BPMtNhWI.js → shikiHighlighter-C6lRYd1O.js} +1 -1
- package/dist-ui/index.html +14 -14
- package/package.json +3 -3
- package/dist-ui/assets/index-CtlCxKGP.css +0 -1
- /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 = 31;
|
|
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,44 +138,44 @@ 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
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'
|
|
176
|
+
const table = this.db.prepare(`
|
|
177
|
+
SELECT 1 AS found FROM sqlite_master
|
|
178
|
+
WHERE type = 'table' AND name = 'sync_checkpoints'
|
|
179
179
|
`).get();
|
|
180
180
|
if (!table)
|
|
181
181
|
return;
|
|
@@ -199,117 +199,117 @@ export class DashboardDB {
|
|
|
199
199
|
return;
|
|
200
200
|
}
|
|
201
201
|
if (version < 1) {
|
|
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
|
-
);
|
|
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
|
+
);
|
|
292
292
|
`);
|
|
293
293
|
}
|
|
294
294
|
if (version < 2) {
|
|
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;
|
|
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;
|
|
313
313
|
`);
|
|
314
314
|
}
|
|
315
315
|
if (version < 3) {
|
|
@@ -319,42 +319,42 @@ export class DashboardDB {
|
|
|
319
319
|
// column + the partial unique index in one step; the V11 block
|
|
320
320
|
// below idempotently adds the column to nodes that upgraded
|
|
321
321
|
// through versions 3..10 first.
|
|
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);
|
|
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);
|
|
335
335
|
`);
|
|
336
336
|
}
|
|
337
337
|
if (version < 4) {
|
|
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);
|
|
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);
|
|
358
358
|
`);
|
|
359
359
|
}
|
|
360
360
|
if (version < 5) {
|
|
@@ -363,77 +363,77 @@ export class DashboardDB {
|
|
|
363
363
|
// fresh-install CREATE so new nodes get the column + its index in one
|
|
364
364
|
// step; the V16 block below idempotently adds it to nodes that upgraded
|
|
365
365
|
// through versions 5..15 first (same pattern as `message_id` in V3/V11).
|
|
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);
|
|
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);
|
|
382
382
|
`);
|
|
383
383
|
}
|
|
384
384
|
if (version < 6) {
|
|
385
|
-
this.db.exec(`
|
|
386
|
-
CREATE TABLE IF NOT EXISTS settings (
|
|
387
|
-
key TEXT PRIMARY KEY,
|
|
388
|
-
value TEXT NOT NULL
|
|
389
|
-
);
|
|
385
|
+
this.db.exec(`
|
|
386
|
+
CREATE TABLE IF NOT EXISTS settings (
|
|
387
|
+
key TEXT PRIMARY KEY,
|
|
388
|
+
value TEXT NOT NULL
|
|
389
|
+
);
|
|
390
390
|
`);
|
|
391
391
|
}
|
|
392
392
|
if (version < 7) {
|
|
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);
|
|
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);
|
|
406
406
|
`);
|
|
407
407
|
}
|
|
408
408
|
if (version < 8) {
|
|
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);
|
|
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);
|
|
429
429
|
`);
|
|
430
430
|
}
|
|
431
431
|
if (version < 9) {
|
|
432
432
|
const columns = this.db.prepare('PRAGMA table_info(context_graph_subscriptions)').all();
|
|
433
433
|
if (!columns.some((column) => column.name === 'shared_memory_synced')) {
|
|
434
|
-
this.db.exec(`
|
|
435
|
-
ALTER TABLE context_graph_subscriptions
|
|
436
|
-
ADD COLUMN shared_memory_synced INTEGER;
|
|
434
|
+
this.db.exec(`
|
|
435
|
+
ALTER TABLE context_graph_subscriptions
|
|
436
|
+
ADD COLUMN shared_memory_synced INTEGER;
|
|
437
437
|
`);
|
|
438
438
|
}
|
|
439
439
|
}
|
|
@@ -502,10 +502,10 @@ export class DashboardDB {
|
|
|
502
502
|
if (!chatCols.has('message_id')) {
|
|
503
503
|
this.db.exec(`ALTER TABLE chat_messages ADD COLUMN message_id TEXT;`);
|
|
504
504
|
}
|
|
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;
|
|
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;
|
|
509
509
|
`);
|
|
510
510
|
}
|
|
511
511
|
if (version < 12) {
|
|
@@ -561,33 +561,33 @@ export class DashboardDB {
|
|
|
561
561
|
// `message_id` is enforced via `message_idempotency` instead.
|
|
562
562
|
// V13 will also preserve `chat_messages.message_id` as
|
|
563
563
|
// nullable + unwritten for rollback safety.
|
|
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);
|
|
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);
|
|
591
591
|
`);
|
|
592
592
|
}
|
|
593
593
|
if (version < 13) {
|
|
@@ -612,8 +612,8 @@ export class DashboardDB {
|
|
|
612
612
|
// table rebuild on downgrade.
|
|
613
613
|
//
|
|
614
614
|
// No data migration. No new tables. Pure DROP INDEX.
|
|
615
|
-
this.db.exec(`
|
|
616
|
-
DROP INDEX IF EXISTS idx_chat_msgid;
|
|
615
|
+
this.db.exec(`
|
|
616
|
+
DROP INDEX IF EXISTS idx_chat_msgid;
|
|
617
617
|
`);
|
|
618
618
|
}
|
|
619
619
|
if (version < 14) {
|
|
@@ -654,10 +654,10 @@ export class DashboardDB {
|
|
|
654
654
|
// operation-correlated log views (`getOperation`,
|
|
655
655
|
// `getFailedOperations`). Substring/text search moves to
|
|
656
656
|
// /api/node-log, which already supports `?q=`.
|
|
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;
|
|
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;
|
|
661
661
|
`);
|
|
662
662
|
// VACUUM cannot run inside a transaction; better-sqlite3 wraps
|
|
663
663
|
// multi-statement exec() in implicit BEGIN/COMMIT, so we issue
|
|
@@ -721,23 +721,23 @@ export class DashboardDB {
|
|
|
721
721
|
// re-parsing the daemon log. One row per decision point (sweep / fetch /
|
|
722
722
|
// promote / already / defer / cursor-advance / core-fill). Pruned by `ts`
|
|
723
723
|
// like the other event tables.
|
|
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
|
+
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
|
+
);
|
|
741
741
|
`);
|
|
742
742
|
this.db.exec(`CREATE INDEX IF NOT EXISTS idx_repl_ts ON replication_events(ts);`);
|
|
743
743
|
this.db.exec(`CREATE INDEX IF NOT EXISTS idx_repl_cg ON replication_events(context_graph_id, ts);`);
|
|
@@ -776,54 +776,54 @@ export class DashboardDB {
|
|
|
776
776
|
// the 2^63 INTEGER max and under the uint96 on-chain field. Like
|
|
777
777
|
// `protocol_outbox`, this state is durable and is NEVER pruned —
|
|
778
778
|
// reclaiming a number could mint a duplicate kaId.
|
|
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
|
-
);
|
|
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
|
+
);
|
|
784
784
|
`);
|
|
785
785
|
}
|
|
786
786
|
if (version < 21) {
|
|
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);
|
|
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);
|
|
796
796
|
`);
|
|
797
797
|
}
|
|
798
798
|
if (version < 22) {
|
|
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
|
-
);
|
|
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
|
+
);
|
|
808
808
|
`);
|
|
809
|
-
this.db.exec(`
|
|
810
|
-
CREATE INDEX IF NOT EXISTS idx_runtime_cursors_namespace_scope
|
|
811
|
-
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);
|
|
812
812
|
`);
|
|
813
813
|
}
|
|
814
814
|
if (version < 23) {
|
|
815
815
|
// OT-RFC-59 SC5: durable per-(peer, contextGraph) changelog cursor. NEVER
|
|
816
816
|
// pruned (see SqliteChangelogCursorStore) — a TTL would defeat the O(delta)
|
|
817
817
|
// cross-restart catch-up this table exists for.
|
|
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
|
-
);
|
|
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
|
+
);
|
|
827
827
|
`);
|
|
828
828
|
}
|
|
829
829
|
if (version < 24) {
|
|
@@ -832,39 +832,39 @@ export class DashboardDB {
|
|
|
832
832
|
// `store.nq` restore does NOT roll back with the RDF store. node-ui.db
|
|
833
833
|
// survives such a restore, so a rollback under the same era rotates the era
|
|
834
834
|
// (forcing peers to full-resync instead of silently skipping). Single row.
|
|
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
|
-
);
|
|
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
|
+
);
|
|
842
842
|
`);
|
|
843
843
|
}
|
|
844
844
|
if (version < 25) {
|
|
845
845
|
// Durable security audit for private-CG open enrollment. V25/V26 also
|
|
846
846
|
// used this table as admission state; V27 projects those legacy rows into
|
|
847
847
|
// the typed operational ledger below.
|
|
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);
|
|
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);
|
|
868
868
|
`);
|
|
869
869
|
}
|
|
870
870
|
if (version < 26) {
|
|
@@ -883,108 +883,108 @@ export class DashboardDB {
|
|
|
883
883
|
// Operational admission state is deliberately separate from the audit
|
|
884
884
|
// stream. Audit retention and event naming must never alter rate-limit,
|
|
885
885
|
// epoch, or commit-idempotency behaviour.
|
|
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);
|
|
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);
|
|
917
917
|
`);
|
|
918
918
|
// Best-effort one-time projection for V25/V26 databases. Invalid legacy
|
|
919
919
|
// JSON cannot abort startup; only rows with a typed numeric epoch can be
|
|
920
920
|
// authoritative operational state. For duplicate audit reservations,
|
|
921
921
|
// retain the newest row for each (CG, digest, epoch).
|
|
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;
|
|
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;
|
|
988
988
|
`);
|
|
989
989
|
// V26's trigger protected live reservation audit rows because they were
|
|
990
990
|
// operational state. The ledger makes that coupling obsolete, so replace
|
|
@@ -1002,21 +1002,21 @@ export class DashboardDB {
|
|
|
1002
1002
|
// idempotent and keeps the audit bound fail-closed on every open.
|
|
1003
1003
|
ensureJoinPolicyAuditCapTrigger();
|
|
1004
1004
|
if (version < 29) {
|
|
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);
|
|
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);
|
|
1020
1020
|
`);
|
|
1021
1021
|
}
|
|
1022
1022
|
if (version < 30) {
|
|
@@ -1025,6 +1025,24 @@ export class DashboardDB {
|
|
|
1025
1025
|
// restart can resume that same list instead of discarding durable work.
|
|
1026
1026
|
ensureSyncCheckpointResponderSessionColumns();
|
|
1027
1027
|
}
|
|
1028
|
+
if (version < 31) {
|
|
1029
|
+
this.db.exec(`
|
|
1030
|
+
CREATE TABLE IF NOT EXISTS snapshot_page_indexes (
|
|
1031
|
+
snapshot_digest TEXT PRIMARY KEY,
|
|
1032
|
+
format_version INTEGER NOT NULL CHECK (format_version > 0),
|
|
1033
|
+
stride INTEGER NOT NULL CHECK (stride > 0),
|
|
1034
|
+
snapshot_file_size INTEGER NOT NULL CHECK (snapshot_file_size >= 0),
|
|
1035
|
+
modification_fingerprint TEXT NOT NULL,
|
|
1036
|
+
offset_count INTEGER NOT NULL CHECK (offset_count > 0),
|
|
1037
|
+
offsets BLOB NOT NULL,
|
|
1038
|
+
checksum TEXT NOT NULL,
|
|
1039
|
+
CHECK (length(snapshot_digest) > 0),
|
|
1040
|
+
CHECK (length(modification_fingerprint) > 0),
|
|
1041
|
+
CHECK (length(offsets) = offset_count * 8),
|
|
1042
|
+
CHECK (length(checksum) > 0)
|
|
1043
|
+
);
|
|
1044
|
+
`);
|
|
1045
|
+
}
|
|
1028
1046
|
this.db.pragma(`user_version = ${SCHEMA_VERSION}`);
|
|
1029
1047
|
if (upgradedExistingDb && !this.explicitRetentionDays) {
|
|
1030
1048
|
this.retentionDays = LEGACY_IMPLICIT_RETENTION_DAYS;
|
|
@@ -1062,10 +1080,10 @@ export class DashboardDB {
|
|
|
1062
1080
|
// not depend on whether the corresponding audit rows still exist. Preserve
|
|
1063
1081
|
// the historical retention envelope for durable commit idempotency while
|
|
1064
1082
|
// allowing fully expired reservation/commit state to age out.
|
|
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 < ?)
|
|
1083
|
+
this.db.prepare(`
|
|
1084
|
+
DELETE FROM context_graph_join_approval_ledger
|
|
1085
|
+
WHERE reserved_at < ?
|
|
1086
|
+
AND (committed_at IS NULL OR committed_at < ?)
|
|
1069
1087
|
`).run(cutoff, cutoff);
|
|
1070
1088
|
this.db.prepare(`DELETE FROM sync_checkpoints WHERE expires_at < ?`).run(Date.now());
|
|
1071
1089
|
// Reconcile negatives are accelerators, never historical records. Once the
|
|
@@ -1118,16 +1136,16 @@ export class DashboardDB {
|
|
|
1118
1136
|
: 'done',
|
|
1119
1137
|
};
|
|
1120
1138
|
}
|
|
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
|
-
)
|
|
1139
|
+
const deleted = this.db.prepare(`
|
|
1140
|
+
DELETE FROM logs
|
|
1141
|
+
WHERE id IN (
|
|
1142
|
+
SELECT id
|
|
1143
|
+
FROM logs
|
|
1144
|
+
WHERE id <= @cutoff
|
|
1145
|
+
AND level NOT IN ('warn', 'error')
|
|
1146
|
+
ORDER BY id ASC
|
|
1147
|
+
LIMIT @batchRows
|
|
1148
|
+
)
|
|
1131
1149
|
`).run({
|
|
1132
1150
|
cutoff: overflowCutoff,
|
|
1133
1151
|
batchRows: this.logVolumePruneBatchRows,
|
|
@@ -1153,12 +1171,12 @@ export class DashboardDB {
|
|
|
1153
1171
|
};
|
|
1154
1172
|
}
|
|
1155
1173
|
routineLogOverflowCutoff() {
|
|
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 ?
|
|
1174
|
+
const row = this.db.prepare(`
|
|
1175
|
+
SELECT id
|
|
1176
|
+
FROM logs
|
|
1177
|
+
WHERE level NOT IN ('warn', 'error')
|
|
1178
|
+
ORDER BY id DESC
|
|
1179
|
+
LIMIT 1 OFFSET ?
|
|
1162
1180
|
`).get(this.routineLogRowCap);
|
|
1163
1181
|
return row?.id ?? null;
|
|
1164
1182
|
}
|
|
@@ -1259,50 +1277,50 @@ export class DashboardDB {
|
|
|
1259
1277
|
}
|
|
1260
1278
|
// --- Metric snapshots ---
|
|
1261
1279
|
insertSnapshot(snap) {
|
|
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
|
-
)
|
|
1280
|
+
this.stmt('insertSnapshot', `
|
|
1281
|
+
INSERT INTO metric_snapshots (
|
|
1282
|
+
ts, cpu_percent, mem_used_bytes, mem_total_bytes,
|
|
1283
|
+
disk_used_bytes, disk_total_bytes, heap_used_bytes, uptime_seconds,
|
|
1284
|
+
peer_count, direct_peers, relayed_peers, mesh_peers, contextGraph_count,
|
|
1285
|
+
total_triples, total_kcs, total_kas, store_bytes,
|
|
1286
|
+
confirmed_kcs, tentative_kcs, rpc_latency_ms, rpc_healthy,
|
|
1287
|
+
relay_capacity, relay_reservation_count, relay_active_circuits,
|
|
1288
|
+
relay_bytes_in, relay_bytes_out
|
|
1289
|
+
) VALUES (
|
|
1290
|
+
@ts, @cpu_percent, @mem_used_bytes, @mem_total_bytes,
|
|
1291
|
+
@disk_used_bytes, @disk_total_bytes, @heap_used_bytes, @uptime_seconds,
|
|
1292
|
+
@peer_count, @direct_peers, @relayed_peers, @mesh_peers, @contextGraph_count,
|
|
1293
|
+
@total_triples, @total_kcs, @total_kas, @store_bytes,
|
|
1294
|
+
@confirmed_kcs, @tentative_kcs, @rpc_latency_ms, @rpc_healthy,
|
|
1295
|
+
@relay_capacity, @relay_reservation_count, @relay_active_circuits,
|
|
1296
|
+
@relay_bytes_in, @relay_bytes_out
|
|
1297
|
+
)
|
|
1280
1298
|
`).run(snap);
|
|
1281
1299
|
}
|
|
1282
1300
|
getLatestSnapshot() {
|
|
1283
1301
|
return this.db.prepare('SELECT * FROM metric_snapshots ORDER BY ts DESC LIMIT 1').get();
|
|
1284
1302
|
}
|
|
1285
1303
|
upsertContextGraphSubscription(record) {
|
|
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
|
|
1304
|
+
this.stmt('upsertContextGraphSubscription', `
|
|
1305
|
+
INSERT INTO context_graph_subscriptions (
|
|
1306
|
+
context_graph_id, name, subscribed, synced, shared_memory_synced, meta_synced,
|
|
1307
|
+
on_chain_id, on_chain_hash, last_reconciled_ordinal, core_hosted, sync_scoped, updated_at
|
|
1308
|
+
) VALUES (
|
|
1309
|
+
@context_graph_id, @name, @subscribed, @synced, @shared_memory_synced, @meta_synced,
|
|
1310
|
+
@on_chain_id, @on_chain_hash, @last_reconciled_ordinal, @core_hosted, @sync_scoped, @updated_at
|
|
1311
|
+
)
|
|
1312
|
+
ON CONFLICT(context_graph_id) DO UPDATE SET
|
|
1313
|
+
name = excluded.name,
|
|
1314
|
+
subscribed = excluded.subscribed,
|
|
1315
|
+
synced = excluded.synced,
|
|
1316
|
+
shared_memory_synced = excluded.shared_memory_synced,
|
|
1317
|
+
meta_synced = excluded.meta_synced,
|
|
1318
|
+
on_chain_id = excluded.on_chain_id,
|
|
1319
|
+
on_chain_hash = excluded.on_chain_hash,
|
|
1320
|
+
last_reconciled_ordinal = excluded.last_reconciled_ordinal,
|
|
1321
|
+
core_hosted = excluded.core_hosted,
|
|
1322
|
+
sync_scoped = excluded.sync_scoped,
|
|
1323
|
+
updated_at = excluded.updated_at
|
|
1306
1324
|
`).run({
|
|
1307
1325
|
context_graph_id: record.context_graph_id,
|
|
1308
1326
|
name: record.name ?? null,
|
|
@@ -1400,37 +1418,37 @@ export class DashboardDB {
|
|
|
1400
1418
|
transition();
|
|
1401
1419
|
}
|
|
1402
1420
|
appendContextGraphJoinPolicyAudit(event) {
|
|
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1421
|
+
this.stmt('appendContextGraphJoinPolicyAudit', `
|
|
1422
|
+
INSERT INTO context_graph_join_policy_audit (
|
|
1423
|
+
ts, context_graph_id, event_type, actor, agent_address, outcome,
|
|
1424
|
+
reason, request_digest, policy_version, details
|
|
1425
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1408
1426
|
`).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);
|
|
1409
1427
|
}
|
|
1410
1428
|
reserveContextGraphAutomaticApproval(input) {
|
|
1411
1429
|
const reserve = this.db.transaction(() => {
|
|
1412
1430
|
const since = input.timestamp - 60 * 60 * 1000;
|
|
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 >= ?
|
|
1431
|
+
const cgRow = this.stmt('countContextGraphAutomaticApprovalReservations', `
|
|
1432
|
+
SELECT COUNT(*) AS count
|
|
1433
|
+
FROM context_graph_join_approval_ledger
|
|
1434
|
+
WHERE context_graph_id = ?
|
|
1435
|
+
AND reserved_at >= ?
|
|
1418
1436
|
`).get(input.contextGraphId, since);
|
|
1419
|
-
const nodeRow = this.stmt('countNodeAutomaticApprovalReservations', `
|
|
1420
|
-
SELECT COUNT(*) AS count
|
|
1421
|
-
FROM context_graph_join_approval_ledger
|
|
1422
|
-
WHERE reserved_at >= ?
|
|
1437
|
+
const nodeRow = this.stmt('countNodeAutomaticApprovalReservations', `
|
|
1438
|
+
SELECT COUNT(*) AS count
|
|
1439
|
+
FROM context_graph_join_approval_ledger
|
|
1440
|
+
WHERE reserved_at >= ?
|
|
1423
1441
|
`).get(since);
|
|
1424
1442
|
const contextGraphCount = Number(cgRow?.count ?? 0);
|
|
1425
1443
|
const nodeCount = Number(nodeRow?.count ?? 0);
|
|
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
|
|
1444
|
+
const existing = this.stmt('findContextGraphAutomaticApprovalReservation', `
|
|
1445
|
+
SELECT 1 AS found
|
|
1446
|
+
FROM context_graph_join_approval_ledger
|
|
1447
|
+
WHERE context_graph_id = ?
|
|
1448
|
+
AND request_digest = ?
|
|
1449
|
+
AND policy_epoch = ?
|
|
1450
|
+
AND reserved_at >= ?
|
|
1451
|
+
LIMIT 1
|
|
1434
1452
|
`).get(input.contextGraphId, input.requestDigest, input.policyEpoch, since);
|
|
1435
1453
|
if (existing) {
|
|
1436
1454
|
return {
|
|
@@ -1460,16 +1478,16 @@ export class DashboardDB {
|
|
|
1460
1478
|
// reservation without durable operational state. An expired replay of
|
|
1461
1479
|
// the same digest+epoch refreshes its reservation timestamp and consumes
|
|
1462
1480
|
// the rolling-window quota again, matching the former append-only model.
|
|
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
|
|
1481
|
+
this.stmt('upsertContextGraphAutomaticApprovalReservation', `
|
|
1482
|
+
INSERT INTO context_graph_join_approval_ledger (
|
|
1483
|
+
context_graph_id, request_digest, policy_epoch, reserved_at,
|
|
1484
|
+
state, committed_at, actor, agent_address, policy_version
|
|
1485
|
+
) VALUES (?, ?, ?, ?, 'reserved', NULL, ?, ?, ?)
|
|
1486
|
+
ON CONFLICT(context_graph_id, request_digest, policy_epoch) DO UPDATE SET
|
|
1487
|
+
reserved_at = excluded.reserved_at,
|
|
1488
|
+
actor = excluded.actor,
|
|
1489
|
+
agent_address = excluded.agent_address,
|
|
1490
|
+
policy_version = excluded.policy_version
|
|
1473
1491
|
`).run(input.contextGraphId, input.requestDigest, input.policyEpoch, input.timestamp, input.actor, input.agentAddress, input.policyVersion);
|
|
1474
1492
|
this.appendContextGraphJoinPolicyAudit({
|
|
1475
1493
|
timestamp: input.timestamp,
|
|
@@ -1495,26 +1513,26 @@ export class DashboardDB {
|
|
|
1495
1513
|
return reserve();
|
|
1496
1514
|
}
|
|
1497
1515
|
markContextGraphAutomaticApprovalRepairPending(input) {
|
|
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'
|
|
1516
|
+
const marked = this.stmt('markContextGraphAutomaticApprovalRepairPending', `
|
|
1517
|
+
UPDATE context_graph_join_approval_ledger
|
|
1518
|
+
SET repair_pending = 1
|
|
1519
|
+
WHERE context_graph_id = ?
|
|
1520
|
+
AND request_digest = ?
|
|
1521
|
+
AND policy_epoch = ?
|
|
1522
|
+
AND state = 'reserved'
|
|
1505
1523
|
`).run(input.contextGraphId, input.requestDigest, input.policyEpoch);
|
|
1506
1524
|
return marked.changes === 1;
|
|
1507
1525
|
}
|
|
1508
1526
|
getContextGraphAutomaticApprovalRepair(contextGraphId, requestDigest) {
|
|
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
|
|
1527
|
+
const repair = this.stmt('getContextGraphAutomaticApprovalRepair', `
|
|
1528
|
+
SELECT policy_epoch, actor, agent_address
|
|
1529
|
+
FROM context_graph_join_approval_ledger
|
|
1530
|
+
WHERE context_graph_id = ?
|
|
1531
|
+
AND request_digest = ?
|
|
1532
|
+
AND state = 'reserved'
|
|
1533
|
+
AND repair_pending = 1
|
|
1534
|
+
ORDER BY reserved_at DESC
|
|
1535
|
+
LIMIT 1
|
|
1518
1536
|
`).get(contextGraphId, requestDigest);
|
|
1519
1537
|
return repair
|
|
1520
1538
|
? {
|
|
@@ -1526,26 +1544,26 @@ export class DashboardDB {
|
|
|
1526
1544
|
}
|
|
1527
1545
|
commitContextGraphAutomaticApproval(input) {
|
|
1528
1546
|
const commit = this.db.transaction(() => {
|
|
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
|
|
1547
|
+
const reservation = this.stmt('findExactContextGraphAutomaticApprovalReservation', `
|
|
1548
|
+
SELECT context_graph_id, request_digest, policy_epoch, reserved_at,
|
|
1549
|
+
state, committed_at, actor, agent_address, policy_version
|
|
1550
|
+
FROM context_graph_join_approval_ledger
|
|
1551
|
+
WHERE context_graph_id = ?
|
|
1552
|
+
AND request_digest = ?
|
|
1553
|
+
AND policy_epoch = ?
|
|
1554
|
+
LIMIT 1
|
|
1537
1555
|
`).get(input.contextGraphId, input.requestDigest, input.policyEpoch);
|
|
1538
1556
|
if (!reservation)
|
|
1539
1557
|
return false;
|
|
1540
1558
|
if (reservation.state === 'committed')
|
|
1541
1559
|
return true;
|
|
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'
|
|
1560
|
+
const updated = this.stmt('commitContextGraphAutomaticApprovalReservation', `
|
|
1561
|
+
UPDATE context_graph_join_approval_ledger
|
|
1562
|
+
SET state = 'committed', committed_at = ?, repair_pending = 0
|
|
1563
|
+
WHERE context_graph_id = ?
|
|
1564
|
+
AND request_digest = ?
|
|
1565
|
+
AND policy_epoch = ?
|
|
1566
|
+
AND state = 'reserved'
|
|
1549
1567
|
`).run(input.timestamp, reservation.context_graph_id, reservation.request_digest, reservation.policy_epoch);
|
|
1550
1568
|
if (updated.changes !== 1)
|
|
1551
1569
|
return false;
|
|
@@ -1569,48 +1587,48 @@ export class DashboardDB {
|
|
|
1569
1587
|
}
|
|
1570
1588
|
getContextGraphAutomaticApprovalUsage(contextGraphId, timestamp) {
|
|
1571
1589
|
const since = timestamp - 60 * 60 * 1000;
|
|
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 >= ?
|
|
1590
|
+
const contextGraphApprovalsLastHour = Number(this.stmt('getContextGraphAutomaticApprovalUsage', `
|
|
1591
|
+
SELECT COUNT(*) AS count
|
|
1592
|
+
FROM context_graph_join_approval_ledger
|
|
1593
|
+
WHERE context_graph_id = ?
|
|
1594
|
+
AND reserved_at >= ?
|
|
1577
1595
|
`).get(contextGraphId, since)?.count ?? 0);
|
|
1578
|
-
const nodeApprovalsLastHour = Number(this.stmt('getNodeAutomaticApprovalUsage', `
|
|
1579
|
-
SELECT COUNT(*) AS count
|
|
1580
|
-
FROM context_graph_join_approval_ledger
|
|
1581
|
-
WHERE reserved_at >= ?
|
|
1596
|
+
const nodeApprovalsLastHour = Number(this.stmt('getNodeAutomaticApprovalUsage', `
|
|
1597
|
+
SELECT COUNT(*) AS count
|
|
1598
|
+
FROM context_graph_join_approval_ledger
|
|
1599
|
+
WHERE reserved_at >= ?
|
|
1582
1600
|
`).get(since)?.count ?? 0);
|
|
1583
1601
|
return { contextGraphApprovalsLastHour, nodeApprovalsLastHour };
|
|
1584
1602
|
}
|
|
1585
1603
|
listContextGraphJoinPolicyAudit(contextGraphId) {
|
|
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
|
|
1604
|
+
return this.stmt('listContextGraphJoinPolicyAudit', `
|
|
1605
|
+
SELECT id, ts, context_graph_id, event_type, actor, agent_address,
|
|
1606
|
+
outcome, reason, request_digest, policy_version, details
|
|
1607
|
+
FROM context_graph_join_policy_audit
|
|
1608
|
+
WHERE context_graph_id = ?
|
|
1609
|
+
ORDER BY id ASC
|
|
1592
1610
|
`).all(contextGraphId);
|
|
1593
1611
|
}
|
|
1594
1612
|
contextGraphJoinPolicyKey(contextGraphId) {
|
|
1595
1613
|
return `contextGraphJoinPolicy:${contextGraphId}`;
|
|
1596
1614
|
}
|
|
1597
1615
|
upsertVmReconcileNegative(record) {
|
|
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
|
|
1616
|
+
this.stmt('upsertVmReconcileNegative', `
|
|
1617
|
+
INSERT INTO vm_reconcile_negative_cache (
|
|
1618
|
+
cache_key, context_graph_id, failures, next_retry_at, swm_gen,
|
|
1619
|
+
candidate_namespaces, peer_topology_key, updated_at
|
|
1620
|
+
) VALUES (
|
|
1621
|
+
@cache_key, @context_graph_id, @failures, @next_retry_at, @swm_gen,
|
|
1622
|
+
@candidate_namespaces, @peer_topology_key, @updated_at
|
|
1623
|
+
)
|
|
1624
|
+
ON CONFLICT(cache_key) DO UPDATE SET
|
|
1625
|
+
context_graph_id = excluded.context_graph_id,
|
|
1626
|
+
failures = excluded.failures,
|
|
1627
|
+
next_retry_at = excluded.next_retry_at,
|
|
1628
|
+
swm_gen = excluded.swm_gen,
|
|
1629
|
+
candidate_namespaces = excluded.candidate_namespaces,
|
|
1630
|
+
peer_topology_key = excluded.peer_topology_key,
|
|
1631
|
+
updated_at = excluded.updated_at
|
|
1614
1632
|
`).run(record);
|
|
1615
1633
|
}
|
|
1616
1634
|
getVmReconcileNegative(cacheKey) {
|
|
@@ -1629,14 +1647,14 @@ export class DashboardDB {
|
|
|
1629
1647
|
* coerced defensively so a malformed event can never abort the insert.
|
|
1630
1648
|
*/
|
|
1631
1649
|
insertReplicationEvent(ev) {
|
|
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
|
-
)
|
|
1650
|
+
this.stmt('insertReplicationEvent', `
|
|
1651
|
+
INSERT INTO replication_events (
|
|
1652
|
+
ts, context_graph_id, on_chain_cg_id, action, ual, ordinal, ka_id,
|
|
1653
|
+
from_watermark, to_watermark, head, reconciled, pending, detail
|
|
1654
|
+
) VALUES (
|
|
1655
|
+
@ts, @context_graph_id, @on_chain_cg_id, @action, @ual, @ordinal, @ka_id,
|
|
1656
|
+
@from_watermark, @to_watermark, @head, @reconciled, @pending, @detail
|
|
1657
|
+
)
|
|
1640
1658
|
`).run({
|
|
1641
1659
|
ts: ev.ts,
|
|
1642
1660
|
context_graph_id: ev.context_graph_id,
|
|
@@ -1683,15 +1701,15 @@ export class DashboardDB {
|
|
|
1683
1701
|
// and inflate the reported latency. fetch/promote for one version always
|
|
1684
1702
|
// share the same ordinal, so this pairs them exactly; a fast-path promote
|
|
1685
1703
|
// with no fetch for its ordinal correctly contributes a 0ms latency.
|
|
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
|
|
1704
|
+
const latencyRows = this.db.prepare(`
|
|
1705
|
+
SELECT p.ts AS promote_ts,
|
|
1706
|
+
(SELECT MAX(f.ts) FROM replication_events f
|
|
1707
|
+
WHERE f.action = 'fetch'
|
|
1708
|
+
AND f.context_graph_id = p.context_graph_id
|
|
1709
|
+
AND f.ordinal = p.ordinal
|
|
1710
|
+
AND f.ts <= p.ts AND f.ts >= ?) AS fetch_ts
|
|
1711
|
+
FROM replication_events p
|
|
1712
|
+
WHERE p.action = 'promote' AND p.ts >= ? AND p.ordinal IS NOT NULL
|
|
1695
1713
|
`).all(cutoff, cutoff);
|
|
1696
1714
|
const latencies = latencyRows
|
|
1697
1715
|
.map((r) => (r.fetch_ts != null ? r.promote_ts - r.fetch_ts : 0))
|
|
@@ -1721,20 +1739,20 @@ export class DashboardDB {
|
|
|
1721
1739
|
}
|
|
1722
1740
|
_computeReplicationPerCg(periodMs) {
|
|
1723
1741
|
const cutoff = Date.now() - periodMs;
|
|
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
|
|
1742
|
+
return this.db.prepare(`
|
|
1743
|
+
SELECT context_graph_id,
|
|
1744
|
+
on_chain_cg_id,
|
|
1745
|
+
SUM(CASE WHEN action = 'promote' THEN 1 ELSE 0 END) AS promotes,
|
|
1746
|
+
SUM(CASE WHEN action = 'fetch' THEN 1 ELSE 0 END) AS fetches,
|
|
1747
|
+
SUM(CASE WHEN action = 'defer' THEN 1 ELSE 0 END) AS defers,
|
|
1748
|
+
SUM(CASE WHEN action = 'cursor-advance' THEN 1 ELSE 0 END) AS cursor_advances,
|
|
1749
|
+
MAX(to_watermark) AS last_watermark,
|
|
1750
|
+
MAX(head) AS last_head,
|
|
1751
|
+
MAX(ts) AS last_event_ts
|
|
1752
|
+
FROM replication_events
|
|
1753
|
+
WHERE ts >= ?
|
|
1754
|
+
GROUP BY context_graph_id, on_chain_cg_id
|
|
1755
|
+
ORDER BY last_event_ts DESC
|
|
1738
1756
|
`).all(cutoff);
|
|
1739
1757
|
}
|
|
1740
1758
|
/**
|
|
@@ -1753,16 +1771,16 @@ export class DashboardDB {
|
|
|
1753
1771
|
const cutoff = Date.now() - opts.periodMs;
|
|
1754
1772
|
const bucket = Math.max(1, Math.floor(opts.bucketMs));
|
|
1755
1773
|
const where = opts.contextGraphId ? 'AND context_graph_id = @cg' : '';
|
|
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
|
|
1774
|
+
return this.db.prepare(`
|
|
1775
|
+
SELECT (ts / @bucket) * @bucket AS bucket,
|
|
1776
|
+
SUM(CASE WHEN action = 'promote' THEN 1 ELSE 0 END) AS promotes,
|
|
1777
|
+
SUM(CASE WHEN action = 'fetch' THEN 1 ELSE 0 END) AS fetches,
|
|
1778
|
+
SUM(CASE WHEN action = 'defer' THEN 1 ELSE 0 END) AS defers,
|
|
1779
|
+
COUNT(*) AS total
|
|
1780
|
+
FROM replication_events
|
|
1781
|
+
WHERE ts >= @cutoff ${where}
|
|
1782
|
+
GROUP BY bucket
|
|
1783
|
+
ORDER BY bucket ASC
|
|
1766
1784
|
`).all({ cutoff, bucket, cg: opts.contextGraphId ?? null });
|
|
1767
1785
|
}
|
|
1768
1786
|
/**
|
|
@@ -1774,13 +1792,13 @@ export class DashboardDB {
|
|
|
1774
1792
|
const safeLimit = Number.isFinite(limit)
|
|
1775
1793
|
? Math.max(1, Math.min(1000, Math.trunc(limit)))
|
|
1776
1794
|
: 100;
|
|
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 ?
|
|
1795
|
+
return this.db.prepare(`
|
|
1796
|
+
SELECT ts, context_graph_id, on_chain_cg_id, action, ual, ordinal, ka_id,
|
|
1797
|
+
from_watermark, to_watermark, head, reconciled, pending, detail
|
|
1798
|
+
FROM replication_events
|
|
1799
|
+
WHERE context_graph_id = ?
|
|
1800
|
+
ORDER BY ts DESC
|
|
1801
|
+
LIMIT ?
|
|
1784
1802
|
`).all(contextGraphId, safeLimit);
|
|
1785
1803
|
}
|
|
1786
1804
|
/**
|
|
@@ -1789,38 +1807,38 @@ export class DashboardDB {
|
|
|
1789
1807
|
* on-disk truth the Replication tab's cursor inspector renders.
|
|
1790
1808
|
*/
|
|
1791
1809
|
getReplicationCursors() {
|
|
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
|
|
1810
|
+
return this.db.prepare(`
|
|
1811
|
+
SELECT s.context_graph_id AS context_graph_id,
|
|
1812
|
+
s.on_chain_id AS on_chain_id,
|
|
1813
|
+
s.last_reconciled_ordinal AS last_reconciled_ordinal,
|
|
1814
|
+
s.core_hosted AS core_hosted,
|
|
1815
|
+
s.subscribed AS subscribed,
|
|
1816
|
+
(SELECT MAX(head) FROM replication_events e
|
|
1817
|
+
WHERE e.context_graph_id = s.context_graph_id) AS last_head,
|
|
1818
|
+
(SELECT MAX(ts) FROM replication_events e
|
|
1819
|
+
WHERE e.context_graph_id = s.context_graph_id) AS last_event_ts
|
|
1820
|
+
FROM context_graph_subscriptions s
|
|
1821
|
+
ORDER BY s.context_graph_id ASC
|
|
1804
1822
|
`).all();
|
|
1805
1823
|
}
|
|
1806
1824
|
upsertContextGraphMember(record) {
|
|
1807
1825
|
const firstSeenAt = record.first_seen_at ?? record.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
|
|
1826
|
+
this.stmt('upsertContextGraphMember', `
|
|
1827
|
+
INSERT INTO context_graph_memberships (
|
|
1828
|
+
context_graph_id, principal_type, principal_id, role, status, source,
|
|
1829
|
+
display_name, metadata, first_seen_at, updated_at
|
|
1830
|
+
) VALUES (
|
|
1831
|
+
@context_graph_id, @principal_type, @principal_id, @role, @status, @source,
|
|
1832
|
+
@display_name, @metadata, @first_seen_at, @updated_at
|
|
1833
|
+
)
|
|
1834
|
+
ON CONFLICT(context_graph_id, principal_type, principal_id) DO UPDATE SET
|
|
1835
|
+
role = excluded.role,
|
|
1836
|
+
status = excluded.status,
|
|
1837
|
+
source = excluded.source,
|
|
1838
|
+
display_name = excluded.display_name,
|
|
1839
|
+
metadata = excluded.metadata,
|
|
1840
|
+
first_seen_at = context_graph_memberships.first_seen_at,
|
|
1841
|
+
updated_at = excluded.updated_at
|
|
1824
1842
|
`).run({
|
|
1825
1843
|
context_graph_id: record.context_graph_id,
|
|
1826
1844
|
principal_type: record.principal_type,
|
|
@@ -1836,15 +1854,15 @@ export class DashboardDB {
|
|
|
1836
1854
|
}
|
|
1837
1855
|
listContextGraphMembers(contextGraphId) {
|
|
1838
1856
|
if (contextGraphId) {
|
|
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
|
|
1857
|
+
return this.db.prepare(`
|
|
1858
|
+
SELECT * FROM context_graph_memberships
|
|
1859
|
+
WHERE context_graph_id = ?
|
|
1860
|
+
ORDER BY principal_type ASC, principal_id ASC
|
|
1843
1861
|
`).all(contextGraphId);
|
|
1844
1862
|
}
|
|
1845
|
-
return this.db.prepare(`
|
|
1846
|
-
SELECT * FROM context_graph_memberships
|
|
1847
|
-
ORDER BY context_graph_id ASC, principal_type ASC, principal_id ASC
|
|
1863
|
+
return this.db.prepare(`
|
|
1864
|
+
SELECT * FROM context_graph_memberships
|
|
1865
|
+
ORDER BY context_graph_id ASC, principal_type ASC, principal_id ASC
|
|
1848
1866
|
`).all();
|
|
1849
1867
|
}
|
|
1850
1868
|
deleteContextGraphMember(contextGraphId, principalType, principalId) {
|
|
@@ -1856,18 +1874,18 @@ export class DashboardDB {
|
|
|
1856
1874
|
return this.db.prepare('SELECT * FROM metric_snapshots WHERE ts >= ? AND ts <= ? ORDER BY ts').all(from, to);
|
|
1857
1875
|
}
|
|
1858
1876
|
const step = Math.ceil(total.c / maxPoints);
|
|
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
|
|
1877
|
+
return this.db.prepare(`
|
|
1878
|
+
SELECT * FROM (
|
|
1879
|
+
SELECT *, ROW_NUMBER() OVER (ORDER BY ts) as rn
|
|
1880
|
+
FROM metric_snapshots WHERE ts >= ? AND ts <= ?
|
|
1881
|
+
) WHERE rn % ? = 0 ORDER BY ts
|
|
1864
1882
|
`).all(from, to, step);
|
|
1865
1883
|
}
|
|
1866
1884
|
// --- Operations ---
|
|
1867
1885
|
insertOperation(op) {
|
|
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)
|
|
1886
|
+
this.stmt('insertOp', `
|
|
1887
|
+
INSERT INTO operations (operation_id, operation_name, started_at, status, peer_id, contextGraph_id, details)
|
|
1888
|
+
VALUES (@operation_id, @operation_name, @started_at, 'in_progress', @peer_id, @contextGraph_id, @details)
|
|
1871
1889
|
`).run({
|
|
1872
1890
|
operation_id: op.operation_id,
|
|
1873
1891
|
operation_name: op.operation_name,
|
|
@@ -1878,10 +1896,10 @@ export class DashboardDB {
|
|
|
1878
1896
|
});
|
|
1879
1897
|
}
|
|
1880
1898
|
completeOperation(op) {
|
|
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'
|
|
1899
|
+
this.stmt('completeOp', `
|
|
1900
|
+
UPDATE operations SET status = 'success', duration_ms = @duration_ms,
|
|
1901
|
+
triple_count = @triple_count, details = COALESCE(@details, details)
|
|
1902
|
+
WHERE operation_id = @operation_id AND status = 'in_progress'
|
|
1885
1903
|
`).run({
|
|
1886
1904
|
operation_id: op.operation_id,
|
|
1887
1905
|
duration_ms: op.duration_ms,
|
|
@@ -1890,10 +1908,16 @@ export class DashboardDB {
|
|
|
1890
1908
|
});
|
|
1891
1909
|
}
|
|
1892
1910
|
failOperation(op) {
|
|
1893
|
-
this.
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1911
|
+
this.finishOperation({ ...op, status: 'error' });
|
|
1912
|
+
}
|
|
1913
|
+
cancelOperation(op) {
|
|
1914
|
+
this.finishOperation({ ...op, status: 'cancelled' });
|
|
1915
|
+
}
|
|
1916
|
+
finishOperation(op) {
|
|
1917
|
+
this.stmt('finishOperation', `
|
|
1918
|
+
UPDATE operations SET status = @status, duration_ms = @duration_ms,
|
|
1919
|
+
error_message = @error_message
|
|
1920
|
+
WHERE operation_id = @operation_id AND status = 'in_progress'
|
|
1897
1921
|
`).run(op);
|
|
1898
1922
|
}
|
|
1899
1923
|
getOperations(opts = {}) {
|
|
@@ -1950,22 +1974,22 @@ export class DashboardDB {
|
|
|
1950
1974
|
}
|
|
1951
1975
|
getErrorHotspots(periodMs = 7 * 86_400_000) {
|
|
1952
1976
|
const cutoff = Date.now() - periodMs;
|
|
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
|
|
1977
|
+
return this.db.prepare(`
|
|
1978
|
+
SELECT
|
|
1979
|
+
p.phase,
|
|
1980
|
+
o.operation_name,
|
|
1981
|
+
COUNT(*) as error_count,
|
|
1982
|
+
(SELECT p2.details FROM operation_phases p2
|
|
1983
|
+
JOIN operations o2 ON o2.operation_id = p2.operation_id
|
|
1984
|
+
WHERE p2.phase = p.phase AND o2.operation_name = o.operation_name
|
|
1985
|
+
AND p2.status = 'error' AND p2.started_at >= ?
|
|
1986
|
+
ORDER BY p2.started_at DESC LIMIT 1) as last_error,
|
|
1987
|
+
MAX(p.started_at) as last_occurred
|
|
1988
|
+
FROM operation_phases p
|
|
1989
|
+
JOIN operations o ON o.operation_id = p.operation_id
|
|
1990
|
+
WHERE p.status = 'error' AND p.started_at >= ?
|
|
1991
|
+
GROUP BY p.phase, o.operation_name
|
|
1992
|
+
ORDER BY error_count DESC
|
|
1969
1993
|
`).all(cutoff, cutoff);
|
|
1970
1994
|
}
|
|
1971
1995
|
getFailedOperations(opts = {}) {
|
|
@@ -1987,17 +2011,17 @@ export class DashboardDB {
|
|
|
1987
2011
|
params.push(like, like, like);
|
|
1988
2012
|
}
|
|
1989
2013
|
params.push(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 ?
|
|
2014
|
+
const rows = this.db.prepare(`
|
|
2015
|
+
SELECT
|
|
2016
|
+
o.*,
|
|
2017
|
+
p.phase AS phase,
|
|
2018
|
+
p.details AS phase_error,
|
|
2019
|
+
p.started_at AS phase_started_at
|
|
2020
|
+
FROM operation_phases p
|
|
2021
|
+
JOIN operations o ON o.operation_id = p.operation_id
|
|
2022
|
+
WHERE ${where}
|
|
2023
|
+
ORDER BY p.started_at DESC
|
|
2024
|
+
LIMIT ?
|
|
2001
2025
|
`).all(...params);
|
|
2002
2026
|
const operations = rows.map(row => {
|
|
2003
2027
|
const logs = this.db.prepare('SELECT * FROM logs WHERE operation_id = ? ORDER BY ts DESC LIMIT 20').all(row.operation_id);
|
|
@@ -2014,42 +2038,48 @@ export class DashboardDB {
|
|
|
2014
2038
|
}
|
|
2015
2039
|
// --- Operation phases ---
|
|
2016
2040
|
insertPhase(op) {
|
|
2017
|
-
this.stmt('insertPhase', `
|
|
2018
|
-
INSERT INTO operation_phases (operation_id, phase, started_at, status)
|
|
2019
|
-
VALUES (@operation_id, @phase, @started_at, 'in_progress')
|
|
2041
|
+
this.stmt('insertPhase', `
|
|
2042
|
+
INSERT INTO operation_phases (operation_id, phase, started_at, status)
|
|
2043
|
+
VALUES (@operation_id, @phase, @started_at, 'in_progress')
|
|
2020
2044
|
`).run(op);
|
|
2021
2045
|
}
|
|
2022
2046
|
completePhase(op) {
|
|
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'
|
|
2047
|
+
this.stmt('completePhase', `
|
|
2048
|
+
UPDATE operation_phases SET status = 'success', duration_ms = @duration_ms
|
|
2049
|
+
WHERE operation_id = @operation_id AND phase = @phase AND status = 'in_progress'
|
|
2026
2050
|
`).run(op);
|
|
2027
2051
|
}
|
|
2028
2052
|
failPhase(op) {
|
|
2029
|
-
this.
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2053
|
+
this.finishPhase({ ...op, status: 'error' });
|
|
2054
|
+
}
|
|
2055
|
+
cancelPhase(op) {
|
|
2056
|
+
this.finishPhase({ ...op, status: 'cancelled' });
|
|
2057
|
+
}
|
|
2058
|
+
finishPhase(op) {
|
|
2059
|
+
this.stmt('finishPhase', `
|
|
2060
|
+
UPDATE operation_phases SET status = @status, duration_ms = @duration_ms,
|
|
2061
|
+
details = @error_message
|
|
2062
|
+
WHERE operation_id = @operation_id AND phase = @phase AND status = 'in_progress'
|
|
2033
2063
|
`).run(op);
|
|
2034
2064
|
}
|
|
2035
2065
|
failAllPhases(op) {
|
|
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'
|
|
2066
|
+
this.stmt('failAllPhases', `
|
|
2067
|
+
UPDATE operation_phases SET status = 'error', duration_ms = @duration_ms,
|
|
2068
|
+
details = @error_message
|
|
2069
|
+
WHERE operation_id = @operation_id AND status = 'in_progress'
|
|
2040
2070
|
`).run(op);
|
|
2041
2071
|
}
|
|
2042
2072
|
// --- Operation cost & tx ---
|
|
2043
2073
|
setOperationCost(op) {
|
|
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
|
|
2074
|
+
this.stmt('setCost', `
|
|
2075
|
+
UPDATE operations SET
|
|
2076
|
+
gas_used = COALESCE(@gas_used, gas_used),
|
|
2077
|
+
gas_price_gwei = COALESCE(@gas_price_gwei, gas_price_gwei),
|
|
2078
|
+
gas_cost_eth = COALESCE(@gas_cost_eth, gas_cost_eth),
|
|
2079
|
+
trac_cost = COALESCE(@trac_cost, trac_cost),
|
|
2080
|
+
tx_hash = COALESCE(@tx_hash, tx_hash),
|
|
2081
|
+
chain_id = COALESCE(@chain_id, chain_id)
|
|
2082
|
+
WHERE operation_id = @operation_id
|
|
2053
2083
|
`).run({
|
|
2054
2084
|
operation_id: op.operation_id,
|
|
2055
2085
|
gas_used: op.gas_used ?? null,
|
|
@@ -2067,23 +2097,26 @@ export class DashboardDB {
|
|
|
2067
2097
|
const params = [cutoff];
|
|
2068
2098
|
if (opts.name)
|
|
2069
2099
|
params.push(opts.name);
|
|
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
|
-
|
|
2076
|
-
AVG(
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2100
|
+
const summaryRow = this.db.prepare(`
|
|
2101
|
+
SELECT
|
|
2102
|
+
COUNT(*) as totalCount,
|
|
2103
|
+
SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END) as successCount,
|
|
2104
|
+
SUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END) as errorCount,
|
|
2105
|
+
SUM(CASE WHEN status IN ('success', 'error') THEN 1 ELSE 0 END) as healthCount,
|
|
2106
|
+
AVG(CASE WHEN status = 'success' THEN duration_ms END) as avgDurationMs,
|
|
2107
|
+
AVG(gas_cost_eth) as avgGasCostEth,
|
|
2108
|
+
SUM(gas_cost_eth) as totalGasCostEth,
|
|
2109
|
+
AVG(trac_cost) as avgTracCost,
|
|
2110
|
+
SUM(trac_cost) as totalTracCost
|
|
2111
|
+
FROM operations WHERE started_at >= ? ${nameFilter}
|
|
2081
2112
|
`).get(...params);
|
|
2082
2113
|
const summary = {
|
|
2083
2114
|
totalCount: summaryRow.totalCount ?? 0,
|
|
2084
2115
|
successCount: summaryRow.successCount ?? 0,
|
|
2085
2116
|
errorCount: summaryRow.errorCount ?? 0,
|
|
2086
|
-
successRate: summaryRow.
|
|
2117
|
+
successRate: summaryRow.healthCount > 0
|
|
2118
|
+
? (summaryRow.successCount ?? 0) / summaryRow.healthCount
|
|
2119
|
+
: 0,
|
|
2087
2120
|
avgDurationMs: summaryRow.avgDurationMs ?? 0,
|
|
2088
2121
|
avgGasCostEth: summaryRow.avgGasCostEth ?? 0,
|
|
2089
2122
|
totalGasCostEth: summaryRow.totalGasCostEth ?? 0,
|
|
@@ -2094,24 +2127,25 @@ export class DashboardDB {
|
|
|
2094
2127
|
const tsParams = [bucketSize, cutoff];
|
|
2095
2128
|
if (opts.name)
|
|
2096
2129
|
tsParams.push(opts.name);
|
|
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
|
-
|
|
2103
|
-
AVG(
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2130
|
+
const timeSeries = this.db.prepare(`
|
|
2131
|
+
SELECT
|
|
2132
|
+
(CAST(started_at / ? AS INTEGER) * ?) as bucket,
|
|
2133
|
+
COUNT(*) as count,
|
|
2134
|
+
SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END) as successCount,
|
|
2135
|
+
SUM(CASE WHEN status IN ('success', 'error') THEN 1 ELSE 0 END) as healthCount,
|
|
2136
|
+
AVG(CASE WHEN status = 'success' THEN duration_ms END) as avgDurationMs,
|
|
2137
|
+
AVG(gas_cost_eth) as avgGasCostEth,
|
|
2138
|
+
SUM(gas_cost_eth) as totalGasCostEth
|
|
2139
|
+
FROM operations
|
|
2140
|
+
WHERE started_at >= ? ${nameFilter}
|
|
2141
|
+
GROUP BY bucket ORDER BY bucket
|
|
2108
2142
|
`).all(bucketSize, bucketSize, ...params);
|
|
2109
2143
|
return {
|
|
2110
2144
|
summary,
|
|
2111
2145
|
timeSeries: timeSeries.map((r) => ({
|
|
2112
2146
|
bucket: r.bucket,
|
|
2113
2147
|
count: r.count,
|
|
2114
|
-
successRate: r.
|
|
2148
|
+
successRate: r.healthCount > 0 ? r.successCount / r.healthCount : 0,
|
|
2115
2149
|
avgDurationMs: r.avgDurationMs ?? 0,
|
|
2116
2150
|
avgGasCostEth: r.avgGasCostEth ?? 0,
|
|
2117
2151
|
totalGasCostEth: r.totalGasCostEth ?? 0,
|
|
@@ -2121,16 +2155,17 @@ export class DashboardDB {
|
|
|
2121
2155
|
// --- Per-type time series ---
|
|
2122
2156
|
getPerTypeTimeSeries(opts) {
|
|
2123
2157
|
const cutoff = Date.now() - opts.periodMs;
|
|
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(
|
|
2132
|
-
|
|
2133
|
-
|
|
2158
|
+
const rows = this.db.prepare(`
|
|
2159
|
+
SELECT
|
|
2160
|
+
(CAST(started_at / ? AS INTEGER) * ?) as bucket,
|
|
2161
|
+
operation_name as type,
|
|
2162
|
+
COUNT(*) as count,
|
|
2163
|
+
AVG(CASE WHEN status = 'success' THEN duration_ms END) as avgMs,
|
|
2164
|
+
SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END) as successCount,
|
|
2165
|
+
SUM(CASE WHEN status IN ('success', 'error') THEN 1 ELSE 0 END) as healthCount,
|
|
2166
|
+
SUM(gas_cost_eth) as gasCostEth
|
|
2167
|
+
FROM operations WHERE started_at >= ?
|
|
2168
|
+
GROUP BY bucket, operation_name ORDER BY bucket
|
|
2134
2169
|
`).all(opts.bucketMs, opts.bucketMs, cutoff);
|
|
2135
2170
|
const bucketSet = new Set();
|
|
2136
2171
|
const typeSet = new Set();
|
|
@@ -2150,7 +2185,7 @@ export class DashboardDB {
|
|
|
2150
2185
|
return {
|
|
2151
2186
|
count: r?.count ?? 0,
|
|
2152
2187
|
avgMs: r?.avgMs ?? 0,
|
|
2153
|
-
successRate: r ? (r.
|
|
2188
|
+
successRate: r ? (r.healthCount > 0 ? r.successCount / r.healthCount : 0) : 0,
|
|
2154
2189
|
gasCostEth: r?.gasCostEth ?? 0,
|
|
2155
2190
|
};
|
|
2156
2191
|
});
|
|
@@ -2160,18 +2195,18 @@ export class DashboardDB {
|
|
|
2160
2195
|
// --- Success rates by operation type ---
|
|
2161
2196
|
getSuccessRatesByType(periodMs) {
|
|
2162
2197
|
const cutoff = Date.now() - periodMs;
|
|
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
|
|
2198
|
+
return this.db.prepare(`
|
|
2199
|
+
SELECT
|
|
2200
|
+
operation_name as type,
|
|
2201
|
+
COUNT(*) as total,
|
|
2202
|
+
SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END) as success,
|
|
2203
|
+
SUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END) as error,
|
|
2204
|
+
AVG(CASE WHEN status = 'success' THEN duration_ms END) as avgMs
|
|
2205
|
+
FROM operations WHERE started_at >= ?
|
|
2206
|
+
GROUP BY operation_name ORDER BY total DESC
|
|
2172
2207
|
`).all(cutoff).map(r => ({
|
|
2173
2208
|
...r,
|
|
2174
|
-
rate: r.
|
|
2209
|
+
rate: r.success + r.error > 0 ? r.success / (r.success + r.error) : 0,
|
|
2175
2210
|
avgMs: r.avgMs ?? 0,
|
|
2176
2211
|
}));
|
|
2177
2212
|
}
|
|
@@ -2191,16 +2226,16 @@ export class DashboardDB {
|
|
|
2191
2226
|
// on-chain (Verifiable Memory commits) are counted, so the publish
|
|
2192
2227
|
// count and the TRAC total are consistent. Free SWM/local/testnet
|
|
2193
2228
|
// publishes record trac_cost = 0 and are intentionally excluded.
|
|
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 >= ?
|
|
2229
|
+
const row = this.db.prepare(`
|
|
2230
|
+
SELECT
|
|
2231
|
+
SUM(CASE WHEN trac_cost > 0 THEN 1 ELSE 0 END) as publishCount,
|
|
2232
|
+
SUM(CASE WHEN status = 'success' AND trac_cost > 0 THEN 1 ELSE 0 END) as successCount,
|
|
2233
|
+
COALESCE(SUM(gas_cost_eth), 0) as totalGasEth,
|
|
2234
|
+
COALESCE(SUM(trac_cost), 0) as totalTrac,
|
|
2235
|
+
COALESCE(AVG(CASE WHEN trac_cost > 0 THEN gas_cost_eth END), 0) as avgGasEth,
|
|
2236
|
+
COALESCE(AVG(CASE WHEN trac_cost > 0 THEN trac_cost END), 0) as avgTrac
|
|
2237
|
+
FROM operations
|
|
2238
|
+
WHERE operation_name = 'publish' AND trac_cost > 0 AND started_at >= ?
|
|
2204
2239
|
`).get(cutoff);
|
|
2205
2240
|
results.periods.push({
|
|
2206
2241
|
label: p.label,
|
|
@@ -2235,9 +2270,9 @@ export class DashboardDB {
|
|
|
2235
2270
|
* still throw — pinned by db.test.ts.
|
|
2236
2271
|
*/
|
|
2237
2272
|
insertChatMessage(msg) {
|
|
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)
|
|
2273
|
+
const info = this.stmt('insertChat', `
|
|
2274
|
+
INSERT INTO chat_messages (ts, direction, peer, peer_name, text, delivered, message_id)
|
|
2275
|
+
VALUES (@ts, @direction, @peer, @peer_name, @text, @delivered, @message_id)
|
|
2241
2276
|
`).run({
|
|
2242
2277
|
ts: msg.ts,
|
|
2243
2278
|
direction: msg.direction,
|
|
@@ -2328,16 +2363,16 @@ export class DashboardDB {
|
|
|
2328
2363
|
return this.db.prepare('SELECT * FROM chat_persistence_jobs WHERE turn_id = ?').get(turnId);
|
|
2329
2364
|
}
|
|
2330
2365
|
insertChatPersistenceJob(job) {
|
|
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
|
-
)
|
|
2366
|
+
this.stmt('insertChatPersistenceJob', `
|
|
2367
|
+
INSERT INTO chat_persistence_jobs (
|
|
2368
|
+
turn_id, session_id, user_message, assistant_reply, tool_calls_json,
|
|
2369
|
+
status, attempts, max_attempts, next_attempt_at, queued_at, updated_at,
|
|
2370
|
+
store_ms, error_message
|
|
2371
|
+
) VALUES (
|
|
2372
|
+
@turn_id, @session_id, @user_message, @assistant_reply, @tool_calls_json,
|
|
2373
|
+
@status, @attempts, @max_attempts, @next_attempt_at, @queued_at, @updated_at,
|
|
2374
|
+
@store_ms, @error_message
|
|
2375
|
+
)
|
|
2341
2376
|
`).run({
|
|
2342
2377
|
...job,
|
|
2343
2378
|
tool_calls_json: job.tool_calls_json ?? null,
|
|
@@ -2346,46 +2381,46 @@ export class DashboardDB {
|
|
|
2346
2381
|
});
|
|
2347
2382
|
}
|
|
2348
2383
|
markChatPersistenceInProgress(turnId, attempts, updatedAt) {
|
|
2349
|
-
this.stmt('markChatPersistenceInProgress', `
|
|
2350
|
-
UPDATE chat_persistence_jobs
|
|
2351
|
-
SET status = 'in_progress', attempts = ?, updated_at = ?, error_message = NULL
|
|
2352
|
-
WHERE turn_id = ?
|
|
2384
|
+
this.stmt('markChatPersistenceInProgress', `
|
|
2385
|
+
UPDATE chat_persistence_jobs
|
|
2386
|
+
SET status = 'in_progress', attempts = ?, updated_at = ?, error_message = NULL
|
|
2387
|
+
WHERE turn_id = ?
|
|
2353
2388
|
`).run(attempts, updatedAt, turnId);
|
|
2354
2389
|
}
|
|
2355
2390
|
markChatPersistenceStored(turnId, storeMs, updatedAt) {
|
|
2356
|
-
this.stmt('markChatPersistenceStored', `
|
|
2357
|
-
UPDATE chat_persistence_jobs
|
|
2358
|
-
SET status = 'stored', store_ms = ?, updated_at = ?, error_message = NULL
|
|
2359
|
-
WHERE turn_id = ?
|
|
2391
|
+
this.stmt('markChatPersistenceStored', `
|
|
2392
|
+
UPDATE chat_persistence_jobs
|
|
2393
|
+
SET status = 'stored', store_ms = ?, updated_at = ?, error_message = NULL
|
|
2394
|
+
WHERE turn_id = ?
|
|
2360
2395
|
`).run(storeMs, updatedAt, turnId);
|
|
2361
2396
|
}
|
|
2362
2397
|
markChatPersistencePendingRetry(turnId, attempts, nextAttemptAt, updatedAt, errorMessage) {
|
|
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 = ?
|
|
2398
|
+
this.stmt('markChatPersistencePendingRetry', `
|
|
2399
|
+
UPDATE chat_persistence_jobs
|
|
2400
|
+
SET status = 'pending', attempts = ?, next_attempt_at = ?, updated_at = ?, error_message = ?
|
|
2401
|
+
WHERE turn_id = ?
|
|
2367
2402
|
`).run(attempts, nextAttemptAt, updatedAt, errorMessage, turnId);
|
|
2368
2403
|
}
|
|
2369
2404
|
markChatPersistenceFailed(turnId, attempts, updatedAt, errorMessage) {
|
|
2370
|
-
this.stmt('markChatPersistenceFailed', `
|
|
2371
|
-
UPDATE chat_persistence_jobs
|
|
2372
|
-
SET status = 'failed', attempts = ?, updated_at = ?, error_message = ?
|
|
2373
|
-
WHERE turn_id = ?
|
|
2405
|
+
this.stmt('markChatPersistenceFailed', `
|
|
2406
|
+
UPDATE chat_persistence_jobs
|
|
2407
|
+
SET status = 'failed', attempts = ?, updated_at = ?, error_message = ?
|
|
2408
|
+
WHERE turn_id = ?
|
|
2374
2409
|
`).run(attempts, updatedAt, errorMessage, turnId);
|
|
2375
2410
|
}
|
|
2376
2411
|
recoverInProgressChatPersistenceJobs(now) {
|
|
2377
|
-
this.stmt('recoverInProgressChatPersistenceJobs', `
|
|
2378
|
-
UPDATE chat_persistence_jobs
|
|
2379
|
-
SET status = 'pending', next_attempt_at = ?, updated_at = ?
|
|
2380
|
-
WHERE status = 'in_progress'
|
|
2412
|
+
this.stmt('recoverInProgressChatPersistenceJobs', `
|
|
2413
|
+
UPDATE chat_persistence_jobs
|
|
2414
|
+
SET status = 'pending', next_attempt_at = ?, updated_at = ?
|
|
2415
|
+
WHERE status = 'in_progress'
|
|
2381
2416
|
`).run(now, now);
|
|
2382
2417
|
}
|
|
2383
2418
|
getRunnableChatPersistenceJobs(now, limit = 10) {
|
|
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 ?
|
|
2419
|
+
return this.db.prepare(`
|
|
2420
|
+
SELECT * FROM chat_persistence_jobs
|
|
2421
|
+
WHERE status = 'pending' AND next_attempt_at <= ?
|
|
2422
|
+
ORDER BY next_attempt_at ASC, queued_at ASC
|
|
2423
|
+
LIMIT ?
|
|
2389
2424
|
`).all(now, limit);
|
|
2390
2425
|
}
|
|
2391
2426
|
getNextPendingChatPersistenceAt() {
|
|
@@ -2393,19 +2428,19 @@ export class DashboardDB {
|
|
|
2393
2428
|
return row?.next_at ?? null;
|
|
2394
2429
|
}
|
|
2395
2430
|
getChatPersistenceHealth(now) {
|
|
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
|
|
2431
|
+
const counts = this.db.prepare(`
|
|
2432
|
+
SELECT
|
|
2433
|
+
SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) AS pending_count,
|
|
2434
|
+
SUM(CASE WHEN status = 'in_progress' THEN 1 ELSE 0 END) AS in_progress_count,
|
|
2435
|
+
SUM(CASE WHEN status = 'stored' THEN 1 ELSE 0 END) AS stored_count,
|
|
2436
|
+
SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) AS failed_count,
|
|
2437
|
+
SUM(CASE WHEN status = 'pending' AND next_attempt_at < ? THEN 1 ELSE 0 END) AS overdue_pending_count
|
|
2438
|
+
FROM chat_persistence_jobs
|
|
2404
2439
|
`).get(now);
|
|
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'
|
|
2440
|
+
const oldest = this.db.prepare(`
|
|
2441
|
+
SELECT MIN(queued_at) AS oldest_pending_queued_at
|
|
2442
|
+
FROM chat_persistence_jobs
|
|
2443
|
+
WHERE status = 'pending'
|
|
2409
2444
|
`).get();
|
|
2410
2445
|
return {
|
|
2411
2446
|
pending_count: counts?.pending_count ?? 0,
|
|
@@ -2418,9 +2453,9 @@ export class DashboardDB {
|
|
|
2418
2453
|
}
|
|
2419
2454
|
// --- Logs ---
|
|
2420
2455
|
insertLog(entry) {
|
|
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)
|
|
2456
|
+
this.stmt('insertLog', `
|
|
2457
|
+
INSERT INTO logs (ts, level, operation_name, operation_id, module, message)
|
|
2458
|
+
VALUES (@ts, @level, @operation_name, @operation_id, @module, @message)
|
|
2424
2459
|
`).run({
|
|
2425
2460
|
ts: entry.ts,
|
|
2426
2461
|
level: entry.level,
|
|
@@ -2475,9 +2510,9 @@ export class DashboardDB {
|
|
|
2475
2510
|
}
|
|
2476
2511
|
// --- Query history ---
|
|
2477
2512
|
insertQueryHistory(entry) {
|
|
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)
|
|
2513
|
+
this.stmt('insertQueryHistory', `
|
|
2514
|
+
INSERT INTO query_history (ts, sparql, duration_ms, result_count, error)
|
|
2515
|
+
VALUES (@ts, @sparql, @duration_ms, @result_count, @error)
|
|
2481
2516
|
`).run({
|
|
2482
2517
|
ts: Date.now(),
|
|
2483
2518
|
sparql: entry.sparql,
|
|
@@ -2521,9 +2556,9 @@ export class DashboardDB {
|
|
|
2521
2556
|
}
|
|
2522
2557
|
// --- Notifications ---
|
|
2523
2558
|
insertNotification(n) {
|
|
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)
|
|
2559
|
+
const result = this.stmt('insertNotif', `
|
|
2560
|
+
INSERT INTO notifications (ts, type, title, message, source, peer, read, meta, context_graph_id)
|
|
2561
|
+
VALUES (@ts, @type, @title, @message, @source, @peer, 0, @meta, @contextGraphId)
|
|
2527
2562
|
`).run({
|
|
2528
2563
|
ts: n.ts,
|
|
2529
2564
|
type: n.type,
|
|
@@ -2559,8 +2594,8 @@ export class DashboardDB {
|
|
|
2559
2594
|
if (cgIds.length === 0)
|
|
2560
2595
|
return [];
|
|
2561
2596
|
const placeholders = cgIds.map(() => '?').join(',');
|
|
2562
|
-
return this.db.prepare(`SELECT * FROM notifications
|
|
2563
|
-
WHERE context_graph_id IN (${placeholders})
|
|
2597
|
+
return this.db.prepare(`SELECT * FROM notifications
|
|
2598
|
+
WHERE context_graph_id IN (${placeholders})
|
|
2564
2599
|
ORDER BY ts DESC LIMIT ?`).all(...cgIds, limit);
|
|
2565
2600
|
}
|
|
2566
2601
|
/**
|
|
@@ -2591,8 +2626,8 @@ export class DashboardDB {
|
|
|
2591
2626
|
if (types.length === 0)
|
|
2592
2627
|
return [];
|
|
2593
2628
|
const placeholders = types.map(() => '?').join(',');
|
|
2594
|
-
return this.db.prepare(`SELECT * FROM notifications
|
|
2595
|
-
WHERE type IN (${placeholders})
|
|
2629
|
+
return this.db.prepare(`SELECT * FROM notifications
|
|
2630
|
+
WHERE type IN (${placeholders})
|
|
2596
2631
|
ORDER BY ts DESC LIMIT ?`).all(...types, limit);
|
|
2597
2632
|
}
|
|
2598
2633
|
/**
|
|
@@ -2612,9 +2647,9 @@ export class DashboardDB {
|
|
|
2612
2647
|
const wallet = walletAddress.trim().toLowerCase();
|
|
2613
2648
|
if (!wallet)
|
|
2614
2649
|
return [];
|
|
2615
|
-
return this.db.prepare(`SELECT * FROM notifications
|
|
2616
|
-
WHERE type = 'pca_cost_covered'
|
|
2617
|
-
AND lower(json_extract(meta, '$.publisherAddress')) = ?
|
|
2650
|
+
return this.db.prepare(`SELECT * FROM notifications
|
|
2651
|
+
WHERE type = 'pca_cost_covered'
|
|
2652
|
+
AND lower(json_extract(meta, '$.publisherAddress')) = ?
|
|
2618
2653
|
ORDER BY ts DESC LIMIT ?`).all(wallet, limit);
|
|
2619
2654
|
}
|
|
2620
2655
|
markNotificationsRead(ids) {
|
|
@@ -2644,10 +2679,10 @@ export class DashboardDB {
|
|
|
2644
2679
|
return [];
|
|
2645
2680
|
const windowStart = parsed.windowBucket * ACTIVITY_DIGEST_WINDOW_MS;
|
|
2646
2681
|
const windowEnd = windowStart + ACTIVITY_DIGEST_WINDOW_MS;
|
|
2647
|
-
const rows = this.db.prepare(`SELECT id FROM notifications
|
|
2648
|
-
WHERE context_graph_id = ?
|
|
2649
|
-
AND type = ?
|
|
2650
|
-
AND ts >= ? AND ts < ?
|
|
2682
|
+
const rows = this.db.prepare(`SELECT id FROM notifications
|
|
2683
|
+
WHERE context_graph_id = ?
|
|
2684
|
+
AND type = ?
|
|
2685
|
+
AND ts >= ? AND ts < ?
|
|
2651
2686
|
AND json_extract(meta, '$.kind') = ?`).all(parsed.contextGraphId, ASSERTION_ACTIVITY_TYPE, windowStart, windowEnd, parsed.kind);
|
|
2652
2687
|
return rows.map((r) => r.id);
|
|
2653
2688
|
}
|
|
@@ -2668,8 +2703,8 @@ export class SqliteSyncCheckpointStore {
|
|
|
2668
2703
|
}
|
|
2669
2704
|
get(key) {
|
|
2670
2705
|
const now = this.clock();
|
|
2671
|
-
const row = this.db.prepare(`SELECT offset, updated_at, expires_at,
|
|
2672
|
-
responder_session_id, responder_session_expires_at
|
|
2706
|
+
const row = this.db.prepare(`SELECT offset, updated_at, expires_at,
|
|
2707
|
+
responder_session_id, responder_session_expires_at
|
|
2673
2708
|
FROM sync_checkpoints WHERE key = ?`).get(key);
|
|
2674
2709
|
if (!row)
|
|
2675
2710
|
return undefined;
|
|
@@ -2699,13 +2734,13 @@ export class SqliteSyncCheckpointStore {
|
|
|
2699
2734
|
if (!Number.isSafeInteger(value) || value < 0) {
|
|
2700
2735
|
throw new Error(`Invalid sync checkpoint offset for ${key}: ${value}`);
|
|
2701
2736
|
}
|
|
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
|
|
2737
|
+
this.db.prepare(`
|
|
2738
|
+
INSERT INTO sync_checkpoints (key, offset, updated_at, expires_at)
|
|
2739
|
+
VALUES (?, ?, ?, ?)
|
|
2740
|
+
ON CONFLICT(key) DO UPDATE SET
|
|
2741
|
+
offset = excluded.offset,
|
|
2742
|
+
updated_at = excluded.updated_at,
|
|
2743
|
+
expires_at = excluded.expires_at
|
|
2709
2744
|
`).run(key, value, nowMs, nowMs + this.ttlMs);
|
|
2710
2745
|
}
|
|
2711
2746
|
setResponderSession(key, sessionId, expiresAtMs, nowMs = this.clock()) {
|
|
@@ -2716,22 +2751,22 @@ export class SqliteSyncCheckpointStore {
|
|
|
2716
2751
|
this.clearResponderSession(key);
|
|
2717
2752
|
return;
|
|
2718
2753
|
}
|
|
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
|
|
2754
|
+
this.db.prepare(`
|
|
2755
|
+
INSERT INTO sync_checkpoints (
|
|
2756
|
+
key, offset, updated_at, expires_at,
|
|
2757
|
+
responder_session_id, responder_session_expires_at
|
|
2758
|
+
) VALUES (?, 0, ?, ?, ?, ?)
|
|
2759
|
+
ON CONFLICT(key) DO UPDATE SET
|
|
2760
|
+
responder_session_id = excluded.responder_session_id,
|
|
2761
|
+
responder_session_expires_at = excluded.responder_session_expires_at
|
|
2727
2762
|
`).run(key, nowMs, nowMs + this.ttlMs, sessionId, expiresAtMs);
|
|
2728
2763
|
}
|
|
2729
2764
|
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 = ?
|
|
2765
|
+
this.db.prepare(`
|
|
2766
|
+
UPDATE sync_checkpoints
|
|
2767
|
+
SET responder_session_id = NULL,
|
|
2768
|
+
responder_session_expires_at = NULL
|
|
2769
|
+
WHERE key = ?
|
|
2735
2770
|
`).run(key);
|
|
2736
2771
|
}
|
|
2737
2772
|
delete(key) {
|
|
@@ -2765,13 +2800,13 @@ export class SqliteChangelogCursorStore {
|
|
|
2765
2800
|
if (!Number.isSafeInteger(seq) || seq < 0) {
|
|
2766
2801
|
throw new Error(`Invalid changelog cursor seq for ${peerId}/${contextGraphId}: ${seq}`);
|
|
2767
2802
|
}
|
|
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
|
|
2803
|
+
this.db.prepare(`
|
|
2804
|
+
INSERT INTO changelog_cursors (peer_id, context_graph_id, era, seq, updated_at)
|
|
2805
|
+
VALUES (?, ?, ?, ?, ?)
|
|
2806
|
+
ON CONFLICT(peer_id, context_graph_id) DO UPDATE SET
|
|
2807
|
+
era = excluded.era,
|
|
2808
|
+
seq = excluded.seq,
|
|
2809
|
+
updated_at = excluded.updated_at
|
|
2775
2810
|
`).run(peerId, contextGraphId, era, seq, nowMs);
|
|
2776
2811
|
}
|
|
2777
2812
|
}
|
|
@@ -2799,13 +2834,13 @@ export class SqliteChangelogEraGuard {
|
|
|
2799
2834
|
if (!Number.isSafeInteger(highSeq) || highSeq < 0) {
|
|
2800
2835
|
throw new Error(`Invalid changelog era high_seq: ${highSeq}`);
|
|
2801
2836
|
}
|
|
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
|
|
2837
|
+
this.db.prepare(`
|
|
2838
|
+
INSERT INTO changelog_era (id, era, high_seq, updated_at)
|
|
2839
|
+
VALUES (1, ?, ?, ?)
|
|
2840
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
2841
|
+
era = excluded.era,
|
|
2842
|
+
high_seq = excluded.high_seq,
|
|
2843
|
+
updated_at = excluded.updated_at
|
|
2809
2844
|
`).run(era, highSeq, this.clock());
|
|
2810
2845
|
}
|
|
2811
2846
|
}
|
|
@@ -2839,7 +2874,7 @@ export class SqliteMessageIdempotencyStore {
|
|
|
2839
2874
|
}
|
|
2840
2875
|
check(peer, protocol, messageId, direction) {
|
|
2841
2876
|
const row = this.db
|
|
2842
|
-
.prepare(`SELECT response_blob FROM message_idempotency
|
|
2877
|
+
.prepare(`SELECT response_blob FROM message_idempotency
|
|
2843
2878
|
WHERE peer_id = ? AND protocol = ? AND message_id = ? AND direction = ?`)
|
|
2844
2879
|
.get(peer, protocol, messageId, direction);
|
|
2845
2880
|
if (!row)
|
|
@@ -2869,9 +2904,9 @@ export class SqliteMessageIdempotencyStore {
|
|
|
2869
2904
|
// SqliteError so the substrate's bug doesn't disguise itself as
|
|
2870
2905
|
// a normal duplicate.
|
|
2871
2906
|
this.db
|
|
2872
|
-
.prepare(`INSERT INTO message_idempotency
|
|
2873
|
-
(peer_id, protocol, message_id, direction, response_blob, response_size, ts)
|
|
2874
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
2907
|
+
.prepare(`INSERT INTO message_idempotency
|
|
2908
|
+
(peer_id, protocol, message_id, direction, response_blob, response_size, ts)
|
|
2909
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
2875
2910
|
ON CONFLICT (peer_id, protocol, message_id, direction) DO NOTHING`)
|
|
2876
2911
|
.run(peer, protocol, messageId, direction, blob, responseSize, this.clock());
|
|
2877
2912
|
}
|
|
@@ -2896,15 +2931,15 @@ export class SqliteProtocolOutboxStore {
|
|
|
2896
2931
|
}
|
|
2897
2932
|
enqueue(peer, protocol, messageId, payload, error, now) {
|
|
2898
2933
|
const existing = this.db
|
|
2899
|
-
.prepare(`SELECT * FROM protocol_outbox
|
|
2934
|
+
.prepare(`SELECT * FROM protocol_outbox
|
|
2900
2935
|
WHERE peer_id = ? AND protocol = ? AND message_id = ?`)
|
|
2901
2936
|
.get(peer, protocol, messageId);
|
|
2902
2937
|
if (existing) {
|
|
2903
2938
|
const newAttempts = existing.attempts + 1;
|
|
2904
2939
|
const nextAttemptAt = now + this.backoffFor(newAttempts);
|
|
2905
2940
|
this.db
|
|
2906
|
-
.prepare(`UPDATE protocol_outbox
|
|
2907
|
-
SET attempts = ?, last_attempt_at = ?, next_attempt_at = ?, last_error = ?
|
|
2941
|
+
.prepare(`UPDATE protocol_outbox
|
|
2942
|
+
SET attempts = ?, last_attempt_at = ?, next_attempt_at = ?, last_error = ?
|
|
2908
2943
|
WHERE peer_id = ? AND protocol = ? AND message_id = ?`)
|
|
2909
2944
|
.run(newAttempts, now, nextAttemptAt, error, peer, protocol, messageId);
|
|
2910
2945
|
return {
|
|
@@ -2923,9 +2958,9 @@ export class SqliteProtocolOutboxStore {
|
|
|
2923
2958
|
const nextAttemptAt = now + this.backoffFor(attempts);
|
|
2924
2959
|
const blob = Buffer.from(payload);
|
|
2925
2960
|
this.db
|
|
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)
|
|
2961
|
+
.prepare(`INSERT INTO protocol_outbox
|
|
2962
|
+
(peer_id, protocol, message_id, payload, attempts,
|
|
2963
|
+
first_failure_at, last_attempt_at, next_attempt_at, last_error)
|
|
2929
2964
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
2930
2965
|
.run(peer, protocol, messageId, blob, attempts, now, now, nextAttemptAt, error);
|
|
2931
2966
|
return {
|
|
@@ -2942,14 +2977,14 @@ export class SqliteProtocolOutboxStore {
|
|
|
2942
2977
|
}
|
|
2943
2978
|
markDelivered(peer, protocol, messageId) {
|
|
2944
2979
|
const result = this.db
|
|
2945
|
-
.prepare(`DELETE FROM protocol_outbox
|
|
2980
|
+
.prepare(`DELETE FROM protocol_outbox
|
|
2946
2981
|
WHERE peer_id = ? AND protocol = ? AND message_id = ?`)
|
|
2947
2982
|
.run(peer, protocol, messageId);
|
|
2948
2983
|
return result.changes > 0;
|
|
2949
2984
|
}
|
|
2950
2985
|
hasEntry(peer, protocol, messageId) {
|
|
2951
2986
|
const row = this.db
|
|
2952
|
-
.prepare(`SELECT 1 FROM protocol_outbox
|
|
2987
|
+
.prepare(`SELECT 1 FROM protocol_outbox
|
|
2953
2988
|
WHERE peer_id = ? AND protocol = ? AND message_id = ? LIMIT 1`)
|
|
2954
2989
|
.get(peer, protocol, messageId);
|
|
2955
2990
|
return row !== undefined;
|
|
@@ -2959,27 +2994,27 @@ export class SqliteProtocolOutboxStore {
|
|
|
2959
2994
|
}
|
|
2960
2995
|
pendingFor(peer) {
|
|
2961
2996
|
const rows = this.db
|
|
2962
|
-
.prepare(`SELECT * FROM protocol_outbox
|
|
2963
|
-
WHERE peer_id = ?
|
|
2997
|
+
.prepare(`SELECT * FROM protocol_outbox
|
|
2998
|
+
WHERE peer_id = ?
|
|
2964
2999
|
ORDER BY first_failure_at ASC, protocol ASC, message_id ASC`)
|
|
2965
3000
|
.all(peer);
|
|
2966
3001
|
return rows.map(SqliteProtocolOutboxStore.rowToEntry);
|
|
2967
3002
|
}
|
|
2968
3003
|
due(now) {
|
|
2969
3004
|
const rows = this.db
|
|
2970
|
-
.prepare(`SELECT * FROM protocol_outbox
|
|
2971
|
-
WHERE next_attempt_at <= ?
|
|
2972
|
-
ORDER BY next_attempt_at ASC, first_failure_at ASC,
|
|
3005
|
+
.prepare(`SELECT * FROM protocol_outbox
|
|
3006
|
+
WHERE next_attempt_at <= ?
|
|
3007
|
+
ORDER BY next_attempt_at ASC, first_failure_at ASC,
|
|
2973
3008
|
peer_id ASC, protocol ASC, message_id ASC`)
|
|
2974
3009
|
.all(now);
|
|
2975
3010
|
return rows.map(SqliteProtocolOutboxStore.rowToEntry);
|
|
2976
3011
|
}
|
|
2977
3012
|
duePage(now, limit) {
|
|
2978
3013
|
const rows = this.db
|
|
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
|
|
3014
|
+
.prepare(`SELECT * FROM protocol_outbox
|
|
3015
|
+
WHERE next_attempt_at <= ?
|
|
3016
|
+
ORDER BY next_attempt_at ASC, first_failure_at ASC,
|
|
3017
|
+
peer_id ASC, protocol ASC, message_id ASC
|
|
2983
3018
|
LIMIT ?`)
|
|
2984
3019
|
.all(now, limit);
|
|
2985
3020
|
return rows.map(SqliteProtocolOutboxStore.rowToEntry);
|
|
@@ -3069,9 +3104,9 @@ export class SqliteKaNumberStore {
|
|
|
3069
3104
|
// `safeIntegers(true)` opts INTO bigint returns so the counter is
|
|
3070
3105
|
// exact past `Number.MAX_SAFE_INTEGER` (codex PR #976 F6).
|
|
3071
3106
|
const row = this.db
|
|
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
|
|
3107
|
+
.prepare(`INSERT INTO ka_numbers (author_address, next_number)
|
|
3108
|
+
VALUES (?, 1)
|
|
3109
|
+
ON CONFLICT(author_address) DO UPDATE SET next_number = next_number + 1
|
|
3075
3110
|
RETURNING next_number - 1 AS number`)
|
|
3076
3111
|
.safeIntegers(true)
|
|
3077
3112
|
.get(author);
|
|
@@ -3083,9 +3118,9 @@ export class SqliteKaNumberStore {
|
|
|
3083
3118
|
// `excluded.next_number` is the proposed floor from the VALUES row.
|
|
3084
3119
|
// `better-sqlite3` accepts bigint statement parameters natively.
|
|
3085
3120
|
this.db
|
|
3086
|
-
.prepare(`INSERT INTO ka_numbers (author_address, next_number)
|
|
3087
|
-
VALUES (?, ?)
|
|
3088
|
-
ON CONFLICT(author_address) DO UPDATE SET
|
|
3121
|
+
.prepare(`INSERT INTO ka_numbers (author_address, next_number)
|
|
3122
|
+
VALUES (?, ?)
|
|
3123
|
+
ON CONFLICT(author_address) DO UPDATE SET
|
|
3089
3124
|
next_number = MAX(next_number, excluded.next_number)`)
|
|
3090
3125
|
.run(author, nextNumberFloor);
|
|
3091
3126
|
}
|