/** * Dynamic-boundary surfacing (change: disclose-dynamic-boundary-regions). * * The analyzer records, per file, every dispatch construct the resolver cannot follow. This module * is the read side shared by every conclusion whose soundness rests on reachability completeness: * given the files a conclusion actually traversed, it produces ONE structured * `known-unknowable` crossing naming the sites in scope — so the answer says *which* construct * bounds it, instead of the whole-README caveat that "dynamic dispatch is not captured". * * Three scoping rules keep the disclosure useful rather than fatiguing: * * 1. **Scoped to the traversal, never the repository.** A conclusion over a clean subgraph in a * repository full of reflection discloses nothing. A repository-wide list would be the blanket * caveat it replaces. * 2. **Bounded, with a receipt.** Deduplicated by kind+file, capped, and the omitted count is * stated — a bounded list must never read as the whole set. * 3. **One-directional.** A boundary can qualify a NEGATIVE conclusion (dead, untested, safe). It * can never promote a symbol to live, tested, or unsafe: an unknown is not evidence. * * A repository with no site has no artifact, so every helper here fails open to "no boundary" and * a clean repository pays one failed `readFile` per conclusion — and, once the report is absent, * nothing else. */ import { type DynamicBoundaryReport, type DynamicBoundaryKind, type FileDynamicBoundary, type DynamicBoundarySite } from '../../analyzer/dynamic-boundary.js'; import type { DisclosedDynamicSite, KnownUnknowableCrossing } from './confidence-boundary.js'; /** How many sites a crossing lists before it collapses to a count. */ export declare const DYNAMIC_BOUNDARY_DISCLOSURE_CAP = 8; /** * The kinds that can hide a CALLER, and so can invert a "nothing reaches this" conclusion. * * Deliberately narrower than the whole vocabulary: `code-eval`, `dynamic-import` and * `metaprogrammed-definition` describe code being *created* or *loaded*, not an existing symbol * being *reached*, so qualifying a dead-code candidate on them would cry wolf. They are still * disclosed on traversals — the reader learns the region is reflective — they just do not move a * verdict on their own. */ export declare const CALLER_HIDING_KINDS: readonly DynamicBoundaryKind[]; /** Drop the memos — test-only hook, so a rewritten artifact is re-read immediately. */ export declare function __resetDynamicBoundaryMemo(): void; /** * Load the persisted report, or `null` when absent/unreadable (a repository with no site). * * Every record is validated — shape, closed vocabularies, and the one numeric field that is summed * — before it is served. This artifact is read on the serving path by seven * conclusions, and a *disclosure* sidecar must never be able to take down the conclusions it exists * to annotate: a malformed, truncated, hand-edited or hostile file degrades to "no boundary", never * to a thrown handler. The read is bounded and refuses to follow a symlink or block on a FIFO, for * the same reason every other `.openlore` reader on this path does. */ export declare function loadDynamicBoundaryReport(absDir: string, now?: number, opts?: { directResolvedOnly?: boolean; }): Promise; /** * Forward import adjacency (repo-relative path → the paths it imports), read from the persisted * dependency graph. This is what bounds a qualification to the files that can NAME a symbol; with * no dependency graph the map is empty and a site qualifies only within its own file — narrower, * which is the safe direction. * * Memoized per directory like the report above, because `find_dead_code` already reads this same * artifact for its own liveness signals and passes its adjacency in directly; every other caller * would otherwise re-read and re-parse a graph that can be the largest artifact in `.openlore`. */ export declare function loadImportAdjacency(absDir: string, now?: number): Promise>; /** The per-file records for the files a conclusion touched, in deterministic path order. */ export declare function recordsForFiles(report: DynamicBoundaryReport | null, touchedFiles: Iterable): FileDynamicBoundary[]; /** * The structured crossing for the sites inside the traversal, or `undefined` when the traversal * crossed none. `count` is the EXACT number of sites in scope; `sites` is the bounded, deduplicated * list; `omittedSites` is the receipt for what the bound dropped. */ export declare function dynamicBoundaryCrossing(report: DynamicBoundaryReport | null, touchedFiles: Iterable, cap?: number): KnownUnknowableCrossing | undefined; /** * `src/a.ts:12 (reflective invocation)` — the shared phrasing for one disclosed site. * * The file path is repository-controlled (a file name may legally contain a newline, and a hostile * `.openlore/` may carry anything), and this string is embedded in a CLI line whose writer keeps * newlines because it treats its input as an OpenLore-authored message. A newline in a VALUE inside * that message forges an extra terminal line — a forged "all clear" under a warning block — so * every untrusted value is neutralized here, at the one place they are rendered. */ export declare function describeSite(s: DisclosedDynamicSite): string; /** One site, with the file it lives in — what a qualification names. */ export interface QualifyingHit { file: string; site: DynamicBoundarySite; } /** * Build the qualifier a negative conclusion consults: given a candidate's file and language, does a * caller-hiding site sit somewhere that can actually name it? * * Scope is COMPUTABLE, not repository-wide. A site qualifies a candidate only when it is in the * candidate's own file, or in a file whose transitive import closure contains the candidate's * module — the set of files that can name the symbol. Restricted further to the candidate's own * language (a Python `getattr` cannot reach a Go function) and to the kinds that can hide a CALLER. * Sites outside that closure are left to the existing whole-repository caveat rather than silently * widening this one into the blanket disclosure it replaces. * * The index is INVERTED once, on the first query, rather than walked per candidate: a repository * with thousands of site-bearing files would otherwise pay one BFS per site file for every dead * candidate, and retain a closure set for each. Built once, every lookup is a map read. * * Two ordering rules make the named site the RIGHT one: a site in the candidate's own file always * wins over one merely able to import it, and within a file the lowest-line qualifying site wins — * the same rule the traversal disclosure uses, so the two never name different lines for one file. */ export declare function buildQualifier(report: DynamicBoundaryReport | null, imports: ReadonlyMap): (file: string, language: string) => QualifyingHit | undefined; /** * The reason string a qualified negative verdict carries. Names the specific construct — which is * the whole point: it REPLACES the generic "dynamic language" caveat rather than adding to it, so * the reader learns which line bounds the answer instead of only that the language is dynamic. */ export declare function qualificationReason(hit: QualifyingHit): string; //# sourceMappingURL=dynamic-boundary-disclosure.d.ts.map