Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/holger

.nornir/retention-guide.md

Retention / GC planner — guide

How to preview what a retention policy would keep vs reclaim in a repository. The planner computes a decision — it deletes nothing (holger's znippy archives are immutable; carrying a plan out is a delegated archive-set drop).

CLI — the dry-run plan

# Keep the 5 newest versions of every package name in `rust-prod`.
holger-server retention --config holger.ron --repo rust-prod --keep-last-n 5

# Every repository at once, with the per-artifact verdict.
holger-server retention --config holger.ron --keep-last-n 3 --verbose

# A store-size quota: keep the newest, evict oldest-first until under 50 GiB.
holger-server retention --config holger.ron --repo cache \
    --max-total-bytes 53687091200

# A keep-min floor + latest-protection off (advanced): never below 2 per name.
holger-server retention --config holger.ron --repo npm-dev \
    --max-age-secs 2592000 --keep-min-per-name 2 --no-protect-latest

Output (roll-up per repo, plus --verbose lines):

retention plan (DRY-RUN — deletes nothing) @ 1752440000
  rust-prod: 812 names, keep 4060 / reclaim 15380 — kept 9.9 GiB bytes, reclaimable 41.2 GiB bytes
    keep    crates/serde@1.0.219 (287431 bytes) [protected-latest,within-keep-last-n]
    RECLAIM crates/serde@1.0.11  (271004 bytes) [superseded-by-newer]

The policy flags

Flag Meaning
--keep-last-n N keep the N newest versions per name
--max-age-secs S reclaim records older than S seconds (needs an ingest time)
--keep-min-per-name N floor: never drop below N versions of a name
--max-total-bytes B store quota: evict oldest-first until the kept set fits
--no-protect-latest turn OFF the "never delete a name's newest" safety net
--verbose print the per-artifact keep/reclaim verdict + reasons

All are optional and compose; omit them all for a keep-everything preview.

Library — plan in-process

use holger_server_lib::retention::RetentionPolicy;

let policy = RetentionPolicy::new().keep_last(5).quota(50 * 1024 * 1024 * 1024);
let now = /* unix seconds */;
let plan = holger.plan_repo_retention("rust-prod", &policy, now)?;
println!("reclaimable: {} bytes across {} artifacts", plan.reclaimed_bytes, plan.delete_count);
for r in plan.to_delete() {
    println!("drop {}@{}", r.id.name, r.id.version);
}

Or drive the pure planner directly over records you already hold:

use holger_server_lib::retention::{plan_retention, ArtifactRecord, RetentionPolicy};
let plan = plan_retention(&records, &RetentionPolicy::new().keep_last(3), now);

Executing a plan (actually reclaim)

The planner only decides; the executor carries a plan out. It is dry-run by default — you must opt in to an actual reclaim.

# Preview (default): prints the KEEP/RECLAIM plan, deletes nothing.
holger-server retention --config holger.ron --repo rust-prod --keep-last-n 5

# Reclaim: drop the superseded artifact bytes, historize the GC to the warehouse.
holger-server retention --config holger.ron --repo rust-prod --keep-last-n 5 --execute

In-process:

use holger_server_lib::retention::{GcOptions, RetentionPolicy};
let policy = RetentionPolicy::new().keep_last(5);
let audit = holger.audit_log();                 // the same append-only warehouse the server writes
let report = holger.run_repo_retention(
    "rust-prod", &policy, now, &GcOptions::execute(), audit.as_ref(), None,
)?;
println!("reclaimed {} artifacts ({} bytes)", report.deleted_count, report.reclaimed_bytes);

Every delete is content-id verified before the unlink, a protected/latest generation is never dropped, and each reclaim is historized as an AuditAction::Delete record. Pass a jera::jobs::JobStore as the last argument to route the run through the durable job ledger (retention-gc).

Notes