Cache Pacts: Deep Dive #89
State Modeling Specialist
Cache pacts are the contract layer that keeps cache surfaces stable across refactors and migrations. They answer a simple question:
When a cache returns truth, what shape of truth is the route allowed to believe?
We already have:
- cache lanes (freshness posture),
- cache ledgers (evidence of cache behavior),
- cache bridges (explicit cache surface hand-offs).
Pacts complete the set by making cache surfaces versionable and render-verifiable.
Required docs links:
Extra docs links for this entry:
A cache pact is a render-time contract. It doesn't prevent drift. It makes drift survivable and visible, and it gives you a stable key to migrate against.
What a cache pact looks like
A cache pact defines:
- the cache surface keys you require,
- the defaults/coercions you allow,
- the failure posture (fail-forward + evidence),
- and the pact id you version over time.
Cache pacts are intentionally different from fetch pacts (#84):
- fetch pacts focus on network surface shape,
- cache pacts focus on cache surface shape (age/ttl/result + value summary).
Worked Example: pact-check a bridged cache surface
Continuing /warehouse, we will:
- define a pact for the
orders:listcache surface, - verify it at render time,
- emit receipts into the cache ledger,
- render pact evidence into the DOM,
- keep the panel navigable even when the cache surface is malformed.
Step 1: Define the pact (versioned id)
export const OrdersCacheSurfacePact = {
id: "pact:cache-surface:orders:list:v1",
required: ["cacheKey", "cacheLaneId", "result", "ageMs", "ttlMs", "value"],
coerce(surface: any) {
return {
result: surface.result ?? "miss",
ageMs: Number(surface.ageMs ?? 0),
ttlMs: Number(surface.ttlMs ?? 0),
value: surface.value ?? { total: 0 },
...surface,
};
},
};
Step 2: Verify at render time (fail-forward)
export function verifyCacheSurfacePact(pact: any, surface: any) {
const failures: any[] = [];
for (const k of pact.required) {
if (surface?.[k] == null) failures.push({ kind: "missing-key", key: k });
}
const coerced = pact.coerce(surface ?? {});
return { ok: failures.length === 0, failures, surface: coerced };
}
Step 3: Write pact evidence into the cache ledger
import { useRenderEffect } from "react";
export function usePactedCacheSurface(opts: {
pact: any;
surface: any;
cacheLedger: any;
lane: any;
cacheLaneId: string;
pactKey: string;
}) {
const { pact, surface, cacheLedger, lane, cacheLaneId, pactKey } = opts;
const verified = verifyCacheSurfacePact(pact, surface);
useRenderEffect(() => {
const receipt = {
id: `${lane.laneKey}:cache-pact:${pactKey}`,
at: Date.now(),
laneId: lane.laneId,
laneKey: lane.laneKey,
requestKey: `cache-pact:${pactKey}`,
url: "cache-pact://surface",
ok: verified.ok,
status: verified.ok ? 200 : 206,
ms: 0,
cache: cacheLaneId,
dedupe: "required",
note: verified.ok ? pact.id : `${pact.id}:failures:${verified.failures.length}`,
};
cacheLedger.write("receipts", [...cacheLedger.read("receipts"), receipt]);
cacheLedger.write("last", receipt);
});
return verified;
}
Step 4: Render pact evidence in the panel
export function CachePactEvidence({ verified, pactId }: any) {
if (verified.ok) return null;
return (
<section data-cache-pact={pactId} data-ok="false">
<strong>Cache pact violations</strong>
<ol>
{verified.failures.map((f: any, i: number) => (
<li key={i} data-failure={f.kind}>
{f.kind}:{f.key}
</li>
))}
</ol>
<pre data-cache-pact-json>{JSON.stringify(verified, null, 2)}</pre>
</section>
);
}
Step 5: Use the pact in a bridged details panel
import { cacheBridgeRead } from "../cache-bridges/useCacheBridge";
import { OrdersCacheSurfacePact } from "./cache-pacts";
import { usePactedCacheSurface } from "./usePactedCacheSurface";
import { CachePactEvidence } from "./CachePactEvidence";
export function OrderDetailsPanel({ cacheBridge, cacheLedger, lane, cacheLaneId }: any) {
const surface = cacheBridgeRead(cacheBridge, "orders:list");
const verified = usePactedCacheSurface({
pact: OrdersCacheSurfacePact,
surface,
cacheLedger,
lane,
cacheLaneId,
pactKey: "orders:list",
});
return (
<aside data-panel="order" data-cache-key={verified.surface.cacheKey} data-result={verified.surface.result}>
<h3>Details</h3>
<CachePactEvidence verified={verified} pactId={OrdersCacheSurfacePact.id} />
<div>Total: {verified.surface.value.total}</div>
<div>
Age: {verified.surface.ageMs}ms / TTL: {verified.surface.ttlMs}ms
</div>
</aside>
);
}
Even when the cache surface is malformed, the UI still renders and the evidence becomes part of the route story.
Checklist
- Cache pact ids are versioned and stable.
- Verification runs at render time and coerces defaults.
- Failures are rendered as evidence (not hidden in logs).
- Pact receipts are written into the cache ledger.
- Tests assert against
data-cache-pactand ledger receipts.