import type { Collection } from '../../kernel/collection.js'; import type { TxContext } from '../../with-commit/tx/transaction.js'; import type { PutDerivedOutputCtx } from '../../kernel/via/dispatch.js'; import type { MaterializedViewRegistry, RegisteredMV } from './registry.js'; import type { MVQueryContext } from './types.js'; import type { NoydbStore } from '../../kernel/types.js'; /** * Accessor shape passed in from the owning Vault. Provides the * registry (used as a stable WeakMap key + to look up MVs by output * collection) and the runtime context the lazy refresh needs. * Mirrors v1's `DerivationStaleAccessor`. */ export interface MVStaleAccessor { registry(): MaterializedViewRegistry; getCollection(name: string): Collection; getActiveTxContext(): TxContext | null; getQueryContext(): MVQueryContext; } /** * Mark an MV as stale. Called from `Collection.dispatchMaterializedViews` * when a source-write fires for a `refresh: 'lazy'` MV. * * @internal */ export declare function markMVStale(registry: MaterializedViewRegistry, mvName: string): void; /** * Test-only: check whether a given MV name is currently flagged stale * against a registry. Exported so the regression suite can pin the * stale-bit lifecycle without touching the internal `WeakMap`. * * @internal */ export declare function isMVStale(registry: MaterializedViewRegistry, mvName: string): boolean; /** * Reserved collection holding CONTENT-FREE lazy-stale markers (record id = * MV name, plaintext `_iv: ''` envelope, no payload) — mirrors the * `_meta`/schema-fence marker envelope shape. Written only from the * delete/forget/elevate dispatcher (#736); ordinary source writes stay * in-memory-only (the cheap path). Read via the adapter directly, never * through `vault.collection()` — same reserved-collection convention as * `_subject_index`/`_meta`. */ export declare const MV_STALE_COLLECTION = "_mv_stale"; /** * Invalidate an MV from the delete/forget/elevate dispatcher (#736) — the ordinary * source-write path (`dispatchMaterializedViews`) never calls this. Deletes EVERY * persisted row in `reg.outputCollection` via `_internalDelete` (the at-rest law: a * stale mark alone leaves the elevated/forgotten source's plaintext sitting in the * output row) using the adapter directly so the read-path's `resolveStaleMVOnRead` * isn't triggered mid-invalidation. `mode: 'lazy'` also persists the stale mark so a * cold session recomputes on next read; `'manual'` gets the purge only — the MV * serves empty until an explicit `vault.refreshView()` (erasure wins). Deletion is * stamp-scoped (`_materializedFrom.mvName === reg.spec.name` — see the loop below), * so another registered MV sharing this output collection keeps its own rows intact: * no cross-MV re-marking is needed here, only `reg` itself is marked stale. * * Returns the count of rows actually `_internalDelete`d (#761 item 1) — folded by the * caller into `dispatchMaterializedViewsOnDelete`'s `deleted` so `ForgetResult.derivedRecordsErased` * counts lazy/manual purges too, not just the eager executor's tombstone leg — plus * `residueUndecodable`, the bare ids whose ownership stamp could NOT be decoded (#776): a * candidate row that fails to decode under the collection's default DEK (e.g. elevated above * tier 0 on a tiered output collection) has UNKNOWN ownership — it might be this MV's own * output, or (same-collection shape) a plain user record — so it is never erased, but it must * be SURFACED rather than silently skipped (the #724 posture), since it may still hold the * forgotten/pre-elevation contribution, decryptable by tier-holders — and `residueDeclined` * (#785), the bare ids that DID decode and stamp-match but whose `_internalDelete` declined * (ownership CONFIRMED, erasure declined). * * @internal */ export declare function invalidateMVAtRest(accessor: MVStaleAccessor, reg: RegisteredMV, mode: 'lazy' | 'manual'): Promise<{ deleted: number; residueUndecodable: string[]; residueDeclined: string[]; }>; /** * Called from `Collection.get` (and any reader that materializes the * MV's output collection). If any MV producing `outputCollection` is * flagged stale, runs the executor against the live source state * before returning. No-op when there is no pending work — keeps the * read fast path negligible. * * `dispatchCtx` (#641): threaded from the calling `Collection`'s * `#dispatchCtx({ collection: outputCollection, id: 'resolve-on-read' })` — a read has no * real "reacting write" record, so `'resolve-on-read'` is the sentinel id, mirroring * `Vault.refreshView()`'s `'refreshView'` sentinel for the same reason. Passed straight * through to the executor so a stale row whose output lands in a closed period follows the * frozen-output rule (skip + `derivation:skipped-frozen`, #637) instead of throwing * `PeriodClosedError` out of a read. * * Dynamic-imports the executor only when a stale flag actually fires * (the floor-bundle isolation pattern v1 derivations established in * floor-bundle isolation pattern). */ export declare function resolveStaleMVOnRead(accessor: MVStaleAccessor, outputCollection: string, dispatchCtx?: PutDerivedOutputCtx): Promise; /** * Drop every stale flag for a registry. Used after a manual * `vault.refreshView(name)` runs the executor explicitly — the * post-refresh state matches the registered strategies, so * lingering stale bits would force a redundant refresh on the next * read. * * @internal */ export declare function clearMVStale(registry: MaterializedViewRegistry, mvName: string): void; /** * `clearMVStale` plus the persisted marker (#736) — `Vault.refreshView()`'s * completion path. A lingering persisted marker after a manual refresh would * make the NEXT cold session recompute redundantly. * * @internal */ export declare function clearMVStaleFully(adapter: NoydbStore, vault: string, registry: MaterializedViewRegistry, mvName: string): Promise;