/** * URL-level TTL cache for the governed web channel (2026-09-19, item 6). * * Why: a medium research round asks the same URL more than once — the lead * and the researcher issue overlapping queries, an `auto` search fans out * over engines whose legs the next query partly repeats, and every one of * those is a fresh 20 s-timeout HTTPS fetch. The fetch is the cost, the * render is free, so the cache sits at the ONE choke point both tools share * (`fetchWebText`) and keys on the URL. * * The governance rule is the part that must not drift: a cached body is * served ONLY after the current hop passed the STATIC allowlist, and a body * is stored ONLY from such a fetch. So a cache hit can never * - resurrect a host the (possibly edited) allowlist no longer admits, * - skip a dialog the current config still requires (a dialog approval is * per-request consent, not a licence to cache), * - or replay a red-line refusal, which is never cached in the first place. * * Freshness is the honest cost, capped by TM_WEB_CACHE_TTL_SEC (default 300); * tm_webfetch says "缓存命中" on the reply and tm_stats counts hits, so a * stale read is visible rather than silently believed. * * Failure posture: every fs operation is best-effort. A cache that cannot be * read is a miss; one that cannot be written is a lost optimization. Neither * is allowed to touch a fetch result. */ /** What a stored entry holds. NOTably NOT the URL: a query string can carry * the very session token the fetch allowlist exists to protect, and the * sha256 key is enough to look it up. */ export interface CachedResponse { status: number; contentType: string; body: string; /** epoch ms of the store, for the freshness note */ at: number; } export interface WebCacheCounts { hits: number; misses: number; sets: number; stale: number; } export interface WebCache { readonly dir: string; readonly ttlSec: number; enabled(): boolean; get(url: string): CachedResponse | null; put(url: string, res: { status: number; contentType: string; body: string; }): void; counts(): WebCacheCounts; } /** Deterministic, collision-proof-for-our-purposes file name for a URL. */ export declare function cacheKeyFor(url: string): string; export declare function cacheFileFor(dir: string, url: string): string; /** Is a stored response still servable? Exported so the rule is testable * without a clock seam through the whole cache. */ export declare function cacheEntryFresh(entry: CachedResponse | null, now: number, ttlSec: number): boolean; export declare function createWebCache(opts: { dir: string; ttlSec: number; now?: () => number; /** Entries kept; the oldest by mtime are pruned on write. */ maxEntries?: number; }): WebCache; //# sourceMappingURL=cache.d.ts.map