agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
57 lines (56 loc) • 3.25 kB
TypeScript
/**
* hosting/sessionRetention — the ONE feature detection for the port's
* retention member, and the refusal a store that has none produces.
*
* ── Why this is a function and not `sessions.retention?.()` ──────────────────
* Because `?.()` answers `undefined`, and `undefined` is exactly the wrong
* answer here. Every other optional member on this port is asked for behind a
* door that refuses by name when it is absent — `session-list` does not report
* "you have no conversations" when a store keeps no index — and retention needs
* that more than either of them, not less: a cleanup job whose call returned
* without complaining looks precisely like a cleanup job that is working. The
* difference surfaces the day somebody asks how long conversations are kept,
* and by then the answer is "all of them, since the beginning".
*
* So the detection lives here, once, and produces
* {@link SessionRetentionUnavailableError} rather than a value that reads as
* "fine". A caller that wants the silent shape can still write
* `sessions.retention?.()` — the port is honest about what it has — but nothing
* in this package does, and the door somebody reaches for first refuses.
*
* ── What it deliberately does not do ────────────────────────────────────────
* It does not sweep, schedule, or decide a cutoff. The cutoff is a policy
* decision — a jurisdiction, a contract, a retention schedule somebody signed —
* and a library that shipped a default would be answering a legal question with
* a constant. It hands back what the store can do; WHEN is yours.
*/
import type { SessionLifecycle, SessionRetention } from './types.js';
/**
* How a store's conversations stop existing — or a refusal that names the
* store's limitation.
*
* The same detection every door in this package uses (`typeof … ===
* 'function'`), in the one place a consumer reaches for it, so a store that
* implements the member and a store that does not are told apart once rather
* than at every call site.
*
* @param sessions The store to ask.
* @param purpose What you are doing, in your own words. It becomes the first
* clause of the refusal, so an operator reading a log knows which job stopped
* — the same reason the session-history refusals carry their op name.
*
* @throws SessionRetentionUnavailableError when the store implements no
* `retention()`. Never returns `undefined`: a caller cannot mistake "this
* store cannot expire anything" for "there was nothing to expire".
*
* @example A nightly job, against whatever store this deployment was built with
* const policy = sessionRetention(sessions, 'the nightly retention job');
* if (policy.deletedBy === 'this-store') {
* let more = true;
* while (more) ({ more } = await policy.forgetOlderThan(Date.now() - THIRTY_DAYS));
* } else {
* // Nothing to run: the backend expires them. Log what turns it on.
* console.log(policy.active ? 'expiry is armed' : policy.enableWith);
* }
*/
export declare function sessionRetention(sessions: SessionLifecycle, purpose?: string): SessionRetention;