diff --git a/server/pruef-auskunft.mjs b/server/pruef-auskunft.mjs index ce3a6379..44a58b53 100644 --- a/server/pruef-auskunft.mjs +++ b/server/pruef-auskunft.mjs @@ -315,21 +315,78 @@ melde("=== 5. Nachvollziehbar ==="); melde(""); melde("=== 6. Der Knopf ==="); { - const { readFileSync } = await import("node:fs"); - for (const seite of ["steckbrief.html", "treff-regeln.html"]) { - const s = readFileSync(new URL("../workspace/" + seite, import.meta.url), "utf8"); - ok(/id="auskunft-knopf"/.test(s), `${seite} hat den Knopf`); - ok(/assets\/js\/auskunft\.js/.test(s), `${seite} lädt das Skript dazu`); + /* NICHT "AUF DIESEN ZWEI SEITEN", SONDERN "FUER JEDE ROLLE + ERREICHBAR" (umgestellt 21.09.2026). + + Hier stand woertlich: `ok(!tafel["…/steckbrief.html"] + .includes("gast"), "die Community hat keinen Steckbrief")`. + Am 19.09. hat Filipe genau das Gegenteil entschieden -- "jeder + soll ein steckbrief haben, jeder der ein account hat ... jeder + soll genau wie ich foto und so hinzufuegen koennen" -- und die + Community hat seitdem einen. Die Zeile war also rot, WEIL etwas + richtig gemacht wurde. + + Das ist die unangenehmste Sorte roter Pruefung: Sie bestraft die + Verbesserung. Wer sie zweimal so erlebt, faengt an, rote Laeufe + zu erklaeren statt sie zu lesen. + + WAS SIE EIGENTLICH SCHUETZEN WOLLTE, steht in ihrem eigenen + Kommentar: "Ohne die zweite Stelle haette die Gruppe mit den + wenigsten Rechten auch dieses nicht -- und sie ist die groesste." + Die Sorge ist richtig und hat mit der Anzahl der Seiten nichts zu + tun. Die Frage lautet: KOMMT JEDE ROLLE AN IHRE AUSKUNFT? + + Gemessen wird sie jetzt so: Welche Seiten tragen den Knopf (aus + den Dateien gelesen, nicht aufgezaehlt), und erreicht jede Rolle + mindestens eine davon (aus der Rechtetafel gelesen, nicht + angenommen)? Kommt morgen eine dritte Seite dazu oder faellt eine + weg, bleibt die Aussage dieselbe. */ + const { readFileSync, readdirSync } = await import("node:fs"); + const ordnerSeiten = new URL("../workspace/", import.meta.url); + const mitKnopf = []; + for (const datei of readdirSync(ordnerSeiten)) { + if (!datei.endsWith(".html")) continue; + const inhalt = readFileSync(new URL(datei, ordnerSeiten), "utf8"); + if (!/id="auskunft-knopf"/.test(inhalt)) continue; + mitKnopf.push(datei); + /* Ein Knopf ohne sein Skript ist eine Sackgasse, die aussieht wie + ein Weg -- man tippt darauf und es passiert nichts. */ + ok(/assets\/js\/auskunft\.js/.test(inhalt), `${datei} lädt das Skript dazu`); } - /* WARUM AUSGERECHNET DIESE ZWEI: Die Community hat keinen Steckbrief. - Ohne die zweite Stelle haette die Gruppe mit den wenigsten Rechten - auch dieses nicht -- und sie ist die groesste. */ + /* DIE ZAHL GEHOERT IN DIE BEDINGUNG. Faende die Suche keine einzige + Seite -- umbenannt, verschoben, anderer Bezeichner --, waere + "jede Rolle kommt hin" unten fuer alle falsch ODER fuer alle + wahr, je nach Schleife. Beides waere ohne diese Zeile nicht zu + unterscheiden. */ + ok(mitKnopf.length >= 1, + `${mitKnopf.length} Seite(n) tragen den Knopf (${mitKnopf.join(", ")})`); + const rechte = await import("./rechte.js"); const tafel = rechte.tafelJetzt(); - ok(!(tafel["/workspace/steckbrief.html"] || []).includes("gast"), - "die Community hat keinen Steckbrief"); - ok((tafel["/workspace/treff-regeln.html"] || []).includes("gast"), - "aber die Treff-Regeln – deshalb steht er auch dort"); + const wege = (rolle) => mitKnopf.filter( + (d) => (tafel["/workspace/" + d] || []).includes(rolle)); + + /* JEDE ROLLE, NICHT EINE AUSGEWAEHLTE. Die Liste kommt aus dem + Haus, damit eine neue Rolle nicht stillschweigend ungeprueft + bleibt -- genau so entstehen Gruppen, die etwas nicht koennen und + es niemandem sagen. */ + const ohneWeg = rechte.ALLE_ROLLEN.filter((r) => wege(r).length === 0); + ok(ohneWeg.length === 0, + ohneWeg.length + ? `DIESE ROLLEN KOMMEN NICHT AN IHRE AUSKUNFT: ${ohneWeg.join(", ")}` + : `alle ${rechte.ALLE_ROLLEN.length} Rollen erreichen mindestens eine ` + + `Seite mit dem Knopf`); + + /* Und die groesste Gruppe ausdruecklich benannt: Sie hat die + wenigsten Rechte, also faellt bei ihr zuerst etwas weg. */ + ok(wege("gast").length >= 1, + `die Community kommt ueber ${wege("gast").join(", ") || "KEINE Seite"} hin`); + + /* GEGENPROBE: Kann diese Rechnung ueberhaupt "nein" sagen? Eine + Rolle, die es nicht gibt, darf keine einzige Seite erreichen -- + sonst waere `wege()` kaputt und alles darueber wertlos. */ + ok(wege("diese-rolle-gibt-es-nicht").length === 0, + "Gegenprobe: eine erfundene Rolle erreicht keine"); } /* ------------------------------------------------------------------- */ diff --git a/server/pruef-treff-werkzeuge.mjs b/server/pruef-treff-werkzeuge.mjs index 39c7e090..f31394e5 100644 --- a/server/pruef-treff-werkzeuge.mjs +++ b/server/pruef-treff-werkzeuge.mjs @@ -562,9 +562,61 @@ melde("=== 8. Der Regeltext ==="); const t = json(meine); ok(meine.code === 200 && t.seiten?.length === rechte.alleSeiten().length, `DogFather bekommt alle ${t.seiten?.length} Seiten`); - const community = (t.rollen || []).find((x) => x.schluessel === "gast"); - ok(community && community.seiten < 5, - `und die Community steht bei ${community?.seiten} von ${t.seiten?.length}`); + /* EINE FESTE ZAHL IST EINE RECHNUNG VON GESTERN (berichtigt + 21.09.2026). + + Hier stand `community.seiten < 5`. Die Zahl stimmte an dem Tag, + an dem sie geschrieben wurde. Seither hat der Treff eigene Seiten + bekommen -- Regeln, Steckbrief, Hilfe, Draussen -- und die + Community steht bei acht. Die Pruefung war damit rot, WEIL der + Treff gewachsen ist. Dieselbe Fehlerklasse wie die Umbruchschwelle + vom 06.09.: Wer eine Zahl notiert, notiert einen Zustand, keine + Regel. + + DIE AUSSAGE, DIE GEMEINT WAR, IST EINE ORDNUNG, KEINE GRENZE: + Ein Mitglied sieht weniger als die Moderation, die weniger als die + rechte Hand, die weniger als DogFather. Wer Rechte vergibt, darf + diese Reihenfolge nicht versehentlich umdrehen -- und genau das + faellt an einer Zahl nicht auf, an der Ordnung sofort. + + Nachgemessen am 21.09.: 8 / 19 / 25 / 32 von 33 Seiten. Die Zahlen + stehen hier nur als Beleg; gemessen wird ihr Verhaeltnis, und das + bleibt richtig, wenn der Treff weiterwaechst. */ + const zahl = (schluessel) => + (t.rollen || []).find((x) => x.schluessel === schluessel)?.seiten; + const kette = ["gast", "modi", "hand", "admin"].map((r) => [r, zahl(r)]); + + /* DIE ZAHL GEHOERT IN DIE BEDINGUNG. Fehlte eine Rolle in der + Auskunft, waere `undefined < undefined` schlicht false -- aber + `[...].every(...)` ueber eine leere Liste waere wahr. Deshalb + zuerst: Sind ueberhaupt alle vier da? */ + ok(kette.every(([, n]) => Number.isFinite(n)), + `die Auskunft nennt alle vier: ${kette.map(([r, n]) => `${r} ${n}`).join(", ")}`); + + const steigt = kette.every(([, n], i) => i === 0 || n > kette[i - 1][1]); + ok(steigt, + steigt + ? `und sie stehen in der richtigen Ordnung ` + + `(${kette.map(([r, n]) => `${r} ${n}`).join(" < ")} von ${t.seiten?.length})` + : `DIE ORDNUNG STIMMT NICHT: ` + + `${kette.map(([r, n]) => `${r} ${n}`).join(", ")} -- jemand ` + + `weiter unten sieht mehr als jemand weiter oben`); + + /* UND DIE COMMUNITY IST DAS MINIMUM UEBER ALLE ROLLEN, nicht nur in + dieser Kette: Die Agentur steht daneben, nicht darueber, und auch + ihr gegenueber darf ein Mitglied nicht mehr Seiten haben. */ + const alleZahlen = (t.rollen || []).filter((x) => Number.isFinite(x.seiten)); + ok(alleZahlen.length >= 4 && alleZahlen.every((x) => x.schluessel === "gast" + || x.seiten > zahl("gast")), + `die Community hat die wenigsten von allen ${alleZahlen.length} Rollen ` + + `(${zahl("gast")} von ${t.seiten?.length})`); + + /* GEGENPROBE: Kann diese Ordnung ueberhaupt "nein" sagen? Umgekehrt + gelesen muss dieselbe Rechnung falsch herauskommen -- sonst waere + sie eine Bestaetigungsmaschine. */ + const rueckwaerts = [...kette].reverse(); + ok(!rueckwaerts.every(([, n], i) => i === 0 || n > rueckwaerts[i - 1][1]), + "Gegenprobe: umgekehrt gelesen faellt dieselbe Rechnung durch"); } /* ------------------------------------------------------------------- */