diff --git a/.gitignore b/.gitignore index 5d3cb8e3..57069a29 100644 --- a/.gitignore +++ b/.gitignore @@ -110,3 +110,11 @@ zz-* # ist die Entscheidung "gehoert das ins Verzeichnis?" ein Umbenennen # und kein Vergessen. tools/_* + +# Der naechtliche Lauf schreibt hier seinen Zustand hin -- Rohausgabe, +# Vergleichszahlen und die Schlossdatei. Alles Laufzeit, nichts davon +# gehoert in die Geschichte: Es aendert sich jede Nacht, und im Repo +# waere es ein taeglicher Konflikt ohne Aussage. Das ERGEBNIS steht in +# der Vault-Notiz (02 Projekte/Pruefstand.md), die Werkzeuge selbst +# sind versioniert. +tools/.nachtlauf-* diff --git a/server/pruef-pruefzaehler.mjs b/server/pruef-pruefzaehler.mjs new file mode 100644 index 00000000..f03f191a --- /dev/null +++ b/server/pruef-pruefzaehler.mjs @@ -0,0 +1,107 @@ +/* ===================================================================== + ZAEHLT DER LAEUFER AUCH WIRKLICH ALLE? (25.09.2026) + ===================================================================== + + Der naechtliche Lauf meldete am 25.09.2026 „37 Pruefungen sind rot". + Acht davon waren gar nicht rot: Sie liefen fehlerfrei durch, + Exitcode 0, keine einzige FEHL-Zeile -- aber `alles-pruefen.mjs` + zaehlte bei ihnen NULL Einzelpruefungen, und „null Pruefungen ist + ein Fehler" ist eine richtige Regel. + + DER GRUND WAR EIN GROSSBUCHSTABE. Der Zaehler suchte `(ok|FEHL)`; + diese acht Dateien schreiben ` OK `. Zwei Schaeden auf einmal: + + 1. Ihre Punkte fehlten in der Gesamtzahl -- allein in fuenf der + acht waren das 114 Stueck. Ausgerechnet die Zahl, die vor + stillen Aussetzern schuetzen soll, war selbst eine Luecke. + 2. Acht dauerhaft rote Eintraege in einer Notiz, die morgens + gelesen wird. Eine Warnung, die immer kommt, ist keine mehr. + + WARUM ES DAFUER EINE PRUEFUNG GIBT UND NICHT NUR EINEN KOMMENTAR: + Weil der Kommentar den naechsten Fall nicht verhindert. Morgen legt + jemand eine Pruefdatei an, die ihre Ergebnisse wieder anders + schreibt -- mit drei Leerzeichen, mit einem Haken, auf englisch -- + und sie verschwindet genauso lautlos aus der Gesamtzahl. Diese + Datei startet deshalb ein paar der schnellsten Pruefungen WIRKLICH + und vergleicht, was der Ausdruck aus ihrer Ausgabe herausliest. + + DER AUSDRUCK WIRD AUS alles-pruefen.mjs GELESEN, nicht hier noch + einmal hingeschrieben. Zwei Fassungen desselben Musters waeren die + Sorte Dopplung, die genau dann auseinanderlaeuft, wenn es darauf + ankommt. + + Aufrufen mit: node server/pruef-pruefzaehler.mjs + ===================================================================== */ +import { readFileSync } from "node:fs"; +import { spawn } from "node:child_process"; +import { join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; + +const WURZEL = join(dirname(fileURLToPath(import.meta.url)), ".."); + +let fehler = 0; +const ok = (gut, text) => { if (!gut) fehler++; console.log(` ${gut ? "ok " : "FEHL"} ${text}`); }; + +/* ---- Den Ausdruck holen, den der Laeufer wirklich benutzt --------- */ +const laeufer = readFileSync(join(WURZEL, "tools", "alles-pruefen.mjs"), "utf8"); +const treffer = laeufer.match(/const einzeln = \(aus\.match\((\/.+?\/gm)\)/); +if (!treffer) { + console.log(" FEHL KONNTE NICHT NACHSEHEN: der Zaehl-Ausdruck steht nicht mehr da,"); + console.log(" wo diese Pruefung ihn sucht (tools/alles-pruefen.mjs)."); + console.log(" Das ist kein Befund am Programm -- diese Datei muss nachgezogen werden."); + process.exit(1); +} +const quelle = treffer[1]; +console.log(`Der Laeufer zaehlt mit: ${quelle}\n`); +const bauen = () => new RegExp(quelle.slice(1, quelle.lastIndexOf("/")), "gm"); + +/* ---- Die schnellsten Vertreter beider Schreibweisen --------------- */ +const PROBEN = [ + { datei: "pruef-tippziele.mjs", schreibweise: "OK" }, + { datei: "pruef-turn-wege.mjs", schreibweise: "OK" }, + { datei: "pruef-leerzustand.mjs", schreibweise: "OK" }, + { datei: "pruef-deutsche-texte.mjs", schreibweise: "ok" }, +]; + +const laufen = (datei) => new Promise((fertig) => { + const kind = spawn(process.execPath, [join(WURZEL, "server", datei)], + { cwd: WURZEL, stdio: ["ignore", "pipe", "pipe"] }); + let aus = ""; + kind.stdout.on("data", (d) => { aus += d; }); + kind.stderr.on("data", (d) => { aus += d; }); + const wecker = setTimeout(() => { try { kind.kill("SIGKILL"); } catch { /* weg */ } }, 120_000); + kind.on("close", (code) => { clearTimeout(wecker); fertig({ aus, code }); }); + kind.on("error", () => { clearTimeout(wecker); fertig({ aus, code: -1 }); }); +}); + +console.log("=== Jede Schreibweise wird gezaehlt ==="); +for (const p of PROBEN) { + const r = await laufen(p.datei); + const zahl = (r.aus.match(bauen()) || []).length; + ok(zahl > 0, + `${p.datei.padEnd(26)} schreibt "${p.schreibweise}" -> ${zahl} Punkte erkannt`); +} + +/* ---- GEGENPROBE: der Ausdruck darf nicht alles zaehlen ------------ */ +console.log("\n=== Gegenprobe: er zaehlt nicht wahllos mit ==="); +const beispiel = [ + "OK ohne Einrueckung ist kein Pruefpunkt", + " OK zu weit eingerueckt", + " OKAY ist kein eigenes Wort", + " Ergebnis: alles gut", + " ok ein echter", + " OK auch ein echter", + " FEHL ein dritter", +].join("\n"); +const gezaehlt = (beispiel.match(bauen()) || []).length; +ok(gezaehlt === 3, + `von sieben Zeilen zaehlen genau die drei echten (${gezaehlt})`); + +/* Und er muss einen Aussetzer auch WIRKLICH sehen koennen. */ +ok(((" ok etwas\n ok noch etwas").match(bauen()) || []).length === 2, + "zwei Punkte werden als zwei gezaehlt"); +ok((("kein einziger Punkt hier").match(bauen()) || []).length === 0, + "und eine Ausgabe ohne Punkte als null -- sonst waere 'null ist ein Fehler' blind"); + +console.log(`\n${fehler === 0 ? "ALLES IN ORDNUNG" : fehler + " Fehler"}`); +process.exit(fehler === 0 ? 0 : 1); diff --git a/server/pruef-rueckmeldung.mjs b/server/pruef-rueckmeldung.mjs index 976655d7..48a88f8a 100644 --- a/server/pruef-rueckmeldung.mjs +++ b/server/pruef-rueckmeldung.mjs @@ -341,8 +341,23 @@ console.log("=== Die Kachel ==="); findet. */ const alle = await kacheln(dogi.keks); const toene = alle.map((x) => x.ton).filter((t) => t !== undefined); - ok(new Set(toene).size === toene.length, - `kein Farbton doppelt (${toene.length} Kacheln vom Server)`); + /* UND WENN DOCH, DANN WELCHER -- die Meldung nennt die Nummer und + beide Kacheln. Vorher stand da nur "kein Farbton doppelt (34 + Kacheln vom Server)"; daraus laesst sich nicht ablesen, WO man + suchen muss, und die Suche in vier Listen ueber zwei Dateien + dauert dann laenger als die ganze Reparatur. Dritter Ausgang im + Kleinen: Eine Pruefung, die nur "nein" sagt, ist eine halbe. */ + const wo = new Map(); + for (const k of alle) { + if (k.ton === undefined) continue; + if (!wo.has(k.ton)) wo.set(k.ton, []); + wo.get(k.ton).push(k.name || k.ziel || "?"); + } + const doppelt = [...wo].filter(([, n]) => n.length > 1) + .map(([t, n]) => `Ton ${t}: ${n.join(" + ")}`); + ok(doppelt.length === 0, + `kein Farbton doppelt (${toene.length} Kacheln vom Server` + + `${doppelt.length ? " -- " + doppelt.join("; ") : ""})`); } d.close(); diff --git a/server/workspace.js b/server/workspace.js index 69bf0b96..bf8eb56c 100644 --- a/server/workspace.js +++ b/server/workspace.js @@ -2857,7 +2857,19 @@ export function zusatzBereicheFuer(person) { ? "Das Rudel, das Team und die Moderation \u2014 auf ihrer eigenen Adresse" : "Creator, Scouts und die Agentur \u2014 auf ihrer eigenen Adresse", zeichen: "hinaus", - ton: 40, + /* KEIN TON (25.09.2026). Hier stand 40 -- dieselbe Nummer wie + „Entwicklung", und DogFather sieht beide nebeneinander. Zwei + Kacheln in einer Farbe sind auf einer Wand, die ueber Farbe + funktioniert, keine Kleinigkeit: Die Farbe ist der einzige + Hinweis, den man ohne Lesen bekommt. + + Eine 46. Bereichsfarbe waere die naheliegende Antwort und die + falsche: Gerechnet bliebe sie unter der Hausgrenze fuer den + Abstand (0,0862 statt 0,09) -- der Farbraum ist voll. Diese + Kachel gehoert aber auch gar keinem Bereich an, sie fuehrt + hinaus. Ohne `ton` faellt sie auf `--akzent` zurueck und ist + damit von allen 45 Bereichskacheln unterscheidbar. Die + Begruendung steht ausfuehrlich in start.css bei Ton 45. */ szene: "portal", ziel: (nachCrew ? CREW_ADRESSE : WORKSPACE_ADRESSE) + "/workspace/start.html", /* AUSSEN heisst: Diese Kachel fuehrt aus dem Haus hinaus. Ohne das diff --git a/tools/alles-pruefen.mjs b/tools/alles-pruefen.mjs index 46d8f0c3..c6a8ede6 100644 --- a/tools/alles-pruefen.mjs +++ b/tools/alles-pruefen.mjs @@ -76,7 +76,28 @@ const laufen = (datei) => new Promise((fertig) => { kind.on("close", (code) => { clearTimeout(wecker); const sek = Math.round((Date.now() - start) / 100) / 10; - const einzeln = (aus.match(/^\s{2}(ok|FEHL)\s/gm) || []).length; + /* ==== GROSS ODER KLEIN -- BEIDES ZAEHLT (25.09.2026) ========== + + Hier stand `(ok|FEHL)`. Acht Pruefdateien schreiben aber „OK" + in Grossbuchstaben (pruef-kachelfarben, -livepunkt, -tippziele, + -turn-wege, -leerzustand, -nachfrage, -tagesruf, + -anruf-klingelt). Fuer sie zaehlte dieser Ausdruck NULL -- + und weil „null Pruefungen ist ein Fehler" (die Regel direkt + darunter, und sie ist richtig), standen sie Nacht fuer Nacht + als ROT in der Notiz, obwohl sie fehlerfrei durchliefen. + Exitcode 0, keine FEHL-Zeile, trotzdem rot. + + Zwei Schaeden auf einmal: Ihre Pruefungen fehlten in der + Gesamtzahl (also genau der Zahl, die vor stillen Aussetzern + schuetzen soll), und acht dauerhaft rote Eintraege gewoehnen + einem das Hinsehen ab. + + DIE SCHREIBWEISE WIRD NICHT VEREINHEITLICHT, SONDERN GELESEN. + Acht Dateien umzuschreiben waere acht Gelegenheiten, etwas + kaputtzumachen -- und die neunte Datei, die jemand morgen + anlegt, schreibt ohnehin wieder, was sie will. Ein Zaehler, der + beide Schreibweisen kennt, braucht niemanden zu erziehen. */ + const einzeln = (aus.match(/^\s{2}(ok|OK|FEHL)\s/gm) || []).length; const schlecht = (aus.match(/^\s{2}FEHL\s/gm) || []).length; if (abgewuergt) { aus += `\n\n FEHL DIE PRUEFUNG HING. Nach ${Math.round(FRIST_MS / 1000)} s kam nichts mehr` diff --git a/tools/nachtlauf.mjs b/tools/nachtlauf.mjs index 3c89b18f..665e19f3 100644 --- a/tools/nachtlauf.mjs +++ b/tools/nachtlauf.mjs @@ -156,11 +156,53 @@ function auswerten(aus) { const rote = []; for (const treffer of aus.matchAll(/^### (pruef-[\w-]+\.mjs) \(Exitcode (-?\d+)\)$/gm)) { /* Die erste FEHL-Zeile darunter ist die Auskunft, die man braucht -- - der Rest ist Zusammenhang. */ + der Rest ist Zusammenhang. + + ZWEI FENSTER, NICHT EINS (25.09.2026): Bisher wurde nur in den + ersten 900 Zeichen gesucht. Prüfungen mit viel Vorlauf (die + browsergestützten vor allem) haben ihre FEHL-Zeile weiter + hinten -- vier davon standen deshalb am 25.09. als „keine + Fehlerzeile gefunden" in der Notiz, und man konnte ihnen nicht + ansehen, ob sie kaputt waren oder nur lang. Jetzt wird zuerst + eng gesucht (damit die erste, wichtigste Zeile gewinnt) und + danach im ganzen Abschnitt. */ const ab = aus.indexOf(treffer[0]) + treffer[0].length; - const stueck = aus.slice(ab, ab + 900); - const erste = (stueck.split("\n").find((z) => /FEHL|Error|error:/.test(z)) || "").trim(); - rote.push({ datei: treffer[1], code: Number(treffer[2]), erste: erste.slice(0, 160) }); + const bisNaechste = aus.indexOf("\n### pruef-", ab); + const ganz = aus.slice(ab, bisNaechste === -1 ? ab + 20000 : bisNaechste); + const suchen = (text) => (text.split("\n") + .find((z) => /FEHL|Error|error:/.test(z)) || "").trim(); + let erste = suchen(ganz.slice(0, 900)) || suchen(ganz); + + /* UND WENN GAR NICHTS DASTEHT, IST DAS AUCH EINE AUSKUNFT. + Dann sind die letzten Zeilen das Beste, was es gibt -- zusammen + mit dem Exitcode. Eine Meldung „kaputt, weiss nicht warum" ist + ehrlich; „keine Fehlerzeile gefunden" klingt nach einer + Kleinigkeit und ist doch dasselbe. */ + let ohneGrund = false; + if (!erste) { + ohneGrund = true; + const letzte = ganz.split("\n").map((z) => z.trim()) + .filter(Boolean).slice(-3).join(" / "); + erste = letzte || "(die Prüfung hat gar nichts ausgegeben)"; + } + + /* DIE HAEUFIGSTE URSACHE HAT EINEN NAMEN -- und sie steht in der + Uebersichtszeile ganz oben, nicht im Fehlerabschnitt. Am + 25.09.2026 waren acht der 37 roten Dateien von dieser Sorte: + fehlerfrei durchgelaufen, Exitcode 0, aber der Zaehler hat ihre + Punkte nicht erkannt (sie schreiben „OK" statt „ok"). „Null + Pruefungen ist ein Fehler" ist dann richtig UND irrefuehrend -- + es klingt nach einer Pruefung, die nichts tut, und war eine, + die niemand zaehlt. */ + const uebersicht = new RegExp( + `^\\s*FEHL\\s+${treffer[1].replace(/[.*+?^$()|[\]\\]/g, "\\$&")}\\s.*$`, "m"); + const zeile = (aus.match(uebersicht) || [""])[0]; + const nullPunkte = /KEINE PRUEFUNG GELAUFEN/.test(zeile); + + rote.push({ + datei: treffer[1], code: Number(treffer[2]), + erste: erste.slice(0, 220), ohneGrund, nullPunkte, + }); } return { dateienOk: dateien ? Number(dateien[1]) : null, @@ -238,7 +280,27 @@ function notizSchreiben({ lage, vorher, minuten, konnteNicht }) { for (const r of lage.rote) { zeilen.push(`### \`${r.datei}\``); zeilen.push(""); - zeilen.push(r.erste ? `\`\`\`\n${r.erste}\n\`\`\`` : "*(keine Fehlerzeile gefunden)*"); + 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(""); diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html index 839adc78..5e7e2ca0 100644 --- a/workspace/anruf-probe.html +++ b/workspace/anruf-probe.html @@ -16,15 +16,15 @@ verdeckt. Gefunden hat es pruef-struktur. Richtig sind `gate.css` (die Farbwerte des Hauses) und `haus.css`. --> - - + + - +