UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

50 lines (49 loc) • 3.23 kB
/** * 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;