import Database from 'better-sqlite3'; import { type IdempotencyCheckResult, type KaNumberStore, type MessageDirection, type MessageIdempotencyStore, type ProtocolOutboxEntry, type ProtocolOutboxStore, type ContextGraphJoinPolicyRecord, type DurableManifestDigest, type DurableManifestPrefixDigest, type SyncCheckpointEntry } from '@origintrail-official/dkg-core'; export { SqliteChainEventCursorStore, SqliteContextGraphRegistryScanCursorStore, } from './chain-cursor-stores.js'; export declare const SCHEMA_VERSION = 34; /** * Rolling window for `assertion_activity` digest collapse (V16 * notifications-pane redesign). Atomic activity rows are persisted at * write time; the scoped read path (A4) collapses them into one digest per * `(contextGraphId, kind, windowBucket)` where `windowBucket = floor(ts / * ACTIVITY_DIGEST_WINDOW_MS)`. This constant is the single source of truth * for the window so digest COLLAPSE (A4) and digest READ-MARKING * (`resolveActivityDigestRowIds`, CR-3) agree on the same bucket boundaries * — a mismatch would mark the wrong rows read. 24h per the implementation * plan (daemon-tunable in future; a constant for now). */ export declare const ACTIVITY_DIGEST_WINDOW_MS: number; /** Notification `type` used for collapsed assertion-lifecycle activity. */ export declare const ASSERTION_ACTIVITY_TYPE = "assertion_activity"; export type AssertionActivityKind = 'created' | 'promoted' | 'published'; /** * Build the stable digest id for an activity digest row. Mirror of the * parse in `parseActivityDigestKey`. Shape: `activity:::` * where `bucket = floor(ts / ACTIVITY_DIGEST_WINDOW_MS)`. The cgId can * itself contain `:` (wallet-scoped ids are `0x…/…`, but URIs use `:`), so * the parser splits on the FIRST and LAST two delimiters, not a naive * `split(':')`. */ export declare function buildActivityDigestKey(contextGraphId: string, kind: AssertionActivityKind, ts: number): string; /** * Parse a digest key back into its parts. Returns null if the key is not a * well-formed activity digest key. Tolerant of `:` inside `contextGraphId` * by anchoring on the `activity:` prefix and the trailing `::`. */ export declare function parseActivityDigestKey(digestKey: string): { contextGraphId: string; kind: AssertionActivityKind; windowBucket: number; } | null; export interface DashboardDBOptions { /** Directory to store the SQLite database file. */ dataDir: string; /** Days to retain data before pruning. Default: 14 */ retentionDays?: number; /** * TTL (ms) for the in-process memo over polled replication rollups. `<= 0` * disables caching. When omitted, falls back to the * `DKG_DASHBOARD_CACHE_TTL_MS` env var, then a 2000ms default. */ cacheTtlMs?: number; /** Maximum retained info/debug/unknown log rows. Warning/error rows are time-bound only. */ routineLogRowCap?: number; /** Maximum oldest routine rows removed by one bounded catch-up tick. */ logVolumePruneBatchRows?: number; } export interface LogVolumePruneResult { deleted: number; status: 'more' | 'reclaim-pending' | 'done' | 'done-compacted'; } export type StoredContextGraphJoinPolicy = ContextGraphJoinPolicyRecord; export interface ContextGraphJoinPolicyAuditInput { timestamp: number; contextGraphId: string; eventType: string; actor?: string; agentAddress?: string; outcome: string; reason?: string; requestDigest?: string; policyVersion?: number; details?: Record; } export interface ContextGraphJoinPolicyRateReservationInput { contextGraphId: string; timestamp: number; contextGraphLimit: number; nodeLimit: number; actor: string; agentAddress: string; requestDigest: string; policyVersion: number; policyEpoch: number; } export interface ContextGraphJoinPolicyRateReservationResult { allowed: boolean; contextGraphApprovalsLastHour: number; nodeApprovalsLastHour: number; reason?: 'context-graph-rate-limit' | 'node-rate-limit'; } export interface ContextGraphAutomaticApprovalCommitInput { contextGraphId: string; timestamp: number; actor: string; agentAddress: string; requestDigest: string; policyEpoch: number; details?: Record; } export interface ContextGraphAutomaticApprovalRepairInput { contextGraphId: string; requestDigest: string; policyEpoch: number; } export interface ContextGraphAutomaticApprovalRepairRecord { policyEpoch: number; actor: string; agentAddress: string; } export declare class DashboardDB { readonly db: Database.Database; readonly dataDir: string; private retentionDays; private readonly explicitRetentionDays; private readonly routineLogRowCap; private readonly logVolumePruneBatchRows; constructor(opts: DashboardDBOptions); getRetentionDays(): number; setRetentionDays(days: number): void; private migrate; private loadRetentionSetting; prune(): void; /** * Remove one bounded batch of the oldest routine (non-warning/error) logs. * A count cap complements time retention: a high-rate sync storm can create * millions of rows inside a single day, long before a 14-day cutoff applies. * * Deletion is deliberately incremental so an upgrade does not block node * startup on a multi-GB transaction. Once the backlog reaches the cap, one * VACUUM returns the accumulated free pages to the OS and the file shrinks. */ pruneLogVolumeBatch(): LogVolumePruneResult; private routineLogOverflowCutoff; private hasRoutineLogOverflow; private reclaimFreePagesIfNeeded; private truncateWal; private _stmts; private stmt; private _memo; private readonly _memoTtlMs; /** * Memoize a rollup for up to `_memoTtlMs`. `minWindowMs` is the smallest time * window the result depends on; caching is bypassed unless that window is at * least MEMO_WINDOW_SAFETY_FACTOR x the TTL. These rollups are computed over a * moving `Date.now()` window, so a cached value can represent a slightly older * window than the live request; requiring a 10x margin keeps that drift a * negligible fraction of the window. A 1x guard is technically sufficient for * `periodMs` (only the window cutoff drives staleness, not `bucketMs`), but per * review (zsculac) we apply the conservative 10x margin to the smallest * relevant window so the cache is used only where staleness is unambiguously * acceptable. Default dashboard windows (1h/24h) are far above the 2s TTL, so * they always cache. * * Returned values are structuredClone'd so a caller that sorts/pushes/annotates * the summary or rows cannot mutate the cached instance and poison later reads * (these methods previously returned a fresh result object on every call). */ private memoized; insertSnapshot(snap: MetricSnapshotRow): void; getLatestSnapshot(): MetricSnapshotRow | undefined; upsertContextGraphSubscription(record: { context_graph_id: string; name?: string | null; subscribed: number; synced: number; shared_memory_synced?: number | null; meta_synced?: number | null; on_chain_id?: string | null; on_chain_hash?: string | null; last_reconciled_ordinal?: number | null; core_hosted?: number | null; sync_scoped: number; updated_at: number; }): void; listContextGraphSubscriptions(): ContextGraphSubscriptionRow[]; getContextGraphSubscription(contextGraphId: string): ContextGraphSubscriptionRow | undefined; deleteContextGraphSubscription(contextGraphId: string): void; /** * CLI-owned, durable provenance for context-graph readiness flags. * * Subscription rows predate per-plane proof and can therefore contain the * v10.0.6 false-ready shape (`synced=1`, `shared_memory_synced=1`) after an * unrelated peer returned an empty response. Keep the proof out of the * agent-owned subscription upsert so older/custom agent stores remain * source-compatible and routine subscription persistence cannot overwrite a * proof established by the daemon's catch-up classifier. */ getContextGraphReadinessProvenance(contextGraphId: string): ContextGraphReadinessProvenance | null; setContextGraphReadinessProvenance(contextGraphId: string, provenance: Omit & { updatedAt?: number; }): void; private contextGraphReadinessProvenanceKey; getContextGraphJoinPolicy(contextGraphId: string): StoredContextGraphJoinPolicy | null; setContextGraphJoinPolicy(record: StoredContextGraphJoinPolicy): void; setContextGraphJoinPolicyWithAudit(record: StoredContextGraphJoinPolicy, event: ContextGraphJoinPolicyAuditInput): void; appendContextGraphJoinPolicyAudit(event: ContextGraphJoinPolicyAuditInput): void; reserveContextGraphAutomaticApproval(input: ContextGraphJoinPolicyRateReservationInput): ContextGraphJoinPolicyRateReservationResult; markContextGraphAutomaticApprovalRepairPending(input: ContextGraphAutomaticApprovalRepairInput): boolean; getContextGraphAutomaticApprovalRepair(contextGraphId: string, requestDigest: string): ContextGraphAutomaticApprovalRepairRecord | null; commitContextGraphAutomaticApproval(input: ContextGraphAutomaticApprovalCommitInput): boolean; getContextGraphAutomaticApprovalUsage(contextGraphId: string, timestamp: number): { contextGraphApprovalsLastHour: number; nodeApprovalsLastHour: number; }; listContextGraphJoinPolicyAudit(contextGraphId: string): Array>; private contextGraphJoinPolicyKey; upsertVmReconcileNegative(record: VmReconcileNegativeRow): void; getVmReconcileNegative(cacheKey: string): VmReconcileNegativeRow | undefined; deleteVmReconcileNegative(cacheKey: string): void; deleteVmReconcileNegativesForContextGraph(contextGraphId: string): void; upsertSelectedVmReconcileCursor(record: SelectedVmReconcileCursorRow): void; getSelectedVmReconcileCursor(deploymentId: string, contextGraphId: string, onChainContextGraphId: string): SelectedVmReconcileCursorRow | undefined; /** * Persist one {@link ReplicationEvent} (best-effort sink target). `ordinal`, * watermarks etc. are nullable per the event shape. Number-typed fields are * coerced defensively so a malformed event can never abort the insert. */ insertReplicationEvent(ev: ReplicationEventRow): void; /** * Network-wide replication KPIs over the trailing `periodMs`: * - promotion latency (P50/P95) — fetch→promote delta per UAL. * - reconcile success rate — promote / (promote + fetch). * - raw action counts. */ getReplicationSummary(periodMs?: number): ReplicationSummary; private _computeReplicationSummary; /** Per-CG rollup of replication activity over the window, newest-active first. */ getReplicationPerCg(periodMs?: number): ReplicationPerCgRow[]; private _computeReplicationPerCg; /** * Time-bucketed event series for the timeline charts. When `contextGraphId` * is given, the series is scoped to that CG. */ getReplicationTimeline(opts: { periodMs: number; bucketMs: number; contextGraphId?: string; }): ReplicationTimelineBucket[]; private _computeReplicationTimeline; /** * Recent raw events for a CG (the per-CG timeline drawer). Newest first. */ getReplicationEventsForCg(contextGraphId: string, limit?: number): ReplicationEventRow[]; /** * Cursor inspector source: every subscription's persisted watermark joined * with the freshest observed chain head from the event stream. This is the * on-disk truth the Replication tab's cursor inspector renders. */ getReplicationCursors(): ReplicationCursorRow[]; upsertContextGraphMember(record: { context_graph_id: string; principal_type: ContextGraphMemberPrincipalType; principal_id: string; role?: string | null; status: ContextGraphMemberStatus; source?: string | null; display_name?: string | null; metadata?: string | null; first_seen_at?: number | null; updated_at: number; }): void; listContextGraphMembers(contextGraphId?: string): ContextGraphMemberRow[]; deleteContextGraphMember(contextGraphId: string, principalType: ContextGraphMemberPrincipalType, principalId: string): void; getSnapshotHistory(from: number, to: number, maxPoints?: number): MetricSnapshotRow[]; insertOperation(op: { operation_id: string; operation_name: string; started_at: number; peer_id?: string | null; contextGraph_id?: string | null; details?: string | null; }): void; completeOperation(op: { operation_id: string; duration_ms: number; triple_count?: number | null; details?: string | null; }): void; failOperation(op: { operation_id: string; duration_ms: number; error_message: string; }): void; cancelOperation(op: { operation_id: string; duration_ms: number; error_message: string; }): void; finishOperation(op: { operation_id: string; duration_ms: number; error_message: string; status: 'error' | 'cancelled'; }): void; getOperations(opts?: { name?: string; names?: string[]; status?: string; operationId?: string; from?: number; to?: number; limit?: number; offset?: number; }): { operations: OperationRow[]; total: number; }; getOperationsWithPhases(opts?: { name?: string; names?: string[]; status?: string; operationId?: string; from?: number; to?: number; limit?: number; offset?: number; }): { operations: (OperationRow & { phases: OperationPhaseRow[]; })[]; total: number; }; getErrorHotspots(periodMs?: number): { phase: string; operation_name: string; error_count: number; last_error: string | null; last_occurred: number | null; }[]; getFailedOperations(opts?: { phase?: string; operationName?: string; periodMs?: number; q?: string; limit?: number; }): { operations: Array; }; getOperation(operationId: string): { operation: OperationRow | null; logs: LogRow[]; phases: OperationPhaseRow[]; }; insertPhase(op: { operation_id: string; phase: string; started_at: number; }): void; completePhase(op: { operation_id: string; phase: string; duration_ms: number; }): void; failPhase(op: { operation_id: string; phase: string; duration_ms: number; error_message: string; }): void; cancelPhase(op: { operation_id: string; phase: string; duration_ms: number; error_message: string; }): void; finishPhase(op: { operation_id: string; phase: string; duration_ms: number; error_message: string; status: 'error' | 'cancelled'; }): void; failAllPhases(op: { operation_id: string; duration_ms: number; error_message: string; }): void; setOperationCost(op: { operation_id: string; gas_used?: number | null; gas_price_gwei?: number | null; gas_cost_eth?: number | null; trac_cost?: number | null; tx_hash?: string | null; chain_id?: number | null; }): void; getOperationStats(opts: { name?: string; periodMs: number; bucketMs: number; }): { summary: OperationStatsSummary; timeSeries: OperationStatsBucket[]; }; getPerTypeTimeSeries(opts: { periodMs: number; bucketMs: number; }): { buckets: number[]; types: string[]; series: Record; }; getSuccessRatesByType(periodMs: number): { type: string; total: number; success: number; error: number; rate: number; avgMs: number; }[]; getSpendingSummary(): SpendingSummary; /** * Insert a chat message. rc.9 PR-3 moved the V11 receiver-side * dedup (the partial unique index `idx_chat_msgid`) out of SQL * and into the Universal Messenger substrate * (`Messenger.register` → `message_idempotency` table from V12). * The substrate intercepts duplicate inbound chats BEFORE they * reach this insert, so the table no longer enforces uniqueness * — and this method no longer carries an `ON CONFLICT` clause. * Returns `true` when a row was inserted (always now, modulo * raw SQL constraint failures). * * The `message_id` COLUMN is preserved as nullable + persisted * so HTTP/MCP readers can still surface it. V13 dropped only * the INDEX, not the column — rollback to rc.8 finds a * structurally-compatible schema. * * Non-dedup SQL constraint failures (NOT NULL on `peer`, etc.) * still throw — pinned by db.test.ts. */ insertChatMessage(msg: { ts: number; direction: 'in' | 'out'; peer: string; peerName?: string | null; text: string; delivered?: boolean | null; messageId?: string | null; }): boolean; /** * Read chat history. * * Server-side `direction` filters BEFORE the LIMIT applies, which * matters for inbox reads — if the filter ran client-side, a burst * of outbound replies in the newest N rows would push inbound * messages past the cap and they'd never be surfaced. * * Forward pagination uses a **compound cursor** `(since, sinceId)` * to avoid losing rows that share the same millisecond `ts`. * Without `sinceId`, the predicate is just `ts > since`, and any * second-or-later row that shares the watermark `ts` is permanently * skipped (Codex PR #510 round 2 flagged this — chat bursts can * easily share `Date.now()` values). The compound cursor uses the * `id INTEGER PRIMARY KEY AUTOINCREMENT` from the schema as a stable * tiebreaker so pagination is lossless. * * `order` defaults to `'desc'` for the dashboard "show recent N" * view. Inbox/feed readers pass `'asc'` so pagination walks * oldest → newest and the cursor advances over rows we have * actually returned, never past unseen older ones. */ getChatMessages(opts?: { peer?: string; since?: number; /** * Secondary cursor — when paired with `since`, the predicate is * `(ts > since) OR (ts = since AND id > sinceId)`, which makes * pagination lossless across rows that share a millisecond. */ sinceId?: number; limit?: number; direction?: 'in' | 'out'; order?: 'asc' | 'desc'; }): ChatMessageRow[]; getChatPersistenceJob(turnId: string): ChatPersistenceJobRow | undefined; insertChatPersistenceJob(job: { turn_id: string; session_id: string; user_message: string; assistant_reply: string; tool_calls_json?: string | null; status: ChatPersistenceStatus; attempts: number; max_attempts: number; next_attempt_at: number; queued_at: number; updated_at: number; store_ms?: number | null; error_message?: string | null; }): void; markChatPersistenceInProgress(turnId: string, attempts: number, updatedAt: number): void; markChatPersistenceStored(turnId: string, storeMs: number, updatedAt: number): void; markChatPersistencePendingRetry(turnId: string, attempts: number, nextAttemptAt: number, updatedAt: number, errorMessage: string): void; markChatPersistenceFailed(turnId: string, attempts: number, updatedAt: number, errorMessage: string): void; recoverInProgressChatPersistenceJobs(now: number): void; getRunnableChatPersistenceJobs(now: number, limit?: number): ChatPersistenceJobRow[]; getNextPendingChatPersistenceAt(): number | null; getChatPersistenceHealth(now: number): ChatPersistenceHealthRow; insertLog(entry: { ts: number; level: string; operation_name?: string | null; operation_id?: string | null; module: string; message: string; }): void; /** * Backwards-compatible DB-backed log search. V15 deliberately removed * the FTS5 shadow table that made `q=` fast because it dominated DB * growth on production nodes. Keep the public method/API surface using * bounded LIKE scans over the retained base `logs` table. */ searchLogs(opts?: { q?: string; operationId?: string; level?: string; module?: string; from?: number; to?: number; limit?: number; offset?: number; }): { logs: LogRow[]; total: number; }; private likeContains; insertQueryHistory(entry: { sparql: string; duration_ms: number; result_count?: number | null; error?: string | null; }): void; getQueryHistory(limit?: number, offset?: number): QueryHistoryRow[]; getSavedQueries(): SavedQueryRow[]; insertSavedQuery(entry: { name: string; description?: string; sparql: string; }): number; updateSavedQuery(id: number, entry: { name?: string; description?: string; sparql?: string; }): void; deleteSavedQuery(id: number): void; insertNotification(n: { ts: number; type: string; title: string; message: string; source?: string | null; peer?: string | null; meta?: string | null; /** * Context-graph scoping key (V16). Written to the indexed * `context_graph_id` column so the scoped read path * (`getScopedNotifications`) can filter/aggregate in SQL. The column is * the authoritative scope source — the wire contract surfaces it as the * top-level `NotifWire.contextGraphId`, NOT nested in `meta`, so we do * NOT re-parse/rewrite caller-supplied `meta` JSON here (that would risk * corrupting already-escaped literals). Callers that also want it inside * `meta` for display include it there themselves (the join handlers * already do). Omitted/undefined → NULL = legacy node/global-scope row, * treated as out-of-scope by the scoped read. */ contextGraphId?: string | null; }): number; getNotifications(opts?: { limit?: number; since?: number; }): { notifications: NotificationRow[]; unreadCount: number; }; /** * Scoped notifications read (Codex round-1 B3): return rows ONLY for the * given context-graph ids, newest-first, capped at `limit`. Pushing the * member-CG filter into SQL (on the indexed `context_graph_id` column) * instead of read-N-then-filter guarantees a caller's actionable rows * (join requests) can't be evicted from the window by a flood of * foreign-CG rows. Empty `cgIds` → empty result (no member CGs in scope). * * better-sqlite3 has no array binding, so the `IN (...)` placeholder list * is built from `cgIds.length`. */ getNotificationsForContextGraphs(cgIds: string[], limit?: number): NotificationRow[]; /** * The set of notification row ids in the caller's scope (Codex round-1 B2): * rows whose `context_graph_id` is in `cgIds`. Used to intersect a `/read` * request so a caller can only mark THEIR OWN scoped rows read — never * foreign rows by guessing ids — and so empty-body "mark all" marks only * the caller's scoped rows, not the entire table. Empty `cgIds` → empty set. */ getScopedNotificationRowIds(cgIds: string[]): Set; /** * Read notification rows of the given types regardless of context graph, * newest-first, capped at `limit`. Used for join confirmations * (join_approved/join_rejected): these are the caller's OWN outbound-request * resolutions and are NOT CG-membership-scoped — a rejected requester is no * longer a member of the CG, so a context-graph filter would drop the * rejection entirely (R3-1). Confirmations are low volume (one per resolved * request), so an unscoped type read is bounded; the caller route then keeps * only the rows whose meta.agentAddress is the caller. */ getNotificationsOfTypes(types: string[], limit?: number): NotificationRow[]; /** * The caller's OWN confirmed-discount rows (`pca_cost_covered`, B8). Wallet- * scoped, NOT CG-membership-scoped (the publishing wallet may not be a member * of the CG it published to — a sponsored edge). Kept on a DEDICATED fetch * (filtered in SQL by type + lowercased `meta.publisherAddress`) rather than * the shared join-confirmation `getNotificationsOfTypes` window: discount rows * are higher-volume (one per discounted publish vs rare join events), so * cap-sharing would let a busy node's join/other volume age a publisher's older * discount rows out of the window → hidden + unmarkable (#1365 round-2). The * SQL pulls ONLY the caller's own rows under its own bound. The type literal * mirrors `PCA_COST_COVERED_TYPE` (kept inline to avoid a notifications-scope * import cycle into the DB layer). */ getPcaCostCoveredRowsForWallet(walletAddress: string, limit?: number): NotificationRow[]; markNotificationsRead(ids?: number[]): number; /** * Resolve an activity digest key (`activity:::`) * to the ids of the underlying atomic `assertion_activity` rows it * collapses (CR-3). Used by the scoped `/read` path so marking a digest * "seen" flips `read=1` on the real rows that compose it. * * Query shape: the indexed `context_graph_id` column is the pre-filter * (cheap), then `ts` is range-bounded to the bucket window, then * `json_extract(meta,'$.kind')` discriminates the lifecycle kind (which * lives in the JSON `meta`, not its own column). Returns [] for a * malformed key so a bad client id is a no-op, not an error. */ resolveActivityDigestRowIds(digestKey: string): number[]; close(): void; } export declare class SqliteSyncCheckpointStore { private readonly db; private readonly clock; private readonly ttlMs; constructor(dashboard: DashboardDB, options?: { clock?: () => number; ttlMs?: number; }); private readRow; get(key: string, now?: number): SyncCheckpointEntry | undefined; private writeEntry; set(key: string, value: number, nowMs?: number, responderSessionOffset?: number): void; setManifestBoundOffset(key: string, value: number, manifestDigest: DurableManifestDigest, nowMs?: number, manifestPrefixDigest?: DurableManifestPrefixDigest, terminal?: boolean, responderSessionOffset?: number): void; setResponderSession(key: string, sessionId: string, expiresAtMs: number, nowMs?: number, manifestDigest?: DurableManifestDigest, manifestPrefixDigest?: DurableManifestPrefixDigest, responderSessionOffset?: number): void; clearResponderSession(key: string): void; delete(key: string): void; pruneExpired(nowMs?: number): number; } /** * OT-RFC-59 SC5 durable changelog cursor store (duck-compatible with the agent's * ChangelogCursorStore). Keyed by (peer_id, context_graph_id); stores the last * APPLIED (era, seq) from that responder. Like `ka_numbers` / `protocol_outbox` * this state is durable: it is NEVER added to `prune()` — a TTL would defeat the * O(delta) cross-restart catch-up the changelog lane exists for. */ export declare class SqliteChangelogCursorStore { private readonly db; private readonly clock; constructor(dashboard: DashboardDB, options?: { clock?: () => number; }); get(peerId: string, contextGraphId: string): { era: string; seq: number; updatedAtMs: number; } | undefined; set(peerId: string, contextGraphId: string, era: string, seq: number, nowMs?: number): void; } /** * OT-RFC-59 §6 P0 durable era guard (duck-compatible with storage's * ChangelogEraGuard). Persists the single (era, high_seq) high-water in * node-ui.db — which a `store.nq` RDF-store restore does NOT roll back — so the * write-side ChangelogStore can detect a restore/rollback (seq regressed under * the same era) and rotate the era, forcing peers to full-resync instead of * silently skipping. `save` is called after every committed seq, so it must be a * single fast upsert. */ export declare class SqliteChangelogEraGuard { private readonly db; private readonly clock; constructor(dashboard: DashboardDB, options?: { clock?: () => number; }); load(): Promise<{ era: string; highSeq: number; } | null>; save(era: string, highSeq: number): Promise; } /** * SQLite-backed `MessageIdempotencyStore` against the V12 * `message_idempotency` table in `DashboardDB`. Receiver-side dedup * cache + sender-side "did we deliver this" cache, keyed by * `(peer, protocol, message_id, direction)`. * * Constructed against an already-opened `DashboardDB` so all DKG * persistence shares a single SQLite file (one WAL, one fsync, one * pragma surface). Doesn't open the DB itself — the daemon's * `lifecycle.ts` owns DB lifecycle and hands one in here in PR-2. * * Response caching policy lives in `RESPONSE_CACHE_BYTES` (256 KiB * fixed limit, exported from `@origintrail-official/dkg-core`). * Responses up to the limit are stored inline in `response_blob`; * larger responses store `response_blob = NULL` with the actual * size in `response_size` (mark-only). Duplicate receives whose * original was mark-only surface as `RESPONSE_GONE` to the sender * — see `RESPONSE_GONE_MARKER` for the canonical signal string. */ export declare class SqliteMessageIdempotencyStore implements MessageIdempotencyStore { private readonly db; private readonly clock; /** @param clock injectable for deterministic tests. Defaults to `Date.now`. */ constructor(dashboard: DashboardDB, options?: { clock?: () => number; }); check(peer: string, protocol: string, messageId: string, direction: MessageDirection): IdempotencyCheckResult; record(peer: string, protocol: string, messageId: string, direction: MessageDirection, response?: Uint8Array): void; pruneOlderThan(tsMs: number): number; } /** * SQLite-backed `ProtocolOutboxStore` against the V12 * `protocol_outbox` table. Sender-side durable retry queue, keyed * by `(peer, protocol, message_id)`. The substrate's reliability * floor: a daemon crash mid-retry doesn't lose the message — the * next startup's `Messenger.processOutboxTick` picks up exactly * where the crash left off (modulo the in-flight bytes that died * with the process, which is documented as the "in-flight queue * caveat" in CHANGELOG for rc.9). * * The backoff ladder + max-age are NOT stored in SQL — they live * on the wrapping `ProtocolOutbox` in `packages/core`, and only * the resulting `next_attempt_at` and `first_failure_at` timestamps * land in the table. This keeps the schema independent of policy * changes: bumping the ladder doesn't require a migration. * * Constructor takes a `maxAgeMs` so `dropExpired` can apply it * directly in SQL (avoiding a full table read). */ export interface SqliteProtocolOutboxStoreOptions { /** * Max age (ms) from `firstFailureAt` before `dropExpired(now)` * evicts an entry. Defaults to 24h. Mirrors the wrapping * `ProtocolOutbox`'s `maxAgeMs` so both layers agree. */ maxAgeMs?: number; /** * Function that returns the backoff (ms) to apply for an entry * about to bump to `attempts`. The schema does NOT store the * ladder; PR-2's `lifecycle.ts` wiring passes the wrapping * `ProtocolOutbox`'s `backoffFor` method here so policy lives in * one place. Defaults to a flat 5s backoff so the store works * standalone in tests + before the wrapping outbox is wired. */ backoffFor?: (attempts: number) => number; } export declare class SqliteProtocolOutboxStore implements ProtocolOutboxStore { private readonly db; private maxAgeMs; private backoffFor; constructor(dashboard: DashboardDB, options?: SqliteProtocolOutboxStoreOptions); configurePolicy(options?: SqliteProtocolOutboxStoreOptions): void; enqueue(peer: string, protocol: string, messageId: string, payload: Uint8Array, error: string, now: number): ProtocolOutboxEntry; markDelivered(peer: string, protocol: string, messageId: string): boolean; hasEntry(peer: string, protocol: string, messageId: string): boolean; hasPendingFor(peer: string): boolean; pendingFor(peer: string): ProtocolOutboxEntry[]; due(now: number): ProtocolOutboxEntry[]; duePage(now: number, limit: number): ProtocolOutboxEntry[]; dropExpired(now: number): ProtocolOutboxEntry[]; size(): number; list(): ProtocolOutboxEntry[]; getEntry(peer: string, protocol: string, messageId: string): ProtocolOutboxEntry | undefined; private static rowToEntry; } /** * SQLite-backed `KaNumberStore` against the V20 `ka_numbers` table. * Per-author durable KA-number allocator for OT-RFC-43 Option-1 * deterministic KA identity (B2 allocator core, OFF-CHAIN only). * * Keyed by the attested author address (stored lowercase). Every * method is a single prepared statement; the read-and-increment in * `allocate` is atomic via `INSERT … ON CONFLICT DO UPDATE … * RETURNING`, so concurrent allocations under the same author never * collide on a number (the same atomic-single-statement contract the * other Sqlite*Store classes rely on — there is no `.transaction()` * in this module). * * **Counter width (codex PR #976 F6):** every value crossing the * `KaNumberStore` interface is a `bigint`. `better-sqlite3` returns * INTEGER columns as JS `number` by default — which silently truncates * once a counter passes `Number.MAX_SAFE_INTEGER (2^53 - 1)`. Each * statement here opts into `.safeIntegers(true)`, returning `bigint` * directly so `allocate()`, `peekNext()` and `observed + 1n` stay * exact across the full SQLite signed INTEGER range (`2^63 - 1`). The * RFC's worst-case load ("1000 alloc/s × 1M years ≈ 2^55") sits well * past the `2^53` precision cliff and far under the `2^63` hard * ceiling; if a single author ever does approach `2^63`, SQLite raises * an INTEGER overflow on the next increment — a fail-loud surface * that's strictly preferable to a silent kaId-collision risk. * * Like `protocol_outbox`, this state is durable: it is NEVER added to * `prune()`. Reclaiming a number could re-mint a kaId already used * on-chain under that author. */ export declare class SqliteKaNumberStore implements KaNumberStore { private readonly db; constructor(dashboard: DashboardDB); allocate(authorAddress: string): bigint; reconcileFloor(authorAddress: string, nextNumberFloor: bigint): void; peekNext(authorAddress: string): bigint; } export interface MetricSnapshotRow { id?: number; ts: number; cpu_percent: number | null; mem_used_bytes: number | null; mem_total_bytes: number | null; disk_used_bytes: number | null; disk_total_bytes: number | null; heap_used_bytes: number | null; uptime_seconds: number | null; peer_count: number | null; direct_peers: number | null; relayed_peers: number | null; mesh_peers: number | null; contextGraph_count: number | null; total_triples: number | null; total_kcs: number | null; total_kas: number | null; store_bytes: number | null; confirmed_kcs: number | null; tentative_kcs: number | null; rpc_latency_ms: number | null; rpc_healthy: number | null; /** * Operator-configured relay reservation cap (DKGNodeConfig.relayServerCapacity). * NULL on edge nodes (no relay server enabled). */ relay_capacity: number | null; /** Live count of held reservations at snapshot time. NULL off-relay. */ relay_reservation_count: number | null; /** * Active forwarded circuits at snapshot time, counted as the number of * open relay STOP streams (`/libp2p/circuit/relay/0.2.0/stop`). NOTE: * forwarded circuits do not appear as `/p2p-circuit` connections on the * relay host — that multiaddr only exists on the edge endpoints. NULL * off-relay. */ relay_active_circuits: number | null; /** * Total bytes received via 'message' events on relay HOP+STOP streams * since the relay started (= bytes ARRIVING at the relay's HOP+STOP * endpoints from the dialer / reservee). Stored as plain integer * (SQLite INTEGER is 8 bytes signed = ~9.2e18, well above any * realistic relay byte total before retention pruning). NULL off-relay. */ relay_bytes_in: number | null; /** * Same as relay_bytes_in but for outbound traffic — bytes sent via * `.send()` on relay HOP+STOP streams (= bytes DEPARTING from the * relay toward the dialer / reservee). NULL off-relay. */ relay_bytes_out: number | null; } export interface OperationRow { id: number; operation_id: string; operation_name: string; started_at: number; duration_ms: number | null; status: string; peer_id: string | null; contextGraph_id: string | null; triple_count: number | null; error_message: string | null; details: string | null; gas_used: number | null; gas_price_gwei: number | null; gas_cost_eth: number | null; trac_cost: number | null; tx_hash: string | null; chain_id: number | null; } export interface OperationPhaseRow { id: number; operation_id: string; phase: string; started_at: number; duration_ms: number | null; status: string; details: string | null; } export interface OperationStatsSummary { totalCount: number; successCount: number; errorCount: number; successRate: number; avgDurationMs: number; avgGasCostEth: number; totalGasCostEth: number; avgTracCost: number; totalTracCost: number; } export interface OperationStatsBucket { bucket: number; count: number; successRate: number; avgDurationMs: number; avgGasCostEth: number; totalGasCostEth: number; } export interface LogRow { id: number; ts: number; level: string; operation_name: string | null; operation_id: string | null; module: string | null; message: string; } export interface QueryHistoryRow { id: number; ts: number; sparql: string; duration_ms: number | null; result_count: number | null; error: string | null; } export interface SavedQueryRow { id: number; name: string; description: string | null; sparql: string; created_at: number; updated_at: number; } export interface NotificationRow { id: number; ts: number; type: string; title: string; message: string; source: string | null; peer: string | null; read: number; meta: string | null; /** Context-graph scoping key (V16). NULL on legacy/global-scope rows. */ context_graph_id: string | null; } export interface ChatMessageRow { id: number; ts: number; direction: 'in' | 'out'; peer: string; peer_name: string | null; text: string; delivered: number | null; /** * Sender-assigned message id (UUID v4 by default; caller-overridable * via `dkg-agent.ts`'s `options.messageId`). Nullable for pre-V11 * rows AND for any future sender that intentionally omits it. * As of V13 (rc.9 PR-3), receiver-side dedup is owned by the * Universal Messenger substrate (`message_idempotency` table + * `Messenger.register` envelope decode), not by this column's * SQL index — the index was dropped, the column persists for * readers + rollback safety. */ message_id: string | null; } export type ChatPersistenceStatus = 'pending' | 'in_progress' | 'stored' | 'failed'; export interface ChatPersistenceJobRow { turn_id: string; session_id: string; user_message: string; assistant_reply: string; tool_calls_json: string | null; status: ChatPersistenceStatus; attempts: number; max_attempts: number; next_attempt_at: number; queued_at: number; updated_at: number; store_ms: number | null; error_message: string | null; } export interface ChatPersistenceHealthRow { pending_count: number; in_progress_count: number; stored_count: number; failed_count: number; overdue_pending_count: number; oldest_pending_queued_at: number | null; } export interface ContextGraphSubscriptionRow { context_graph_id: string; name: string | null; subscribed: number; synced: number; shared_memory_synced: number | null; meta_synced: number | null; on_chain_id: string | null; on_chain_hash: string | null; last_reconciled_ordinal: number | null; core_hosted: number | null; sync_scoped: number; updated_at: number; } export interface ContextGraphReadinessProvenance { version: number; durableVerified: boolean; sharedMemoryVerified: boolean; updatedAt: number; } export interface VmReconcileNegativeRow { cache_key: string; context_graph_id: string; failures: number; next_retry_at: number; swm_gen: string; candidate_namespaces: string; peer_topology_key: string; updated_at: number; } export interface SelectedVmReconcileCursorRow { deployment_id: string; context_graph_id: string; on_chain_context_graph_id: string; name_hash: string; watermark: number; updated_at: number; } export interface ReplicationEventRow { ts: number; context_graph_id: string; on_chain_cg_id?: string | null; action: string; ual?: string | null; ordinal?: number | null; ka_id?: string | null; from_watermark?: number | null; to_watermark?: number | null; head?: number | null; reconciled?: number | null; pending?: number | null; detail?: string | null; } export interface ReplicationSummary { periodMs: number; counts: Record; promotes: number; fetches: number; defers: number; /** promote / (promote + defer); null when no attempts in window. */ successRate: number | null; latencyP50Ms: number | null; latencyP95Ms: number | null; totalEvents: number; } export interface ReplicationPerCgRow { context_graph_id: string; on_chain_cg_id: string | null; promotes: number; fetches: number; defers: number; cursor_advances: number; last_watermark: number | null; last_head: number | null; last_event_ts: number | null; } export interface ReplicationTimelineBucket { bucket: number; promotes: number; fetches: number; defers: number; total: number; } export interface ReplicationCursorRow { context_graph_id: string; on_chain_id: string | null; last_reconciled_ordinal: number | null; core_hosted: number | null; subscribed: number | null; last_head: number | null; last_event_ts: number | null; } export type ContextGraphMemberPrincipalType = 'node' | 'agent' | 'identity'; export type ContextGraphMemberStatus = 'active' | 'removed' | 'pending'; export interface ContextGraphMemberRow { context_graph_id: string; principal_type: ContextGraphMemberPrincipalType; principal_id: string; role: string | null; status: ContextGraphMemberStatus; source: string | null; display_name: string | null; metadata: string | null; first_seen_at: number; updated_at: number; } export interface SpendingPeriod { label: string; publishCount: number; successCount: number; totalGasEth: number; totalTrac: number; avgGasEth: number; avgTrac: number; } export interface SpendingSummary { periods: SpendingPeriod[]; } //# sourceMappingURL=db.d.ts.map