Files
felhom.eu/documentation/audits/app-page-2026-10-15b/P0/VERDICT.md
T
adminandClaude Opus 5 b6638ac4fc
gates / gates (push) Successful in 6m46s
docs: the app page's layout, the asset tag, and a version number pushed once (controller v0.316.0)
02-controller-module-map.md "The app page" gains the layout rules [DESIGN] — one column of one
width, a section is a hairline not a card, a box means something (blue = the app is waiting for
you, amber = something is risky, nothing else), the hero is not a box, and a success is not a
banner. Plus the asset-tag rule: every shell's stylesheet address is derived from the BUILD, not
the version, because the origin sends no validator and a cached copy can therefore only expire or
be addressed differently.

09-update-architecture.md gains §0 [DESIGN]: a version number is pushed once. It belongs at the TOP
of that document because everything below it compares versions — the floor moves a box by comparing
a number, the button offers the highest tag, and a box re-pulls a tag only when the tag changes.
Two images under one number is therefore not untidiness; it is a box running the wrong code that
can never be told.

R-1080 filed and closed: 0.315.0 was pushed three times from three different commits. Closed rather
than left open because no box is stranded — exactly one guest ran it and it ran the registry's
current digest, and the floor was 0.314.0 so nothing else could have taken it.

audits/app-page-2026-10-15b/P0/ carries the measurement and its honest caveat: the stale Cloudflare
object is no longer reproducible, because the first probe of the session forced its revalidation.
What is shown is every precondition, the proof an aged entry existed (cf-cache-status EXPIRED), and
the elimination of the only other candidate. The six stylesheet bodies are reduced to their sizes,
sha256s and rule counts — one 99 KB file does not need six copies in git to make the point.

.claude/rules/unprompted-work.md item 6, all four versioned copies: a version number is pushed once,
stated as a FACT about what the build now enforces rather than as a new rule.

STATUS: the operator's one look, through the public address.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117kRTSgs3rJWUFGxyoWN6Y
2026-10-15 20:33:10 +02:00

3.4 KiB

Part 0 — why the tab bar rendered unstyled through the public address

Measured 2026-10-15 ~19:55 CEST from DooPlex (Hungary, so the geo rule passes). Nothing was changed.

Verdict: (a) a cache — and the mechanism is named

Not (b): exactly one box runs 0.315.0 and it runs the registry's current image.

Box pinned tag status
demo-hp guest 9201 felhom-controller:0.315.0 Up, healthy
demo-hp guest 9202 felhom-controller:0.314.0 Up, healthy
felhom-pve (N100) guest 9201 felhom-controller:0.314.0 Up, healthy
c11-appliance (Tester 1) unreachable from DooPlex right now —
  • demo-hp 9201's running image: gitea.dooplex.hu/admin/felhom-controller@sha256:8887343011d64a2da9c65674a777a099708315063e3abc7c9463c03151dff041
  • The registry's current 0.315.0: sha256:8887343011d64a2da9c65674a777a099708315063e3abc7c9463c03151dff041 — identical.
  • The global floor is 0.314.0 (read from the hub's /configuration), so no box could have taken 0.315.0 by itself; the one that has it was deployed by hand. Tester 1 therefore cannot be on a stale 0.315.0 even though it is off right now.

The four facts that make a cache the cause

  1. The stylesheet URL does not change between builds. layout.html L12 asks for /static/style.css?v={{.Version}} → ?v=0.315.0 for all three different 0.315.0 builds (e66d466, fe0228c, db52f5a). The first of those had no .app-tabs rules at all.
  2. The origin offers no validator. serveCSSHandler (server.go:1130) sets only Cache-Control: public, max-age=3600. Measured on the box: ETag present = 0, Last-Modified present = 0. With neither, no client and no edge can revalidate a copy — it can only expire.
  3. Cloudflare caches this URL and extends the browser's TTL. Three requests in a row: cf-cache-status: HIT, age: 99 → 100, and cache-control: public, max-age=14400 — Cloudflare rewrites the origin's 1 hour to 4 hours for the browser.
  4. Cloudflare held a stale entry for that exact URL. The very first request of the session returned cf-cache-status: EXPIRED — which only happens when the edge has an entry that has aged past its TTL. Having no validator, it had to refetch the whole body.

What is true right now, and the honest caveat

Origin, edge and box agree byte-for-byte (sha256 c1774f63daf65c56 both sides) and all contain the .app-tabs rules. The page itself is never cached (cf-cache-status: DYNAMIC); only the stylesheet is.

The caveat, stated rather than glossed: the specific stale object is no longer observable — the first probe of this session forced its revalidation, which is exactly what EXPIRED records. So I cannot produce a HIT whose body lacks .app-tabs. What is shown is every precondition for it, the proof that an aged entry existed, and the elimination of the only other candidate (b).

Why this authorises Part 1.2

The fix is not "purge the cache" — that hides the cause and returns at the next build. The fix is that the stylesheet's address changes with every build, so no cache at any layer (browser, Cloudflare, or anything between) can serve a previous build's CSS under an address the new build still asks for. Cache-Control stays as it is: the new URL is the fix, not a shorter cache.

Part 1.1 removes the precondition entirely — a version number can only be pushed once, so two different images can never share a number again.