Files
DogFatherGitandClaude Opus 5 1d41e29bf7 Drei weitere Prüfungen, die das Falsche prüften
Der Durchgang, zweiter Teil. Gesucht nach dem schaerfsten Filter, den
es dafuer gibt: dem VERSPRECHEN GEGEN DIE WIRKLICHKEIT -- Pruefsaetze,
die „jede Rolle" oder „jede Seite" sagen. Genau so ist pruef-glocke
heute frueh aufgefallen.

1. pruef-workspace-seiten meldete aufgaben.html als „Breite 1560px".
   KEIN FEHLER: Die Seite traegt zusaetzlich `.inhalt--brett`, und die
   setzt ausdruecklich 1560 px -- mit Begruendung in aufgaben.css („ein
   Brett darf breiter sein als Text, der Kopfbereich bleibt lesbar
   schmal"). Die Pruefung verglich starr mit 1240 und kannte die
   Klasse nicht; sie meldete damit eine Absicht als Fehler, seit dem
   Tag, an dem die Klasse entstand. Mit Gegenprobe belegt: schon vor
   allen Aenderungen von heute rot.
   Jetzt kommt die erwartete Breite aus den KLASSEN des Elements.
   Beide Zahlen bleiben stehen, weil sie etwas aussagen -- kommt
   weder 1240 noch 1560 an, ist die Regel verloren.

2. pruef-meldungen meldete „ohne Satz: ungueltiger_stand". Zuerst ein
   ECHTER Fehler, und zwar meiner vom selben Tag: In
   workspace-support.js stand eine nackte Kennung statt eines Satzes.
   Behoben -- und danach meldete die Pruefung sie WEITER, weil sie das
   Zitat im Kommentar las, der die Behebung begruendet.
   Dieselbe Falle wie ein Grep ueber eine Datei, die ihre eigene
   Geschichte enthaelt; mir ist sie heute schon einmal passiert. Eine
   Pruefung, die verbietet, ueber einen behobenen Fehler zu SCHREIBEN,
   erzieht dazu, die Begruendung wegzulassen. Kommentare zaehlen jetzt
   nicht mehr; Adressen mit // in Zeichenketten bleiben unberuehrt.

3. pruef-auskunft meldete „NICHT EINGEORDNET:
   vorlagen_bewerbungen.aufgabe_id". ECHT: Die Spalte zeigt auf eine
   Aufgabe, nicht auf einen Menschen, und stand in keiner der beiden
   Listen. Das ist mehr als Ordnungsliebe -- bei einer
   Auskunftsanfrage muss das Haus sagen koennen, welche Spalten auf
   eine Person zeigen. Eine unbekannte Spalte ist eine, bei der
   niemand weiss, ob sie mitgehoert.

ZWISCHENSTAND: ACHT Pruefungen an einem Tag, die rot waren oder das
Falsche prueften. Das Muster ist immer dasselbe -- eine Liste oder
Zahl, die zum Zeitpunkt des Schreibens stimmte. Sie wird nicht falsch,
sie wird unzustaendig.

Gruen: pruef-meldungen (8), pruef-auskunft (46),
pruef-workspace-seiten, pruef-support (45), pruef-vorlagen.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-24 13:01:25 +02:00

241 lines
11 KiB
JavaScript

/* =====================================================================
pruef-meldungen.mjs — Kein Benutzer liest je eine Maschinenkennung.
Filipe, 19.09.2026: „keine kryptischen oder technischen
Fehlermeldungen, sondern klare Aussagen darüber, was passiert ist
und wie man das Problem lösen kann."
---------------------------------------------------------------------
WARUM DIESE PRÜFUNG DIE EIGENTLICHE ARBEIT IST
Die Übersetzungstabelle in `workspace/assets/js/meldung.js` zu
schreiben war eine Stunde Arbeit. Sie in einem halben Jahr noch
vollständig zu haben, ist die schwierigere Aufgabe — und zwar aus
einem Grund, den dieses Haus teuer gelernt hat:
„Eine abgeschriebene Liste ist dieselbe Falle wie eine feste Zahl."
(11.09.2026, als die Umstellung drei Spalten verlor, obwohl ein
Kommentar direkt daneben davor warnte.)
Wer morgen `fehler: "zu_frueh"` erfindet, wird nicht daran denken,
hier einen Satz nachzutragen. Er merkt es auch nicht: Die Oberfläche
zeigt dann klaglos den Ersatzsatz. Es sieht aus, als funktioniere es.
Deshalb rechnet diese Prüfung die Liste der Kennungen AUS DEM SERVER
aus, statt sie zu kennen. Eine neue Kennung ohne Satz macht sie rot.
Man KANN es nicht mehr vergessen.
---------------------------------------------------------------------
DREI DINGE WERDEN GEPRÜFT
1. VOLLSTÄNDIG — jede Kennung, die der Server schicken kann, hat
einen Satz.
2. ANGESCHLOSSEN — keine Seite zeigt `.fehler` noch roh an, und
jede Seite, deren Skript `sagWas` benutzt, lädt auch
`meldung.js`. (Der zweite Teil ist der wichtigere: Ohne die
Datei wäre `sagWas` nicht definiert, und aus einer Fehlermeldung
würde ein Absturz — schlimmer als vorher.)
3. LESBAR — kein Satz enthält eine Kennung, eine nackte Zahl oder
ein englisches Wort.
Dazu drei GEGENPROBEN. Eine Prüfung, die immer bestätigt, bestätigt
nichts (01.09.2026): Jede der drei muss beweisen, dass sie auch
„nicht in Ordnung" sagen kann.
AUFRUF: node server/pruef-meldungen.mjs
Läuft in unter einer Sekunde, ohne Server, ohne Browser.
===================================================================== */
import { readFileSync, readdirSync, existsSync } from "node:fs";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
const HIER = dirname(fileURLToPath(import.meta.url));
const WURZEL = join(HIER, "..");
const JS = join(WURZEL, "workspace", "assets", "js");
const SEITEN = join(WURZEL, "workspace");
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 : ""}`); }
};
/* ---------------------------------------------------------------------
Die Tabelle aus meldung.js lesen — ohne einen Browser zu bauen.
`meldung.js` hängt sich an `window`. Statt eine halbe Browserwelt
nachzustellen, wird der Block zwischen `var SAETZE = {` und der
schließenden Klammer ausgeschnitten und als JavaScript ausgewertet.
Das ist ehrlicher als ein zweiter Auszug der Namen: Es ist dieselbe
Quelle, kein abgeschriebener Zwilling.
------------------------------------------------------------------- */
function saetzeLesen() {
const quelle = readFileSync(join(JS, "meldung.js"), "utf8");
const start = quelle.indexOf("var SAETZE = {");
if (start < 0) return null;
const auf = quelle.indexOf("{", start);
let tiefe = 0, ende = -1;
for (let i = auf; i < quelle.length; i++) {
if (quelle[i] === "{") tiefe++;
else if (quelle[i] === "}") { tiefe--; if (tiefe === 0) { ende = i; break; } }
}
if (ende < 0) return null;
// eslint-disable-next-line no-new-func
return Function(`"use strict"; return (${quelle.slice(auf, ende + 1)});`)();
}
/* ---------------------------------------------------------------------
Alle Kennungen, die der Server schicken kann.
ABGELEITET, NICHT ABGESCHRIEBEN. Gesucht wird `fehler: "…"` in allen
Server-Dateien; als Kennung gilt, was nur aus Kleinbuchstaben,
Ziffern und Unterstrichen besteht. Alles andere ist ein fertiger
Satz und braucht keine Übersetzung.
------------------------------------------------------------------- */
function kennungenAusServer() {
const gefunden = new Map();
for (const datei of readdirSync(HIER)) {
if (!datei.endsWith(".js")) continue;
if (datei.startsWith("pruef-") || datei.startsWith("test-")) continue;
/* KOMMENTARE ZAEHLEN NICHT ALS KENNUNG (24.09.2026).
Gefunden an einer Stelle, die genau das dokumentierte, was
diese Pruefung verlangt: In workspace-support.js stand
fehler: "ungueltiger_stand" -- behoben, und der Kommentar
daneben zitierte die alte Zeile als Begruendung. Die Pruefung
las das Zitat und meldete den Fehler weiter, obwohl er behoben
war.
Dieselbe Falle wie ein Grep ueber eine Datei, die ihre eigene
Geschichte enthaelt -- mir ist sie heute schon einmal
passiert. Eine Pruefung, die verbietet, ueber einen behobenen
Fehler zu SCHREIBEN, erzieht dazu, die Begruendung
wegzulassen.
Zeichenketten mit // darin (Adressen) bleiben unberuehrt:
geschnitten wird nur, wo kein Doppelpunkt und kein
Anfuehrungszeichen davorsteht. */
const ohneKommentare = (roh) => roh
.replace(/\/\*[\s\S]*?\*\//g, " ")
.replace(/(^|[^:"'\\])\/\/[^\n]*/gm, "$1");
const text = ohneKommentare(readFileSync(join(HIER, datei), "utf8"));
for (const m of text.matchAll(/fehler:\s*"([a-z0-9_]+)"/g)) {
if (!gefunden.has(m[1])) gefunden.set(m[1], datei);
}
}
return gefunden;
}
console.log("\n=== Meldungen: sagt das Haus ganze Sätze? ===\n");
const SAETZE = saetzeLesen();
/* DER DRITTE AUSGANG. Lässt sich die Tabelle nicht lesen, ist das
weder „in Ordnung" noch „ein Satz fehlt" — es heißt, die Prüfung
kann ihre Frage nicht beantworten. Dann bricht sie ab, statt grün
zu melden. Genau dieser Fall hat am 03.09. wochenlang eine
Pflichtprüfung wertlos gemacht. */
if (!SAETZE) {
console.log(" KONNTE NICHT NACHSEHEN — in meldung.js ist kein Block `var SAETZE = {` zu finden.");
console.log(" Grund: entweder wurde die Datei umgebaut oder sie fehlt.");
console.log(" Ohne sie lässt sich nicht sagen, ob Sätze fehlen. Abbruch.\n");
process.exit(2);
}
const kennungen = kennungenAusServer();
console.log(` (${kennungen.size} Kennungen im Server, ${Object.keys(SAETZE).length} Sätze in der Tabelle)\n`);
/* ---- 1. Vollständig ------------------------------------------------- */
const ohneSatz = [...kennungen.keys()].filter((k) => !SAETZE[k]);
pruef("Jede Server-Kennung hat einen Satz",
kennungen.size > 0 && ohneSatz.length === 0,
ohneSatz.length ? `ohne Satz: ${ohneSatz.map((k) => `${k} (${kennungen.get(k)})`).join(", ")}` : "");
/* Die Zahl gehört in die BEDINGUNG, nicht nur in den Meldetext.
`every()` auf einem leeren Feld liefert `true` — eine Prüfung, die
nichts gefunden hat, wäre sonst grün (01.09.2026, Shop). */
pruef("Es wurden überhaupt Kennungen gefunden", kennungen.size >= 30,
`nur ${kennungen.size} — findet der Suchausdruck noch, was er soll?`);
/* ---- 2. Angeschlossen ----------------------------------------------- */
const skripte = readdirSync(JS).filter((f) => f.endsWith(".js"));
/* 2a: Keine Rohanzeige mehr. Gesucht wird `.fehler` in derselben Zeile
wie eine Ausgabe — aber nur dort, wo KEIN `sagWas` und kein
Vergleich (`===`) steht. Ein Vergleich übersetzt selbst. */
const roh = [];
for (const f of skripte) {
if (f === "meldung.js") continue;
const zeilen = readFileSync(join(JS, f), "utf8").split("\n");
zeilen.forEach((z, i) => {
if (!/\.fehler/.test(z)) return;
if (!/(textContent\s*=|melde[n]?\(|alert\(|innerHTML\s*=)/.test(z)) return;
if (/sagWas/.test(z) || /\.fehler\s*===/.test(z) || /\[\s*\w+\.fehler\s*\]/.test(z)) return;
roh.push(`${f}:${i + 1}`);
});
}
pruef("Keine Seite zeigt eine Kennung roh an", roh.length === 0,
roh.length ? roh.join(", ") : "");
/* 2b: Wer sagWas benutzt, muss meldung.js laden. DAS IST DIE
WICHTIGERE HÄLFTE: Fehlt die Datei, ist `sagWas` nicht definiert,
und aus einer Fehlermeldung wird ein Absturz. */
const benutzt = new Set(skripte.filter(
(f) => f !== "meldung.js" && /\bsagWas\(/.test(readFileSync(join(JS, f), "utf8"))));
const luecken = [];
for (const seite of readdirSync(SEITEN).filter((f) => f.endsWith(".html"))) {
const html = readFileSync(join(SEITEN, seite), "utf8");
const geladen = [...html.matchAll(/src="assets\/js\/([a-z0-9-]+)\.js/g)].map((m) => m[1] + ".js");
const braucht = geladen.filter((g) => benutzt.has(g));
if (braucht.length && !geladen.includes("meldung.js")) {
luecken.push(`${seite} (braucht es wegen ${braucht.join(", ")})`);
}
}
pruef("Jede Seite, die sagWas braucht, lädt meldung.js",
benutzt.size > 0 && luecken.length === 0,
luecken.length ? luecken.join("; ") : (benutzt.size ? "" : "keine Datei benutzt sagWas — stimmt das?"));
/* ---- 3. Lesbar ------------------------------------------------------ */
const schlecht = [];
for (const [k, satz] of Object.entries(SAETZE)) {
if (/[a-z]+_[a-z]+/.test(satz)) schlecht.push(`${k}: enthält eine Kennung`);
else if (/\b(?:400|401|403|404|409|429|500|503)\b/.test(satz)) schlecht.push(`${k}: enthält eine Fehlernummer`);
else if (/\b(error|failed|invalid|denied|request|server error)\b/i.test(satz)) schlecht.push(`${k}: englisches Wort`);
else if (satz.length < 15) schlecht.push(`${k}: zu kurz ("${satz}")`);
}
pruef("Jeder Satz ist deutsch, ohne Kennung und ohne Zahl",
Object.keys(SAETZE).length > 0 && schlecht.length === 0,
schlecht.length ? schlecht.join("; ") : "");
/* =====================================================================
DIE GEGENPROBEN
Zu jeder Prüfung, die „alles in Ordnung" sagen kann, gehört der
Beweis, dass sie auch „nicht in Ordnung" sagen kann. Sonst weiß
niemand, ob sie überhaupt hinsieht.
===================================================================== */
console.log("\n --- Gegenproben (jede MUSS anschlagen) ---");
const g1 = (() => { const t = { ...SAETZE }; delete t.nicht_verfuegbar;
return [...kennungen.keys()].filter((k) => !t[k]).length > 0; })();
pruef("Gegenprobe: fehlt ein Satz, schlägt Prüfung 1 an", g1);
const g2 = (() => {
const z = " $('x').textContent = d.fehler || 'Ging nicht.';";
return /\.fehler/.test(z)
&& /(textContent\s*=|melde[n]?\(|alert\(|innerHTML\s*=)/.test(z)
&& !/sagWas/.test(z) && !/\.fehler\s*===/.test(z);
})();
pruef("Gegenprobe: eine Rohanzeige würde Prüfung 2a finden", g2);
const g3 = (() => {
const s = "Fehler 503: request denied";
return /[a-z]+_[a-z]+/.test(s)
|| /\b(?:400|401|403|404|409|429|500|503)\b/.test(s)
|| /\b(error|failed|invalid|denied|request)\b/i.test(s);
})();
pruef("Gegenprobe: ein unlesbarer Satz würde Prüfung 3 finden", g3);
console.log(`\n ${ok} bestanden, ${fehl} fehlgeschlagen\n`);
process.exit(fehl ? 1 : 0);