Canonical Home Artifact Cache Key
Issue #1199 defines the canonical Home artifact key for the #1195 responsiveness work. This document is a spec for later foreground-read, stale-first, and warmer wiring; the #1199 implementation does not change production foreground serving behavior.
The tracked implementation is app/lib/board/cache_key.py. It is pure and side-effect-free: callers provide an explicit HomeArtifactKeyInputs snapshot and receive a deterministic home-artifact-v1:<digest> key plus a human-readable hit/miss/stale comparison report.
The key is used today only by the inert local/shadow harness:
app/lib/board/cache_key_shadow.pymodels foreground current inputs, stale-first prior artifact inputs, and warmer-produced inputs, then compares the stale-first and warmer snapshots against the foreground canonical key.imperfect-cli home-cache-key-shadow <snapshot.json>reads explicit JSON snapshots into that adapter and emits the report as JSON or compact text.tests/lib/board/test_cache_key_shadow.pyandtests/cli/commands/test_home_cache_key_shadow.pycover that usage path.
Future #1200/#1203 work can wire the same adapter shape into live foreground/stale/warm behavior. This PR does not import it from UserBoard._prepare_generation or change production foreground serving decisions.
Freshness Boundary
The canonical key includes every input family that can change the rendered Home artifact:
| Family | Inputs | Source |
|---|---|---|
| Request day/context | local date, timezone, locale, units, client platform, rounded location bucket | foreground request or latest stored client profile |
| Provider state | active provider set, hashed provider refs, permissions, connection sync token | imperfect-api connection state |
| Health freshness | opaque provider/data-type freshness token | Alice-owned monotonic watermark in production; local tests may use row-count/date proxies |
| Event | active event id and version | UserEvent snapshot |
| Plan | plan id, plan start, version, content hash | WeeklyPlan snapshot |
| Schema/client bucket | board schema version, app-version bucket, schema source hash | API/schema release rules |
| Prompt/context | prompt version/hash and context version/hash | board generation code and replay context |
The existing production cache telemetry still reports event-plan-v1. That current serving boundary checks only board date, active event id, and plan version timestamp. home-artifact-v1 is stricter and is not wired into UserBoard._prepare_generation yet.
Comparison Semantics
compare_home_artifact_inputs(current=..., prior=...) returns:
misswithno_prior_artifactwhen no prior artifact exists.hitwhen the canonical key and all compared dimensions match.stalewith stable reason codes when a prior artifact exists but any key dimension changed.
Reason codes are intentionally human-readable in reports: timezone_changed, locale_changed, units_changed, provider_set_changed, health_freshness_changed, active_event_added, event_version_changed, plan_version_changed, app_version_bucket_changed, prompt_context_changed, and the adjacent subfield reasons for plan identity/content, board schema/source, platform, and location bucket.
Foreground reads and stale-first candidates should compute the same current HomeArtifactKeyInputs. Stale-first is a serving policy over a prior artifact after comparison; it is not a separate key.
For role-level local checks, compare_home_artifact_shadow(...) returns one report with:
- the foreground current key
- a stale-first prior artifact comparison row
- a warmer-produced artifact comparison row
- request-context mismatch fields for local date, timezone, locale, units, app-version bucket, client platform, and rounded location bucket
Warmer Contract
Warmers should write artifacts per canonical client-context bucket when they have a target bucket. The request-context dimensions are:
- local date
- timezone
- locale
- units
- app-version bucket / board schema bucket
- client platform
- rounded location bucket
A warmer that only knows the latest stored client profile can warm that bucket. If a later foreground request arrives with a different timezone, locale/units, old-client schema bucket, platform, or location bucket, the canonical comparison must report a warm miss or stale reason. That is the intended safe tradeoff: accept request-context warm misses instead of serving a stale/no-activity or wrong-locale artifact as fresh.
Health Freshness
The #1199 local shadow used row count plus min/max observed dates as a health token proxy. Production should not do SQL-ish probes from imperfect-api for this key. The key accepts opaque HomeHealthFreshnessToken values so #1200/#1203 can later consume Alice-owned monotonic freshness watermarks without changing the key shape.
Local Shadow Findings
The local #1199 shadow result showed that the current event-plan-v1 boundary catches date, active-event-id, and newer-plan changes, but misses locale/units, provider set, health freshness, event-version-only, schema/app-version bucket, prompt/context, and app-visible location/settings changes.
Warmer-style keys agreed for database/event-driven changes when reading the same canonical state, but missed request/client-context changes. home-artifact-v1 makes that mismatch explicit by requiring the same key inputs and by reporting the request-context stale reason.