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:
1 parent
3ac011abd7
commit
3aa6ce314c
4 files changed
+720
-14
No files matched your search
@@ -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(())
|
||||
}
|
||||
Reference in new issue
Block a user