@origintrail-official/dkg-node-ui 10.0.20 → 10.0.21

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.
Files changed (66) hide show
  1. package/README.md +1 -0
  2. package/dist/api.d.ts +7 -1
  3. package/dist/api.d.ts.map +1 -1
  4. package/dist/api.js +22 -1
  5. package/dist/api.js.map +1 -1
  6. package/dist/chain-cursor-stores.d.ts +11 -108
  7. package/dist/chain-cursor-stores.d.ts.map +1 -1
  8. package/dist/chain-cursor-stores.js +11 -287
  9. package/dist/chain-cursor-stores.js.map +1 -1
  10. package/dist/chain-event-log-store.d.ts +10 -112
  11. package/dist/chain-event-log-store.d.ts.map +1 -1
  12. package/dist/chain-event-log-store.js +9 -360
  13. package/dist/chain-event-log-store.js.map +1 -1
  14. package/dist/chat-memory.d.ts +1 -0
  15. package/dist/chat-memory.d.ts.map +1 -1
  16. package/dist/chat-memory.js +31 -39
  17. package/dist/chat-memory.js.map +1 -1
  18. package/dist/chat-turn-subject.d.ts +18 -0
  19. package/dist/chat-turn-subject.d.ts.map +1 -0
  20. package/dist/chat-turn-subject.js +52 -0
  21. package/dist/chat-turn-subject.js.map +1 -0
  22. package/dist/db.d.ts +2 -143
  23. package/dist/db.d.ts.map +1 -1
  24. package/dist/db.js +9 -387
  25. package/dist/db.js.map +1 -1
  26. package/dist/index.d.ts +3 -3
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +7 -3
  29. package/dist/index.js.map +1 -1
  30. package/dist/metrics-collector.d.ts +11 -1
  31. package/dist/metrics-collector.d.ts.map +1 -1
  32. package/dist/metrics-collector.js +77 -39
  33. package/dist/metrics-collector.js.map +1 -1
  34. package/dist/protocol-outbox-store.d.ts +9 -64
  35. package/dist/protocol-outbox-store.d.ts.map +1 -1
  36. package/dist/protocol-outbox-store.js +11 -198
  37. package/dist/protocol-outbox-store.js.map +1 -1
  38. package/dist-ui/assets/{3d-force-graph-CehOGxux.js → 3d-force-graph-DbVwxMKf.js} +1 -1
  39. package/dist-ui/assets/{ActivityFeed-BE4MTD8M.js → ActivityFeed-Bfg6fmQQ.js} +1 -1
  40. package/dist-ui/assets/{AgentHub-CT6UhLF2.js → AgentHub-DyF5Pa70.js} +1 -1
  41. package/dist-ui/assets/{AgentProfilePage-ygi-GXKM.js → AgentProfilePage-BPrSuKjV.js} +1 -1
  42. package/dist-ui/assets/{ApproveWalletsModal-YFfDrGth.js → ApproveWalletsModal-Dd_tHLRP.js} +1 -1
  43. package/dist-ui/assets/{ContextGraphPrimitives-Dh-w2D_i.js → ContextGraphPrimitives-BXormriD.js} +1 -1
  44. package/dist-ui/assets/{ConvictionDetailView-CfRmqqDD.js → ConvictionDetailView-BsPTd17P.js} +1 -1
  45. package/dist-ui/assets/{MemoryLayerView-DL0zLYAQ.js → MemoryLayerView-CloJeM33.js} +2 -2
  46. package/dist-ui/assets/{MemoryStackView-BsV_JnOe.js → MemoryStackView-BbUJ2uiM.js} +1 -1
  47. package/dist-ui/assets/{Network-BkxRENe4.js → Network-BKvJttOu.js} +1 -1
  48. package/dist-ui/assets/{OnChainProvenanceCard-CnnkMMNI.js → OnChainProvenanceCard-BV9-G1cT.js} +1 -1
  49. package/dist-ui/assets/{Operations-BaWWLSUw.js → Operations-LAhUQsnj.js} +1 -1
  50. package/dist-ui/assets/{ProjectView-BJmZ5f62.js → ProjectView-0zCSblAV.js} +3 -3
  51. package/dist-ui/assets/{PublishingConviction-CKB6JBYk.js → PublishingConviction-CF6352oT.js} +1 -1
  52. package/dist-ui/assets/{Settings-DXGMPiJk.js → Settings-runaZqPv.js} +1 -1
  53. package/dist-ui/assets/{browserWalletTransaction-BdmrJpK1.js → browserWalletTransaction-CBgx0i4w.js} +3 -3
  54. package/dist-ui/assets/{ccip-BNYiIkjs.js → ccip-BM07w5uU.js} +1 -1
  55. package/dist-ui/assets/{index-DOGXDQkQ.js → index-CfVUpsfM.js} +3 -3
  56. package/dist-ui/assets/{index-DK8XzE3T.js → index-MQh8C4aJ.js} +90 -90
  57. package/dist-ui/assets/index-xKO-U_eV.css +1 -0
  58. package/dist-ui/assets/{jsonld-32FQRO67-Ca-KnQfb.js → jsonld-32FQRO67-DBZOBQji.js} +2 -2
  59. package/dist-ui/assets/{jsonld-mRC9FbFR.js → jsonld-DOX7dKpf.js} +1 -1
  60. package/dist-ui/assets/{renderer-3d-2EVDZII7-AEMBPIY7.js → renderer-3d-2EVDZII7-CVCpAmwB.js} +2 -2
  61. package/dist-ui/assets/{shikiHighlighter-CUQ07vzt.js → shikiHighlighter-CzU2U2Tx.js} +1 -1
  62. package/dist-ui/assets/{useEntityOnChainReceipt-C8NHlPPN.js → useEntityOnChainReceipt-BvWQ6qdT.js} +1 -1
  63. package/dist-ui/assets/{useProjectActivity-CVFCfbMj.js → useProjectActivity-Cxr4QGX1.js} +1 -1
  64. package/dist-ui/index.html +2 -2
  65. package/package.json +6 -4
  66. 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 { RESPONSE_CACHE_BYTES, parseContextGraphJoinPolicyRecord, DEFAULT_SYNC_CHECKPOINT_TTL_MS, isValidSyncCheckpointEntry, transitionSyncCheckpointManifestOffset, transitionSyncCheckpointOffset, transitionSyncCheckpointResponderSession, withoutSyncCheckpointResponderSession, } from '@origintrail-official/dkg-core';
3
+ import { parseContextGraphJoinPolicyRecord, } from '@origintrail-official/dkg-core';
4
4
  import { RoutineLogRetention, installRoutineLogRetentionSchema, } from './routine-log-retention.js';
5
- export { SqliteChainEventCursorStore, SqliteContextGraphAuthorityIndexStore, SqliteContextGraphAuthorityHistoryStore, SqliteContextGraphRegistryScanCursorStore, SqliteContextGraphStorageDiscoveryStore, } from './chain-cursor-stores.js';
6
- export { SqliteProtocolOutboxStore } from './protocol-outbox-store.js';
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