Data Fetching
Dec 19, 2023180 min read

Cache Pacts: Deep Dive #89

NR
Niko Rahman

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:

  1. the cache surface keys you require,
  2. the defaults/coercions you allow,
  3. the failure posture (fail-forward + evidence),
  4. 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:

  1. define a pact for the orders:list cache surface,
  2. verify it at render time,
  3. emit receipts into the cache ledger,
  4. render pact evidence into the DOM,
  5. 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-pact and ledger receipts.