Retention is opt-in per canonical project, environment, and ROS domain scope. make_retention_policy(...) produces flightops.retention-policy/1; RetentionManager.set_policy(policy) persists the exact digest and budgets in the same SQLite catalog as recording sessions. Absent or restored-inert policy never authorizes deletion. The independent collector applies a current policy between closed writer epochs; it leaves recording blocked when safe expiry cannot complete. It never deletes an active or interrupted segment.
The manager exposes prepare_pin(scope_key, start_ns, end_ns, topics, prerequisite_segment_ids, prerequisite_record_ids, required_prerequisites, authorize), commit_pin(pin_id, authorize), and the combined pin_window(...). The time interval is half-open MCAP log time. Topics are exact names. Required TF, static TF, and clock references must be finalized segments with the matching indexed topic; configuration needs a validated configuration snapshot, and history needs a resolved evidence receipt. Calibration has no typed native reference yet and is refused rather than asserted. The catalog verifies finalized segment digests, current source-use policy and caller authorization, protected-byte budget, headroom, and free-space reserve again at commit. A failed prerequisite or permission returns a typed refusal. A pin protects source bytes; it never grants reading, sharing, inference, or training. Compatible overlapping incident windows coalesce only when topic and prerequisite metadata match; all segment references remain protected. release_pin(pin_id) ends protection.
RecordingCatalog.acquire_read_lease(segment_ids, purpose) reserves 1โ100 finalized, available segments against expiry transactionally. Purpose is one of selection, analysis, export, backup, pin-index, or recording-reduction. It returns a dict with id, state, and the segment IDs and frozen SHA-256 digests. The same owner calls release_read_lease(lease['id']) in finally; read_lease(...) is a context manager. Readers must still use resolved_path, hash the bytes, and check historical source-use plus current caller authorization. Selection holds the lease through its bounded verified snapshot and query. Durable outputs can use register_derivative(derived_id, segment_ids) while the lease remains held. With an active scoped retention policy, the call reserves source bytes under the pinned/headroom budgets and returns state=active; these dependents block expiry until release_derivative(derived_id) or invalidation after source loss. Without an active retention policy it returns state=lineage-only, which records provenance but does not protect bytes. A released or invalidated derivative ID cannot be reused. release_derivative(derived_id) removes active protection explicitly.
flightops record status prints the scope_key of the current project; the flightops retention commands take it as --scope-key.
coverage_horizons(scope_key) reports only available indexed per-topic first/last log timestamps, counts, and gap/overlap indicators. A segment without a topic index is omitted. The horizon query uses last catalog availability and does not rehash every file. These are measured message bounds, not continuous DDS delivery or an established sensor/application clock relationship. Pin prerequisites therefore remain explicit; the catalog does not invent missing TF, static transforms, configuration, or clock evidence.
plan_expiry(scope_key) lists exact segment IDs, hashes, paths, reasons, exclusions, and policy digest, with bounded inventory. expire(plan) recalculates eligibility at the actual deletion time, reserves candidates atomically against new leases/pins/dependents, moves each candidate through a private quarantine using a pinned parent-directory descriptor, hashes the quarantined bytes, then unlinks only the verified entry through that descriptor. Historical finalized state and SHA remain unchanged; availability becomes expired and a deletion tombstone persists. A missing, changed, restored, active, interrupted, protected, or ambiguous source is refused. A crash or identity mismatch leaves the quarantine path in the tombstone for inspection, and reconcile_retention() marks interrupted provenance; missing/changed bytes and dependent invalidation stay visible. Neither expiry nor reconciliation claims to recover deleted bytes.
On finalization the catalog publishes a source-use record for recording-segment:<id> from the standing capture policy, with capture and retain only. Its stable timestamp prevents a retry from superseding a later explicit source-use decision. Historical consent is not current authorization; every pin, query, export, and reduction still needs the current callback decision and upstream source-use intersection. Backups preserve history but restore no live lease, deletion reservation, active retention policy, or capture grant.
The Standard flightops retention command accepts configure, status, horizons, list-pins, pin, release-pin, release-derivative, plan-expiry, expire, and reconcile. Use --database for an explicit catalog. pin needs exact --topic values, a half-open --start-ns/--end-ns interval, and explicit repeated --allow-source values for the direct and every upstream artifact. plan-expiry --output FILE creates a new bounded JSON file; expire --plan FILE --expect-digest SHA256 checks that reviewed plan before the catalog recomputes eligibility. The command never runs a shell, a ROS process, or a model.