Source code for cadrumo.adapters.outbound.llm.retention
"""Canonical two-stage retention selection for the LLM secure-object stores.The response cache (:class:`~adapters.persistence.llm.cache.LLMCache`), the usageledger (:class:`~adapters.persistence.llm.usage.UsageRecorder`), and the run-telemetryrecorder (:class:`~adapters.persistence.llm.run_telemetry.LLMRunTelemetryRecorder`) each boundtheir store under one operational obligation: an age cutoff, then anoldest-first record-count cap. The namespaces, the settings that supply thebounds, and the record timestamp field differ per store by design -- the cacheand the usage ledger stamp ``created_at`` at write time while a run recordcarries its own ``started_at``. The *ordering and boundary policy* is not aper-store choice, so it lives here once and each store supplies its timestampas a projection.Deletion stays with the caller: each store owns its own secure-objectnamespace and its own missing-key contract, so this module selects keys andnever removes anything."""from__future__importannotationsfromcollections.abcimportCallable,Sequencefromdatetimeimportdatetime__all__=["select_retention_removal_keys"]
[docs]defselect_retention_removal_keys[RecordT](rows:Sequence[tuple[RecordT,str]],*,cutoff:datetime,max_records:int,timestamp:Callable[[RecordT],datetime],)->list[str]:"""Select object keys to delete under the two-stage retention/count bound. Args: rows: ``(record, object_key)`` pairs sorted oldest-first. The oldest-first order is what makes the count cap evict the oldest excess rather than an arbitrary slice; an unsorted input silently evicts the wrong records. cutoff: Age boundary. A record strictly older than ``cutoff`` is selected for removal; a record exactly at ``cutoff`` is retained, so the boundary is exclusive. max_records: Maximum number of records to retain *after* the age cutoff has been applied. When more than ``max_records`` survive the cutoff, the oldest excess survivors are selected too. timestamp: Projection returning the record's retention timestamp -- ``created_at`` for cache and usage records, ``started_at`` for run telemetry. Returns: Object keys to delete, age-expired first then count-capped. The two stages cannot overlap, so no key is returned twice. """to_remove=[object_keyforrecord,object_keyinrowsiftimestamp(record)<cutoff]remaining=[rowforrowinrowsiftimestamp(row[0])>=cutoff]iflen(remaining)>max_records:excess_count=len(remaining)-max_recordsto_remove.extend(object_keyfor_,object_keyinremaining[:excess_count])returnto_remove