"""What a record's own check MEANS — shared by rules and notes. Two record types carry `verify_with` / `expires_when` / `verified_at`: rules (milestone 312) and notes (milestone 317). What they share is BEHAVIOUR — how a stamp is read, how old it is, what "never" means — not storage and not the query. Note 3163 is the rule this file is an instance of: `semantic_search_*` could never have been shared between them because a rule scopes by rulebook ownership and a note scopes by the note ACL, so the two sweeps are siblings. These functions are the part that genuinely is common, factored so it exists once rather than twice. Everything here is pure, sync and DUCK-TYPED: it reads `verify_with` and `verified_at` off whatever it is handed. That is deliberate. A shared base class or a protocol would tie two SQLAlchemy models together to share four lines of date arithmetic — the DRY costume, in the same note's words. """ from datetime import datetime, timezone def last_verified_label(record) -> str | None: """How long ago this record's check passed — None when it carries none. Three states, and the distinction between the last two is the whole point: - `None` — this record is a DECISION. There is nothing to go and check, and the question does not apply. Most records. - "never" — it asserts a fact and NOBODY HAS EVER CONFIRMED IT. The one worth acting on, and why the sweep sorts these first. - a date — somebody checked, then. Callers attach this to a payload only when it is not None, so "no key" and "never verified" do not become two states every client has to tell apart. """ if not getattr(record, "verify_with", None): return None stamp = getattr(record, "verified_at", None) return stamp.date().isoformat() if stamp else "never" def days_since_verified(record) -> int | None: """Whole days since the check last passed; None if it never has. Computed rather than left to the reader, because "2026-06-14" and "74 days" prompt different reactions and only one of them is the question being asked. Naive stamps are read as UTC. Postgres hands these back tz-aware, but a restore, a fixture or a sqlite-backed test may not, and subtracting an aware datetime from a naive one raises — which would make the sweep fail on exactly the rows it exists to surface. """ stamp = getattr(record, "verified_at", None) if stamp is None: return None if stamp.tzinfo is None: stamp = stamp.replace(tzinfo=timezone.utc) return (datetime.now(timezone.utc) - stamp).days