import type { Logger, OperationalMetrics } from '@cat-factory/kernel'; /** * One shared, cross-instance generation directory. The contract every implementation must * honour: counters are MONOTONIC per (cache, group) and a group's counters are readable in * ONE round trip: the batched read is what lets every coherent cache share a single probe * per group per window. On the Worker this is a Durable Object namespace sharded by group; * tests inject an in-memory fake. */ export interface CacheGenerationStore { /** * Every cache's generation counter for one group. A cache absent from the result has * generation 0 (never bumped). */ getGenerations(group: string): Promise>; /** Increment one cache's counter for a group; resolves to the NEW generation. */ bump(cacheName: string, group: string): Promise; } /** * The reserved group that carries cache-WIDE invalidation (`invalidateAll`) as an epoch * counter, so the store interface stays two methods. It is probed beside the real group on * its own timestamp, and a moved epoch maps to a full local clear. */ export declare const CACHE_EPOCH_GROUP = "*"; /** What one coherent cache hands the tracker: its identity, cadence, and the 16.1 primitives. */ export interface CoherentCacheRegistration { cacheName: string; /** Probe cadence; a group snapshot older than this re-reads the directory before serving. */ windowMsecs: number; /** * Whether this cache ever invalidates CACHE-WIDE, and so needs the reserved epoch group * probed beside its own. * * Declared per cache because the epoch shard is ONE globally-placed Durable Object: probing * it is a cross-colo round trip awaited on the read path of every isolate, and a cache with * no `invalidateAll` call site can never move that counter, so it would be a round trip that * structurally cannot return news. Declared rather than inferred from call sites, and the * handle REFUSES an undeclared `invalidateAll` rather than dropping entries locally while * peers keep serving them. */ probesEpoch: boolean; /** layered-loader's local, fencing, non-publishing group invalidation. */ applyRemoteInvalidationForGroup(group: string): void; /** Its cache-wide form, for a moved epoch. */ applyRemoteInvalidation(): void; } export interface GroupGenerationTrackerOptions { logger?: Logger; metrics?: OperationalMetrics; now?: () => number; /** * Supplied ONLY by an isolate runtime; see `CreateAppCachesOptions.currentInvocation`. It * decides which in-flight probes may be joined. Absent ⇒ every probe coalesces, which is * correct wherever one I/O context serves everything. */ currentInvocation?: () => object | undefined; /** * LRU bound on per-group bookkeeping. Evicting a group loses its baselines, which the next * probe treats as "possibly missed a bump" and re-invalidates (an over-invalidation, never * a stale serve), so the bound trades a little re-loading for bounded memory. */ maxTrackedGroups?: number; } /** * The per-instance bookkeeping half of the scheme: which generation each (cache, group) was * last seen at, when each group was last probed, and the application of a moved counter onto * the registered caches. One tracker serves the whole cache bag, so all coherent caches share * each group's probe. * * Error posture, deliberately asymmetric: * - a PROBE failure fails CLOSED: the affected caches are locally invalidated and the read * loads fresh, so a directory outage degrades to pass-through performance, never to serving * stale (`container.ts`'s "a stale-serving cache would be a correctness bug" stance); * - a BUMP failure fails OPEN: the write and its local invalidation already happened, peers * heal at the cache TTL, and the counter + warn line are the visible trace. */ export declare class GroupGenerationTracker { private readonly store; private readonly registrations; private readonly groups; /** Held outside the LRU map: the epoch baseline must never be evicted. */ private readonly epoch; private readonly log; private readonly metrics; private readonly now; private readonly maxTrackedGroups; private readonly currentInvocation; constructor(store: CacheGenerationStore, options?: GroupGenerationTrackerOptions); register(registration: CoherentCacheRegistration): void; /** * Called by a coherent handle before its read. Resolves without I/O while the group's (and * the epoch's) snapshot is inside the calling cache's window; otherwise one coalesced store * round trip refreshes every registered cache's view of the group. Never rejects. */ ensureFresh(cacheName: string, group: string): Promise; /** * Called by a coherent handle AFTER its local invalidation, awaited by the write path so * "invalidate right after the write commits" spans the directory too. The returned * generation is max-merged into the baseline, so the writing instance's next probe does not * read its own bump as a peer's change and re-invalidate what it just reloaded. */ noteLocalInvalidation(cacheName: string, group: string): Promise; private isStale; /** LRU-touch the group's state, evicting the oldest when over the bound. */ private touchGroup; private probe; private runProbe; /** * The caches one probe speaks for. A GROUP probe speaks for every registered cache (they * share the group's shard, which is what makes the batched read worth doing); an EPOCH probe * speaks only for those that declared a cache-wide invalidation path, so a cache that opted * out is neither invalidated by it nor has its baseline moved by it. */ private affectedBy; private applyInvalidation; } //# sourceMappingURL=generationCoherency.d.ts.map