From 25b5cf62248b788f755237a3af8af3422d554880 Mon Sep 17 00:00:00 2001 From: Dogfather Date: Mon, 21 Sep 2026 13:04:38 +0200 Subject: [PATCH] "Unveraenderlich" stand auch auf Adressen, die sich aendern Beim Nachmessen der Crew-Sperre von vorhin: Der Ursprung lieferte korrekt 404 und eine bereinigte gate.css -- Cloudflare lieferte weiterhin die ALTE Fassung, 27 806 Byte, `cf-cache-status: HIT`. DIE URSACHE STAND IM EIGENEN KOMMENTAR. In index.js hiess es: "`originalUrl` steht hier nicht zur Verfuegung -- geprueft wird deshalb die Dateiart." Nachgemessen stimmt das nicht: `res.req .originalUrl` ist da und liefert "/start.html?v=123". Die Annahme war nie geprueft worden, und aus ihr folgte eine Zusage, die das Haus nicht halten kann: JEDE CSS- und JS-Datei ging mit `max-age=31536000, immutable` hinaus -- auch unter ihrer Adresse OHNE Stempel. "immutable" heisst woertlich: Der Inhalt unter DIESER Adresse aendert sich nie. Fuer `gate.css?v=...` stimmt das, der Name wechselt ja mit dem Inhalt. Fuer `gate.css` ist es falsch -- und dort haette die alte Fassung ein Jahr im Zwischenspeicher gelegen, waehrend der Server laengst etwas anderes sagt. Ein Zwischenspeicher, der etwas Falsches zeigt, ist schlimmer als gar keiner: man glaubt ihm. Jetzt haengt die Zusage an der Bedingung, die sie traegt. Mit Stempel bleibt alles wie bisher -- die Seiten rufen ohnehin immer `?v=...` auf, dort kostet es nichts. Ohne Stempel wird nachgefragt. Die oeffentliche Seite benutzt ebenfalls `?v=` (mit Buchstabe am Ende); geprueft wird nur, OB einer da ist, nicht wie er aussieht. DIESELBE SORTE FEHLER GLEICH NEBENAN: pruef-zwischenspeicher hat einen Abschnitt "Was einen Stempel traegt, darf liegenbleiben" -- und rief die Dateien OHNE Stempel ab. Auch hier beschrieb der Text etwas, das der Code nicht tat. Er liest den Stempel jetzt aus der Seite und misst damit genau die Adresse, die ein Browser anfordert. Dazu vier neue Zeilen fuer den umgekehrten Fall, mit Gegenprobe an derselben Datei -- sonst waere "ohne Stempel: no-cache" auch dann gruen, wenn ALLES auf no-cache stuende, und jeder Seitenaufruf waere unnoetig langsam. UND MEIN EIGENER FEHLER VON GESTERN, behoben: Die Stempelpruefung verglich seit dem 20.09. den Stempel mit dem ZEITPUNKT des letzten Commits an assets/. Das kann gar nicht aufgehen -- der Stempel wird immer VOR dem Committen gezogen, die Commit-Zeit ist also immer die juengere. Stempeln um 12:59 und Committen um 13:00 genuegte fuer einen Fehlalarm; heute ist genau das passiert. Gefragt wird jetzt die REIHENFOLGE der Commits, die hat keine Uhr: Liegt der letzte Commit an den HTML-Seiten (dort steht der Stempel) auf oder nach dem letzten an assets/? `merge-base --is-ancestor` beantwortet das ohne Zeitstempel. Mit Gegenprobe ueber das Elternteil -- und mit drittem Ausgang, wenn es keines gibt. pruef-zwischenspeicher: 27 Pruefungen, 0 Fehler (war 22, davon 2 rot) NOCH OFFEN und nur von Filipe zu machen: Cloudflare haelt die alten Fassungen unter den stempellosen Adressen weiter im Zwischenspeicher. Der Ursprung ist sauber; geleert werden muss der Rand einmal von Hand. Co-Authored-By: Claude Opus 5 --- server/index.js | 39 ++++++- server/pruef-zwischenspeicher.mjs | 164 +++++++++++++++++++++--------- 2 files changed, 152 insertions(+), 51 deletions(-) diff --git a/server/index.js b/server/index.js index a6be4e86..56773b1f 100644 --- a/server/index.js +++ b/server/index.js @@ -430,10 +430,37 @@ app.use(express.static(SITE_DIR, { res.setHeader("Cache-Control", "no-cache"); return; } - /* Mit Versionsstempel in der Adresse: Der Name ist der Beweis. - `originalUrl` steht hier nicht zur Verfuegung -- gepruefft wird - deshalb die Dateiart, und die Stempel setzt ohnehin nur das - Haus selbst. */ + /* "UNVERAENDERLICH" NUR, WENN DIE ADRESSE DAS AUCH HERGIBT + (berichtigt 21.09.2026). + + Hier stand: "`originalUrl` steht hier nicht zur Verfuegung -- + geprueft wird deshalb die Dateiart." Nachgemessen stimmt das + nicht: `res.req.originalUrl` ist da und liefert + "/start.html?v=123". Die Annahme war nie geprueft worden, und + aus ihr folgte eine Zusage, die das Haus nicht halten kann. + + WAS DARAUS WURDE: Jede CSS- und JS-Datei ging mit + `max-age=31536000, immutable` hinaus -- auch unter ihrer + Adresse OHNE Stempel. "immutable" heisst woertlich: Der Inhalt + unter dieser Adresse aendert sich nie. Fuer `gate.css?v=...` + stimmt das; fuer `gate.css` ist es falsch, denn genau dort + aendert er sich bei jeder Auslieferung. + + UND ES WAR NICHT THEORETISCH. Am 21.09. wurde crew-haus.css auf + die zweite Adresse begrenzt und die Crew-Adresse aus gate.css + entfernt. Der Ursprung lieferte danach korrekt 404 und eine + bereinigte Datei -- Cloudflare aber weiterhin die ALTE Fassung + aus seinem Zwischenspeicher, 27 806 Byte, `cf-cache-status: + HIT`. Mit einem Jahr Zusage haette sie dort ein Jahr gelegen. + Ein Zwischenspeicher, der etwas Falsches zeigt, ist schlimmer + als gar keiner -- man glaubt ihm ja. + + Mit Stempel bleibt alles wie bisher: Die Seiten rufen ohnehin + immer `?v=...` auf, dort kostet die Aenderung nichts. Ohne + Stempel wird nachgefragt (`no-cache` heisst nicht "nicht + speichern", sondern "vorher rueckfragen"). */ + const adresse = String(res.req?.originalUrl || ""); + const mitStempel = /[?&]v=/.test(adresse); /* AUSNAHME: sw.js. Der Service Worker wird OHNE Stempel geladen (glocke.js: register('/workspace/sw.js')). Ein Jahr unveraenderlich hiesse: Ein Fehler darin bliebe ein Jahr stehen. Browser @@ -444,7 +471,9 @@ app.use(express.static(SITE_DIR, { return; } if (/\.(css|js|mjs)$/.test(pfad)) { - res.setHeader("Cache-Control", "public, max-age=31536000, immutable"); + res.setHeader("Cache-Control", mitStempel + ? "public, max-age=31536000, immutable" + : "no-cache"); return; } if (/\.(webp|png|jpe?g|svg|gif|ico|woff2?)$/.test(pfad)) { diff --git a/server/pruef-zwischenspeicher.mjs b/server/pruef-zwischenspeicher.mjs index 50c04d56..55befb97 100644 --- a/server/pruef-zwischenspeicher.mjs +++ b/server/pruef-zwischenspeicher.mjs @@ -100,11 +100,58 @@ for (const p of ["/workspace/start.html", "/workspace/aufgaben.html", `${p}: HTTP ${r.status}, ${r.cc || "(keine Angabe)"}`); } +/* DIESER ABSCHNITT RIEF BIS ZUM 21.09.2026 OHNE STEMPEL AB. + + Er heisst "Was einen Stempel traegt" und fragte + "/workspace/assets/css/start.css" -- ohne `?v=`. Gemessen hat er + damit etwas anderes, als er behauptet. Aufgefallen ist es erst, als + der Server anfing, die beiden Faelle wirklich zu unterscheiden. + + Der Stempel wird aus der Seite GELESEN, nicht erfunden: So misst + dieser Abschnitt genau die Adresse, die ein Browser auch anfordert. */ +const seiteRoh = await (await fetch(BASIS + "/workspace/start.html", + { headers: { cookie: KEKS } })).text(); +const STEMPEL = (seiteRoh.match(/[?&]v=(\d{6,})/) || [])[1] || ""; +ok(STEMPEL.length >= 6, `der Stempel der Seite ist ablesbar (${STEMPEL || "KEINER"})`); + console.log("\n=== Was einen Stempel trägt, darf liegenbleiben"); for (const p of ["/workspace/assets/css/start.css", "/workspace/assets/js/kopf.js", "/workspace/assets/css/module.css"]) { + const r = await kopf(`${p}?v=${STEMPEL}`); + ok(/max-age=\d{6,}/.test(r.cc || ""), `${p}?v=…: ${r.cc || "(keine Angabe)"}`); +} + +/* ===================================================================== + OHNE STEMPEL DARF NICHTS "UNVERAENDERLICH" HEISSEN (21.09.2026) + + `immutable` ist eine woertliche Zusage: Der Inhalt unter DIESER + Adresse aendert sich nie. Mit Stempel stimmt sie -- der Name wechselt + ja mit dem Inhalt. Ohne Stempel ist sie falsch, und zwar genau dort, + wo es wehtut. + + WAS PASSIERT IST: Am selben Tag wurde crew-haus.css auf die zweite + Adresse begrenzt und die Crew-Adresse aus gate.css entfernt. Der + Ursprung lieferte danach 404 beziehungsweise eine bereinigte Datei. + Cloudflare lieferte weiter die ALTE -- 27 806 Byte, + `cf-cache-status: HIT`. Mit einem Jahr Zusage haette sie dort ein + Jahr gelegen, waehrend der Server laengst etwas anderes sagt. + + Ein Zwischenspeicher, der etwas Falsches zeigt, ist schlimmer als + gar keiner -- man glaubt ihm ja. + ===================================================================== */ +console.log("\n=== Ohne Stempel wird nachgefragt"); +for (const p of ["/workspace/assets/css/start.css", "/workspace/assets/js/kopf.js"]) { const r = await kopf(p); - ok(/max-age=\d{6,}/.test(r.cc || ""), `${p}: ${r.cc || "(keine Angabe)"}`); + ok(/no-cache|no-store|max-age=0/.test(r.cc || ""), + `${p} (ohne ?v=): ${r.cc || "(keine Angabe)"}`); + /* GEGENPROBE an derselben Datei: Mit Stempel muss dieselbe Adresse + das Gegenteil sagen. Ohne diese Zeile waere der Haken darueber + auch dann gruen, wenn ALLES auf "no-cache" stuende -- und dann + waere jeder Seitenaufruf unnoetig langsam, ohne dass es jemandem + auffiele. */ + const mit = await kopf(`${p}?v=${STEMPEL}`); + ok(/immutable/.test(mit.cc || ""), + ` und mit Stempel sehr wohl: ${mit.cc || "(keine Angabe)"}`); } console.log("\n=== Der Service Worker NICHT"); @@ -289,55 +336,80 @@ console.log("\n=== Der Versionsstempel — daran hängt das automatische Update" if (!letzterCommit) { console.log(" -- konnte nicht nachsehen: kein Commit an workspace/assets gefunden"); } else { - /* DIE FRAGE IST NICHT "STEHT BEIDES IM SELBEN COMMIT", SONDERN - "KOMMT DIE ÄNDERUNG AN" (geschärft 20.09.2026). + /* DIE FRAGE IST "KOMMT DIE ÄNDERUNG AN" -- UND SIE WIRD AN DER + REIHENFOLGE BEANTWORTET, NICHT AN DER UHR (21.09.2026). - Vorher verlangte diese Stelle, dass derselbe Commit die - Stilvorlage UND die HTML ändert. Das ist der Normalfall (der - Stempel wird vor dem Committen gezogen), aber nicht die Regel: - Wird der Stempel in einem FOLGE-Commit nachgezogen, kommt die - Änderung genauso an — die Prüfung blieb trotzdem rot und - beschuldigte einen Commit, an dem nichts falsch war. + DREI FASSUNGEN, ZWEI DAVON FALSCH -- der Weg hierher gehört + in die Akte: - Verglichen wird deshalb der Stempel selbst mit dem Zeitpunkt - der letzten Änderung an assets/. Ein Stempel, der JÜNGER ist - als die Änderung, wurde danach gezogen — egal in welchem - Commit. Einer, der älter ist, kann sie nicht enthalten. + (1) Bis 20.09.: "derselbe Commit muss Stilvorlage UND HTML + ändern". Das ist der Normalfall, aber nicht die Regel. + Wird der Stempel in einem FOLGE-Commit nachgezogen, kommt + die Änderung genauso an -- die Prüfung blieb trotzdem rot + und beschuldigte einen Commit, an dem nichts falsch war. - Das ist strenger als die alte Fassung, nicht lockerer: Eine - HTML-Änderung aus einem ganz anderen Grund (ein Tippfehler im - Text) hätte die alte Prüfung besänftigt, ohne dass der Stempel - gezogen wurde. Die Zahl lügt nicht. */ - const wannRoh = git("log", "-1", "--format=%cI", letzterCommit).stdout.trim(); - const wann = new Date(wannRoh); - /* Der Stempel heißt JJJJMMTTSSMM in ORTSZEIT -- so setzt ihn - tools/workspace-stempel.mjs. Also auch in Ortszeit vergleichen - und nicht in UTC, sonst wäre die Prüfung zwei Stunden lang im - Jahr falsch. */ - const z = (n) => String(n).padStart(2, "0"); - const alsStempel = (dd) => Number(`${dd.getFullYear()}${z(dd.getMonth() + 1)}` - + `${z(dd.getDate())}${z(dd.getHours())}${z(dd.getMinutes())}`); - const stempelZahl = Number(alle[0]); - const commitZahl = alsStempel(wann); - const passt = Number.isFinite(stempelZahl) && Number.isFinite(commitZahl) - && stempelZahl >= commitZahl; - ok(passt, - passt - ? `der Stempel (${stempelZahl}) ist jünger als die letzte Änderung an assets/` - + ` (${letzterCommit.slice(0, 7)}, ${commitZahl}) — sie kommt an` - : `der Stempel (${stempelZahl}) ist ÄLTER als die letzte Änderung an assets/` - + ` (${letzterCommit.slice(0, 7)}, ${commitZahl}) — der Browser behält seine` - + " alte Kopie, die Änderung käme bei niemandem an"); + (2) Am 20.09. daraufhin auf einen ZEITVERGLEICH umgestellt: + Stempel gegen Commit-Zeitpunkt. Klingt sauber und ist es + nicht -- der Stempel wird IMMER vor dem Committen gezogen, + also ist die Commit-Zeit immer die jüngere. Stempeln um + 12:59 und Committen um 13:00 genügte für einen Fehlalarm. + Am 21.09. ist genau das passiert, eine Minute Versatz. - /* GEGENPROBE: Diese Zeile ist ein Zahlenvergleich, und ein - Zahlenvergleich bestaetigt gern. Sie ist auf dem Weg hierher - einmal an NaN gescheitert -- also an einem Fehler in ihr - selbst, nicht an einem echten Befund. Deshalb wird hier - nachgewiesen, dass sie einen ECHTEN alten Stempel auch - erkennt. */ - const alterStempel = commitZahl - 1; - ok(!(alterStempel >= commitZahl) && stempelZahl >= commitZahl, - `Gegenprobe: ein Stempel von ${alterStempel} wuerde durchfallen, ${stempelZahl} nicht`); + (3) Jetzt: die REIHENFOLGE der Commits. Sie hat keine Uhr, und + damit auch keinen Versatz. + + Der Stempel steht in den HTML-Dateien. Die Frage "wurde + nach der letzten Änderung an assets/ neu gestempelt?" + lautet deshalb: Liegt der letzte Commit an den HTML-Seiten + auf oder nach dem letzten Commit an assets/? + + gleicher Commit -> zusammen gestempelt, gut + HTML ist Nachfahre -> danach nachgezogen, gut + sonst -> assets/ ist weiter, der + Browser behält seine alte + Kopie + + `merge-base --is-ancestor` beantwortet das ohne Zeitstempel: + Rückgabewert 0 heißt "A liegt auf dem Weg zu B". */ + const htmlCommit = git("log", "-1", "--format=%H", "--", "workspace/*.html") + .stdout.trim(); + if (!htmlCommit) { + /* DRITTER AUSGANG. Ohne HTML-Commit ist die Frage nicht zu + beantworten -- und das ist kein "in Ordnung". */ + console.log(" -- konnte nicht nachsehen: kein Commit an den HTML-Seiten"); + } else { + const istVorfahr = (a, b) => + git("merge-base", "--is-ancestor", a, b).status === 0; + const gleich = htmlCommit === letzterCommit; + const danach = gleich || istVorfahr(letzterCommit, htmlCommit); + ok(danach, + danach + ? (gleich + ? `Stempel und Stilvorlagen stehen im selben Commit ` + + `(${letzterCommit.slice(0, 7)}) -- die Änderung kommt an` + : `der Stempel wurde nach der Änderung an assets/ nachgezogen ` + + `(${letzterCommit.slice(0, 7)} → ${htmlCommit.slice(0, 7)})`) + : `assets/ wurde zuletzt in ${letzterCommit.slice(0, 7)} geändert, die ` + + `HTML-Seiten schon in ${htmlCommit.slice(0, 7)} -- es wurde seither ` + + `NICHT neu gestempelt, der Browser behält seine alte Kopie`); + + /* GEGENPROBE: Kann dieser Vergleich überhaupt "nein" sagen? + Ein Elternteil des HTML-Commits ist garantiert ÄLTER -- + dreht man die Frage um, muss sie falsch herauskommen. Ohne + diese Zeile wäre der Haken darüber auch dann grün, wenn + `is-ancestor` immer 0 lieferte. + + Beim allerersten Commit gibt es kein Elternteil; dann ist + die Gegenprobe nicht durchführbar und sagt das auch. */ + const eltern = git("rev-parse", `${htmlCommit}^`).stdout.trim(); + if (!eltern || eltern.startsWith("fatal")) { + console.log(" -- Gegenprobe nicht möglich: der Commit hat kein Elternteil"); + } else { + ok(!istVorfahr(htmlCommit, eltern), + `Gegenprobe: umgekehrt gefragt sagt derselbe Vergleich nein ` + + `(${htmlCommit.slice(0, 7)} liegt nicht vor ${eltern.slice(0, 7)})`); + } + } } } }