Skip to content

Render-Side Freshness Contract

Every cached or derived surface vita renders β€” e-ink displays, memory views, briefing tiles, API responses, working memory β€” used to answer the question "can I trust this number?" on its own, or not at all. The freshness contract replaces N ad-hoc staleness checks with one:

A figure whose source is missing, stale, or suspect renders as unknown, never as a real value.

Status: shipped 2026-07-26 (forge idea 051, closing plans/proposals/2026-04-29-epd-freshness-guard.md after 16 revisions). 20 sources registered, 60 tests. Live check: uv run python -m freshness --status.

Why this exists

The failure mode has a name in vita's working memory: render-fresh / engine-dark. A surface looks alive because it has a recent timestamp, while the engine that fills it has been dead for weeks. The Jul-6 2026 truth audit measured it on six independent surfaces:

Surface What it asserted What was true
EPD display CEO transformation: 0% no aggregator existed; real value unknown
memory/views/* "current" up to 85 days since last build
data-source-health tokens_session_sync: never_synced syncing daily
Sleep-gate chain 5.05h slept watch fell off; ~7.5h actually slept
Living briefing live tiles zero adapter writes for ~60 days
data/doc-audit/latest.json "100% coverage" file was ~5 months old

Each was fixed by hand, individually. The contract exists so the next one isn't.

The finding that shaped the design

Building this turned up something sharper than the proposal assumed: the guard had already shipped. On 2026-06-06, scripts/utils.render_metric landed with 8 passing tests β€” and exactly one consumer. The EPD display it was written for never imported it. Separately, memory/view_loader.load_view(), the correct consumer-side primitive, has zero production consumers, while scripts/sentinel/calendar_guard.py reimplements its own _load_view 100 lines away.

So the gap was never a missing check. A primitive nobody calls is indistinguishable from no primitive β€” the render-fresh/engine-dark pathology applied to its own fix. That is why this ship prioritized wiring over library surface, and why the registry is deliberately the cheap part.

Architecture

                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   registry.py ────▢│  assess("view.health-snapshot")   β”‚
   (20 sources,     β”‚                                   β”‚
    each with its   β”‚   pick probe by kind ──┐          β”‚
    OWN cadence     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    + named owner)                           β”‚
                                             β–Ό
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
       probes.py    β”‚ view        β†’ delegates to view_loader  β”‚
                    β”‚ sqlite_max  β†’ MAX(ts) of a table        β”‚
                    β”‚ json_field  β†’ dotted path + mtime bak   β”‚
                    β”‚ mtime       β†’ filesystem fallback       β”‚
                    β”‚ dated_file  β†’ newest of a glob          β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                         β–Ό
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
      validity.py   β”‚ value plausible? coverage sufficient?  β”‚
                    β”‚   fresh-but-wrong β†’ SUSPECT            β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                         β–Ό
                             β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                             β”‚     Verdict       β”‚
                             β”‚  state + age +    β”‚
                             β”‚  reason + owner   β”‚
                             β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                       β–Ό
        contract.render(value, verdict)   contract.require(verdict)
        "6.9h" | "6.9h (stale 9d)"        raises if it can't be backed
        | "unknown β€” source missing"

The six states

Only FRESH permits rendering a bare value. Everything else is a specific reason the number would be an assertion the data can't back.

State Meaning Renders as
FRESH within the source's declared cadence 6.9h
STALE source exists, older than its own max_age 6.9h (stale 9d)
MISSING absent, unreadable, or empty unknown β€” file not found
SUSPECT present and recent, but the value fails a validity rule unknown β€” duration 3.2h below floor
CONTESTED two sources that should agree don't unknown β€” contested
UNKNOWN exists but carries no usable timestamp unknown β€” no timestamp

A stale value is still shown, because a labelled stale number is useful and honest. A missing or suspect one is not shown at all, because there is no number to label β€” that's the case where surfaces used to invent a 0.

Failing closed

verdict_from_age() treats an unparseable or absent timestamp as UNKNOWN, never FRESH. This is the single most important line in the package: at most prior call sites, an unreadable timestamp meant "assume fine", which is exactly how a source goes dark without any surface noticing.

Probes never raise, either. An exception while probing is a freshness answer (MISSING), because a probe that could take down its caller would just push consumers back to reading raw.

Freshness vs. validity

These fail independently, and vita has been bitten harder by the second.

  • Freshness asks when was this written?
  • Validity asks is what was written believable?

The Jul-4 garmin reading was written on time and was junk β€” the watch came off, 5.05h recorded against ~7.5h actually slept β€” and the gate calc, the sleep-risk scorer, the 19:30 warning builder and an OODA working-memory correction all consumed it as fact. Two false gate warnings went out before a human noticed.

validity.py carries the rules:

  • sleep_duration_plausible β€” below 4.0h is more likely sensor dropout than a night slept. Deliberately generous: bio-zack logs real 5.5h nights; 4.0h is where "the sensor lost him" becomes the better explanation.
  • sleep_score_present β€” duration without a score is a partial record.
  • coverage_rule(ratio) β€” an aggregate computed over too little of its window is an artifact. This is the 2026-05-28 case, where missing sync days were averaged in as zeros and a 3-of-7-night average rendered as a 7-night average (4.49h shown against 7.87h actually slept on the nights recorded).

Validity rules only run on otherwise-trustworthy sources β€” there's no point second-guessing a value already known to be stale.

The registry

registry.py is one declarative table. Adding a source there is the entire cost of putting a new surface under the contract.

SourceSpec(
    id="briefing.engine",
    kind="sqlite_max",
    max_age=2 * DAY,
    target=("data/briefing/briefing.db", "briefing_items", "created_at"),
    owner="briefing adapters",
    note="dark May-8 -> Jul-7 (~60d) while tiles rendered as live",
)

Two fields carry more weight than they look like:

  • max_age is per-source. A view rebuilt nightly and a lab panel refreshed quarterly are both honest at ages that would be alarming for the other. A global default would make the whole surface cry wolf.
  • owner names what is supposed to keep it fresh. A source owned by "button-only" or "manual" is not broken when it drifts, and the status output says so rather than alarming nightly.

Registered today: memory views (health-snapshot, training-status, goal-progress, identity-snapshot, calendar-travel-context, ceo-progress, relationship-health), engines (briefing, feed, executive loop, proactive queue, stepwise scheduler, capability usage), working memory (reflection.md, identity-state.md, daily scratch), audit instruments (doc-audit, homeostasis predictions), and health data (garmin daily + sleep).

Using it

from freshness import assess, render, require

verdict = assess("view.health-snapshot")
print(render(snapshot["avg_sleep_hours"], verdict, unit="h", fmt="{:.1f}"))
# fresh   -> "6.9h"
# stale   -> "6.9h (stale 9d)"
# missing -> "unknown β€” view file absent"

Two render options exist for surfaces with layout constraints:

  • terse=True collapses untrustworthy states to an em-dash, for big-number cards and e-paper. The reason still reaches logs via verdict.line() β€” it's dropped from the pixel, not from the record.
  • annotate=False drops the (stale 9d) suffix for split layouts that show the verdict in an adjacent slot. Only pass it when the verdict is displayed elsewhere; dropping the suffix without replacing it turns a stale number back into a fact.

require(verdict) raises StaleSourceError instead of degrading. Most surfaces should degrade; use require only where acting on a stale number is worse than not acting β€” a proactive message that would assert something to bio-zack, for instance.

The status surface

$ uv run python -m freshness --status
freshness contract: 20 sources, 6 cannot currently back a number
  STALE    view.relationship-health   age=  86d  β€” aged_out_7d  [aggregators/relationship_health.py β€” UNWIRED]
  STALE    view.ceo-progress          age=  79d  β€” aged_out_2d  [aggregators/ceo_progress.py β€” no scheduled rebuild]
  STALE    garmin.sleep               age=   1d  β€” newest sleep is 2026-07-25 (1d back), not last night
  STALE    view.training-status       age=   4h  β€” RWGPS trips synced
  STALE    view.health-snapshot       age=   4h  β€” garmin data updated
  unknown  view.calendar-travel-context  age=  ?  β€” not tracked by staleness.json
  ok       view.goal-progress         age=   1d
  ...

This is the single answer to "which instruments are lying right now". Exit code is 1 when any source is untrustworthy, so it can gate a flow step.

--liars shows only the untrustworthy rows; --json is machine-readable; --source <id> checks one.

view.relationship-health at 86 days is the canonical exhibit β€” it read 100/100 on track straight through the 2026-06-07 marital rupture, because nothing rebuilds it and no consumer honored the freshness signal the detector was already emitting.

Wired consumers

Surface What changed
E-ink display (epd47_canvas.py) sleep card reads 8.3h Β· 2026-07-25 Β· 1d old instead of 8.3h Β· last night; a suspect night renders β€”; mirror mode won't make an accusation from an untrustworthy source; the exploring agent's source list is generated from live verdicts
flows/sleep/data_source_health.py freshness.* rows join the nightly rollup, so a dark source appears there instead of waiting to be noticed
GET /api/v1/goals/{id}/progress response carries freshness_state / freshness_reason / freshness_age_seconds
scripts/utils.render_metric now delegates to the contract, so there is one code path

The EPD wiring is the sharpest single example. _load_sleep_last_night() used to fall back from today's garmin file to yesterday's silently and hand back a bare dict β€” so a two-day-old number reached the wall under a "last night" label. The fallback was always fine; hiding it was the bug.

Honest gaps

  • EPD hardware is dark pending a physical power-cycle, so the pixel-level claim is verified against the render path, not against glass.
  • The API holds stale bytecode for the utils.py delegation until an out-of-band restart. code_fresh.api flagged this the same night β€” the verification machinery working β€” and it was not self-restarted mid-turn, per the self-restart-kill guard.
  • 20 sources is not all of them. Briefing tiles, X drafts, and the doctor working-memory are not yet consumers.
  • The contract makes drift visible; it does not rebuild anything. That's the write-side sibling (2026-04-29-view-refresh-cadence-enforcement.md). Two chronically-cold views (ceo-progress, relationship-health) will keep reporting stale until someone owns their rebuild.