Files
dogfather-universe/server/pruef-modi-wortleck.mjs
DogFatherGitandClaude Opus 5 ea8ceaa539 Das Farbhaus von Team Dogi lag oeffentlich im Netz
Ausgeloest von pruef-modi-wortleck, die seit Tagen rot war: "24
Fundstellen -- der verborgene Zugang waere damit auffindbar." Die
erste Frage war, ob sie recht hat. Gemessen: ja, und schlimmer als
gedacht.

WER DIESE DATEIEN BEKOMMT: Nicht nur Kollegen. `curl` ohne jeden Keks
auf workspace.dogfather-universe.com liefert die Skripte und
Stilvorlagen des Arbeitsplatzes mit HTTP 200 -- fuer jeden im Netz
lesbar.

DARUNTER crew-haus.css, 27 806 Byte, das ganze Farbhaus von Team Dogi
samt seiner Kommentare. In deren Kopf stand seit dem 10.09. woertlich
"Diese Datei wird NUR auf crew.dogfather-universe.com ausgeliefert."
Das war eine Behauptung, keine Tatsache: Die Weiche biegt `haus.css`
auf diesen Namen um, wer den Namen direkt nannte, ging an ihr vorbei.
Ein Kommentar, der eine Absicht beschreibt, erfuellt sie nicht.

Zwei Stellen im Haus haben sich damit widersprochen -- die Datei sagte
"nur auf crew.", pruef-crew-adresse verlangte ausdruecklich das
Gegenteil ("sie ist kein Geheimnis"). Durchgesetzt war die offene
Fassung. Filipe hat am 21.09. entschieden: zusperren. Die alte
Begruendung dagegen war die Sorge, eine Sperre koenne die Pruefung
blind machen -- die ist jetzt beantwortet statt vermieden: Jeder
Eintrag der Tafel wird in BEIDE Richtungen gemessen, und dass die
Umbiegung von `haus.css` auf crew. weiterhin greift, ebenfalls.

DIE ADRESSE WAR DAS EIGENTLICHE GEHEIMNIS, nicht das Wort. Die
Rollennamen stehen auf der Zugangswand von Team Dogi ohnehin offen --
so gewollt. WO sie liegt, stand dagegen im Klartext in gate.css,
gate.js und crew-haus.css, alle drei oeffentlich abrufbar. Jetzt steht
sie in keiner ausgelieferten Datei mehr.

DIE PRUEFUNG SELBST HATTE DREI MAENGEL:

  1. Sie kannte die Mehrzahl nicht. `\bmodi\b` fand "ein Modi", nicht
     "die Modis" -- 23 weitere Stellen, darunter der Satz "Fuer die
     rechte Hand und die Modis gibt es dort den stillen Zugang". Also
     genau die Auskunft, die sie verhindern soll, in der Form, die sie
     nicht sah.
  2. Ihre Ausnahmeliste war abgeschrieben. Sie nannte eine Datei; die
     zweite, die der Server auf crew. begrenzt, haette sie nie
     erfahren. Ausgenommen ist jetzt genau das, was der Server auch
     durchsetzt (GEHOERT_ZU_ADRESSE) -- kein Muster, sondern die
     Durchsetzung selbst.
  3. Sie mahnte 45 Kommentare an. Filipes Entscheidung: in Kommentaren
     ja, in Code und sichtbaren Texten nein. Rot, das immer da ist,
     wird ueberlesen -- und dann faengt sie auch den echten Fall nicht.

Dafuer neu: helfer-ohne-kommentar.mjs, ein Zerleger, der Kommentare
zeichengenau durch Leerzeichen ersetzt, ohne Zeichenketten,
Vorlagen oder Suchmuster zu beschaedigen. KEIN Suchausdruck -- genau
daran ist am 20.09. die Portumstellung gescheitert, mit gueltigem
JavaScript ohne das Feld `port`. Dreizehn Proben in beide Richtungen
beweisen, dass er weder zu viel noch zu wenig wegnimmt; ein Zerleger,
der zu viel nimmt, macht die ganze Pruefung STILL gruen.

Von 45 blieben damit drei echte, alle behoben:
  - daten.modis  -> daten.leute (Feldname in der Auskunft, also Code)
  - zwei Saetze, die Menschen lesen, auf "die Moderation" umgestellt --
    fuer jemanden, der neu im Treff ist, sogar klarer

Nebenbei, beim selben Durchgang gefunden und geschlossen:
  - teamlage.js fiel auf einen Rollennamen zurueck. Nachgemessen: Die
    Stilvorlage kennt zu dieser Karte gar keine solche Regel -- der
    Rueckfall hat nie etwas bewirkt und nur das Wort ausgeliefert.
  - werdegang.js fuehrte die Saetze ueber den Rollengruppen selbst,
    mit den Rollennamen als Schluessel. Der Kommentar daneben
    behauptete sogar, sie seien "abgeleitet, nicht abgeschrieben".
    Sie kommen jetzt vom Server, und pruef-werdegang misst, dass
    jede Gruppe ihren eigenen behaelt.

pruef-modi-wortleck  8/0  (war 5, davon 1 rot)
pruef-crew-adresse 144/0  (war 135)
pruef-werdegang    98 statt 95 Pruefungen, dieselben 3 alten Fehler
pruef-team-ampel 32/0, pruef-team-stufen 28/0, pruef-hilfe 84/0

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

263 lines
13 KiB
JavaScript

/* =====================================================================
STEHT DAS WORT IN EINER DATEI, DIE JEDER BEKOMMT?
Der verborgene Zugang haelt genau so lange, wie niemand den
Rollennamen im Quelltext findet. Alles unter workspace/ wird an JEDEN
ausgeliefert, der die Seite oeffnet -- an jeden Manager, jeden Scout,
jeden Creator. Ein einziges `if (ich.rolle === '...')` dort, und der
ganze Aufwand darueber ist umsonst.
WARUM ES DIESE PRUEFUNG GIBT: Weil genau das passiert ist. Am
10.09.2026 habe ich beim Bau der Kategorien SECHS Vergleiche und VIER
Kommentare mit dem Rollennamen in zwei ausgelieferte Dateien
geschrieben -- waehrend ich an anderer Stelle penibel darauf achtete,
ihn herauszuhalten. Gefunden habe ich es durch Nachsehen, nicht durch
Nachdenken. Nachdenken hatte ich vorher schon getan.
Die Regel ist deshalb ab jetzt keine Absicht mehr, sondern ein
Werkzeug: Wer den Namen dort hineinschreibt, bekommt einen roten
Lauf, keinen erhobenen Zeigefinger im Kommentar.
NICHT BETROFFEN ist die oeffentliche Seite (team-modis.html und
Verwandte). Dort steht das Modi-Team seit jeher offen -- das ist
gewollt. Verborgen ist nicht das TEAM, sondern dass es einen eigenen
Zugang zum Workspace hat.
===================================================================== */
import { readdirSync, readFileSync, statSync } from "node:fs";
import { join, dirname, extname, relative } from "node:path";
import { fileURLToPath } from "node:url";
import { ohneKommentar } from "./helfer-ohne-kommentar.mjs";
const HIER = dirname(fileURLToPath(import.meta.url));
const ORDNER = join(HIER, "..", "workspace");
let fehler = 0;
let geprueft = 0;
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
/* Alles, was der Server aus diesem Ordner herausgibt. Bewusst eine
Liste von Endungen und kein "alles ausser": Kaeme morgen eine neue
Art Datei dazu, faellt sie hier auf, statt stillschweigend
ungeprueft zu bleiben. */
const ENDUNGEN = new Set([".js", ".html", ".css", ".webmanifest", ".json", ".svg", ".txt"]);
/* `\bmodi\b` und nicht `modi`: "modifiziert", "Modul" und "Modus"
sollen nicht anschlagen -- eine Pruefung, die staendig Fehlalarm gibt,
wird abgeschaltet, und dann faengt sie auch den echten Fall nicht
mehr. Nachgemessen: Keines der drei Woerter passt auf dieses Muster,
weil nach "modi" dort ein Buchstabe folgt. */
/* DIE MEHRZAHL KAM AM 21.09.2026 DAZU -- und sie fehlte elf Tage.
`\bmodi\b` fand "ein Modi", aber nicht "die Modis". Nachgemessen:
23 weitere Fundstellen, darunter Saetze wie "Fuer die rechte Hand
und die Modis gibt es dort den stillen Zugang" -- also genau die
Auskunft, die diese Pruefung verhindern soll, in der Form, die sie
nicht sah. Eine Pruefung, die die Haelfte der Faelle nicht kennt,
beruhigt mehr, als sie schuetzt.
Die Gegenprobe unten bleibt gueltig: "modifiziert", "Modul",
"motion" und "Modus" passen auch auf das erweiterte Muster nicht. */
const MUSTER = /\bmodis?\b/i;
function dateien(pfad, aus = []) {
for (const name of readdirSync(pfad)) {
const voll = join(pfad, name);
if (statSync(voll).isDirectory()) { dateien(voll, aus); continue; }
if (ENDUNGEN.has(extname(name).toLowerCase())) aus.push(voll);
}
return aus;
}
console.log("\n=== Der Rollenname darf in keiner ausgelieferten Datei stehen ===\n");
/* =====================================================================
DIE EINE DATEI, DIE DEN NAMEN TRAGEN DARF (10.09.2026)
crew-index.html ist die Zugangswand von crew.dogfather-universe.com.
Filipe: "3 rollen. dogfather. rechte hand und modis." Auf dieser Wand
STEHEN die drei Namen -- als Kacheln, sichtbar, so gewollt.
WARUM DAS KEIN WIDERSPRUCH ZUR REGEL IST: Die Regel schuetzt nicht
den Namen, sondern die Tatsache, dass es einen eigenen Zugang zum
WORKSPACE gibt. Diese Datei wird ausserhalb von crew. ueberhaupt
nicht ausgeliefert -- der Server antwortet dort mit "gibt es nicht".
UND DAS IST DIE BEDINGUNG DER AUSNAHME, nicht ihre Begruendung: Sie
gilt nur, solange jene Sperre wirklich haelt. Gemessen wird das nicht
hier, sondern in pruef-crew-adresse.mjs ("die zweite Wand gibt es
dort nicht", drei Adressen plus doppelter Schraegstrich). Faellt jene
Pruefung, ist diese Ausnahme ungueltig -- deshalb steht der Verweis
hier und nicht nur in einem Kommentar irgendwo.
AUSDRUECKLICH EINE EINZELNE DATEI und kein Muster wie "crew-*":
Sonst waere die naechste crew-Datei stillschweigend mit ausgenommen,
und niemand haette entschieden, ob sie es sein darf. */
const { GEHOERT_ZU_ADRESSE } = await import("./crew-adresse.js");
/* KEIN MUSTER WIE "crew-*" -- ABER AUCH KEINE ZWEITE LISTE
(geaendert 21.09.2026).
Hier stand `new Set(["crew-index.html"])`, mit der Begruendung, ein
Muster wuerde die naechste crew-Datei stillschweigend mit
ausnehmen. Der Gedanke war richtig, die Umsetzung war eine
Abschrift: Am 21.09. kam crew-haus.css dazu (27 806 Byte, bis dahin
von der Agenturadresse OHNE Anmeldung abrufbar) -- und diese Liste
haette davon nichts gewusst.
Ausgenommen ist jetzt genau das, was der Server auch WIRKLICH auf
die zweite Adresse begrenzt: die Tafel GEHOERT_ZU_ADRESSE aus
crew-adresse.js. Das ist kein Muster, sondern die Durchsetzung
selbst. Wer eine Datei dort eintraegt, hat die Entscheidung
getroffen; wer es nicht tut, bekommt hier einen roten Lauf. Die
beiden koennen nicht mehr auseinanderlaufen.
DIE BEDINGUNG DER AUSNAHME BLEIBT DIESELBE: Sie gilt nur, solange
jene Sperre haelt. Gemessen wird das in pruef-crew-adresse.mjs, in
beide Richtungen und fuer jeden Eintrag der Tafel. */
const AUSGENOMMEN = new Set(
GEHOERT_ZU_ADRESSE.map((w) => relative(ORDNER, join(ORDNER, "..", w.datei))));
const alle = dateien(ORDNER);
/* DIE ZAHL GEHOERT IN DIE BEDINGUNG. Faende die Suche keine einzige
Datei -- verschobener Ordner, andere Endungen --, waere unten alles
gruen, ohne dass irgendetwas angesehen wurde. Genau diese Sorte
gruener Haken ist im Haus schon zweimal aufgefallen. */
ok(alle.length >= 30, `${alle.length} ausgelieferte Dateien durchsucht`);
const treffer = [];
let ausgenommen = 0;
for (const datei of alle) {
if (AUSGENOMMEN.has(relative(ORDNER, datei))) { ausgenommen++; continue; }
/* KOMMENTARE ZAEHLEN NICHT MIT (Entscheidung von Filipe,
21.09.2026).
Bis heute suchte diese Pruefung in der ganzen Datei und fand
45 Stellen -- fast alle in Kommentaren. Die Frage war also nicht,
ob sie recht hat, sondern was eigentlich geschuetzt werden soll.
GEMESSEN, WORUM ES WIRKLICH GEHT: Auf der Zugangswand von Team
Dogi stehen die drei Rollennamen ohnehin offen -- als Kacheln,
sichtbar, so gewollt. Wer die Adresse hat, liest sie dort.
Geschuetzt wird der Zugang durch den Code, nicht durch ein Wort.
Und die ADRESSE, um die es wirklich geht, steht seit dem
21.09.2026 in keiner ausgelieferten Datei mehr.
45 Kommentare anzumahnen waere damit eine Dauer-Rotmeldung --
und Rot, das immer da ist, wird ueberlesen. Dann faengt diese
Pruefung auch den Tag nicht mehr, an dem jemand einen Vergleich
auf den Rollennamen in eine ausgelieferte Datei schreibt. Genau
das ist am 10.09.2026 passiert und der Grund, warum es sie gibt.
GEPRUEFT WIRD DESHALB, WAS DAS PROGRAMM TUT UND WAS EIN MENSCH ZU
SEHEN BEKOMMT: Code, Zeichenketten, sichtbarer HTML-Text.
Kommentare werden vorher durch Leerzeichen ersetzt -- zeichengenau,
damit die Zeilennummer eines Fundes weiter stimmt.
Der Zerleger dafuer steht in helfer-ohne-kommentar.mjs. Er wird
unten mit dreizehn Proben gegengeprueft, und das ist nicht
Zierde: Ein Zerleger, der zu viel wegnimmt, macht diese ganze
Pruefung still gruen. */
const text = ohneKommentar(readFileSync(datei, "utf8"), extname(datei));
const zeilen = text.split("\n");
for (let i = 0; i < zeilen.length; i++) {
if (MUSTER.test(zeilen[i])) {
treffer.push(`${relative(ORDNER, datei)}:${i + 1} ${zeilen[i].trim().slice(0, 70)}`);
}
}
}
ok(treffer.length === 0, treffer.length
? `${treffer.length} Fundstelle(n) -- der verborgene Zugang waere damit auffindbar:\n `
+ treffer.slice(0, 12).join("\n ")
: "keine einzige Fundstelle");
/* DIE AUSNAHME MUSS AUCH WIRKLICH GEBRAUCHT WERDEN.
Waere crew-index.html eines Tages umbenannt oder geloescht, stuende
hier eine Ausnahme fuer eine Datei, die es nicht gibt -- und beim
naechsten Umbau wuerde jemand annehmen, sie decke etwas ab. Eine
Ausnahme, die ins Leere zeigt, ist schlimmer als keine: Sie sieht aus
wie ein Schutz. */
ok(ausgenommen === AUSGENOMMEN.size,
`${ausgenommen} von ${AUSGENOMMEN.size} ausgenommenen Dateien gibt es wirklich`);
/* ---------- Gegenprobe ------------------------------------------------
Findet dieselbe Suche den Namen ueberhaupt, wenn er dasteht? Ohne
diesen Schritt bewiese der Haken oben nichts: Er waere auch gruen,
wenn das Muster kaputt oder die Dateiliste leer waere.
Auf einem Text im Speicher, nicht auf einer echten Datei -- eine
Pruefung, die zum Beweisen etwas anlegt, hinterlaesst irgendwann
etwas. */
console.log("\n=== Gegenprobe: schlaegt die Suche ueberhaupt an? ===\n");
{
const probe = " if (ich.rolle === 'modi') { ziel.hidden = true; return; }";
ok(MUSTER.test(probe), "eine eingebaute Fundstelle wird erkannt");
/* Und die drei Woerter, die NICHT anschlagen duerfen -- sonst waere
die Pruefung nach dem ersten Fehlalarm abgeschaltet. */
const harmlos = ["const modifiziert = true;", "/* Modul */", "prefers-reduced-motion",
"let modus = 'hell';"];
ok(harmlos.every((z) => !MUSTER.test(z)),
`und "modifiziert", "Modul", "motion", "Modus" schlagen nicht an`);
}
/* ---------- DIE ZWEITE GEGENPROBE: TAUGT DER ZERLEGER? --------------
Seit dem 21.09.2026 werden Kommentare vor der Suche entfernt. Das
ist die gefaehrlichste Zeile dieser Datei: Ein Zerleger, der zu viel
wegnimmt, macht die ganze Pruefung STILL GRUEN -- sie laeuft, sie
meldet "keine Fundstelle", und sie sieht nichts mehr.
Deshalb dreizehn Proben, und zwar in beide Richtungen. Sechs
muessen gefunden werden, sieben nicht. Faende der Zerleger gar
nichts mehr, fielen die sechs durch; naehme er nichts weg, die
sieben.
Zwei davon sind die Fallen, an denen ein einfacher Suchausdruck
zerbricht: eine Adresse mit Sternchen-Schraegstrich in einer
Zeichenkette, und ein Teilen, dem ein Zeilenkommentar folgt. */
console.log("\n=== Gegenprobe: taugt der Zerleger? ===\n");
{
const PROBEN = [
{ text: "// Modi hier", endung: ".js", soll: false, satz: "ein Zeilenkommentar zaehlt nicht" },
{ text: "/* Modi im Block */", endung: ".js", soll: false, satz: "ein Blockkommentar auch nicht" },
{ text: "const r = { modi: 1 };", endung: ".js", soll: true, satz: "ein Feldname im Code SCHON" },
{ text: "const s = \"Nur du und der Modi\";", endung: ".js", soll: true, satz: "und eine Zeichenkette auch" },
{ text: "const p = \"https://x.de/*nicht*/mehr/modi\";", endung: ".js", soll: true, satz: "eine Adresse mit Sternchen-Schraegstrich bleibt heil" },
{ text: "a = b / c; // Modi", endung: ".js", soll: false, satz: "ein Teilen verwirrt den Zerleger nicht" },
{ text: "x = `Hallo ${name} Modi`;", endung: ".js", soll: true, satz: "eine Vorlage zaehlt als Text" },
{ text: "/* Modi */ .k[data-rolle=\"modi\"] {}", endung: ".css", soll: true, satz: "in CSS: Kommentar weg, Auswahlregel bleibt" },
{ text: "<!-- Modi --><p>x</p>", endung: ".html", soll: false, satz: "ein HTML-Kommentar zaehlt nicht" },
{ text: "<p>Der Modi sieht das</p>", endung: ".html", soll: true, satz: "sichtbarer Text auf der Seite SCHON" },
{ text: "<script>/* Modi */ var a=1;</script>", endung: ".html", soll: false, satz: "ein Kommentar IM eingebetteten Skript auch nicht" },
{ text: "<script>var t=\"Modi\";</script>", endung: ".html", soll: true, satz: "eine Zeichenkette im eingebetteten Skript schon" },
{ text: "<style>/* Modi */ p{}</style>", endung: ".html", soll: false, satz: "und im eingebetteten Stil ebenso wenig" },
];
let richtig = 0;
for (const pr of PROBEN) {
const rest = ohneKommentar(pr.text, pr.endung);
const gefunden = MUSTER.test(rest);
if (gefunden === pr.soll) richtig++;
else ok(false, `${pr.satz} -- ABER die Probe lief falsch: ${JSON.stringify(pr.text)}`);
}
/* DIE ZAHL GEHOERT IN DIE BEDINGUNG. "alle richtig" waere auch bei
einer leeren Liste wahr. */
ok(richtig === PROBEN.length && PROBEN.length === 13,
`alle ${richtig} von ${PROBEN.length} Proben treffen zu`);
/* Und die Laenge bleibt gleich -- daran haengen die Zeilennummern,
mit denen ein Fund oben gemeldet wird. Waere sie es nicht, zeigte
die Meldung auf die falsche Zeile, und wer nachsieht, findet
nichts und glaubt der Pruefung nicht mehr. */
const roh = "var a = 1; /* Modi */\nvar b = 2; // Modi\nvar c = 3;";
const ohne = ohneKommentar(roh, ".js");
ok(ohne.length === roh.length,
`der Text bleibt zeichengenau gleich lang (${ohne.length} von ${roh.length})`);
ok(ohne.split("\n").length === roh.split("\n").length,
`und behaelt seine ${roh.split("\n").length} Zeilen`);
}
console.log(`\n${fehler ? "FEHLGESCHLAGEN" : "BESTANDEN"} -- ${geprueft} geprueft, ${fehler} Fehler`);
process.exit(fehler ? 1 : 0);