import { AgencyConfig } from "../config/config.js"; export type DocLedgerEntry = { sourceHash: string; /** Absolute paths, exactly the fingerprint's sorted order — hashed in * this stored order by writer and checker alike. */ deps: string[]; depsHash: string; cacheable: boolean; hasPkgImports: boolean; stdlibHash: string; compilerStamp: string; /** Output-dir-relative; informational only — deletion recomputes the * deterministic mapping and never trusts this field. */ outputPath: string; /** Hash of the Markdown bytes as written; a hand-edited or truncated * page hashes differently and gets repaired. */ outputHash: string; /** The file's pass-1 registry contributions: the names that pass * `isDocumented`, which is the same predicate the renderer anchors on. */ registrySymbols: string[]; /** Every registry lookup this page's rendering made: name → target md * path, or null for "rendered unlinked". Re-checked against the * rebuilt registry every run. */ linkTargets: Record; /** Lines with a `@hidden` that attached to nothing, so a page served * from cache still warns. Optional: an older ledger keeps authority * and warns nothing until the page next re-renders. */ strayHiddenLines?: number[]; }; export type DocLedger = { version: 1; /** Realpath of the directory this ledger governs; a mismatch with the * directory it was loaded from (copied tree) forfeits authority. */ outputDir: string; /** Current invocation identity — data, not a namespace: changing it is * a transition that reconciles, never a fresh start that forgets. */ identity: { inputDir: string; ignoreDirs: string[]; }; renderKey: string; /** Keyed by validated input-dir-relative source path. */ entries: Record; }; export declare const DOC_LEDGER_NAME = ".agency-doc.json"; export declare const DOC_LOCK_NAME = ".agency-doc.lock"; /** The doc generator, relative to dist/lib. The doc cache's compiler stamp * covers the modules it imports. */ export declare const DOC_GENERATOR_ENTRY = "cli/doc.js"; /** A safe ledger key: relative, normalized, no `..`, and ending exactly * in `.agency`. The suffix rule is deletion-critical: `outputPathFor` on * a non-`.agency` key like "README" would be a no-op mapping, and * reconciliation could then delete a handmade `out/README`. */ export declare function isSafeSourceRel(rel: string): boolean; /** The deterministic source→page mapping. The ONLY place keys become * output paths; throws on anything isSafeSourceRel rejects. */ export declare function outputPathFor(sourceRelPath: string): string; export declare class OwnedPathError extends Error { } export type ResolvedOwnedOutputPath = { abs: string; leafIsSymlink: boolean; }; /** * Resolve an owned output path, refusing symlinked ancestors. Walks each * EXISTING component under outDirReal with lstat; with `createParents`, * creates missing ones and re-verifies their realpath. A symlink at the * LEAF is reported, not followed: rendering and hashing must reject it, * while reconciliation may unlink the link itself. */ export declare function resolveOwnedOutputPath(outDirReal: string, outRel: string, opts?: { createParents?: boolean; }): ResolvedOwnedOutputPath; /** Structured, not concatenated: a string boundary could alias two * distinct (config, baseUrl) states. */ export declare function docRenderKey(config: AgencyConfig, effectiveBaseUrl: string): string; /** Recursive: one malformed nested value anywhere denies the WHOLE ledger * authority — deletion evidence must be fully trustworthy or not at all. */ export declare function ledgerEntryHasValidShape(entry: unknown): entry is DocLedgerEntry; /** * Load and FULLY validate the ledger before granting authority. Callers * never dereference an unvalidated field: `authority: true` implies every * top-level field, every entry, and every key checked out. */ export declare function loadDocLedger(outDirReal: string): { ledger: DocLedger | null; authority: boolean; }; /** Atomic (tmp + rename), and refuses unsafe keys — a save must never * produce a ledger that load would strip of authority. */ export declare function saveDocLedger(outDirReal: string, ledger: DocLedger): void; export type DocLock = { lockPath: string; token: string; }; /** * O_EXCL acquire; stale locks are removed MANUALLY (auto-break races two * processes past the same dead lock). The token — not the pid — is the * ownership identity: the same process can acquire again later, and a * stale handle carrying only a pid could remove its successor's lock. */ export declare function acquireDocLock(outDirReal: string): DocLock; /** Removes only a lock still carrying our own token. Token verification * narrows the successor-deletion window but read-then-unlink is not an * atomic compare-and-delete — users must not remove a LIVE lock. */ export declare function releaseDocLock(lock: DocLock): void; export type DocFreshnessContext = { inputDir: string; outputDir: string; stdlibDir: string; stdlibHash: string; stdlibNamesHash: string; compilerStamp: string; }; export declare function buildDocFreshnessContext(inputDirReal: string, outDirReal: string): DocFreshnessContext; /** * Spec §3 checks 2–6 and 9. Checks 1/7 (identity, render key) are * ledger-level; check 8 (link re-check) needs the rebuilt registry and * lives in the doc command's flow. */ export declare function isDocEntryFresh(sourceRel: string, entry: DocLedgerEntry, ctx: DocFreshnessContext): boolean; /** The dependency state of a page's inputs at one instant: the resolved * transitive closure plus each member's content hash. Captured BEFORE * rendering (rendering is when SymbolTable.build consumes dependency * semantics) and compared against the post-render state by * buildDocLedgerEntry — a dep saved mid-render would otherwise pair * stale Markdown with fresh hashes, and the next run would accept it. */ export type DepSnapshot = { deps: string[]; depHashes: (string | null)[]; }; export declare function captureDepSnapshot(absSource: string, config: AgencyConfig, stdlibDir: string): DepSnapshot; /** * The one writer of entries, paired with isDocEntryFresh: dependencies * are stored in the fingerprint's exact sorted order and hashed in that * same order; a missing dependency hash forces `cacheable: false`. */ export declare function buildDocLedgerEntry(args: { sourceRel: string; ctx: DocFreshnessContext; config: AgencyConfig; registrySymbols: string[]; linkTargets: Record; strayHiddenLines: number[]; writtenBytes: string; /** Source hash captured when the page's program was PARSED. Rendering * happens between that parse and this builder; if an editor saved the * file in that window, the Markdown describes old bytes while we would * record new hashes — the drift forces cacheable:false so the next run * repairs the page instead of accepting it as fresh. */ sourceHashAtParse?: string; /** Dependency closure + hashes captured immediately BEFORE rendering. * Compared against the post-render state below: membership or byte * drift means the symbol table may have consumed different dependency * semantics than these hashes describe — never cacheable. */ preRender?: DepSnapshot; }): DocLedgerEntry;