import type { DbAdapter, DbBatchOp } from '@gobing-ai/ts-db'; import type { ImportOptions, JsonObject, ReconcileSummary, SourceDefinition } from './types'; interface CheckpointRow { readonly last_imported_line: number; readonly source_size?: number | null; readonly source_mtime_ms?: number | null; } /** File identity + line for one checkpoint row (0675 R1/R5). */ export interface SourceCheckpoint { readonly line: number; readonly size: number | null; readonly mtimeMs: number | null; } /** * Typed contract tables created and populated by the importer. * * Promoted to public surface per task 0749 (ADR-105). */ export declare const TYPED_HISTORY_TABLES: readonly ["history_message", "history_tool_call", "history_skill_call"]; declare function targetTableFor(table: string): string; declare function timestamp(now: ImportOptions['now']): string; declare function ETL_TABLE_DDL(table: string): string; /** * Apply importer-owned schema to the target database. * * Creates the checkpoint, ledger, and typed contract tables from the static SQL. * Generic ETL tables are created lazily with the first accepted row, so applying * the schema or scanning an empty source never leaves vestigial empty tables. */ export declare function applyHistoryImportSchema(db: ImportOptions['db']): Promise; /** Ensure one accepted record's target exists; typed targets come from the static schema. */ declare function ensureTargetTable(db: ImportOptions['db'], targetTable: string): Promise; /** * Ensure the ETL table(s) for a source definition exist. * * WHY: the static {@link HISTORY_IMPORT_SCHEMA_SQL} creates only the checkpoint, ledger, and * typed contract tables. Callers that explicitly request a definition's generic targets use this * helper; the import pipeline instead creates only targets reached by accepted rows. The * table name is already gated by * {@link VALID_TABLE_NAME} in {@link validateSourceDefinition} / {@link targetTableFor}, * so it is safe to interpolate into DDL. `CREATE TABLE IF NOT EXISTS` is * idempotent, so apply-then-import and a second apply are both safe. */ declare function ensureTargetTables(db: ImportOptions['db'], definition: SourceDefinition): Promise; declare function readCheckpoint(db: ImportOptions['db'], source: string, sourceFile: string): Promise; /** * One query per source (0675 R5): all checkpoint rows for `source` keyed by file, * replacing the per-file SELECT in the import loop. * * Fails open when the identity columns are missing (0678): a database created through a * migration path that has not yet applied 0675's ALTERs would otherwise hard-fail every * import. Line-level behavior degrades to pre-identity semantics — the short-circuit * and identity stamping no-op until the columns exist. */ export declare function loadSourceCheckpoints(db: ImportOptions['db'], source: string): Promise>; declare function resetCheckpoints(db: ImportOptions['db'], source: string, files: readonly string[]): Promise; declare function writeCheckpoint(db: ImportOptions['db'], source: string, sourceFile: string, line: number, now: ImportOptions['now']): Promise; declare function ledgerExists(db: ImportOptions['db'], recordHash: string): Promise; /** * Tool identity for one `history_tool_call` row (0739). * * `effective` is extraction — recover the name when the agent recorded it inside a wrapper's * arguments or only in the call id. `alias` is canonicalization — grouping the same logical tool * across agents — and starts at identity, because the alias vocabulary is a curated decision * that belongs to the consumer, not to the importer. * * Mirrors Spur migration 0034's backfill CASE so a row imported after the migration and a row * backfilled by it resolve identically. */ export declare function resolveToolIdentity(payload: JsonObject): { effective: string; alias: string; }; /** * Build the insert op for one imported record, routing to the typed or JSON * column path per target table. */ export declare function recordInsertOp(targetTable: string, recordHash: string, sourceFile: string, sourceLine: number, splitIndex: number, payload: JsonObject, now: ImportOptions['now']): DbBatchOp; /** INSERT op for one ledger row (task 0060 F9). */ export declare function ledgerInsertOp(recordHash: string, source: string, sourceFile: string, sourceLine: number, splitIndex: number, targetTable: string, now: ImportOptions['now']): DbBatchOp; /** UPSERT op for one checkpoint row (task 0060 F9: checkpoint joins the record batch). */ export declare function checkpointUpsertOp(source: string, sourceFile: string, line: number, now: ImportOptions['now'], identity?: { readonly size: number | null; readonly mtimeMs: number | null; }): DbBatchOp; /** * Targeted UPDATE op attaching a tool duration to a previously-inserted * `history_tool_call` row (task 0564 R1). Keyed by `record_hash` (PK), never an * unindexed predicate. Idempotent: re-imports write the same values. * * Bounds are written alongside a FALLBACK duration so the figure is auditable; * a wallTimeMs-derived duration keeps `started_at`/`completed_at` NULL so the * two measurement paths stay distinguishable. */ declare function toolCallDurationUpdateOp(recordHash: string, startedAt: string | null, completedAt: string | null, durationMs: number | null): DbBatchOp; /** * Targeted UPDATE op attaching a result size to a previously-inserted * `history_tool_call` row (task 0624 R2). Keyed by `record_hash` (PK). Idempotent. */ declare function toolCallResultBytesUpdateOp(recordHash: string, resultBytes: number): DbBatchOp; /** Targeted UPDATE op attributing a codex usage-carrier row's token counts to the * latest assistant message of its session (task 0678 R3). Keyed by `record_hash` (PK). */ export declare function codexUsageAttributionUpdateOp(recordHash: string, tokens: { readonly input: number | null; readonly output: number | null; readonly cacheRead: number | null; }): DbBatchOp; /** * Query which of the given hashes already exist in the ledger, in chunks of at * most 200 per `IN (...)` (task 0060 F9 — replaces the per-record SELECT loop). */ export declare function ledgerExistingHashes(db: ImportOptions['db'], hashes: readonly string[]): Promise>; /** Candidate assistant-message row for duration derivation. */ export interface AssistantDurationCandidate { readonly recordHash: string; readonly deltaMs: number | null; } /** * Fetch candidate assistant rows lacking duration_ms along with timestamp delta to preceding record. */ export declare function findAssistantDurationCandidates(db: DbAdapter): Promise; /** * Update duration_ms and duration_source for a previously unmeasured assistant row. */ export declare function updateAssistantDuration(db: DbAdapter, recordHash: string, durationMs: number, durationSource: string): Promise; declare function insertRecord(db: ImportOptions['db'], targetTable: string, recordHash: string, sourceFile: string, sourceLine: number, splitIndex: number, payload: JsonObject, now: ImportOptions['now']): Promise; declare function insertLedger(db: ImportOptions['db'], recordHash: string, source: string, sourceFile: string, sourceLine: number, splitIndex: number, targetTable: string, now: ImportOptions['now']): Promise; /** * Idempotent migration that rewrites `source_file` to its realpath across the checkpoint, * ledger, and any contract table present (R4). * * WHY: `record_hash` is `sha256({source, sourceFile, sourceLine, splitIndex, record})`, so * `sourceFile` is inside the hash. Normalizing its representation at discovery (R1) changes * every future hash; pre-normalization rows carry unnormalized paths and their old hashes * remain valid as dedupe keys. This migration rewrites the `source_file` *column* so old and * new rows agree on path identity, and collapses duplicate checkpoint rows produced when one * physical file was imported via both a symlinked and a real path — keeping the highest * `last_imported_line` so an incremental resume does not re-import already-seen content. * * `record_hash` is intentionally NOT touched: it is path-representation dependent by * construction, pre-migration rows are grandfathered, and recomputing it would require the * original record payload that the ledger does not store. * * @param resolveRealPath - resolves a `source_file` to its canonical real path. Falls back to * the original value when it returns null/undefined or throws. Decouples the DAO from the * runtime `FileSystem` seam. */ export declare function normalizeSourceFilePaths(db: ImportOptions['db'], resolveRealPath: (sourceFile: string) => string | null | undefined): Promise; /** * Reconcile a source's persisted derived rows against the desired set produced by a full * import (task 0504 R1). Rows whose `record_hash` is no longer reproduced by current source * data or mapper output are stale: their target rows (typed contract tables — including tool * calls — and ETL tables), ledger rows, and checkpoints for vanished source files are removed * in ONE source-scoped batch (atomic). Dry-run returns the exact same counts without mutating * the database, so `--dry-run` is a safe preview and a second full write reports zero changes. * * Deletes are keyed by `record_hash` (PK) and checkpoint `(source, source_file)` (PK) — never * by an unindexed ledger predicate — so reconciliation cost scales with the stale count, not * with ledger size. */ declare function reconcileFullImport(db: ImportOptions['db'], source: string, desiredHashes: ReadonlySet, discoveredFiles: readonly string[], dryRun: boolean): Promise; /** OpenCode message row projected from its SQLite store. */ export interface OpenCodeMessageRow { id: string; session_id: string; time_created: number; data: string; directory: string; } /** OpenCode message-part row projected from its SQLite store. */ export interface OpenCodePartRow { id: string; message_id: string; time_created: number; data: string; } /** Previously imported OpenCode ledger entry. */ export interface OpenCodeExistingEntry { record_hash: string; target_table: string; } /** Normalized OpenCode entry awaiting a history-table write. */ export interface OpenCodeQueuedEntry { targetTable: 'history_message' | 'history_tool_call' | 'history_skill_call'; splitIndex: number; record: JsonObject; recordHash: string; sourceFile: string; } /** Read one ordered page of OpenCode messages after the supplied cursor. */ export declare function readOpenCodeMessages(db: DbAdapter, lastTime: number, lastId: string, limit: number): Promise; /** Read all parts belonging to the supplied OpenCode message IDs. */ export declare function readOpenCodeParts(db: DbAdapter, messageIds: readonly string[]): Promise; /** Load existing OpenCode ledger entries grouped by source message ID. */ export declare function readOpenCodeExistingEntries(db: DbAdapter): Promise>; /** Build deletion operations for records superseded by a forced import. */ export declare function openCodeDeleteOperations(entries: readonly OpenCodeExistingEntry[]): DbBatchOp[]; /** Build checkpoint deletion operation for a source file. */ export declare function deleteCheckpointOperation(source: string, sourceFile: string): DbBatchOp; /** Build batched history, ledger, and checkpoint writes for OpenCode entries. */ export declare function openCodeBulkWriteOperations(entries: readonly OpenCodeQueuedEntry[], checkpointFiles: readonly string[], now: ImportOptions['now']): DbBatchOp[]; export type { CheckpointRow }; export { ETL_TABLE_DDL, ensureTargetTable, ensureTargetTables, insertLedger, insertRecord, ledgerExists, readCheckpoint, reconcileFullImport, resetCheckpoints, targetTableFor, timestamp, toolCallDurationUpdateOp, toolCallResultBytesUpdateOp, writeCheckpoint, }; //# sourceMappingURL=jsonl-importer-dao.d.ts.map