From 8f55c5e4d49c43ce2b504ee4c283f9c166cacde6 Mon Sep 17 00:00:00 2001 From: Manas Srivastava Date: Sat, 30 May 2026 14:00:05 +0530 Subject: [PATCH] fix(api): /healthz Cache-Control: no-store (BUG-API-300) /healthz is the rule-14 build-SHA gate surface. Without a Cache-Control directive, intermediaries (CF edge, browser fetch cache, NR synthetic, kubectl-port-forward'd browser tab) may return a stale commit_id for seconds-to-minutes after a rollout, silently breaking the "did the new image actually land" verification. Stamp `Cache-Control: no-store` so probes always hit the live pod. Coverage block: Symptom: /healthz returns identical JSON every call (BUG-API-300) Enumeration: rg -F '/healthz' internal/router/router.go (1 emit) Sites found: 1 (router.go:414) Sites touched: 1 Coverage test: TestHealthzShape pins Cache-Control: no-store header so a future deletion of c.Set(HeaderCacheControl, "no-store") fails before merge. Live verified: pending auto-deploy + curl https://api.instanode.dev/healthz -I | grep -i cache-control Co-Authored-By: Claude Opus 4.7 (1M context) --- internal/router/healthz_test.go | 14 ++++++++++++++ internal/router/router.go | 11 +++++++++++ 2 files changed, 25 insertions(+) diff --git a/internal/router/healthz_test.go b/internal/router/healthz_test.go index 6736a3bd..cd5bbe30 100644 --- a/internal/router/healthz_test.go +++ b/internal/router/healthz_test.go @@ -52,6 +52,10 @@ func TestHealthzShape(t *testing.T) { app.Get("/healthz", func(c *fiber.Ctx) error { m := reader.Get(c.UserContext()) uptimeSeconds := int64(time.Since(fixedStart).Seconds()) + // BUG-API-300: mirror the router.go stamp of + // Cache-Control: no-store so the wire-contract assertion + // below catches a future regression that drops the header. + c.Set(fiber.HeaderCacheControl, "no-store") return c.JSON(fiber.Map{ "ok": true, "service": "instanode-api", @@ -145,6 +149,16 @@ func TestHealthzShape(t *testing.T) { require.Equal(t, float64(int64(uptimeRaw)), uptimeRaw, "BUG-P272: uptime_seconds must round to an integer; got %f", uptimeRaw) + // BUG-API-300 (QA 2026-05-29): /healthz is the rule-14 build-SHA + // gate surface. Without a Cache-Control directive every intermediary + // (CF edge, browser fetch cache, NR synthetic) may return a stale + // commit_id for seconds-to-minutes after a rollout, silently breaking + // the "did the new image actually land" verification. Pin + // `no-store` here so any future deletion of the header on the + // router.go path fails this assertion before merge. + require.Equal(t, "no-store", resp.Header.Get("Cache-Control"), + "BUG-API-300: /healthz must stamp Cache-Control: no-store so build-SHA reads never come from a cached layer") + require.NoError(t, mock.ExpectationsWereMet()) } diff --git a/internal/router/router.go b/internal/router/router.go index 48f2341f..8a9155e0 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -445,6 +445,17 @@ func NewWithHooks(cfg *config.Config, db *sql.DB, rdb *redis.Client, geoDbs *mid // jitter has no diagnostic value at this surface and would defeat // HTTP-cache deduplication if anyone proxies the response. uptimeSeconds := int64(time.Since(processStartFunc()).Seconds()) + // BUG-API-300 (QA 2026-05-29): /healthz is the canonical surface + // the rule-14 build-SHA gate reads after every deploy. Without a + // Cache-Control hint, any intermediary (Cloudflare edge, browser + // fetch cache, kubectl-port-forward'd browser tab, NR synthetic + // monitor) may return a stale commit_id read for seconds-to-minutes + // after a rollout — silently breaking the "did the new image + // actually land" verification. Stamp `no-store` so probes always + // hit the live pod. Cost: a few hundred bytes of header per probe; + // upside: zero stale build-SHA reads, matching the contract canaries + // already assume. Pairs with the OPTIONS shim above. + c.Set(fiber.HeaderCacheControl, "no-store") return c.JSON(fiber.Map{ "ok": true, "service": "instanode-api",