/* ===================================================================== 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: "
x
", endung: ".html", soll: false, satz: "ein HTML-Kommentar zaehlt nicht" }, { text: "Der Modi sieht das
", endung: ".html", soll: true, satz: "sichtbarer Text auf der Seite SCHON" }, { text: "", endung: ".html", soll: false, satz: "ein Kommentar IM eingebetteten Skript auch nicht" }, { text: "", endung: ".html", soll: true, satz: "eine Zeichenkette im eingebetteten Skript schon" }, { text: "", 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);