diff --git a/server/pruef-glocke.mjs b/server/pruef-glocke.mjs
index 4faafb49..f9fbedbf 100644
--- a/server/pruef-glocke.mjs
+++ b/server/pruef-glocke.mjs
@@ -44,9 +44,17 @@
GEGENPROBEN durchgehend. */
-import { mkdtempSync, rmSync, appendFileSync } from "node:fs";
+import { mkdtempSync, rmSync, appendFileSync, readdirSync, readFileSync }
+ from "node:fs";
import { tmpdir } from "node:os";
-import { join } from "node:path";
+import { join, dirname } from "node:path";
+import { fileURLToPath } from "node:url";
+
+/* WO DIE SEITEN LIEGEN. Abgeleitet vom eigenen Ort, nicht als
+ fester Pfad -- damit die Pruefung auch laeuft, wenn jemand das
+ Verzeichnis woandershin legt. */
+const SEITEN_ORDNER = join(dirname(fileURLToPath(import.meta.url)),
+ "..", "workspace");
const ordner = mkdtempSync(join(tmpdir(), "ws-glocke-"));
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
@@ -84,7 +92,7 @@ const hinweis = (t) => melde(" -- " + t);
/* ---- Bestand ------------------------------------------------------------ */
const { DatabaseSync } = await import("node:sqlite");
-const { scryptSync, randomBytes } = await import("node:crypto");
+const { scryptSync, randomBytes, createHmac } = await import("node:crypto");
await fetch(BASIS + "/workspace/api/anmelden", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ rolle: "admin", code: "x" }) }).catch(() => {});
@@ -96,17 +104,38 @@ for (let i = 0; i < 60; i++) {
}
const d = new DatabaseSync(process.env.WORKSPACE_DB);
const jetzt = new Date().toISOString();
+/* DER SUCHSCHLUESSEL FUER DEN STILLEN ZUGANG (24.09.2026).
+
+ Rechte Hand, linke Hand und Modi haben keine eigene Kachel; der
+ Server findet sie ueber `code_kennung`, einen HMAC des Codes.
+ Fehlt diese Spalte, kommen sie gar nicht herein -- und das sah in
+ dieser Pruefung aus wie ein Fehler an der Glocke, war aber einer
+ am Aufbau der Testdaten. */
+const kennSchluessel = randomBytes(32).toString("hex");
+d.prepare("INSERT INTO einstellungen (schluessel, wert, geaendert, von)"
+ + " VALUES (?,?,?,NULL) ON CONFLICT(schluessel) DO NOTHING")
+ .run("code_kennung_schluessel", kennSchluessel, jetzt);
function anlegen(name, rolle, code) {
const salt = randomBytes(16).toString("hex");
const hash = scryptSync(code, salt, 64, { N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex");
- d.prepare("INSERT INTO personen (name, rolle, code_hash, code_salt, code_n, aktiv, erstellt) VALUES (?,?,?,?,?,1,?)")
- .run(name, rolle, hash, salt, 32768, jetzt);
+ d.prepare("INSERT INTO personen (name, rolle, code_hash, code_salt, code_n,"
+ + " code_kennung, aktiv, erstellt) VALUES (?,?,?,?,?,?,1,?)")
+ .run(name, rolle, hash, salt, 32768,
+ createHmac("sha256", kennSchluessel).update(code).digest("hex"), jetzt);
return d.prepare("SELECT last_insert_rowid() AS id").get().id;
}
const idDogi = anlegen("Filipe", "admin", "CODE-DOGI-0001");
anlegen("Cigdem", "manager", "CODE-MANA-0001");
const idScout = anlegen("Patrick", "scout", "CODE-SCOU-0001");
const idLuna = anlegen("Luna", "creator", "CODE-CREA-0001");
+/* DIE UEBRIGEN VIER ROLLEN (24.09.2026).
+ Sie fehlten, und deshalb fehlten sie auch im Abschnitt "Jede Rolle
+ bekommt ihn" weiter unten -- der pruefte vier von acht und hiess
+ trotzdem "jede". */
+anlegen("VanVan", "hand", "CODE-HAND-0001");
+anlegen("Funny", "linke", "CODE-LINK-0001");
+anlegen("Miss", "modi", "CODE-MODI-0001");
+anlegen("Spicy Media", "spicy", "CODE-SPIC-0001");
d.prepare("INSERT INTO betreuung (creator_id, betreuer_id, seit) VALUES (?,?,?)").run(idLuna, idScout, jetzt);
d.close();
@@ -179,7 +208,22 @@ function berechtigung(stand) {
async function anmelden(ctx, rolle, code) {
const seite = await ctx.newPage();
await seite.goto(BASIS + "/workspace/", { waitUntil: "networkidle" });
- await seite.click(`.rolle[data-rolle="${rolle}"]`);
+ /* DER STILLE ZUGANG (09.09.2026, hier nachgezogen am 24.09.2026).
+
+ Rechte Hand, linke Hand und Modi haben ABSICHTLICH keine eigene
+ Kachel auf der Anmeldeseite -- Filipes Wunsch: "damit die von der
+ workspace auch nicht mal sehen dass die modis von mir einen
+ eigenen zugang haben." Sie tippen auf irgendeine vorhandene
+ Kachel, und der Code allein entscheidet.
+
+ Solange diese Pruefung nur vier Rollen ansah, fiel es nicht auf.
+ Seit sie alle acht ansieht, waere `click` auf eine Kachel, die
+ es nicht gibt, ein Abbruch -- und der saehe aus wie ein Fehler
+ an der Glocke, obwohl er einer an der Pruefung waere. */
+ const kachel = await seite.$(`.rolle[data-rolle="${rolle}"]`)
+ || await seite.$(".rolle");
+ if (!kachel) throw new Error("keine einzige Rollenkachel auf der Anmeldeseite");
+ await kachel.click();
await seite.fill("#code", code);
await seite.click("button[type=submit]");
await seite.waitForURL("**/start.html", { timeout: 25000 });
@@ -236,8 +280,39 @@ melde("\n=== Der Knopf ist überall ===");
await ctx.addInitScript(berechtigung("default"));
const seite = await anmelden(ctx, "admin", "CODE-DOGI-0001");
- const seitenNamen = ["start", "aufgaben", "kalender", "bereich", "content",
- "dateien", "wissen", "report"];
+ /* DIE LISTE WIRD ABGELEITET, NICHT ABGESCHRIEBEN (24.09.2026).
+
+ Hier standen acht Namen -- und der Abschnitt darueber heisst
+ "Der Knopf ist ueberall". Er war gruen, weil alle acht ihn
+ hatten. Gemessen am selben Tag: VIERZEHN WEITERE Seiten haben
+ eine Kopfleiste und hatten keinen Knopf, darunter befinden.html,
+ werdegang.html und teilen.html -- genau die Seiten, auf denen
+ eine Creator ihre Zeit verbringt, und talente.html und
+ bewerbungen.html, wo ein Scout sitzt. Gemeldet hat es Filipe
+ ("dieser button soll bei jeder rolle perfekt funktionieren"),
+ nicht diese Pruefung.
+
+ DIE LISTE KOMMT JETZT AUS DEM VERZEICHNIS: Jede Seite, die eine
+ Kopfleiste hat, muss den Knopf haben. Eine Seite, die morgen
+ dazukommt, ist damit von selbst dabei -- eine Liste, die niemand
+ pflegt, kann nicht veralten. */
+ const alleSeiten = readdirSync(SEITEN_ORDNER)
+ .filter((f) => f.endsWith(".html"))
+ .filter((f) => readFileSync(join(SEITEN_ORDNER, f), "utf8")
+ .includes("kopfleiste__rechts"))
+ .map((f) => f.replace(/\.html$/, ""));
+ /* EINE BEGRUENDETE AUSNAHME, und nur eine: anruf-probe.html ist
+ eine eigenstaendige Diagnoseseite ohne kopf.js, und
+ anruf-probe.js verweist ausdruecklich auf "im Chat oben auf die
+ Glocke tippen". Sie steht hier NAMENTLICH -- eine stille
+ Ausnahme waere ein Loch mit Zustimmung. */
+ const AUSNAHMEN = new Set(["anruf-probe"]);
+ const seitenNamen = alleSeiten.filter((x) => !AUSNAHMEN.has(x));
+ /* DIE ZAHL GEHOERT IN DIE BEDINGUNG: Faende das Verzeichnis morgen
+ keine Seite mehr, waere "auf allen 0 geprueften" sonst gruen. */
+ ok(seitenNamen.length >= 20,
+ `${seitenNamen.length} Seiten mit Kopfleiste gefunden `
+ + `(ausgenommen: ${[...AUSNAHMEN].join(", ")})`);
let gefunden = 0;
const ohne = [];
for (const n of seitenNamen) {
@@ -245,9 +320,9 @@ melde("\n=== Der Knopf ist überall ===");
await seite.waitForTimeout(250);
if (await seite.$("#glocke")) gefunden++; else ohne.push(n);
}
- ok(gefunden === seitenNamen.length,
+ ok(gefunden === seitenNamen.length && gefunden > 0,
ohne.length ? `fehlt auf: ${ohne.join(", ")}`
- : `auf allen ${gefunden} geprüften Seiten`);
+ : `auf allen ${gefunden} Seiten mit Kopfleiste`);
/* Und NICHT auf der Anmeldeseite -- dort ist niemand angemeldet,
den man benachrichtigen könnte. */
@@ -262,8 +337,14 @@ melde("\n=== Jede Rolle bekommt ihn ===");
{
const browser = await pw.chromium.launch();
for (const [name, rolle, code] of [
+ /* ALLE ACHT (24.09.2026). Hier standen vier -- unter der
+ Ueberschrift "Jede Rolle bekommt ihn". Die rechte Hand, die
+ linke Hand, die Modis und Spicy Media fehlten; ausgerechnet
+ die Modis sind die groesste Gruppe im Haus. */
["DogFather", "admin", "CODE-DOGI-0001"], ["Manager", "manager", "CODE-MANA-0001"],
["Scout", "scout", "CODE-SCOU-0001"], ["Creator", "creator", "CODE-CREA-0001"],
+ ["Rechte Hand", "hand", "CODE-HAND-0001"], ["Linke Hand", "linke", "CODE-LINK-0001"],
+ ["Modi", "modi", "CODE-MODI-0001"], ["Spicy Media", "spicy", "CODE-SPIC-0001"],
]) {
const ctx = await browser.newContext();
await ctx.addInitScript(berechtigung("default"));
diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html
index d130166c..bae2b423 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`. -->
-
-
+
+
-
+