feat(dates): pre-cutover audit utility for the filename date class (#117)

One-shot, operator-run, strictly read-only utility that lists every
filename-class file with its stored date/source and the date/source/
offset-source the re-based resolver would assign it — the human
spot-check that gates the production backfill drain (#118).

- src/filename_audit.rs: StoredDate/AuditRow/SkippedLibrary; walk +
  PathExcluder (depth-0 guarded) + match_filename_date -> one
  resolve_dates_batch pass with EMPTY priors (mirrors the drain
  exactly); disabled and probe-stale libraries reported as skipped,
  never partially processed; output stable-sorted by (library, path).
- src/bin/filename_audit.rs: operator binary (dotenv, DATABASE_URL,
  EXCLUDED_DIRS, libraries::load_all, format_report).
- src/memories.rs: match_filename_date canonical matcher returning
  FilenameMatch { pattern, date }; extract_date_from_filename is now
  a thin wrapper (byte-for-byte behavior-preserving, verified by
  review).

4 audit tests (inversion + utc_direct + exclusions, offline/disabled
skip, stable sort, report determinism incl. no debug quotes) + 2
matcher tests. 645 lib + 718 integration pass; clippy/fmt clean.
Standards review applied: {:?}->{}, depth-0 walk guard, dead
per-file exclude check removed, unused Default derive removed,
pattern column widened, doc comment reworded.
This commit is contained in:
Cameron Cordes committed 2026-09-25 18:03:38 -04:00
1 parent 3ac011abd7
commit 3aa6ce314c
4 files changed
+720 -14

No files matched your search

+76
View File
@@ -0,0 +1,76 @@
//! Pre-cutover audit utility for the filename date class (ticket #117).
//!
//! One-shot, operator-run: lists every file matching a filename-class
//! pattern across the configured libraries — the file's current stored
//! date + source and the date / source / offset-source the re-based
//! resolver would assign it. Strictly read-only: it changes nothing.
//!
//! This is the human spot-check that gates the production backfill
//! drain: review the report, fix any mis-tagged pattern or
//! misclassified file, then start the drain.
//!
//! Usage: `cargo run --bin filename_audit` (reads DATABASE_URL and
//! EXCLUDED_DIRS from the environment, like the server).
use std::path::PathBuf;
use diesel::Connection;
use diesel::sqlite::SqliteConnection;
use image_api::filename_audit::{format_report, run_audit};
use image_api::libraries::load_all;
fn main() -> anyhow::Result<()> {
env_logger::init();
dotenv::dotenv().ok();
let database_url = dotenv::var("DATABASE_URL")
.map_err(|_| anyhow::anyhow!("DATABASE_URL is not set (check .env)"))?;
let base_path = dotenv::var("BASE_PATH").ok();
let mut conn =
SqliteConnection::establish(&database_url).map_err(|e| anyhow::anyhow!(e.to_string()))?;
// Migrations are already run by the server; the audit only SELECTs.
// A fresh database without the date-class columns would fail the
// select below with a clear SQL error — that is the expected signal.
let global_excludes: Vec<String> = dotenv::var("EXCLUDED_DIRS")
.unwrap_or_default()
.split(',')
.map(str::trim)
.filter(|s| !s.is_empty())
.map(String::from)
.collect();
let libraries = load_all(&mut conn);
if libraries.is_empty() {
anyhow::bail!(
"no rows in the `libraries` table — is DATABASE_URL pointing at the live database?"
);
}
// BASE_PATH is informational for the operator (the libraries table
// carries the authoritative roots); warn when it differs from every
// known root so a stale env var doesn't mislead the review.
if let Some(base) = &base_path
&& let base = PathBuf::from(base)
&& !libraries
.iter()
.any(|l| PathBuf::from(&l.root_path) == base)
{
eprintln!(
"note: BASE_PATH ({}) does not match any library root; auditing the library table",
base.display()
);
}
println!("Filename date class audit (read-only)");
println!("======================================");
println!("Libraries: {}", libraries.len());
println!();
let (rows, skipped) = run_audit(&mut conn, libraries, &global_excludes);
print!("{}", format_report(&rows, &skipped));
Ok(())
}