import type { CallEdge, FunctionNode, ClassNode, InheritanceEdge } from '../analyzer/call-graph.js'; import type { FunctionCfg } from '../analyzer/cfg.js'; import type { DecisionNode, DecisionAffectsEdge } from '../decisions/project.js'; import type { FileProvenance } from '../provenance/git-provenance.js'; import type { FileChangeCoupling, ChangeCouplingResult } from '../provenance/change-coupling.js'; /** * Why a just-opened graph store cannot be trusted to answer a read (change: * harden-index-store-lifecycle): * - `schema-mismatch` — the on-disk store was built by a different OpenLore * (a SCHEMA_VERSION bump). A read leaves it byte-untouched and reports this, * rather than the old drop-and-rebuild-on-read that destroyed the index. * - `quarantined` — the DB file failed to open (corrupt/truncated); it was * moved aside to `*.corrupt-` and this handle wraps an ephemeral empty DB, * never a silently recreated on-disk store presented as healthy. * In both cases the recovery is the same single command: `openlore analyze`. */ export interface StoreLifecycleFault { reason: 'schema-mismatch' | 'quarantined'; /** Human-readable, names the recovery command. */ message: string; /** Present for `quarantined`: where the corrupt bytes were preserved (null if unmovable). */ quarantinePath?: string | null; /** Present for `schema-mismatch`: the SCHEMA_VERSION found on disk. */ onDiskVersion?: number; } /** Bump when schema changes. Old DBs are dropped and rebuilt on next analyze --force. */ export declare const SCHEMA_VERSION = 8; export declare class EdgeStore { private readonly db; /** * True when opening this DB found a stale SCHEMA_VERSION and wiped it (rebuild-on-bump). * The data is gone until the next analyze repopulates it — callers that READ * (vs. analyze, which repopulates) should treat the store as unavailable so they * can tell the user to re-run analyze instead of serving an empty graph. */ private _wasReset; get wasReset(): boolean; /** * Non-null when this handle must NOT answer reads: a schema-version mismatch met on a * read-mode open (the store is left intact on disk) or a corrupt DB quarantined at open. * Read consumers MUST check this before querying and surface the not-ready conclusion — * never an empty graph served as current fact (change: harden-index-store-lifecycle). */ private _fault; get notReady(): StoreLifecycleFault | null; private constructor(); private initSchema; /** All distinct files that call into calleeFile (reverse lookup before delete). */ getCallerFiles(calleeFile: string): string[]; /** All outgoing + incoming edges touching a file. */ getEdgesForFile(file: string): { outgoing: CallEdge[]; incoming: CallEdge[]; }; /** Outgoing edges from a node ID (its direct callees). */ getCallees(nodeId: string): CallEdge[]; /** Incoming edges to a node ID (its direct callers). */ getCallers(nodeId: string): CallEdge[]; /** * Cross-package consumer edges for a symbol name: edges whose callee is an * unresolved external reference (`confidence === 'external'`) with this exact * name. These are the call sites in *this* repo that reach a symbol published * by another repo — the consumer side of federated cross-repo resolution. * Matched on the exact name; arity/signature is unavailable at an external call * site, so callers must disclose name-collision risk. */ getExternalConsumers(symbolName: string): CallEdge[]; /** * Distinct caller FILES that make an unresolved external reference to this * exact name (`confidence === 'external'`). When an incremental edit ADDS a * symbol, these are the prior non-callers whose `external::` call sites * should now bind to the new internal symbol — the files `getCallerFiles` * misses (they hold an external edge, not an edge into the changed file). The * change-driven closure re-resolves them so the graph converges with * `analyze --force` (change: fix-transitive-incremental-staleness). */ getExternalConsumerFiles(symbolName: string): string[]; /** * Caller FILE + current resolved callee id for every `name_only` edge to this * exact name (the lowest, ambiguity-tolerant tier — no import, no receiver * type). When an incremental edit ADDS a symbol, the winning candidate for a * `name_only` call is the lowest candidate id, so the new symbol only flips a * consumer whose current target id sorts AFTER the new id. The caller compares * `calleeId` to prune the no-op majority (a common-name add would otherwise * needlessly re-resolve and stale-flag every consumer) * (fix-transitive-incremental-staleness). One row per (file, target) pair. */ getNameOnlyConsumers(symbolName: string): Array<{ file: string; calleeId: string; }>; /** * The distinct names of every unresolved external reference this repo makes * (`confidence === 'external'`) — the upstream interfaces this repo consumes from * the rest of the fleet. The producer side of federation resolves each of these to * the repo that publishes it. Non-fleet externals (npm/stdlib) appear here too and * are filtered downstream when no registered repo produces them. */ getExternalReferenceNames(): string[]; /** Batch: outgoing edges for a set of caller IDs — one query instead of N. */ getCalleesForIds(callerIds: string[]): CallEdge[]; /** Batch: incoming edges for a set of callee IDs — one query instead of N. */ getCallersForIds(calleeIds: string[]): CallEdge[]; /** * Every production call edge. Used to recompute the production-graph content digest * when validating an imported portable artifact against its bundled attestation * (change: add-shareable-graph-artifact) — read-only, mirrors getAllInternalNodes(). */ getAllEdges(): CallEdge[]; /** Remove all edges where this file is caller or callee. */ deleteEdgesForFile(file: string): void; /** Remove only outgoing edges from this file (incoming edges remain). */ deleteOutgoingEdgesForFile(file: string): void; /** Bulk-insert edges in a single transaction. */ insertEdges(edges: CallEdge[]): void; /** Bulk-insert inheritance edges in a single transaction. */ insertInheritanceEdges(edges: InheritanceEdge[]): void; getNode(id: string): FunctionNode | null; getNodesForFile(file: string): FunctionNode[]; /** * Resolve a node by its content-addressed `stableId` * (add-content-addressed-stable-symbol-ids). Returns the match only when it is * unambiguous — a single internal node. Ambiguous (a collision the ordinal pass * still left, or two files momentarily sharing one) or absent → null, so a * rename-resolution caller never guesses between candidates. */ getNodeByStableId(stableId: string): FunctionNode | null; /** * All internal (non-external) nodes. Used to seed cross-file call resolution * during an incremental subset rebuild, so calls into files outside the * re-parsed subset still resolve to their real node instead of `external::`. */ getAllInternalNodes(): FunctionNode[]; /** Case-insensitive substring search on node name. FTS5 trigram for ≥3 chars, LIKE fallback otherwise. */ searchNodes(pattern: string, limit?: number): FunctionNode[]; getHubs(limit?: number): FunctionNode[]; getEntryPoints(limit?: number): FunctionNode[]; countNodes(): number; /** Distinct source files contributing production nodes. Reconciliation input for the attestation. */ countFiles(): number; /** Production (non-tested_by) call-edge count. Reconciliation input for the index attestation. */ countEdges(): number; /** Class/module node count. Reconciliation input for the index attestation. */ countClasses(): number; /** * The SCHEMA_VERSION recorded in this store. After an open() that found a stale * version, this is the CURRENT SCHEMA_VERSION (the store was wiped + re-stamped), * so an attestation written at an older version reconciles as `mismatched`. */ getSchemaVersion(): number; /** * Fold a lagging write-ahead log into the main database so a recount sees the latest * committed rows. Used by the integrity check to rule out a WAL-lag false positive * before declaring an index `degraded` (change: add-index-integrity-attestation). * * PASSIVE, not TRUNCATE: this runs on the hot read path (readCachedContext), and the * goal is only "recount against the folded-in WAL", not "shrink the file". PASSIVE * checkpoints what it can without waiting on a writer, so it never blocks the read path * up to busy_timeout when the watcher or a concurrent `analyze` holds the write lock. */ checkpoint(): void; deleteNodesForFile(file: string): void; /** * Bulk-insert nodes. hubIds/entryIds are optional sets used to mark flags; * omit them during incremental watcher updates (flags preserved from last analyze). */ insertNodes(nodes: FunctionNode[], hubIds?: Set, entryIds?: Set): void; /** * Lazily load one function's control-flow + reaching-definitions overlay. * Returns null when the function has no overlay (unsupported language, a parse * that produced no CFG, or a pre-overlay store). DB-only — never resident. */ getCfg(functionId: string): FunctionCfg | null; /** True when any overlay rows exist (used to tell "no overlay" from "absent feature"). */ hasCfgOverlay(): boolean; /** Delete every overlay row for a file (per-file incremental recompute). */ deleteCfgForFile(file: string): void; /** Bulk-insert per-function overlays in a single transaction. */ insertCfgs(cfgs: Array<{ functionId: string; filePath: string; cfg: FunctionCfg; }>): void; getClass(id: string): ClassNode | null; /** * Every class/module node. Used to recompute the production-graph content digest * when validating an imported portable artifact against its bundled attestation * (change: add-shareable-graph-artifact) — read-only, mirrors getAllInternalNodes(). */ getAllClasses(): ClassNode[]; getClassesForFile(file: string): ClassNode[]; deleteClassesForFile(file: string): void; insertClasses(classes: ClassNode[]): void; /** Replace the projected decision graph wholesale (idempotent re-projection). */ insertDecisions(nodes: DecisionNode[], edges: DecisionAffectsEdge[]): void; /** Every projected decision node (deterministic order). */ getAllDecisions(): DecisionNode[]; countDecisions(): number; /** * Governing decisions for a set of files — the deterministic graph join that * replaces orient's runtime affectedFiles set-membership filter (spec-16). * * Path forms differ across callers (edge-store nodes are repo-relative; some * callers pass absolute paths), and decisions are few, so we match in JS with a * tolerant suffix comparator rather than relying on exact SQL equality. */ getDecisionsForFiles(files: string[]): DecisionNode[]; /** Replace the per-file provenance wholesale (idempotent re-extraction). */ insertProvenance(records: FileProvenance[]): void; countProvenance(): number; /** * Provenance for a set of files. Path forms differ across callers (edge-store * nodes are repo-relative; some callers pass absolute paths), so match with the * same tolerant comparator used for decisions (spec-18). */ getProvenanceForFiles(files: string[]): FileProvenance[]; /** Replace the per-file change-coupling snapshot wholesale (idempotent re-mine). */ insertChangeCoupling(result: ChangeCouplingResult): void; countChangeCoupling(): number; /** Change-coupling records for a set of files (tolerant path match, spec-22). */ getChangeCouplingForFiles(files: string[]): FileChangeCoupling[]; /** Top-churn (most volatile) files, descending. */ getTopVolatile(limit?: number): FileChangeCoupling[]; getFileHash(filePath: string): string | null; setFileHash(filePath: string, hash: string): void; /** * Mark files as explicitly stale — their topology was NOT recomputed by a * budget-exceeded incremental update. Idempotent (re-marking refreshes the * timestamp). Sound over-approximation: it is always safe to mark more. */ markFilesStale(files: readonly string[], at?: number): void; /** * Clear the stale mark for files that have just been recomputed (self-heal). * No-op for files that were never stale. */ clearFilesStale(files: readonly string[]): void; /** True when a file is in the explicitly-stale region. */ isFileStale(file: string): boolean; /** Every file currently in the explicitly-stale region (deterministic order). */ getStaleFiles(): string[]; /** Count of files in the explicitly-stale region. */ countStaleFiles(): number; /** Drop all graph data — used by full analyze rebuild. */ clearAll(): void; /** Run fn inside a single SQLite transaction. */ transaction(fn: () => void): void; close(): void; /** * Open the graph store on a READ path. NEVER mutates the store: a schema-version * mismatch or a corrupt DB yields a handle whose {@link notReady} is set (the on-disk * store is preserved — untouched on mismatch, quarantined to `*.corrupt-` on * corruption), which read consumers surface as a not-ready conclusion instead of * serving an empty graph or crashing (change: harden-index-store-lifecycle). */ static open(dbPath: string): EdgeStore; /** * Open the graph store on an ANALYZE/WRITE path. A SCHEMA_VERSION bump drops and * rebuilds the tables (rebuild-on-bump) and a corrupt DB is quarantined then reopened * fresh — both legitimate here because the caller repopulates the store in the same * operation. Only this path may destroy data. */ static openForAnalyze(dbPath: string): EdgeStore; private static openInternal; static exists(outputDir: string): boolean; static dbPath(outputDir: string): string; } //# sourceMappingURL=edge-store.d.ts.map