@origintrail-official/dkg-node-ui 10.0.20 → 10.0.22
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/README.md +1 -0
- package/dist/api.d.ts +7 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +22 -1
- package/dist/api.js.map +1 -1
- package/dist/chain-cursor-stores.d.ts +11 -108
- package/dist/chain-cursor-stores.d.ts.map +1 -1
- package/dist/chain-cursor-stores.js +11 -287
- package/dist/chain-cursor-stores.js.map +1 -1
- package/dist/chain-event-log-store.d.ts +10 -112
- package/dist/chain-event-log-store.d.ts.map +1 -1
- package/dist/chain-event-log-store.js +9 -360
- package/dist/chain-event-log-store.js.map +1 -1
- package/dist/chat-memory.d.ts +1 -0
- package/dist/chat-memory.d.ts.map +1 -1
- package/dist/chat-memory.js +31 -39
- package/dist/chat-memory.js.map +1 -1
- package/dist/chat-turn-subject.d.ts +18 -0
- package/dist/chat-turn-subject.d.ts.map +1 -0
- package/dist/chat-turn-subject.js +52 -0
- package/dist/chat-turn-subject.js.map +1 -0
- package/dist/db.d.ts +2 -143
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +9 -387
- package/dist/db.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -3
- package/dist/index.js.map +1 -1
- package/dist/metrics-collector.d.ts +11 -1
- package/dist/metrics-collector.d.ts.map +1 -1
- package/dist/metrics-collector.js +77 -39
- package/dist/metrics-collector.js.map +1 -1
- package/dist/protocol-outbox-store.d.ts +9 -64
- package/dist/protocol-outbox-store.d.ts.map +1 -1
- package/dist/protocol-outbox-store.js +11 -198
- package/dist/protocol-outbox-store.js.map +1 -1
- package/dist-ui/assets/{3d-force-graph-CehOGxux.js → 3d-force-graph-DbVwxMKf.js} +1 -1
- package/dist-ui/assets/{ActivityFeed-BE4MTD8M.js → ActivityFeed-Bfg6fmQQ.js} +1 -1
- package/dist-ui/assets/{AgentHub-CT6UhLF2.js → AgentHub-DyF5Pa70.js} +1 -1
- package/dist-ui/assets/{AgentProfilePage-ygi-GXKM.js → AgentProfilePage-BPrSuKjV.js} +1 -1
- package/dist-ui/assets/{ApproveWalletsModal-YFfDrGth.js → ApproveWalletsModal-Dd_tHLRP.js} +1 -1
- package/dist-ui/assets/{ContextGraphPrimitives-Dh-w2D_i.js → ContextGraphPrimitives-BXormriD.js} +1 -1
- package/dist-ui/assets/{ConvictionDetailView-CfRmqqDD.js → ConvictionDetailView-BsPTd17P.js} +1 -1
- package/dist-ui/assets/{MemoryLayerView-DL0zLYAQ.js → MemoryLayerView-CloJeM33.js} +2 -2
- package/dist-ui/assets/{MemoryStackView-BsV_JnOe.js → MemoryStackView-BbUJ2uiM.js} +1 -1
- package/dist-ui/assets/{Network-BkxRENe4.js → Network-BKvJttOu.js} +1 -1
- package/dist-ui/assets/{OnChainProvenanceCard-CnnkMMNI.js → OnChainProvenanceCard-BV9-G1cT.js} +1 -1
- package/dist-ui/assets/{Operations-BaWWLSUw.js → Operations-LAhUQsnj.js} +1 -1
- package/dist-ui/assets/{ProjectView-BJmZ5f62.js → ProjectView-0zCSblAV.js} +3 -3
- package/dist-ui/assets/{PublishingConviction-CKB6JBYk.js → PublishingConviction-CF6352oT.js} +1 -1
- package/dist-ui/assets/{Settings-DXGMPiJk.js → Settings-runaZqPv.js} +1 -1
- package/dist-ui/assets/{browserWalletTransaction-BdmrJpK1.js → browserWalletTransaction-CBgx0i4w.js} +3 -3
- package/dist-ui/assets/{ccip-BNYiIkjs.js → ccip-BM07w5uU.js} +1 -1
- package/dist-ui/assets/{index-DOGXDQkQ.js → index-CfVUpsfM.js} +3 -3
- package/dist-ui/assets/{index-DK8XzE3T.js → index-MQh8C4aJ.js} +90 -90
- package/dist-ui/assets/index-xKO-U_eV.css +1 -0
- package/dist-ui/assets/{jsonld-32FQRO67-Ca-KnQfb.js → jsonld-32FQRO67-DBZOBQji.js} +2 -2
- package/dist-ui/assets/{jsonld-mRC9FbFR.js → jsonld-DOX7dKpf.js} +1 -1
- package/dist-ui/assets/{renderer-3d-2EVDZII7-AEMBPIY7.js → renderer-3d-2EVDZII7-CVCpAmwB.js} +2 -2
- package/dist-ui/assets/{shikiHighlighter-CUQ07vzt.js → shikiHighlighter-CzU2U2Tx.js} +1 -1
- package/dist-ui/assets/{useEntityOnChainReceipt-C8NHlPPN.js → useEntityOnChainReceipt-BvWQ6qdT.js} +1 -1
- package/dist-ui/assets/{useProjectActivity-CVFCfbMj.js → useProjectActivity-Cxr4QGX1.js} +1 -1
- package/dist-ui/index.html +2 -2
- package/package.json +6 -4
- package/dist-ui/assets/index-BLQUVvVz.css +0 -1
package/dist/db.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import Database from 'better-sqlite3';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import {
|
|
3
|
+
import { parseContextGraphJoinPolicyRecord, } from '@origintrail-official/dkg-core';
|
|
4
4
|
import { RoutineLogRetention, installRoutineLogRetentionSchema, } from './routine-log-retention.js';
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
// Protocol persistence (outbox, sync checkpoints, changelog cursors, KA
|
|
6
|
+
// numbers, message idempotency, chain log and cursors) moved to
|
|
7
|
+
// `@origintrail-official/dkg-node-store`. `DashboardDB` still opens
|
|
8
|
+
// `node-ui.db` and still owns every table and migration below, so the stores
|
|
9
|
+
// stay re-exported here and existing importers of this module keep working.
|
|
10
|
+
export { SqliteChainEventCursorStore, SqliteChangelogCursorStore, SqliteChangelogEraGuard, SqliteContextGraphAuthorityIndexStore, SqliteContextGraphAuthorityHistoryStore, SqliteContextGraphRegistryScanCursorStore, SqliteContextGraphStorageDiscoveryStore, SqliteKaNumberStore, SqliteMessageIdempotencyStore, SqliteProtocolOutboxStore, SqliteSyncCheckpointStore, } from '@origintrail-official/dkg-node-store';
|
|
7
11
|
export const SCHEMA_VERSION = 38;
|
|
8
12
|
// Default operator retention. Lowered from 90 → 14 days on V15 (2026-05) after
|
|
9
13
|
// a production incident in which the `logs` table + its FTS5 shadow tables
|
|
@@ -1139,6 +1143,8 @@ export class DashboardDB {
|
|
|
1139
1143
|
// carry the current user_version while missing a trigger; recreating it is
|
|
1140
1144
|
// idempotent and keeps the audit bound fail-closed on every open.
|
|
1141
1145
|
ensureJoinPolicyAuditCapTrigger();
|
|
1146
|
+
// Historical V29 schema is retained for compatibility. Its rows are no
|
|
1147
|
+
// longer read, written or pruned by VM reconcile (GH#2990).
|
|
1142
1148
|
if (version < 29) {
|
|
1143
1149
|
this.db.exec(`
|
|
1144
1150
|
CREATE TABLE IF NOT EXISTS vm_reconcile_negative_cache (
|
|
@@ -1280,7 +1286,6 @@ export class DashboardDB {
|
|
|
1280
1286
|
// Reconcile negatives are accelerators, never historical records. Once the
|
|
1281
1287
|
// retry window elapses they must not survive indefinitely or accumulate one
|
|
1282
1288
|
// row per previously-seen KA across restarts.
|
|
1283
|
-
this.db.prepare(`DELETE FROM vm_reconcile_negative_cache WHERE next_retry_at < ?`).run(Date.now());
|
|
1284
1289
|
// Universal Messenger idempotency table. Shorter TTL than the
|
|
1285
1290
|
// operator retention: no realistic dedup window extends beyond
|
|
1286
1291
|
// a day. The protocol_outbox table is intentionally not pruned
|
|
@@ -1776,34 +1781,6 @@ export class DashboardDB {
|
|
|
1776
1781
|
contextGraphJoinPolicyKey(contextGraphId) {
|
|
1777
1782
|
return `contextGraphJoinPolicy:${contextGraphId}`;
|
|
1778
1783
|
}
|
|
1779
|
-
upsertVmReconcileNegative(record) {
|
|
1780
|
-
this.stmt('upsertVmReconcileNegative', `
|
|
1781
|
-
INSERT INTO vm_reconcile_negative_cache (
|
|
1782
|
-
cache_key, context_graph_id, failures, next_retry_at, swm_gen,
|
|
1783
|
-
candidate_namespaces, peer_topology_key, updated_at
|
|
1784
|
-
) VALUES (
|
|
1785
|
-
@cache_key, @context_graph_id, @failures, @next_retry_at, @swm_gen,
|
|
1786
|
-
@candidate_namespaces, @peer_topology_key, @updated_at
|
|
1787
|
-
)
|
|
1788
|
-
ON CONFLICT(cache_key) DO UPDATE SET
|
|
1789
|
-
context_graph_id = excluded.context_graph_id,
|
|
1790
|
-
failures = excluded.failures,
|
|
1791
|
-
next_retry_at = excluded.next_retry_at,
|
|
1792
|
-
swm_gen = excluded.swm_gen,
|
|
1793
|
-
candidate_namespaces = excluded.candidate_namespaces,
|
|
1794
|
-
peer_topology_key = excluded.peer_topology_key,
|
|
1795
|
-
updated_at = excluded.updated_at
|
|
1796
|
-
`).run(record);
|
|
1797
|
-
}
|
|
1798
|
-
getVmReconcileNegative(cacheKey) {
|
|
1799
|
-
return this.stmt('getVmReconcileNegative', 'SELECT * FROM vm_reconcile_negative_cache WHERE cache_key = ?').get(cacheKey);
|
|
1800
|
-
}
|
|
1801
|
-
deleteVmReconcileNegative(cacheKey) {
|
|
1802
|
-
this.stmt('deleteVmReconcileNegative', 'DELETE FROM vm_reconcile_negative_cache WHERE cache_key = ?').run(cacheKey);
|
|
1803
|
-
}
|
|
1804
|
-
deleteVmReconcileNegativesForContextGraph(contextGraphId) {
|
|
1805
|
-
this.stmt('deleteVmReconcileNegativesForContextGraph', 'DELETE FROM vm_reconcile_negative_cache WHERE context_graph_id = ?').run(contextGraphId);
|
|
1806
|
-
}
|
|
1807
1784
|
upsertSelectedVmReconcileCursor(record) {
|
|
1808
1785
|
this.stmt('upsertSelectedVmReconcileCursor', `
|
|
1809
1786
|
INSERT INTO selected_vm_reconcile_cursors (
|
|
@@ -2894,359 +2871,4 @@ export class DashboardDB {
|
|
|
2894
2871
|
this.db.close();
|
|
2895
2872
|
}
|
|
2896
2873
|
}
|
|
2897
|
-
// --- Sync requester checkpoints (issue #1138 A3) ---
|
|
2898
|
-
export class SqliteSyncCheckpointStore {
|
|
2899
|
-
db;
|
|
2900
|
-
clock;
|
|
2901
|
-
ttlMs;
|
|
2902
|
-
constructor(dashboard, options = {}) {
|
|
2903
|
-
this.db = dashboard.db;
|
|
2904
|
-
this.clock = options.clock ?? (() => Date.now());
|
|
2905
|
-
this.ttlMs = options.ttlMs ?? DEFAULT_SYNC_CHECKPOINT_TTL_MS;
|
|
2906
|
-
}
|
|
2907
|
-
readRow(key) {
|
|
2908
|
-
const row = this.db.prepare(`SELECT offset, updated_at, expires_at,
|
|
2909
|
-
responder_session_id, responder_session_expires_at, responder_session_offset,
|
|
2910
|
-
manifest_digest, manifest_prefix_digest, terminal
|
|
2911
|
-
FROM sync_checkpoints WHERE key = ?`).get(key);
|
|
2912
|
-
if (!row)
|
|
2913
|
-
return undefined;
|
|
2914
|
-
return {
|
|
2915
|
-
offset: row.offset,
|
|
2916
|
-
updatedAtMs: row.updated_at,
|
|
2917
|
-
expiresAtMs: row.expires_at,
|
|
2918
|
-
...(row.terminal === 1 ? { terminal: true } : {}),
|
|
2919
|
-
...(row.manifest_digest ? { manifestDigest: row.manifest_digest } : {}),
|
|
2920
|
-
...(row.manifest_prefix_digest
|
|
2921
|
-
? { manifestPrefixDigest: row.manifest_prefix_digest }
|
|
2922
|
-
: {}),
|
|
2923
|
-
// Preserve each nullable session column independently so the shared
|
|
2924
|
-
// validator can distinguish an absent session from a torn/malformed
|
|
2925
|
-
// persisted session. Collapsing a partial row to no session fields would
|
|
2926
|
-
// turn corrupt durable state into an apparently valid ordinary offset.
|
|
2927
|
-
...(row.responder_session_id !== null
|
|
2928
|
-
? { responderSessionId: row.responder_session_id }
|
|
2929
|
-
: {}),
|
|
2930
|
-
...(row.responder_session_expires_at !== null
|
|
2931
|
-
? { responderSessionExpiresAtMs: row.responder_session_expires_at }
|
|
2932
|
-
: {}),
|
|
2933
|
-
...(row.responder_session_offset !== null
|
|
2934
|
-
? { responderSessionOffset: row.responder_session_offset }
|
|
2935
|
-
: {}),
|
|
2936
|
-
};
|
|
2937
|
-
}
|
|
2938
|
-
get(key, now = this.clock()) {
|
|
2939
|
-
const entry = this.readRow(key);
|
|
2940
|
-
if (!entry)
|
|
2941
|
-
return undefined;
|
|
2942
|
-
if (!isValidSyncCheckpointEntry(entry) || entry.expiresAtMs < now) {
|
|
2943
|
-
this.delete(key);
|
|
2944
|
-
return undefined;
|
|
2945
|
-
}
|
|
2946
|
-
if (entry.responderSessionId
|
|
2947
|
-
&& (entry.responderSessionExpiresAtMs ?? 0) <= now) {
|
|
2948
|
-
const withoutExpiredSession = withoutSyncCheckpointResponderSession(entry);
|
|
2949
|
-
this.writeEntry(key, withoutExpiredSession);
|
|
2950
|
-
return withoutExpiredSession;
|
|
2951
|
-
}
|
|
2952
|
-
return entry;
|
|
2953
|
-
}
|
|
2954
|
-
writeEntry(key, entry) {
|
|
2955
|
-
this.db.prepare(`
|
|
2956
|
-
INSERT INTO sync_checkpoints (
|
|
2957
|
-
key, offset, updated_at, expires_at,
|
|
2958
|
-
responder_session_id, responder_session_expires_at, responder_session_offset,
|
|
2959
|
-
manifest_digest, manifest_prefix_digest, terminal
|
|
2960
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2961
|
-
ON CONFLICT(key) DO UPDATE SET
|
|
2962
|
-
offset = excluded.offset,
|
|
2963
|
-
updated_at = excluded.updated_at,
|
|
2964
|
-
expires_at = excluded.expires_at,
|
|
2965
|
-
responder_session_id = excluded.responder_session_id,
|
|
2966
|
-
responder_session_expires_at = excluded.responder_session_expires_at,
|
|
2967
|
-
responder_session_offset = excluded.responder_session_offset,
|
|
2968
|
-
manifest_digest = excluded.manifest_digest,
|
|
2969
|
-
manifest_prefix_digest = excluded.manifest_prefix_digest,
|
|
2970
|
-
terminal = excluded.terminal
|
|
2971
|
-
`).run(key, entry.offset, entry.updatedAtMs, entry.expiresAtMs, entry.responderSessionId ?? null, entry.responderSessionExpiresAtMs ?? null, entry.responderSessionOffset ?? null, entry.manifestDigest ?? null, entry.manifestPrefixDigest ?? null, entry.terminal ? 1 : 0);
|
|
2972
|
-
}
|
|
2973
|
-
set(key, value, nowMs = this.clock(), responderSessionOffset) {
|
|
2974
|
-
const transition = this.db.transaction(() => {
|
|
2975
|
-
this.writeEntry(key, transitionSyncCheckpointOffset({
|
|
2976
|
-
key,
|
|
2977
|
-
existing: this.get(key, nowMs),
|
|
2978
|
-
value,
|
|
2979
|
-
nowMs,
|
|
2980
|
-
ttlMs: this.ttlMs,
|
|
2981
|
-
responderSessionOffset,
|
|
2982
|
-
}));
|
|
2983
|
-
});
|
|
2984
|
-
transition();
|
|
2985
|
-
}
|
|
2986
|
-
setManifestBoundOffset(key, value, manifestDigest, nowMs = this.clock(), manifestPrefixDigest, terminal = false, responderSessionOffset) {
|
|
2987
|
-
const transition = this.db.transaction(() => {
|
|
2988
|
-
this.writeEntry(key, transitionSyncCheckpointManifestOffset({
|
|
2989
|
-
key,
|
|
2990
|
-
existing: this.get(key, nowMs),
|
|
2991
|
-
value,
|
|
2992
|
-
manifestDigest,
|
|
2993
|
-
nowMs,
|
|
2994
|
-
ttlMs: this.ttlMs,
|
|
2995
|
-
manifestPrefixDigest,
|
|
2996
|
-
terminal,
|
|
2997
|
-
responderSessionOffset,
|
|
2998
|
-
}));
|
|
2999
|
-
});
|
|
3000
|
-
transition();
|
|
3001
|
-
}
|
|
3002
|
-
setResponderSession(key, sessionId, expiresAtMs, nowMs = this.clock(), manifestDigest, manifestPrefixDigest, responderSessionOffset) {
|
|
3003
|
-
if (expiresAtMs <= nowMs) {
|
|
3004
|
-
this.clearResponderSession(key);
|
|
3005
|
-
return;
|
|
3006
|
-
}
|
|
3007
|
-
const transition = this.db.transaction(() => {
|
|
3008
|
-
this.writeEntry(key, transitionSyncCheckpointResponderSession({
|
|
3009
|
-
key,
|
|
3010
|
-
existing: this.get(key, nowMs),
|
|
3011
|
-
sessionId,
|
|
3012
|
-
expiresAtMs,
|
|
3013
|
-
nowMs,
|
|
3014
|
-
ttlMs: this.ttlMs,
|
|
3015
|
-
manifestDigest,
|
|
3016
|
-
manifestPrefixDigest,
|
|
3017
|
-
responderSessionOffset,
|
|
3018
|
-
}));
|
|
3019
|
-
});
|
|
3020
|
-
transition();
|
|
3021
|
-
}
|
|
3022
|
-
clearResponderSession(key) {
|
|
3023
|
-
const transition = this.db.transaction(() => {
|
|
3024
|
-
const existing = this.readRow(key);
|
|
3025
|
-
if (existing)
|
|
3026
|
-
this.writeEntry(key, withoutSyncCheckpointResponderSession(existing));
|
|
3027
|
-
});
|
|
3028
|
-
transition();
|
|
3029
|
-
}
|
|
3030
|
-
delete(key) {
|
|
3031
|
-
this.db.prepare(`DELETE FROM sync_checkpoints WHERE key = ?`).run(key);
|
|
3032
|
-
}
|
|
3033
|
-
pruneExpired(nowMs = this.clock()) {
|
|
3034
|
-
return this.db.prepare(`DELETE FROM sync_checkpoints WHERE expires_at < ?`).run(nowMs).changes;
|
|
3035
|
-
}
|
|
3036
|
-
}
|
|
3037
|
-
/**
|
|
3038
|
-
* OT-RFC-59 SC5 durable changelog cursor store (duck-compatible with the agent's
|
|
3039
|
-
* ChangelogCursorStore). Keyed by (peer_id, context_graph_id); stores the last
|
|
3040
|
-
* APPLIED (era, seq) from that responder. Like `ka_numbers` / `protocol_outbox`
|
|
3041
|
-
* this state is durable: it is NEVER added to `prune()` — a TTL would defeat the
|
|
3042
|
-
* O(delta) cross-restart catch-up the changelog lane exists for.
|
|
3043
|
-
*/
|
|
3044
|
-
export class SqliteChangelogCursorStore {
|
|
3045
|
-
db;
|
|
3046
|
-
clock;
|
|
3047
|
-
constructor(dashboard, options = {}) {
|
|
3048
|
-
this.db = dashboard.db;
|
|
3049
|
-
this.clock = options.clock ?? (() => Date.now());
|
|
3050
|
-
}
|
|
3051
|
-
get(peerId, contextGraphId) {
|
|
3052
|
-
const row = this.db.prepare(`SELECT era, seq, updated_at FROM changelog_cursors WHERE peer_id = ? AND context_graph_id = ?`).get(peerId, contextGraphId);
|
|
3053
|
-
if (!row)
|
|
3054
|
-
return undefined;
|
|
3055
|
-
return { era: row.era, seq: row.seq, updatedAtMs: row.updated_at };
|
|
3056
|
-
}
|
|
3057
|
-
set(peerId, contextGraphId, era, seq, nowMs = this.clock()) {
|
|
3058
|
-
if (!Number.isSafeInteger(seq) || seq < 0) {
|
|
3059
|
-
throw new Error(`Invalid changelog cursor seq for ${peerId}/${contextGraphId}: ${seq}`);
|
|
3060
|
-
}
|
|
3061
|
-
this.db.prepare(`
|
|
3062
|
-
INSERT INTO changelog_cursors (peer_id, context_graph_id, era, seq, updated_at)
|
|
3063
|
-
VALUES (?, ?, ?, ?, ?)
|
|
3064
|
-
ON CONFLICT(peer_id, context_graph_id) DO UPDATE SET
|
|
3065
|
-
era = excluded.era,
|
|
3066
|
-
seq = excluded.seq,
|
|
3067
|
-
updated_at = excluded.updated_at
|
|
3068
|
-
`).run(peerId, contextGraphId, era, seq, nowMs);
|
|
3069
|
-
}
|
|
3070
|
-
}
|
|
3071
|
-
/**
|
|
3072
|
-
* OT-RFC-59 §6 P0 durable era guard (duck-compatible with storage's
|
|
3073
|
-
* ChangelogEraGuard). Persists the single (era, high_seq) high-water in
|
|
3074
|
-
* node-ui.db — which a `store.nq` RDF-store restore does NOT roll back — so the
|
|
3075
|
-
* write-side ChangelogStore can detect a restore/rollback (seq regressed under
|
|
3076
|
-
* the same era) and rotate the era, forcing peers to full-resync instead of
|
|
3077
|
-
* silently skipping. `save` is called after every committed seq, so it must be a
|
|
3078
|
-
* single fast upsert.
|
|
3079
|
-
*/
|
|
3080
|
-
export class SqliteChangelogEraGuard {
|
|
3081
|
-
db;
|
|
3082
|
-
clock;
|
|
3083
|
-
constructor(dashboard, options = {}) {
|
|
3084
|
-
this.db = dashboard.db;
|
|
3085
|
-
this.clock = options.clock ?? (() => Date.now());
|
|
3086
|
-
}
|
|
3087
|
-
async load() {
|
|
3088
|
-
const row = this.db.prepare(`SELECT era, high_seq FROM changelog_era WHERE id = 1`).get();
|
|
3089
|
-
return row ? { era: row.era, highSeq: row.high_seq } : null;
|
|
3090
|
-
}
|
|
3091
|
-
async save(era, highSeq) {
|
|
3092
|
-
if (!Number.isSafeInteger(highSeq) || highSeq < 0) {
|
|
3093
|
-
throw new Error(`Invalid changelog era high_seq: ${highSeq}`);
|
|
3094
|
-
}
|
|
3095
|
-
this.db.prepare(`
|
|
3096
|
-
INSERT INTO changelog_era (id, era, high_seq, updated_at)
|
|
3097
|
-
VALUES (1, ?, ?, ?)
|
|
3098
|
-
ON CONFLICT(id) DO UPDATE SET
|
|
3099
|
-
era = excluded.era,
|
|
3100
|
-
high_seq = excluded.high_seq,
|
|
3101
|
-
updated_at = excluded.updated_at
|
|
3102
|
-
`).run(era, highSeq, this.clock());
|
|
3103
|
-
}
|
|
3104
|
-
}
|
|
3105
|
-
// --- Universal Messenger substrate stores (rc.9 plan PR-1) ---
|
|
3106
|
-
/**
|
|
3107
|
-
* SQLite-backed `MessageIdempotencyStore` against the V12
|
|
3108
|
-
* `message_idempotency` table in `DashboardDB`. Receiver-side dedup
|
|
3109
|
-
* cache + sender-side "did we deliver this" cache, keyed by
|
|
3110
|
-
* `(peer, protocol, message_id, direction)`.
|
|
3111
|
-
*
|
|
3112
|
-
* Constructed against an already-opened `DashboardDB` so all DKG
|
|
3113
|
-
* persistence shares a single SQLite file (one WAL, one fsync, one
|
|
3114
|
-
* pragma surface). Doesn't open the DB itself — the daemon's
|
|
3115
|
-
* `lifecycle.ts` owns DB lifecycle and hands one in here in PR-2.
|
|
3116
|
-
*
|
|
3117
|
-
* Response caching policy lives in `RESPONSE_CACHE_BYTES` (256 KiB
|
|
3118
|
-
* fixed limit, exported from `@origintrail-official/dkg-core`).
|
|
3119
|
-
* Responses up to the limit are stored inline in `response_blob`;
|
|
3120
|
-
* larger responses store `response_blob = NULL` with the actual
|
|
3121
|
-
* size in `response_size` (mark-only). Duplicate receives whose
|
|
3122
|
-
* original was mark-only surface as `RESPONSE_GONE` to the sender
|
|
3123
|
-
* — see `RESPONSE_GONE_MARKER` for the canonical signal string.
|
|
3124
|
-
*/
|
|
3125
|
-
export class SqliteMessageIdempotencyStore {
|
|
3126
|
-
db;
|
|
3127
|
-
clock;
|
|
3128
|
-
/** @param clock injectable for deterministic tests. Defaults to `Date.now`. */
|
|
3129
|
-
constructor(dashboard, options = {}) {
|
|
3130
|
-
this.db = dashboard.db;
|
|
3131
|
-
this.clock = options.clock ?? (() => Date.now());
|
|
3132
|
-
}
|
|
3133
|
-
check(peer, protocol, messageId, direction) {
|
|
3134
|
-
const row = this.db
|
|
3135
|
-
.prepare(`SELECT response_blob FROM message_idempotency
|
|
3136
|
-
WHERE peer_id = ? AND protocol = ? AND message_id = ? AND direction = ?`)
|
|
3137
|
-
.get(peer, protocol, messageId, direction);
|
|
3138
|
-
if (!row)
|
|
3139
|
-
return { seen: false };
|
|
3140
|
-
// better-sqlite3 returns Node Buffer for BLOB columns; copy into a
|
|
3141
|
-
// Uint8Array so callers cannot mutate the cached DB snapshot.
|
|
3142
|
-
if (row.response_blob === null)
|
|
3143
|
-
return { seen: true };
|
|
3144
|
-
return {
|
|
3145
|
-
seen: true,
|
|
3146
|
-
cachedResponse: new Uint8Array(row.response_blob),
|
|
3147
|
-
};
|
|
3148
|
-
}
|
|
3149
|
-
record(peer, protocol, messageId, direction, response) {
|
|
3150
|
-
const responseSize = response?.length ?? 0;
|
|
3151
|
-
// Mark-only when over the cache limit. Stores NULL blob + the
|
|
3152
|
-
// actual size, so a future duplicate receive can surface
|
|
3153
|
-
// `RESPONSE_GONE`. The 256 KiB cutoff is the rc.9 plan's locked
|
|
3154
|
-
// design decision — no per-protocol/per-call knob.
|
|
3155
|
-
const blob = response !== undefined && response.length <= RESPONSE_CACHE_BYTES
|
|
3156
|
-
? Buffer.from(response)
|
|
3157
|
-
: null;
|
|
3158
|
-
// Targeted ON CONFLICT — never the broader INSERT OR IGNORE which
|
|
3159
|
-
// would silently swallow unrelated constraint violations (the
|
|
3160
|
-
// Codex #534 lesson). Idempotent re-record on the same key is a
|
|
3161
|
-
// no-op; any other constraint violation surfaces as a thrown
|
|
3162
|
-
// SqliteError so the substrate's bug doesn't disguise itself as
|
|
3163
|
-
// a normal duplicate.
|
|
3164
|
-
this.db
|
|
3165
|
-
.prepare(`INSERT INTO message_idempotency
|
|
3166
|
-
(peer_id, protocol, message_id, direction, response_blob, response_size, ts)
|
|
3167
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
3168
|
-
ON CONFLICT (peer_id, protocol, message_id, direction) DO NOTHING`)
|
|
3169
|
-
.run(peer, protocol, messageId, direction, blob, responseSize, this.clock());
|
|
3170
|
-
}
|
|
3171
|
-
pruneOlderThan(tsMs) {
|
|
3172
|
-
const result = this.db
|
|
3173
|
-
.prepare(`DELETE FROM message_idempotency WHERE ts < ?`)
|
|
3174
|
-
.run(tsMs);
|
|
3175
|
-
return result.changes;
|
|
3176
|
-
}
|
|
3177
|
-
}
|
|
3178
|
-
/**
|
|
3179
|
-
* SQLite-backed `KaNumberStore` against the V20 `ka_numbers` table.
|
|
3180
|
-
* Per-author durable KA-number allocator for OT-RFC-43 Option-1
|
|
3181
|
-
* deterministic KA identity (B2 allocator core, OFF-CHAIN only).
|
|
3182
|
-
*
|
|
3183
|
-
* Keyed by the attested author address (stored lowercase). Every
|
|
3184
|
-
* method is a single prepared statement; the read-and-increment in
|
|
3185
|
-
* `allocate` is atomic via `INSERT … ON CONFLICT DO UPDATE …
|
|
3186
|
-
* RETURNING`, so concurrent allocations under the same author never
|
|
3187
|
-
* collide on a number (the same atomic-single-statement contract the
|
|
3188
|
-
* other Sqlite*Store classes rely on — there is no `.transaction()`
|
|
3189
|
-
* in this module).
|
|
3190
|
-
*
|
|
3191
|
-
* **Counter width (codex PR #976 F6):** every value crossing the
|
|
3192
|
-
* `KaNumberStore` interface is a `bigint`. `better-sqlite3` returns
|
|
3193
|
-
* INTEGER columns as JS `number` by default — which silently truncates
|
|
3194
|
-
* once a counter passes `Number.MAX_SAFE_INTEGER (2^53 - 1)`. Each
|
|
3195
|
-
* statement here opts into `.safeIntegers(true)`, returning `bigint`
|
|
3196
|
-
* directly so `allocate()`, `peekNext()` and `observed + 1n` stay
|
|
3197
|
-
* exact across the full SQLite signed INTEGER range (`2^63 - 1`). The
|
|
3198
|
-
* RFC's worst-case load ("1000 alloc/s × 1M years ≈ 2^55") sits well
|
|
3199
|
-
* past the `2^53` precision cliff and far under the `2^63` hard
|
|
3200
|
-
* ceiling; if a single author ever does approach `2^63`, SQLite raises
|
|
3201
|
-
* an INTEGER overflow on the next increment — a fail-loud surface
|
|
3202
|
-
* that's strictly preferable to a silent kaId-collision risk.
|
|
3203
|
-
*
|
|
3204
|
-
* Like `protocol_outbox`, this state is durable: it is NEVER added to
|
|
3205
|
-
* `prune()`. Reclaiming a number could re-mint a kaId already used
|
|
3206
|
-
* on-chain under that author.
|
|
3207
|
-
*/
|
|
3208
|
-
export class SqliteKaNumberStore {
|
|
3209
|
-
db;
|
|
3210
|
-
constructor(dashboard) {
|
|
3211
|
-
this.db = dashboard.db;
|
|
3212
|
-
}
|
|
3213
|
-
allocate(authorAddress) {
|
|
3214
|
-
const author = authorAddress.toLowerCase();
|
|
3215
|
-
// Atomic read-and-increment. `next_number` is the value to hand
|
|
3216
|
-
// out; we increment it and RETURN the value just consumed
|
|
3217
|
-
// (`next_number - 1` after the update), so the first call for an
|
|
3218
|
-
// author returns 0n. On first insert `next_number` starts at 1, so
|
|
3219
|
-
// `1 - 1 = 0` is returned there too — uniform either way.
|
|
3220
|
-
// `safeIntegers(true)` opts INTO bigint returns so the counter is
|
|
3221
|
-
// exact past `Number.MAX_SAFE_INTEGER` (codex PR #976 F6).
|
|
3222
|
-
const row = this.db
|
|
3223
|
-
.prepare(`INSERT INTO ka_numbers (author_address, next_number)
|
|
3224
|
-
VALUES (?, 1)
|
|
3225
|
-
ON CONFLICT(author_address) DO UPDATE SET next_number = next_number + 1
|
|
3226
|
-
RETURNING next_number - 1 AS number`)
|
|
3227
|
-
.safeIntegers(true)
|
|
3228
|
-
.get(author);
|
|
3229
|
-
return row.number;
|
|
3230
|
-
}
|
|
3231
|
-
reconcileFloor(authorAddress, nextNumberFloor) {
|
|
3232
|
-
const author = authorAddress.toLowerCase();
|
|
3233
|
-
// Raise `next_number` to at least the floor, never lowering it.
|
|
3234
|
-
// `excluded.next_number` is the proposed floor from the VALUES row.
|
|
3235
|
-
// `better-sqlite3` accepts bigint statement parameters natively.
|
|
3236
|
-
this.db
|
|
3237
|
-
.prepare(`INSERT INTO ka_numbers (author_address, next_number)
|
|
3238
|
-
VALUES (?, ?)
|
|
3239
|
-
ON CONFLICT(author_address) DO UPDATE SET
|
|
3240
|
-
next_number = MAX(next_number, excluded.next_number)`)
|
|
3241
|
-
.run(author, nextNumberFloor);
|
|
3242
|
-
}
|
|
3243
|
-
peekNext(authorAddress) {
|
|
3244
|
-
const author = authorAddress.toLowerCase();
|
|
3245
|
-
const row = this.db
|
|
3246
|
-
.prepare(`SELECT next_number FROM ka_numbers WHERE author_address = ?`)
|
|
3247
|
-
.safeIntegers(true)
|
|
3248
|
-
.get(author);
|
|
3249
|
-
return row?.next_number ?? 0n;
|
|
3250
|
-
}
|
|
3251
|
-
}
|
|
3252
2874
|
//# sourceMappingURL=db.js.map
|