contract

cache-purge specs/cache-purge/contract.kmd

Specification body

Cache-purge contract

This spec defines the cross-component contract that every Koder Stack implementation of "cache-purge HTML injection" MUST follow. It exists because the same mechanism is implemented in four places (Koder Jet v1.28+, the Go SDK middleware, koder_kit Flutter web, and koder_web_kit vanilla JS), and we need a single source of truth for which meta tag to use, what content values mean, and how idempotency is detected so implementations never disagree.

The contract is the meta tag. Everything else (snippet shape, gate strategy, transport encoding) is implementation detail and may diverge.

1. Meta tag

Every implementation that injects cache-clearing logic into an HTML response MUST emit, somewhere inside <head> and BEFORE any logic that clears caches:

<meta name="koder-cache-purge" content="<value>">

The name attribute MUST be exactly koder-cache-purge (lowercase, no whitespace). Implementations that scan for the marker MUST be case-insensitive on the name value but the canonical emission is lowercase.

2. Reserved content values

Value Meaning
self The application (or its framework) injected the snippet itself. The host is asserting ownership of the purge policy. Outer layers (jet, SDK middleware) MUST skip injection on responses carrying this value.
injected-by-jet-121 Koder Jet 1.28+ performed the injection. The 121 suffix tracks the issue that established the convention.
injected-by-sdk-go-002 The Koder Go SDK middleware (SDKGO-002) performed the injection.
injected-by-kit-016 koder_kit (KIT-016) performed the injection (Flutter web build-time or runtime).
injected-by-web-kit-006 koder_web_kit (WEBKIT-006) performed the injection.

A new implementation MUST register a new content value here in the form injected-by-<slug>-<ticket-id> before merging. Do not reuse an existing value.

3. Idempotency rule

Any layer that would inject a cache-purge snippet MUST first scan the response head buffer for <meta name="koder-cache-purge" ...> (any content value). If found, the layer MUST skip injection — including both the meta tag and the script.

The match SHOULD be case-insensitive on the name attribute and quote-style tolerant (single quotes, double quotes, or unquoted are all accepted). A regex equivalent to:

(?i)<meta\b[^>]*\bname\s*=\s*["']?koder-cache-purge["']?[^>]*>

is the canonical pattern. The reference implementation lives in infra/net/jet/internal/middleware/cache_purge.go (cachePurgeMetaTagRe).

This single rule covers two scenarios with one mechanism:

  • App self-declaration — the application or a higher-level SDK (koder_kit.declareCachePurgeSelf()) emits content="self" so outer layers (jet, server-side SDK middleware) defer to it.
  • Pipeline double-pass — a response that already passed through one cache-purge layer carries the marker; a second pass detects it and becomes a no-op. This makes the chain safe to compose in any order.

4. Snippet body (informative)

The cache-clearing JavaScript is not part of the contract. Each implementation may emit whatever IIFE it prefers, as long as it follows the meta tag in document order. The reference body used by Koder Jet:

<script>
(function(){
  if ('caches' in window) {
    caches.keys().then(function(k){ k.forEach(function(n){ caches.delete(n); }); });
  }
  if ('serviceWorker' in navigator) {
    navigator.serviceWorker.getRegistrations().then(function(r){
      r.forEach(function(x){ x.unregister(); });
    });
  }
})();
</script>

SDKs MAY emit a different snippet (e.g. with subresource cleanup, with SDK-version annotations) provided it is functionally equivalent and runs synchronously enough to complete before the page begins making new fetches.

5. Insertion point

The meta tag + script SHOULD be inserted immediately before </head>. Implementations MAY fall back to immediately before <body> when no </head> exists. Implementations MUST NOT inject into responses without either marker.

6. Encoding

Implementations MUST round-trip the response encoding. If the response arrives compressed (gzip, br, zstd), the implementation MUST decode → inject → re-encode with the same algorithm and adjust Content-Length. When re-encoding fails, the implementation MUST drop Content-Encoding and send identity, NEVER deliver a corrupt encoded body.

7. Scoping & gates (informative)

Whether to inject for a given request is implementation-specific. The Koder Jet reference offers three orthogonal axes (domain glob, client IP allow-list, client IP deny-list with deny-wins-allow precedence) but SDKs may expose simpler or richer policy. The contract does NOT mandate a specific gate set — only the meta tag and idempotency.

8. Versioning

This spec is versioned by date in the date frontmatter field. Breaking changes to the meta tag name or to the idempotency match pattern require:

  1. Updating this spec with a new section "Breaking change YYYY-MM-DD"
  2. Updating every listed implementation in the same release window
  3. Adding a transition period during which both old and new markers are recognised

Reserved content values are append-only — never remove or repurpose.

References