mixle.substrate.freshness module¶
Freshness checks for substrate knowledge items.
Models drift; so does knowledge. An item can go stale three ways, each independently checkable:
moved data – the item references a file (
payload.ref/provenance.source/path) that no longer exists, or whose content hash no longer matches the one recorded at ingest;superseded – a newer item shares the same lineage parent or declares
supersedeson this one, so this item is no longer the current version of its knowledge;aged out – older than the caller’s
max_age_spolicy for its kind (a soft signal: age alone is a review trigger, not proof of wrongness – the finding says so).
check_freshness() audits one item and returns a three-state FreshnessState verdict
(MXR-080-0267, mirrors LineageState/MXR-080-0260/0261) rather than a
bare boolean: FRESH (every check ran, nothing fired), STALE (a confirmed signal fired), or
UNVERIFIABLE (a referenced path exists and carries a recorded ingest-time hash, but its CURRENT
content could not be read to check against that hash – a directory, a permission error, or any other
read failure). Before this fix, an unreadable path’s hash comparison was silently skipped as “no
signal,” so a directory with a deliberately wrong recorded hash was reported fresh; unreadable content
is now always UNVERIFIABLE, never silently promoted to FRESH. content_hash() records a full,
algorithm-labelled digest ("sha256:<64-hex>"), not a bare hex string truncated to 128 bits with no
algorithm on it. now and max_age_s are validated (finite; max_age_s also non-negative)
before use, and a NaN or future-dated item.created_at is itself a named STALE signal rather than a
silently clamped age_s=0.0.
freshness_report() sweeps a store. These checks help monitoring workflows
surface stale citations, unverifiable artifacts, and moved data before they affect downstream
answers.
- class FreshnessState(*values)[source]
Bases:
StrEnumThe three-state verdict of one item’s freshness check (MXR-080-0267) – a closed vocabulary, the same discipline
LineageStateapplies to lineage verification andMergeStrategyapplies to merge strategies. Never a bare boolean: a barefresh=Truecannot distinguish “every check ran, nothing fired” from “couldn’t check some of it, so I don’t actually know” – exactly the fail-open overclaim this finding fixes.FRESH: every staleness check ran to completion and no signal fired.
STALE: at least one CONFIRMED signal fired – moved, changed, superseded, aged out past policy, or an invalid (NaN / future-dated) recorded timestamp.
UNVERIFIABLE: a referenced path exists and an ingest-time hash was recorded for it, but the CURRENT content could not be read to compare against that hash (a directory, a permission error, or any other read failure). Never silently promoted to FRESH just because nothing contradicted the recorded hash – there was no comparison to contradict it WITH.
- class Freshness(item_id, state, signals=<factory>, age_s=0.0)[source]
Bases:
objectOne item’s freshness verdict: a three-state
state, never a bare boolean (MXR-080-0267; mirrorsLineageReport). Every signal – confirmed or unverifiable – is named insignals.- property fresh: bool
TrueiffstateisFreshnessState.FRESH.Never itself the source of truth –
stateis (MXR-080-0267): bothFreshnessState.STALEandFreshnessState.UNVERIFIABLEreadFalsehere, where the pre-fix code could readTruefor content it had never actually been able to check against its recorded hash – a directory (or any other unreadable path) with a deliberately wrong recorded hash was reported fresh.- Type:
Read-only convenience view
- content_hash(path)[source]
The full, algorithm-labelled digest of a file’s bytes (
"sha256:<64-hex>"), orNoneif unreadable – record this verbatim at ingest, and compare it verbatim at audit time.MXR-080-0267: previously a bare hex string truncated to the first 32 characters (128 bits), with no algorithm recorded – collision resistance silently weaker than sha256’s own, and no way for a future reader to know what algorithm produced a recorded value (or to add a second scheme without guessing which one an old value used). The label makes the digest self-describing and algorithm-agile; the digest itself is never truncated.
- check_freshness(substrate, item_id, *, max_age_s=None, now=None)[source]
Audit one item for the staleness signals (see module docstring), returning a three-state
FreshnessStateverdict (MXR-080-0267) – never a bare boolean. Missing item -> stale.now(default: the current wall clock, injectable for deterministic tests) andmax_age_sare caller-supplied ARGUMENTS, validated up front: NaN or infinite (and, formax_age_s, negative) values are always a caller bug, so an invalid one raisesValueErrorimmediately rather than being silently clamped or ignored. By contrast, a NaN or future-dateditem.created_atis substrate DATA being audited, exactly like a moved file or a changed hash: it is named in the result (stateFreshnessState.STALE) rather than raised, so one corrupted record can never abort afreshness_report()sweep over everything else.Precedence when more than one kind of finding applies to the same item (mirrors
verify_lineage()’s BROKEN-outranks-UNVERIFIED rule):FreshnessState.STALE(a confirmed signal) outranksFreshnessState.UNVERIFIABLE(an open question) outranksFreshnessState.FRESH.
- freshness_report(substrate, *, max_age_s=None, scope=None, now=None)[source]
Sweep a store for stale/unverifiable knowledge (MXR-080-0267 three-state; mirrors
audit_substrate()’s broken/unverified split):{n_items, n_fresh, n_stale, n_unverifiable, stale: [...], unverifiable: [...]}– the monitor feed.stalenames every item with a confirmed signal (moved/changed/superseded/aged/invalid timestamp);unverifiablenames every item whose referenced content exists and carries a recorded hash but could not be read to check against it – kept separate fromstalebecause “could not verify” is never the same claim as “confirmed defective.”