/**
 * knob-hygiene #98 — one validator for every RETENTION policy a bundled store accepts.
 *
 * These knobs share a property nothing else in the engine does: they are the only numbers whose job is
 * to DELETE, so an unevaluable one is not merely ignored, it decides. The shapes actually reachable:
 *
 *   - `keep` is consumed as `rows.slice(Math.max(0, keep))` / `i >= keep`. `Math.max(0, NaN)` is `NaN`
 *     and `slice(NaN)` means `slice(0)` — so a policy whose `keep` arrived as NaN (the ordinary
 *     `Number(cfg.keep)` on a missing or misspelled field) deleted EVERY terminal row instead of
 *     keeping N of them. A negative `keep` reaches the same place by a different route.
 *   - `maxAgeMs` on the roster feeds `now - createdAt <= maxAgeMs`, which is false for NaN — so every
 *     row reads as expired, the next write persists the survivors, and the whole roster is gone. A
 *     roster row is a durable ADDRESS; losing one turns a live agent unreachable.
 *   - the remaining `*MaxAgeMs` arms fail the other way (NaN comparisons are false ⇒ nothing is
 *     reaped), which is safe but equally silent: retention that quietly stopped running is how a disk
 *     fills up. They are validated for the same reason — an operator deserves the same answer either way.
 *
 * Fail-loud rather than fall back to a default: a retention policy is the deployment saying what it
 * wants kept, and quietly substituting a different policy is exactly the class of silence this sweep
 * exists to remove. Every store passes its options through here BEFORE touching a row, so no
 * implementation can drift from another (the RB-74 / RB-87 / RB-111 history in the store family is
 * three consecutive "fixed one of the two implementations" misses).
 */
export interface RetentionPolicyKnobs {
    /** Age bound in ms. */
    maxAgeMs?: number;
    /** Row-count bound, whole rows. */
    keep?: number;
    /** Age bound in ms for the stale-running flip arm. */
    staleRunningMaxAgeMs?: number;
    /** Row-count bound, whole rows (roster's write-time cap). */
    maxEntries?: number;
}
/**
 * The family's typed refusal, exported so a seam whose bound has its own ADMISSIBLE RANGE refuses in
 * the same grammar under the same code instead of minting a second one. The one in-tree case:
 * file-history's boundary cap requires `>= 1` where the row stores accept `0` ("keep no rows" is a
 * coherent policy for a ledger; for a history whose every commit mints a boundary it is not — the
 * cap would reap the boundary its own commit just published), so that door cannot reuse
 * {@link assertRetentionPolicy}'s "0 or more" wording without telling an operator something false.
 */
export declare function refuseRetentionPolicy(label: string, knob: string, value: unknown, requirement: string): never;
/**
 * Refuse a malformed retention policy at the door. `undefined` on any field keeps its documented
 * meaning — that arm is simply not applied — so a deployment that sets nothing is untouched.
 *
 * @param label the caller's own name, so the error names the seam the operator configured.
 */
export declare function assertRetentionPolicy(label: string, opts: RetentionPolicyKnobs | undefined): void;
