From 954426615fb403e91f5089ff21aaf6d3022b9fe2 Mon Sep 17 00:00:00 2001 From: Dogfather Date: Fri, 25 Sep 2026 23:19:50 +0200 Subject: [PATCH] Der Pruefstand bekommt selbst eine Pruefung -- und der Mensch ein Stueck der Notiz MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Jede Nacht laeuft tools/nachtlauf.mjs ueber 193 Pruefdateien und schreibt das Ergebnis als Notiz in Filipes Vault. Diese Notiz ist das Einzige, was er von dem ganzen Aufwand zu sehen bekommt. Und sie war die einzige Datei im Haus, die niemand geprueft hat. === WARUM DAS VORHER GAR NICHT GING === nachtlauf.mjs startet BEIM IMPORT sofort einen Lauf. Wer die Notiz messen wollte, haette damit eine halbe Stunde Rechenzeit ausgeloest -- und nebenbei das echte Ergebnis auf Filipes Startseite ueberschrieben. Am 04.09.2026 hat genau so ein Griff bei RunOne einen roten Alarm ueber einer fehlerfreien Buchhaltung erzeugt, am Morgen einer Vorstellung. „Ein Prueflauf ist kein harmloser Lesevorgang." Der Textbau steht deshalb jetzt in tools/nachtlauf-notiz.mjs: nur Textbau, kein Nebeneffekt, Importieren ist folgenlos. === UND EIN STUECK DER NOTIZ GEHOERT AB JETZT DEM MENSCHEN === Oben in der Notiz stand: „von Hand aendern bringt nichts, beim naechsten Lauf ist es wieder ueberschrieben." Das war ehrlich und trotzdem falsch herum gedacht. Das Wertvollste an diesem Pruefstand ist naemlich nicht die Zahl, sondern der Satz daneben: „die hier ist rot, weil sie den Umbau vom 24.09. nicht mitbekommen hat" oder „die ist echt, Finger weg". Genau dieser Satz wurde jede Nacht geloescht. Heute Nacht standen 37 Dateien in der Liste, und bei 25 davon waere so ein Satz die halbe Arbeit gewesen. Jetzt gibt es einen Bereich zwischen zwei Marken, den der Lauf wieder einsetzt statt ihn zu ueberschreiben. Fehlt er, wird er leer neu angelegt -- mit der Erklaerung darin, wozu er da ist. === 30 PRUEFUNGEN, JEDE MIT GEGENPROBE === 1. Der Block „von Hand" ueberlebt einen Lauf -- UND das alte Ergebnis daneben verschwindet wirklich. Ohne die zweite Haelfte wuerde die erste nur beweisen, dass die Datei nicht angefasst wurde. 2. Ein leerer Block zaehlt als nicht vorhanden; sonst waere die Erklaerung fuer immer weg, sobald jemand sie einmal loescht. 3. Weniger Pruefungen als gestern werden gemeldet, auch wenn alles gruen ist (die Lehre vom 28.08.: 789 statt 804, gruen, und acht Pruefungen je Geraet waren still weggefallen). Gegenprobe: Bei MEHR Pruefungen darf der Satz NICHT kommen -- eine Warnung, die immer kommt, ist keine Warnung mehr. 4. Der dritte Ausgang sagt „konnte nicht nachsehen", nennt den Grund und wie alt der letzte echte Stand ist -- und behauptet nirgends, alles sei in Ordnung. 5. „Keine Pruefung gezaehlt" wird als das benannt, was es ist: kein Befund, sondern ein Zaehlproblem. Acht Dateien standen heute Nacht aus diesem Grund als rot da. Punkt 5 der Pruefung hat sich beim ersten Lauf gleich bezahlt gemacht: Sie zaehlt nach, ob JEDER der vier Aufrufe in nachtlauf.mjs den Notizpfad mitreicht. Drei davon sind die seltenen Ausgaenge, die man beim Ausprobieren nie erwischt -- und an jedem einzelnen waere der Block „von Hand" spurlos verschwunden. Beim Umbau hatte ich genau diese drei zuerst vergessen. GEMESSEN: 30 Pruefungen, 0 Fehler. Die echte Notiz im Vault wird dabei nicht angefasst -- gearbeitet wird in einem mktemp-Ordner. Co-Authored-By: Claude Opus 5 --- server/pruef-nachtlauf.mjs | 277 +++++++++++++++++++++++++++++++++++++ tools/nachtlauf-notiz.mjs | 166 ++++++++++++++++++++++ tools/nachtlauf.mjs | 115 +-------------- 3 files changed, 450 insertions(+), 108 deletions(-) create mode 100644 server/pruef-nachtlauf.mjs create mode 100644 tools/nachtlauf-notiz.mjs diff --git a/server/pruef-nachtlauf.mjs b/server/pruef-nachtlauf.mjs new file mode 100644 index 00000000..2adf609f --- /dev/null +++ b/server/pruef-nachtlauf.mjs @@ -0,0 +1,277 @@ +/* ===================================================================== + pruef-nachtlauf.mjs — Wer prüft den Prüfstand? (25.09.2026) + + Jede Nacht läuft `tools/nachtlauf.mjs` über alle Prüfdateien und + schreibt das Ergebnis als Notiz in Filipes Vault. Diese Notiz ist + das Einzige, was er von dem ganzen Aufwand zu sehen bekommt. + + Und sie war bis heute die einzige Datei im Haus, die niemand geprüft + hat. Das ist genau die Stelle, an der ein stiller Fehler am + teuersten ist: Schreibt die Notiz Unsinn, merkt es keiner — sie + sieht ja aus wie immer. + + --------------------------------------------------------------------- + WARUM DAS VORHER GAR NICHT GING + + `nachtlauf.mjs` startet beim Import SOFORT einen Lauf über 193 + Dateien. Wer die Notiz messen wollte, hätte damit eine halbe Stunde + Rechenzeit ausgelöst — und nebenbei das echte Ergebnis auf Filipes + Startseite überschrieben. Das ist keine Kleinigkeit: Am 04.09.2026 + hat genau so ein Griff bei RunOne einen roten Alarm über einer + fehlerfreien Buchhaltung erzeugt, am Morgen einer Vorstellung. + + Deshalb wurde der Textbau nach `tools/nachtlauf-notiz.mjs` + ausgelagert. Diese Prüfung importiert nur das Modul; es hat keine + Nebenwirkungen, schreibt nichts und misst nichts. + + --------------------------------------------------------------------- + WAS GEPRÜFT WIRD + + 1. Der Block „VON HAND" überlebt jeden Lauf — und nur er. + 2. Fehlt er, wird er mit seiner Erklärung neu angelegt. + 3. Ein Rückgang der Prüfungszahl steht in der Notiz (28.08.2026). + 4. Der dritte Ausgang sagt „konnte nicht nachsehen" und behauptet + NICHT, alles sei in Ordnung. + 5. Jeder Aufruf in nachtlauf.mjs reicht den Notizpfad mit — + sonst ginge der Block an genau diesem Ausgang verloren. + + Zu jeder Aussage gehört eine Gegenprobe: eine Prüfung, die immer + bestätigt, bestätigt nichts. + + Aufrufen mit: node server/pruef-nachtlauf.mjs + ===================================================================== */ +import { mkdtempSync, rmSync, writeFileSync, readFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import { + MARKE_AUF, MARKE_ZU, handNotizLesen, notizSchreiben, zeitWort, +} from "../tools/nachtlauf-notiz.mjs"; + +const HIER = dirname(fileURLToPath(import.meta.url)); +const NACHTLAUF = join(HIER, "..", "tools", "nachtlauf.mjs"); + +let ok = 0, fehl = 0; +const pruef = (name, bedingung, zusatz = "") => { + if (bedingung) { ok++; console.log(` ok ${name}`); } + else { fehl++; console.log(` FEHL ${name}${zusatz ? " — " + zusatz : ""}`); } +}; +const melde = (t) => console.log(t); + +/* Ein Wegwerf-Ordner. In den Vault wird hier nichts geschrieben -- + die echte Notiz bleibt unberührt, auch wenn die Prüfung abstürzt. */ +const ORDNER = mkdtempSync(join(tmpdir(), "pruef-nachtlauf-")); +const NOTIZ = join(ORDNER, "Pruefstand.md"); +process.on("exit", () => { try { rmSync(ORDNER, { recursive: true, force: true }); } catch { /* egal */ } }); + +const LAGE_GRUEN = { dateien: 193, dateienOk: 193, punkte: 8900, punkteOk: 8900, rote: [] }; +const LAGE_ROT = { + dateien: 193, dateienOk: 192, punkte: 8900, punkteOk: 8899, + rote: [{ datei: "pruef-beispiel.mjs", code: 1, erste: "FEHL irgendetwas stimmt nicht" }], +}; + +/* ======================================================================= + 1. DER BLOCK „VON HAND" ÜBERLEBT + ======================================================================= */ +melde("=== 1. Was von Hand dasteht, bleibt stehen ==="); + +const HANDTEXT = "## Von Hand\n\npruef-beispiel ist rot, weil sie den Umbau vom 24.09.\n" + + "nicht mitbekommen hat. Nicht anfassen, ist in Arbeit."; + +/* Erster Lauf: keine Notiz da -- der Block muss entstehen. */ +writeFileSync(NOTIZ, notizSchreiben({ lage: LAGE_GRUEN, vorher: null, minuten: 12 }, NOTIZ), "utf8"); +{ + const t = readFileSync(NOTIZ, "utf8"); + pruef("eine neue Notiz bekommt den Block von selbst", + t.includes(MARKE_AUF) && t.includes(MARKE_ZU)); + pruef("und er erklärt, wozu er da ist", + /überlebt jeden Lauf/.test(t), + "ein leerer Block ohne Erklärung wird beim ersten Überschreiben gelöscht"); +} + +/* Jetzt schreibt ein Mensch etwas hinein. */ +{ + const t = readFileSync(NOTIZ, "utf8"); + const a = t.indexOf(MARKE_AUF) + MARKE_AUF.length; + const b = t.indexOf(MARKE_ZU); + writeFileSync(NOTIZ, t.slice(0, a) + "\n" + HANDTEXT + "\n" + t.slice(b), "utf8"); +} + +/* Und der nächste Lauf schreibt die Notiz neu -- mit ganz anderem + Ergebnis, damit auch wirklich alles andere ausgetauscht wird. */ +writeFileSync(NOTIZ, notizSchreiben({ lage: LAGE_ROT, vorher: null, minuten: 31 }, NOTIZ), "utf8"); +{ + const t = readFileSync(NOTIZ, "utf8"); + pruef("der Satz von Hand steht nach dem nächsten Lauf noch da", + t.includes("Nicht anfassen, ist in Arbeit.")); + pruef("mit seiner Überschrift", t.includes("## Von Hand")); + /* GEGENPROBE. Bliebe ALLES stehen, wäre die Prüfung oben wertlos: + Sie würde dann nur beweisen, dass die Datei nicht angefasst wurde. */ + pruef("Gegenprobe: das alte Ergebnis ist verschwunden", + !t.includes("Alles in Ordnung"), + "der grüne Stand von vorher steht noch drin -- dann wird gar nicht überschrieben"); + pruef("Gegenprobe: das neue Ergebnis ist da", t.includes("pruef-beispiel.mjs")); +} + +/* Und ein Block ohne Inhalt darf nicht als „von Hand" durchgehen -- + sonst bliebe die Erklärung für immer weg, sobald jemand sie löscht. */ +{ + writeFileSync(NOTIZ, `# x\n\n${MARKE_AUF}\n \n${MARKE_ZU}\n`, "utf8"); + pruef("ein leerer Block zählt als nicht vorhanden", + handNotizLesen(NOTIZ) === null); + writeFileSync(NOTIZ, "# x\n\nkeine Marken hier\n", "utf8"); + pruef("Gegenprobe: ohne Marken wird nichts gefunden", + handNotizLesen(NOTIZ) === null); + writeFileSync(NOTIZ, `# x\n\n${MARKE_AUF}\nhallo\n${MARKE_ZU}\n`, "utf8"); + pruef("Gegenprobe: mit Inhalt wird er gefunden", + handNotizLesen(NOTIZ) === "hallo"); +} + +/* ======================================================================= + 2. EIN RÜCKGANG DER ANZAHL STEHT IN DER NOTIZ + + Die Lehre vom 28.08.2026: Ein Lauf meldete 789 statt 804 Prüfungen + und war dabei grün. Acht Prüfungen je Gerät waren stillschweigend + weggefallen. Wer nur „bestanden" liest, hätte sie ab da dauerhaft + ungeprüft gelassen. + ======================================================================= */ +melde(""); +melde("=== 2. Weniger Prüfungen als gestern ist ein Befund ==="); +{ + const weniger = notizSchreiben({ + lage: { ...LAGE_GRUEN, punkte: 8700, punkteOk: 8700 }, + vorher: { wann: "24.09.2026 um 05:49", zeit: Date.now() - 86400000, + dateien: 193, dateienOk: 193, punkte: 8900, punkteOk: 8900 }, + minuten: 12, + }, NOTIZ); + pruef("200 Prüfungen weniger werden gemeldet, obwohl alles grün ist", + /200 Prüfungen WENIGER/.test(weniger)); + pruef("und es steht dabei, warum das trotzdem ein Befund ist", + /beweist nichts/.test(weniger)); + + const mehr = notizSchreiben({ + lage: LAGE_GRUEN, + vorher: { wann: "24.09.2026 um 05:49", zeit: Date.now() - 86400000, + dateien: 193, dateienOk: 193, punkte: 8700, punkteOk: 8700 }, + minuten: 12, + }, NOTIZ); + /* GEGENPROBE: Steigt die Zahl, darf der Satz NICHT kommen. Eine + Warnung, die immer kommt, ist keine Warnung mehr. */ + pruef("Gegenprobe: bei MEHR Prüfungen kommt die Warnung nicht", + !/Prüfungen WENIGER/.test(mehr)); + + const wenigerDateien = notizSchreiben({ + lage: { ...LAGE_GRUEN, dateien: 190, dateienOk: 190 }, + vorher: { wann: "24.09.2026 um 05:49", zeit: Date.now() - 86400000, + dateien: 193, dateienOk: 193, punkte: 8900, punkteOk: 8900 }, + minuten: 12, + }, NOTIZ); + pruef("auch eine verschwundene Prüfdatei wird genannt", + /3 Prüfdatei\(en\) weniger/.test(wenigerDateien)); +} + +/* ======================================================================= + 3. DER DRITTE AUSGANG + + „Konnte nicht nachsehen" ist weder gut noch schlecht -- es ist die + dritte Antwort, und sie darf auf keinen Fall wie „alles in Ordnung" + aussehen. + ======================================================================= */ +melde(""); +melde("=== 3. Konnte nicht nachsehen ==="); +{ + const t = notizSchreiben({ + vorher: { wann: "24.09.2026 um 05:49", zeit: Date.now() - 3 * 86400000, + dateien: 193, dateienOk: 193, punkte: 8900, punkteOk: 8900 }, + konnteNicht: "Ein Lauf läuft schon seit 01:12.", + }, NOTIZ); + pruef("es steht als eigener Zustand da", /Konnte nicht nachsehen/.test(t)); + pruef("und sagt NICHT, dass alles in Ordnung ist", !/Alles in Ordnung/.test(t)); + pruef("der Grund steht dabei", /läuft schon seit 01:12/.test(t)); + pruef("und wie alt der letzte echte Stand ist", + /3 Tage her/.test(t), t.split("\n").filter((z) => /gemessen/.test(z)).join(" | ")); + + const ohneVorher = notizSchreiben({ vorher: null, konnteNicht: "irgendein Grund" }, NOTIZ); + pruef("Gegenprobe: ohne früheres Ergebnis wird das auch gesagt", + /noch kein früheres Ergebnis/.test(ohneVorher)); +} + +/* ======================================================================= + 4. „KEINE PRÜFUNG GEZÄHLT" IST KEIN BEFUND + + Acht Dateien standen im Nachtlauf vom 25.09. als rot, ohne dass + irgendetwas kaputt war: Sie schreiben ihre Ergebnisse als ` OK ` + statt ` ok `, und der Zähler erkannte davon nichts. Die Notiz muss + diesen Unterschied benennen, sonst sucht man im Programm. + ======================================================================= */ +melde(""); +melde("=== 4. Null gezählte Prüfungen ==="); +{ + const t = notizSchreiben({ + lage: { dateien: 193, dateienOk: 192, punkte: 8900, punkteOk: 8900, + rote: [{ datei: "pruef-still.mjs", code: 0, nullPunkte: true, erste: "alles bestanden" }] }, + vorher: null, minuten: 12, + }, NOTIZ); + pruef("die Notiz nennt es beim Namen", + /Kein Befund – es wurde keine einzige Prüfung GEZÄHLT/.test(t)); + pruef("und erklärt, woran es meist liegt", /statt/.test(t) && /OK/.test(t)); + pruef("warnt aber trotzdem davor, es liegenzulassen", + /still aufhören zu prüfen/.test(t)); +} + +/* ======================================================================= + 5. JEDER AUSGANG REICHT DEN PFAD MIT + + Der Block „von Hand" wird aus der VORHANDENEN Datei gelesen. Wer + `notizSchreiben(...)` ohne den Pfad aufruft, schreibt an genau + diesem Ausgang eine Notiz ohne ihn -- und der Block ist weg. Das + sind vier Stellen in nachtlauf.mjs, und drei davon sind die + seltenen Ausgänge, die man beim Ausprobieren nie erwischt. + ======================================================================= */ +melde(""); +melde("=== 5. Alle Ausgänge in nachtlauf.mjs ==="); +{ + const quelle = readFileSync(NACHTLAUF, "utf8"); + /* `notizSchreiben(` trifft nur Aufrufe -- die Importzeile schreibt + den Namen ohne Klammer. */ + const aufrufe = (quelle.match(/notizSchreiben\(/g) || []).length; + const mitPfad = (quelle.match(/\}, NOTIZ\)/g) || []).length; + pruef(`alle ${aufrufe} Aufrufe reichen den Notizpfad mit`, + aufrufe >= 4 && mitPfad === aufrufe, + `${aufrufe} Aufrufe, davon ${mitPfad} mit Pfad`); + /* GEGENPROBE: Der Zähler oben darf nicht einfach immer gleich sein. */ + pruef("Gegenprobe: ein Aufruf ohne Pfad würde auffallen", + (("notizSchreiben({ a })").match(/\}, NOTIZ\)/g) || []).length === 0); + pruef("nachtlauf.mjs baut den Text nicht mehr selbst", + !/function notizSchreiben/.test(quelle), + "sonst gäbe es zwei Fassungen, und geprüft würde die falsche"); +} + +/* ======================================================================= + 6. KLEINIGKEITEN, DIE SONST NIEMAND MISST + ======================================================================= */ +melde(""); +melde("=== 6. Datum und Rahmen ==="); +{ + pruef("das Datum wird deutsch geschrieben", + /^\d{2}\.\d{2}\.\d{4} um \d{2}:\d{2}$/.test(zeitWort(new Date(2026, 8, 25, 5, 7))), + zeitWort(new Date(2026, 8, 25, 5, 7))); + pruef("und zweistellig, auch am Morgen", + zeitWort(new Date(2026, 0, 3, 5, 7)) === "03.01.2026 um 05:07"); + + const gruen = notizSchreiben({ lage: LAGE_GRUEN, vorher: null, minuten: 12 }, NOTIZ); + pruef("bei null roten steht der beruhigende Satz", + /Keine Datei ist rot/.test(gruen)); + const rot = notizSchreiben({ lage: LAGE_ROT, vorher: null, minuten: 31 }, NOTIZ); + pruef("bei einer roten steht die Einzahl richtig", + /## 1 Prüfung ist rot/.test(rot), rot.split("\n")[7] || ""); + pruef("und der Befehl zum Einzelnachsehen steht dabei", + /node server\/pruef-beispiel\.mjs/.test(rot)); + pruef("samt dem Hinweis, dass rot nicht automatisch kaputt heißt", + /Rot heißt nicht automatisch kaputt/.test(rot)); +} + +melde(""); +melde(` ${ok} bestanden, ${fehl} fehlgeschlagen`); +melde(""); +process.exit(fehl ? 1 : 0); diff --git a/tools/nachtlauf-notiz.mjs b/tools/nachtlauf-notiz.mjs new file mode 100644 index 00000000..d4e00c4a --- /dev/null +++ b/tools/nachtlauf-notiz.mjs @@ -0,0 +1,166 @@ +/* ===================================================================== + DIE NOTIZ FÜR DEN PRÜFSTAND (ausgelagert am 25.09.2026) + + Stand vorher mitten in `nachtlauf.mjs`. Ausgelagert, damit sie + PRÜFBAR wird: `nachtlauf.mjs` startet beim Import sofort einen + Lauf über alle Prüfdateien -- wer die Notiz messen wollte, hätte + dafür eine halbe Stunde Rechenzeit ausgelöst und nebenbei das + echte Ergebnis auf Filipes Startseite überschrieben. + + Hier drin steht nur Textbau, kein Nebeneffekt. Importieren ist + folgenlos. Geprüft von `server/pruef-nachtlauf.mjs`. + ===================================================================== */ +import { readFileSync } from "node:fs"; + +const zweiZiffern = (n) => String(n).padStart(2, "0"); +function zeitWort(d = new Date()) { + return `${zweiZiffern(d.getDate())}.${zweiZiffern(d.getMonth() + 1)}.${d.getFullYear()}` + + ` um ${zweiZiffern(d.getHours())}:${zweiZiffern(d.getMinutes())}`; +} + +export { zeitWort }; + +/* ===================================================================== + EIN STÜCK DER NOTIZ GEHÖRT DEM MENSCHEN (25.09.2026) + + Bis heute stand oben in der Notiz: "von Hand ändern bringt nichts, + beim nächsten Lauf ist es wieder überschrieben." Das war ehrlich + und trotzdem falsch herum gedacht. + + Denn das Wertvollste an diesem Prüfstand ist gerade NICHT die + Zahl, sondern der Satz daneben: "Diese Prüfung ist rot, weil sie + den Umbau vom 24.09. nicht mitbekommen hat" oder "die hier ist + echt, Finger weg". Wer das aufschreiben will, schreibt es genau + hierhin -- und fand es am nächsten Morgen gelöscht. + + Deshalb gibt es jetzt einen Bereich zwischen zwei Marken, den der + Lauf beim Schreiben WIEDER EINSETZT statt ihn zu überschreiben. + Fehlt er (neue Notiz, jemand hat ihn gelöscht), wird er leer neu + angelegt -- mit der Erklärung drin, wozu er da ist. + ===================================================================== */ +export const MARKE_AUF = ""; +export const MARKE_ZU = ""; + +export function handNotizLesen(NOTIZ) { + let alt = ""; + try { alt = readFileSync(NOTIZ, "utf8"); } catch { return null; } + const a = alt.indexOf(MARKE_AUF); + const b = alt.indexOf(MARKE_ZU, a + 1); + if (a < 0 || b < 0) return null; + const drin = alt.slice(a + MARKE_AUF.length, b).trim(); + return drin.length ? drin : null; +} + +export function notizSchreiben({ lage, vorher, minuten, konnteNicht }, notizPfad) { + const jetzt = new Date(); + const hand = handNotizLesen(notizPfad); + const zeilen = []; + zeilen.push("# Prüfstand Team Dogi"); + zeilen.push(""); + zeilen.push("*Diese Notiz schreibt `tools/nachtlauf.mjs`. Alles außerhalb des Blocks"); + zeilen.push("„VON HAND“ ist beim nächsten Lauf wieder überschrieben — was DRIN steht,"); + zeilen.push("bleibt stehen.*"); + zeilen.push(""); + zeilen.push(MARKE_AUF); + if (hand) { + zeilen.push(hand); + } else { + zeilen.push("## Von Hand"); + zeilen.push(""); + zeilen.push("*Hier hinein gehört, was die Zahlen nicht sagen können: welche rote"); + zeilen.push("Zeile schon untersucht ist, welche echt ist, woran gerade gearbeitet"); + zeilen.push("wird. Dieser Block überlebt jeden Lauf — der Rest der Notiz nicht.*"); + } + zeilen.push(MARKE_ZU); + zeilen.push(""); + + if (konnteNicht) { + zeilen.push("## ⚠ Konnte nicht nachsehen"); + zeilen.push(""); + zeilen.push(konnteNicht); + zeilen.push(""); + if (vorher?.wann) { + const tage = Math.floor((Date.now() - vorher.zeit) / 86400000); + zeilen.push(`Zuletzt wirklich gemessen: **${vorher.wann}**` + + (tage >= 1 ? ` — das ist **${tage} Tag${tage === 1 ? "" : "e"} her**.` : ".")); + } else { + zeilen.push("Es gibt noch kein früheres Ergebnis."); + } + return zeilen.join("\n") + "\n"; + } + + const rot = lage.rote.length; + zeilen.push(rot + ? `## ${rot} Prüfung${rot === 1 ? "" : "en"} ${rot === 1 ? "ist" : "sind"} rot` + : "## Alles in Ordnung"); + zeilen.push(""); + zeilen.push(`Gemessen am **${zeitWort(jetzt)}**, Dauer ${minuten} Minuten.`); + zeilen.push(""); + zeilen.push("| | jetzt | vorher |"); + zeilen.push("|---|---|---|"); + zeilen.push(`| Dateien in Ordnung | ${lage.dateienOk} von ${lage.dateien} | ` + + `${vorher ? `${vorher.dateienOk} von ${vorher.dateien}` : "—"} |`); + zeilen.push(`| Einzelprüfungen bestanden | ${lage.punkteOk} von ${lage.punkte} | ` + + `${vorher ? `${vorher.punkteOk} von ${vorher.punkte}` : "—"} |`); + zeilen.push(""); + + /* EIN RÜCKGANG DER ANZAHL IST EIN BEFUND, AUCH WENN ALLES GRÜN IST. + Am 28.08.2026 meldete ein Lauf 789 statt 804 Prüfungen und war + dabei grün: Acht Prüfungen je Gerät waren stillschweigend + weggefallen -- ausgerechnet die, die die wichtigste Funktion + absichern. Wer nur "bestanden" liest, hätte sie ab da dauerhaft + ungeprüft gelassen. */ + if (vorher && lage.punkte != null && vorher.punkte != null + && lage.punkte < vorher.punkte) { + zeilen.push(`> **Es werden ${vorher.punkte - lage.punkte} Prüfungen WENIGER gemacht als beim letzten Lauf.**`); + zeilen.push("> Das ist auch dann ein Befund, wenn alles grün ist: Eine Prüfung, die"); + zeilen.push("> still nicht mehr läuft, beweist nichts — und niemandem fällt es auf."); + zeilen.push(""); + } + if (vorher && lage.dateien != null && vorher.dateien != null + && lage.dateien < vorher.dateien) { + zeilen.push(`> **Es gibt ${vorher.dateien - lage.dateien} Prüfdatei(en) weniger als beim letzten Lauf.**`); + zeilen.push(""); + } + + if (rot) { + zeilen.push("## Was rot ist"); + zeilen.push(""); + for (const r of lage.rote) { + zeilen.push(`### \`${r.datei}\``); + zeilen.push(""); + if (r.nullPunkte) { + zeilen.push(`**Kein Befund – es wurde keine einzige Prüfung GEZÄHLT** ` + + `(Exitcode ${r.code}).` ); + zeilen.push(""); + zeilen.push("Das ist etwas anderes als „kaputt“: Die Datei ist " + + "durchgelaufen. Der Zähler hat ihre Punkte nur nicht erkannt – " + + "meist, weil sie ihre Ergebnisse anders schreibt als erwartet " + + "(` OK ` statt ` ok `, andere Einrückung, eigenes Format). " + + "Nachsehen lohnt trotzdem: Eine Datei, deren Punkte niemand " + + "zählt, kann still aufhören zu prüfen, ohne dass es auffällt. " + + "Zuletzt stand da:"); + zeilen.push(""); + } else if (r.ohneGrund) { + zeilen.push(`**Konnte nicht sagen, woran es lag** (Exitcode ${r.code}). ` + + "Das ist etwas anderes als ein Befund: Die Prüfung ist nicht " + + "durchgelaufen, hat aber auch keine FEHL-Zeile hinterlassen. " + + "Meist steckt dahinter eine Zeitüberschreitung oder ein Absturz " + + "im Vorlauf, kein Fehler im Programm. Zuletzt stand da:"); + zeilen.push(""); + } + zeilen.push(`\`\`\`\n${r.erste}\n\`\`\``); + zeilen.push(""); + zeilen.push(`Einzeln nachsehen: \`node server/${r.datei}\``); + zeilen.push(""); + } + zeilen.push("**Rot heißt nicht automatisch kaputt.** Dreimal am 23.09.2026 war"); + zeilen.push("die Prüfung veraltet und das Programm richtig — einmal war es"); + zeilen.push("umgekehrt und ein Rollenname stand offen im Netz. Erst nachsehen,"); + zeilen.push("dann entscheiden, wer recht hat."); + } else { + zeilen.push("Keine Datei ist rot. Der nächste Lauf kommt in der nächsten Nacht."); + } + zeilen.push(""); + return zeilen.join("\n") + "\n"; +} diff --git a/tools/nachtlauf.mjs b/tools/nachtlauf.mjs index 665e19f3..57e041f1 100644 --- a/tools/nachtlauf.mjs +++ b/tools/nachtlauf.mjs @@ -71,6 +71,7 @@ import { } from "node:fs"; import { join, dirname } from "node:path"; import { fileURLToPath } from "node:url"; +import { notizSchreiben, zeitWort } from "./nachtlauf-notiz.mjs"; const HIER = dirname(fileURLToPath(import.meta.url)); const WURZEL = join(HIER, ".."); @@ -94,11 +95,8 @@ const SCHLOSS = join(WURZEL, "tools", ".nachtlauf-laeuft"); was blockiert, wird abgeschafft. */ const SCHLOSS_FRIST_MS = 3 * 60 * 60 * 1000; -const zweiZiffern = (n) => String(n).padStart(2, "0"); -function zeitWort(d = new Date()) { - return `${zweiZiffern(d.getDate())}.${zweiZiffern(d.getMonth() + 1)}.${d.getFullYear()}` - + ` um ${zweiZiffern(d.getHours())}:${zweiZiffern(d.getMinutes())}`; -} +/* `zeitWort` kommt aus nachtlauf-notiz.mjs -- zwei Kopien derselben + Datumsschreibweise waeren zwei Staende, die auseinanderlaufen. */ function schlossNehmen() { if (existsSync(SCHLOSS)) { @@ -216,105 +214,6 @@ function auswerten(aus) { /* --------------------------------------------------------------------- Die Notiz --------------------------------------------------------------------- */ -function notizSchreiben({ lage, vorher, minuten, konnteNicht }) { - const jetzt = new Date(); - const zeilen = []; - zeilen.push("# Prüfstand Team Dogi"); - zeilen.push(""); - zeilen.push("*Diese Notiz schreibt `tools/nachtlauf.mjs` — von Hand ändern bringt nichts,"); - zeilen.push("beim nächsten Lauf ist es wieder überschrieben.*"); - zeilen.push(""); - - if (konnteNicht) { - zeilen.push("## ⚠ Konnte nicht nachsehen"); - zeilen.push(""); - zeilen.push(konnteNicht); - zeilen.push(""); - if (vorher?.wann) { - const tage = Math.floor((Date.now() - vorher.zeit) / 86400000); - zeilen.push(`Zuletzt wirklich gemessen: **${vorher.wann}**` - + (tage >= 1 ? ` — das ist **${tage} Tag${tage === 1 ? "" : "e"} her**.` : ".")); - } else { - zeilen.push("Es gibt noch kein früheres Ergebnis."); - } - return zeilen.join("\n") + "\n"; - } - - const rot = lage.rote.length; - zeilen.push(rot - ? `## ${rot} Prüfung${rot === 1 ? "" : "en"} ${rot === 1 ? "ist" : "sind"} rot` - : "## Alles in Ordnung"); - zeilen.push(""); - zeilen.push(`Gemessen am **${zeitWort(jetzt)}**, Dauer ${minuten} Minuten.`); - zeilen.push(""); - zeilen.push("| | jetzt | vorher |"); - zeilen.push("|---|---|---|"); - zeilen.push(`| Dateien in Ordnung | ${lage.dateienOk} von ${lage.dateien} | ` - + `${vorher ? `${vorher.dateienOk} von ${vorher.dateien}` : "—"} |`); - zeilen.push(`| Einzelprüfungen bestanden | ${lage.punkteOk} von ${lage.punkte} | ` - + `${vorher ? `${vorher.punkteOk} von ${vorher.punkte}` : "—"} |`); - zeilen.push(""); - - /* EIN RÜCKGANG DER ANZAHL IST EIN BEFUND, AUCH WENN ALLES GRÜN IST. - Am 28.08.2026 meldete ein Lauf 789 statt 804 Prüfungen und war - dabei grün: Acht Prüfungen je Gerät waren stillschweigend - weggefallen -- ausgerechnet die, die die wichtigste Funktion - absichern. Wer nur "bestanden" liest, hätte sie ab da dauerhaft - ungeprüft gelassen. */ - if (vorher && lage.punkte != null && vorher.punkte != null - && lage.punkte < vorher.punkte) { - zeilen.push(`> **Es werden ${vorher.punkte - lage.punkte} Prüfungen WENIGER gemacht als beim letzten Lauf.**`); - zeilen.push("> Das ist auch dann ein Befund, wenn alles grün ist: Eine Prüfung, die"); - zeilen.push("> still nicht mehr läuft, beweist nichts — und niemandem fällt es auf."); - zeilen.push(""); - } - if (vorher && lage.dateien != null && vorher.dateien != null - && lage.dateien < vorher.dateien) { - zeilen.push(`> **Es gibt ${vorher.dateien - lage.dateien} Prüfdatei(en) weniger als beim letzten Lauf.**`); - zeilen.push(""); - } - - if (rot) { - zeilen.push("## Was rot ist"); - zeilen.push(""); - for (const r of lage.rote) { - zeilen.push(`### \`${r.datei}\``); - zeilen.push(""); - if (r.nullPunkte) { - zeilen.push(`**Kein Befund – es wurde keine einzige Prüfung GEZÄHLT** ` - + `(Exitcode ${r.code}).` ); - zeilen.push(""); - zeilen.push("Das ist etwas anderes als „kaputt“: Die Datei ist " - + "durchgelaufen. Der Zähler hat ihre Punkte nur nicht erkannt – " - + "meist, weil sie ihre Ergebnisse anders schreibt als erwartet " - + "(` OK ` statt ` ok `, andere Einrückung, eigenes Format). " - + "Nachsehen lohnt trotzdem: Eine Datei, deren Punkte niemand " - + "zählt, kann still aufhören zu prüfen, ohne dass es auffällt. " - + "Zuletzt stand da:"); - zeilen.push(""); - } else if (r.ohneGrund) { - zeilen.push(`**Konnte nicht sagen, woran es lag** (Exitcode ${r.code}). ` - + "Das ist etwas anderes als ein Befund: Die Prüfung ist nicht " - + "durchgelaufen, hat aber auch keine FEHL-Zeile hinterlassen. " - + "Meist steckt dahinter eine Zeitüberschreitung oder ein Absturz " - + "im Vorlauf, kein Fehler im Programm. Zuletzt stand da:"); - zeilen.push(""); - } - zeilen.push(`\`\`\`\n${r.erste}\n\`\`\``); - zeilen.push(""); - zeilen.push(`Einzeln nachsehen: \`node server/${r.datei}\``); - zeilen.push(""); - } - zeilen.push("**Rot heißt nicht automatisch kaputt.** Dreimal am 23.09.2026 war"); - zeilen.push("die Prüfung veraltet und das Programm richtig — einmal war es"); - zeilen.push("umgekehrt und ein Rollenname stand offen im Netz. Erst nachsehen,"); - zeilen.push("dann entscheiden, wer recht hat."); - } else { - zeilen.push("Keine Datei ist rot. Der nächste Lauf kommt in der nächsten Nacht."); - } - zeilen.push(""); - return zeilen.join("\n") + "\n"; -} /* --------------------------------------------------------------------- Los @@ -326,7 +225,7 @@ const schloss = erzwingen ? { ok: true } : schlossNehmen(); if (!schloss.ok) { /* DER DRITTE AUSGANG. Kein Fehler -- nur: hier war nichts zu messen. */ mkdirSync(dirname(NOTIZ), { recursive: true }); - writeFileSync(NOTIZ, notizSchreiben({ vorher, konnteNicht: schloss.grund }), "utf8"); + writeFileSync(NOTIZ, notizSchreiben({ vorher, konnteNicht: schloss.grund }, NOTIZ), "utf8"); console.log("Konnte nicht nachsehen: " + schloss.grund); process.exit(0); } @@ -343,7 +242,7 @@ if (!ergebnis.ging) { mkdirSync(dirname(NOTIZ), { recursive: true }); writeFileSync(NOTIZ, notizSchreiben({ vorher, konnteNicht: `Der Läufer ließ sich nicht starten: ${ergebnis.grund}`, - }), "utf8"); + }, NOTIZ), "utf8"); console.log("Konnte nicht nachsehen: " + ergebnis.grund); process.exit(0); } @@ -360,13 +259,13 @@ if (lage.dateien == null || lage.punkte == null || lage.punkte === 0) { konnteNicht: "Der Lauf hat keine einzige Zahl geliefert — vermutlich ist er" + " abgebrochen, bevor er anfangen konnte.\n\nLetzte Ausgabe:\n\n```\n" + ergebnis.aus.slice(-700) + "\n```", - }), "utf8"); + }, NOTIZ), "utf8"); console.log("Konnte nicht nachsehen: keine Zahlen in der Ausgabe."); process.exit(0); } mkdirSync(dirname(NOTIZ), { recursive: true }); -writeFileSync(NOTIZ, notizSchreiben({ lage, vorher, minuten: ergebnis.minuten }), "utf8"); +writeFileSync(NOTIZ, notizSchreiben({ lage, vorher, minuten: ergebnis.minuten }, NOTIZ), "utf8"); /* Der Stand für den nächsten Vergleich -- erst NACH der Notiz, damit ein Absturz beim Schreiben nicht den alten Vergleichswert wegnimmt. */