Freshness identity and cache contract¶
This document is the normative Site-side contract for identifying the exact
published documentation revision and for communicating document and integrated
Glossary-model freshness to browser clients. It complements PUBLISHING.md for
publication provenance and MAINTENANCE.md for PWA operation.
Objective¶
The documentation portal must distinguish four different facts:
- which reviewed Git revisions produced a Pages artifact;
- which Site revision produced a particular generated HTML document;
- whether a browser has verified that a document response is current; and
- whether an inline Glossary definition came from the currently verified integrated read model or from an explicitly unverified saved copy.
A cached or offline document or Glossary model must never be represented as network-verified merely because its build revision is known. Build identity is evidence about the content that was generated; runtime freshness is evidence about whether the current published response was successfully revalidated.
Build-time freshness identity¶
After every human-readable and generated Site surface has been finalized, the
build-provenance step writes build-provenance.json and projects the same exact
checked-out revisions into the client-facing /site-version.json read model.
The Site and provider revisions are full lowercase 40-character Git commit SHAs
from the actual build checkouts, not mutable branch names, tags, or abbreviated
revisions.
/site-version.json uses schema version 1 and records site_revision, the
existing deployment timestamp (or null for preview builds), and exact skill,
policy, and webapp revisions. The generator rejects symbolic-link or
non-regular outputs and generated HTML paths.
Per-document build identity¶
Every generated HTML page that participates in the normal Site runtime receives
exactly one element in its <head>:
<meta name="templates-site-revision" content="<full Site commit SHA>">
The value must equal /site-version.json's site_revision. Existing conflicting
or duplicate metadata fails the build. Generated HTML with malformed or ambiguous
<head> boundaries also fails rather than being heuristically rewritten.
No provider source previews are generated by Site; GitHub owns immutable source
and directory browsing.
After writing the projection, the build re-reads /site-version.json and every
eligible generated HTML page. It verifies the complete canonical JSON payload and
exactly one matching revision meta element before artifact upload can proceed.
Runtime freshness states¶
The Site uses the following runtime state vocabulary:
verified-current: the current document or supported read-model request was successfully obtained or revalidated from the network;checking: a stored document is visible while network freshness verification is still pending;cached-unverified: a stored document or explicitly supported read model is being used because current network freshness could not be verified;update-available: a newer or materially different verified response has been obtained while an older stored document remains visible.
The Service Worker exposes this vocabulary through the
templates:get-freshness-capabilities / templates:freshness-capabilities
message contract together with /site-version.json, the document-cache
namespace, the Glossary-cache namespace, the integrated Glossary model URL, and
softTimeoutMs: 1500. The capability response also identifies the current
Service Worker instance so request-generation ordering cannot be confused across
a normal worker restart. Matching build identity alone never establishes
verified-current.
Runtime document-cache behavior¶
Normal browser navigations and same-origin document-like instant-navigation
requests remain network-first. Every online document request uses
fetch(request, { cache: "no-cache" }), preserving HTTP-cache revalidation.
A successful same-origin HTTP 200 response is cacheable only when its
Content-Type contains text/html. The response is cloned before asynchronous
cache work begins, so returning the network response does not race with Cache
Storage consumption of the same body stream.
The fetch event registers its document-lifetime promise synchronously inside the
Service Worker event callback, before asynchronous network completion. The
response promise later binds any cache-write or slow-network convergence task
into that already-registered lifetime. Code must not make its first waitUntil()
call only after awaiting the network response or the soft timeout; background
cache and convergence work must remain covered by the original trusted event
lifetime.
The runtime document cache is templates-portal-documents-v1, independent of the
versioned shell cache. Service Worker activation removes incompatible shell
namespaces but preserves the compatible document namespace.
Cache mutations for one exact request URL are both serialized and ordered by a request generation allocated before that request begins network I/O. A mutation from an older generation is discarded after a newer generation has already been applied. An authoritative 404/410 records its generation before deletion begins, so a slower HTTP 200 request that started earlier cannot later resurrect the deleted document. The worker also retains an in-memory authoritative-deletion tombstone until a newer successful cache write supersedes it; if physical cache deletion fails, the stale entry therefore remains ineligible for fallback.
Network outcomes have distinct semantics:
- cacheable HTTP 200: return the network response and update the exact document cache entry when its request generation is not stale;
- HTTP 404 or 410: mark the request generation authoritative, remove the exact cached document before returning the network response, and if entry deletion throws attempt to delete the entire document-cache namespace rather than knowingly preserve an authoritative stale copy;
- ordinary non-transient 4xx such as 403: return the network response and do not fall back to stale documentation when it completes before cached content has been exposed;
- HTTP 5xx: return cached documentation only when stale indication can be proven; otherwise return the original 5xx;
- network/DNS/TLS/connection failure: return cached documentation only when stale indication can be proven; otherwise return the explicit HTTP 503 fallback.
Slow-network convergence¶
A document request has a 1500 ms soft timeout. The timeout does not abort, cancel, or replace the original network request. If the network completes before the soft timeout, ordinary network-first behavior applies. If no safe stored document exists when the timeout expires, the browser continues waiting for the original network result rather than synthesizing an early failure.
If a safe stored document does exist after the timeout, the Site may expose that
stored representation with the explicit checking state while the original
network request continues. Full navigation carries checking in the decorated
HTML itself. Instant navigation must first apply and acknowledge the persistent
checking UI through the same generation-bound MessageChannel safety gate used
for cached-unverified fallback.
Background completion never replaces the visible document automatically. It converges the freshness state instead:
- matching cached and network
templates-site-revisionvalues becomeverified-current; - a different revision, a cache-ineligible successful response, an unextractable
or ambiguous revision, or authoritative 404/410 becomes
update-available; - a network failure or other completed result that cannot verify the visible
saved representation becomes
cached-unverified.
update-available exposes an explicit Reload action. The user remains in control
of the representation change. The Service Worker reads revision metadata without
assuming HTML attribute order, accepts ordinary quoted or unquoted attribute
values, ignores commented-out metadata, requires one unambiguous valid full SHA,
and fails conservatively to update-available when identity cannot be established.
The worker retains a bounded amount of per-document and per-client freshness state
so a page that starts listening after an earlier message can request the current
state with templates:get-current-freshness-state. Each state is ordered by the
request generation and scoped to a randomly generated Service Worker instance ID.
A worker restart therefore establishes a new ordering epoch rather than causing a
new low generation to be rejected by an already-open page. Client code also checks
the exact document URL before applying a non-commit freshness update, and a newer
committed network representation retires older convergence for that visible
document. These rules prevent late slow requests from adding or clearing warnings
on a different or newer page.
Full-navigation stale indication¶
For a full browser navigation, the cached HTML response itself carries the
freshness indication. The Service Worker requires one unambiguous <html> start
tag, one <body> start tag, and one closing </body> tag. It marks the cached
representation with data-templates-cached-fallback="true" and its current
freshness state on <html>, and inserts the templates-freshness-status element
immediately after the opening <body> tag, before later page scripts can execute.
If those boundaries cannot be established consistently, the cached representation
fails closed and is not exposed.
The decorated representation also includes a minimal inline fallback style for
the status element and Reload action. This makes the warning fixed at the top of
standalone pages whose CSP permits their existing inline styles but which do not
load the shared freshness-status.css; ordinary Site pages continue to receive
the equivalent shared stylesheet. The inline fallback contains only fixed
Site-owned CSS and no content-derived values.
A full-navigation page may emit Zensical's document$ event during initial page
setup. pwa.js therefore reads the explicit <html> cached-representation marker
(and accepts an already present checking or cached-unverified status as a
defensive fallback), preserves the warning across the initial commit, and consumes
the marker only when that cached commit boundary is observed. The initial event is
not interpreted as proof of fresh content.
Instant-navigation stale indication, acknowledgement, and commit boundary¶
A document-like fetch used by instant navigation cannot assume that an indication
embedded in fetched HTML will survive partial-DOM replacement. Before returning
a cached response for such a request, the Service Worker sends the requesting
client a templates:freshness-state message carrying checking or
cached-unverified, the exact request URL, the request generation, the current
worker-instance ID, and a MessageChannel acknowledgement port.
Current pwa.js applies that state to persistent DOM by creating the same fixed
status element and records a pending commit whose representation is cached.
It then replies through the port with templates:freshness-state-applied,
echoing the state, request generation, and worker-instance ID. The Service Worker
waits for that exact acknowledgement, with a bounded timeout, before returning
cached HTML.
This acknowledgement is a safety condition, not an optimization. If an older
open page is controlled by the updated worker but still runs a client script that
does not implement the freshness UI, no acknowledgement arrives. The worker then
refuses to expose cached HTML: a network failure returns the explicit 503 and a
transient HTTP 5xx returns the original 5xx. A slow request whose checking
acknowledgement cannot be established keeps waiting for the original network
result instead of exposing an unindicated stored document.
Network response completion and document replacement are separate events under
Zensical instant navigation. The worker therefore sends a separate
templates:document-commit intent for non-cached network representations. That
message carries the exact URL, representation kind, request generation, and
worker-instance ID, but does not clear an existing warning. pwa.js retains only
the newest observed commit generation and waits for Zensical's document$ event
before acting on the pending representation.
At the document-commit boundary, a committed cached representation keeps the
warning and consumes its pending marker. A committed network representation
clears the warning. This representation-aware correlation prevents two opposite
failures: a successful fetch that is prefetched or cancelled cannot clear a stale
warning before it is rendered, and a later fresh retry to the same URL cannot be
mistaken for an earlier cancelled cached fallback merely because the URLs match.
An uncorrelated document$ event alone is never authority to clear the freshness
warning.
This fail-safe ordering deliberately permits an old warning to remain longer than necessary if a non-Zensical surface cannot expose a document-commit signal. It never permits stale content to become unindicated merely because a network fetch finished before the caller replaced the visible DOM.
Synthetic cached document response¶
Cached document fallback responses carry X-Templates-Freshness with either
checking or cached-unverified, as appropriate, and Cache-Control: no-store.
Because the cached HTML body is modified, Content-Encoding, Content-Length,
ETag, and Last-Modified are removed. A cached response that is not HTML,
cannot be read, has a redirected final URL different from the exact request URL,
or lacks the single unambiguous <html>/<body> structure required for safe
marking and insertion is not used as fallback. Redirects fail closed because
constructing a synthetic decorated Response cannot preserve the original
response URL needed for canonical relative-URL resolution.
Runtime Glossary-model cache behavior¶
The integrated /glossary/index.json read model has a separate runtime cache,
templates-portal-glossary-v1. It is deliberately not part of STATIC_ASSETS:
online Glossary activation remains network-first and uses cache: "no-cache",
so a previously saved definition is never preferred merely because the shell is
available. The document soft timeout does not implicitly apply to this independent
Glossary route.
A Glossary response is cacheable only when it is same-origin HTTP 200 and its
Content-Type is JSON (application/json or a +json media type). Successful
responses are cloned for asynchronous cache writes while the original network
response is returned immediately. The fetch event binds any resulting cache
mutation to an already-registered lifetime promise, matching the document-cache
lifetime rule.
Because the integrated model has one stable URL, its mutations use one serialized request-generation sequence. An older cache mutation cannot overwrite a newer one. HTTP 404/410 is authoritative: its deletion is recorded only if its request generation is not older than the newest applied cache mutation, and physical entry deletion is generation-ordered. This prevents both a delayed old 200 from resurrecting a deleted model and a delayed old 404 from invalidating a newer verified model. If authoritative deletion cannot remove the entry, the worker attempts to remove the entire Glossary cache namespace and retains an in-memory tombstone until a newer successful write supersedes it.
Network outcomes are intentionally analogous to documents but narrower:
- cacheable HTTP 200: return the network model and update the Glossary cache;
- HTTP 404 or 410: treat the absence as authoritative and remove the saved model;
- ordinary non-transient 4xx: return the network response without stale fallback;
- HTTP 5xx: use a saved model only for a freshness-aware Glossary runtime; otherwise return the original 5xx;
- network/DNS/TLS/connection failure: use a saved model only for a freshness-aware Glossary runtime; otherwise let the request fail so the runtime presents its ordinary unavailable-definition state.
A saved Glossary response keeps the exact cached JSON bytes rather than rewriting
the model. The synthetic response carries
X-Templates-Freshness: cached-unverified and Cache-Control: no-store, and
removes Content-Encoding, Content-Length, ETag, and Last-Modified because
a new Response object is constructed around those bytes. The inline Glossary
runtime reads the freshness header before displaying a definition and exposes
Saved glossary data · latest version not verified. in the same dialog.
Cached Glossary fallback is fail-closed across Service Worker/client-version
boundaries. The current runtime opts in on its same-origin model request with
X-Templates-Glossary-Accepts-Cached: 1. The Service Worker refuses cached
Glossary fallback when that request header is absent. Consequently an older open
page that is claimed by the new worker but still runs a runtime that cannot show
the cached-unverified warning receives no saved model. This is the Glossary
counterpart of the document cache's stale-UI acknowledgement requirement, but it
does not require a MessageChannel: the Glossary dialog is constructed and its
freshness text is populated before that definition is presented.
Shell cache behavior¶
The shell cache is versioned independently and precaches the Site-owned common
assets needed to render previously viewed documentation, including the manifest,
icon, common stylesheets, freshness-status stylesheet, and common local
JavaScript. Exact shell assets continue to use background cache: "no-cache"
revalidation when requested.
The Chromium capability checker derives its install-asset preflight directly from
the Service Worker's STATIC_ASSETS declaration. A newly added precache asset
therefore cannot be omitted silently from the preflight and leave the browser
waiting on a Service Worker installation that has already failed.
The Glossary cache is versioned independently of both shell and document caches.
Activation may delete older templates-portal-glossary-* namespaces when the
Glossary storage/representation contract changes, while preserving the compatible
templates-portal-documents-v1 document cache. Adding Glossary caching does not
by itself change the shell namespace because the shell cache strategy and stored
shell representation are unchanged.
Browser regression contract¶
The Chromium freshness lifecycle verifies at least:
- online v1 -> v2 document revalidation and runtime cache update;
- document-cache survival across a Service Worker shell update;
- an old/unaware controlled client without stale-UI acknowledgement receives 503 rather than cached HTML during offline instant navigation;
- a current client receives cached v2 only after the persistent stale warning is applied and acknowledged;
- committing that cached fallback retains its persistent stale warning;
- an uncommitted ordinary 4xx response does not clear the warning;
- a later verified HTTP 200 response does not clear the warning before the corresponding document commit, while the subsequent committed navigation does;
- a cancelled cached navigation followed by a fresh retry to the same URL is correlated to the fresh representation rather than the stale URL alone;
- offline full navigation returns cached v2 with exactly one visible stale indication and preserves it across the initial document commit;
- the standalone/full-navigation warning remains fixed in the viewport using its CSP-compatible inline fallback style even without the shared warning stylesheet;
- an uncached offline request retains explicit 503;
- ordinary 4xx responses never fall back to stale documentation;
- transient 5xx may fall back only with acknowledged stale indication;
- authoritative 404 removes the cached document so later offline access cannot resurrect it, including when an older delayed 200 completes afterward;
- a slow cache miss continues waiting for the original network response after the 1500 ms soft timeout;
- a slow cache hit can expose
checkingwithout aborting the original network request, and background completion converges without replacing visible DOM; - matching and reversed-order revision metadata converge to
verified-current, while missing/ambiguous revision metadata or a non-HTML success converges toupdate-available; - slow network failure or transient failure after
checkingconverges tocached-unverified; - direct full navigation can expose its self-marked stored representation without requiring a pre-existing client acknowledgement;
- Service Worker restart, previous-document completion, and newer same-URL network commit races cannot apply an older freshness conclusion to the visible page;
- Service Worker update propagation, manifest convergence, and the live
freshness-capability message contract, including
softTimeoutMs, remain valid.
Glossary-specific regression coverage additionally preserves the dedicated
network-first cache route, generation-ordered authoritative deletion, exact-byte
cached response decoration, freshness-aware client opt-in, and visible
cached-unverified dialog state. A future browser-level Glossary cache lifecycle
checker may extend this list without changing the freshness semantics above.
Evolution rule¶
Slow-network convergence is part of the active runtime contract. Future tuning may change timeout values or extend supported read models only if the capability contract and browser regression evidence are updated together. Such changes must not turn a soft timeout into cancellation, must not infer freshness from build identity alone, and must not replace visible document content automatically after background verification.
Any future change must preserve the central invariant: a document or reader-visible semantic read model whose current network freshness has not been verified is visibly identified as unverified before it is presented as ordinary readable content.