/* ===================================================================== DIE HAUSTRENNUNG BEI DEN EINTRAEGEN (03.10.2026) Filipe, 24.09.2026: "wenn ich bei der einen was mache soll nichts bei der anderen passieren. ... Kein Bestand wird geteilt." WAS AM 03.10.2026 GEMESSEN WURDE (server/_mess-haeuser.mjs, jedes Brett x beide Adressen x vier Rollen): Die Anmeldung war dicht -- eine Agenturrolle kam nicht auf die Crew-Adresse und umgekehrt. Durch griff genau EINE Rolle: DogFather. Er wohnt in beiden Haeusern, und die Riegel in sichtbarEintrag() fragten nach der ROLLE, nicht nach der Adresse. Auf der Crew-Adresse stand damit das Brett der Agentur samt Inhalt. Behoben durch Riegel 0 in sichtbarEintrag(): Wer auf einer Adresse angemeldet ist, sieht nur Eintraege dieses Hauses. --------------------------------------------------------------------- WARUM ES DIESE DATEI BRAUCHT UND NICHT NUR DEN RIEGEL Der Riegel haengt an der Spalte `eintraege.haus`. Die wird beim ANLEGEN gesetzt -- und zwar von Hand, an jeder Stelle einzeln, die einen Eintrag anlegt. Gemessen am 03.10.2026: FUENF von ACHT Stellen taten es nicht (workspace-video.js, workspace-content.js, workspace-bewerbung.js, workspace-treff.js, workspace-vorlagen.js). Das ist dieselbe Falle wie die abgeschriebene Spaltenliste vom 11.09.2026: Eine Angabe, die an acht Stellen von Hand gepflegt werden muss, ist an der neunten vergessen -- und zwar still. Ein Kommentar daneben verhindert das nicht (auch das steht so im Projektgedaechtnis: der Hinweis stand drei Tage lang direkt neben der Stelle und wurde beim naechsten Mal trotzdem nicht gelesen). Eine Pruefung schon. Diese hier wird rot, sobald eine neunte Stelle dazukommt, die `haus` vergisst. ===================================================================== */ import { mkdtempSync, rmSync, appendFileSync, readFileSync, readdirSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; const ordner = mkdtempSync(join(tmpdir(), "ws-hl-")); process.env.WORKSPACE_DB = join(ordner, "workspace.db"); import { notbremse } from "./helfer-notbremse.mjs"; const { eigenerPort } = await import("./helfer-port.mjs"); const PORT = await eigenerPort(import.meta, "die Haus-Luecken-Pruefung"); process.env.PORT = String(PORT); process.env.SITE_ACCESS_SECRET = "lokaler-test"; process.env.SITE_ACCESS_CODE_DOGI = "DOGI-TEST-1"; process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; const PROTOKOLL = join(process.cwd(), "pruef-haus-luecke-lauf.txt"); try { rmSync(PROTOKOLL, { force: true }); } catch { /* gab es noch nicht */ } const melde = (z) => { console.log(z); try { appendFileSync(PROTOKOLL, z + "\n"); } catch { /* Zugabe */ } }; let fehler = 0, geprueft = 0; const ok = (b, t) => { geprueft++; melde((b ? " ok " : " FEHL ") + t); if (!b) fehler++; }; /* ======================================================================= 1. OHNE SERVER: Schreibt jede Stelle das Haus mit? ======================================================================= */ melde("=== 1. Jede Stelle, die einen Eintrag anlegt, setzt `haus` ==="); { const dateien = readdirSync(".") .filter((f) => f.startsWith("workspace") && f.endsWith(".js")); /* DIE DATEIEN WERDEN GESUCHT, NICHT AUFGEZAEHLT. Eine Liste von Dateinamen hier waere genau die Sorte Angabe, die beim naechsten neuen Modul veraltet -- und dann prueft diese Datei die neue Stelle nicht, ohne dass etwas rot wird. */ ok(dateien.length > 10, `${dateien.length} Servermodule durchsucht`); const stellen = []; for (const datei of dateien) { const text = readFileSync(datei, "utf8"); /* Jede Einfuegung in `eintraege` samt ihrer Spaltenliste. Die Spaltenliste endet an der ersten schliessenden Klammer vor VALUES. */ const muster = /INSERT\s+INTO\s+eintraege\s*\(([^)]*)\)/gi; let t; while ((t = muster.exec(text)) !== null) { const zeile = text.slice(0, t.index).split("\n").length; const spalten = t[1].split(",").map((x) => x.trim()); stellen.push({ datei, zeile, hatHaus: spalten.includes("haus") }); } } ok(stellen.length >= 8, `${stellen.length} Einfuege-Stellen gefunden (erwartet mindestens 8)`); const ohne = stellen.filter((s) => !s.hatHaus); for (const s of stellen) { melde(` -- ${s.datei}:${s.zeile} ${s.hatHaus ? "setzt haus" : "OHNE haus"}`); } ok(ohne.length === 0, ohne.length ? `diese Stellen vergessen das Haus: ${ohne.map((s) => s.datei + ":" + s.zeile).join(", ")}` : "alle setzen es"); /* GEGENPROBE: Findet dieses Muster ueberhaupt etwas, das OHNE haus ist? Sonst koennte es an einem Tippfehler im regulaeren Ausdruck liegen, und die Pruefung waere dauerhaft gruen, ohne je etwas gesehen zu haben. */ const probe = "INSERT INTO eintraege (bereich, art, titel)\nVALUES (?,?,?)"; const m = /INSERT\s+INTO\s+eintraege\s*\(([^)]*)\)/i.exec(probe); ok(!!m && !m[1].split(",").map((x) => x.trim()).includes("haus"), "das Suchmuster erkennt eine Stelle OHNE haus (Gegenprobe)"); const probe2 = "INSERT INTO eintraege (bereich, art, haus)\nVALUES (?,?,?)"; const m2 = /INSERT\s+INTO\s+eintraege\s*\(([^)]*)\)/i.exec(probe2); ok(!!m2 && m2[1].split(",").map((x) => x.trim()).includes("haus"), "und eine MIT haus (Gegenprobe andersherum)"); } /* ======================================================================= 2. MIT SERVER: Greift der Riegel wirklich? ======================================================================= */ const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; await import("./index.js"); notbremse(240_000, "pruef-haus-luecke"); await new Promise((r) => setTimeout(r, 1000)); const BASIS = "http://127.0.0.1:" + PORT; const { DatabaseSync } = await import("node:sqlite"); const { scryptSync, randomBytes } = 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(() => {}); for (let i = 0; i < 60; i++) { try { const p = new DatabaseSync(process.env.WORKSPACE_DB); try { p.prepare("SELECT 1 FROM personen LIMIT 1").get(); break; } finally { p.close(); } } catch { await new Promise((r) => setTimeout(r, 250)); } } const d = new DatabaseSync(process.env.WORKSPACE_DB); const jetzt = new Date().toISOString(); 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); return d.prepare("SELECT last_insert_rowid() AS id").get().id; } anlegen("Dogfather", "admin", "CODE-DOGI-0001"); const heute = new Date().toISOString().slice(0, 10); /* Drei Marken auf demselben Brett: eine je Haus und eine ohne. */ for (const [titel, haus] of [["Marke-CREW", "crew"], ["Marke-AGENTUR", "agentur"], ["Marke-OHNE", null]]) { d.prepare("INSERT INTO eintraege (titel, text, bereich, art, status, datum, dringlichkeit, erstellt, erstellt_von, haus)" + " VALUES (?,?,'anschlag','ansage','offen',?, 'mittel', ?, 1, ?)") .run(titel, "Test.", heute, jetzt, haus); } d.close(); const { request } = await import("node:http"); function roh(host, pfad, o = {}) { return new Promise((f, s) => { const daten = o.rumpf === undefined ? null : Buffer.from(JSON.stringify(o.rumpf)); const a = request({ host: "127.0.0.1", port: PORT, path: pfad, method: o.methode || (daten ? "POST" : "GET"), headers: { Host: host, Origin: "https://" + host, ...(o.keks ? { Cookie: o.keks } : {}), ...(daten ? { "Content-Length": daten.length, "Content-Type": "application/json" } : {}), }, }, (r) => { const t = []; r.on("data", (x) => t.push(x)); r.on("end", () => f({ code: r.statusCode, kopf: r.headers, text: Buffer.concat(t).toString("utf8") })); }); a.on("error", s); if (daten) a.write(daten); a.end(); }); } const CREW = "crew.dogfather-universe.com"; const AGEN = "workspace.dogfather-universe.com"; async function titelAuf(host) { const anm = await roh(host, "/workspace/api/anmelden", { rumpf: { rolle: "admin", code: "CODE-DOGI-0001" } }); if (anm.code !== 200) return null; const keks = [].concat(anm.kopf["set-cookie"] || []).map((c) => c.split(";")[0]).join("; "); const a = await roh(host, "/workspace/api/bereich/anschlag", { keks }); if (a.code !== 200) return null; try { return (JSON.parse(a.text).eintraege || []).map((e) => e.titel).sort(); } catch { return null; } } melde("\n=== 2. DogFather sieht nur das Haus seiner Adresse ==="); { const aufCrew = await titelAuf(CREW); const aufAgentur = await titelAuf(AGEN); /* DER DRITTE AUSGANG: Antwortet der Server gar nicht, ist das kein "alles sauber" -- dann konnte hier nichts gemessen werden. */ if (!aufCrew || !aufAgentur) { melde("ABBRUCH: das Brett antwortet nicht -- hier wurde nichts geprueft."); process.exit(3); } melde(" -- auf crew.: " + JSON.stringify(aufCrew)); melde(" -- auf workspace.: " + JSON.stringify(aufAgentur)); ok(aufCrew.includes("Marke-CREW"), "auf crew. steht die Crew-Marke"); ok(!aufCrew.includes("Marke-AGENTUR"), "auf crew. steht die Agentur-Marke NICHT"); ok(aufAgentur.includes("Marke-AGENTUR"), "auf workspace. steht die Agentur-Marke"); ok(!aufAgentur.includes("Marke-CREW"), "auf workspace. steht die Crew-Marke NICHT"); /* DER RUECKFALL IST ABSICHT UND WIRD ALS SOLCHER GEPRUEFT: Ein Eintrag ohne Haus bleibt in beiden sichtbar. Das ist die sichere Richtung -- ein Eintrag zu viel faellt auf, ein fehlender nicht. Teil 1 sorgt dafuer, dass dieser Fall gar nicht erst entsteht. */ ok(aufCrew.includes("Marke-OHNE") && aufAgentur.includes("Marke-OHNE"), "ein Eintrag OHNE Haus bleibt in beiden sichtbar (bewusster Rueckfall)"); /* GEGENPROBE ZUR MESSUNG SELBST: Wenn hier nur eine Marke stuende, waeren drei der Zusagen oben gruen, ohne etwas zu beweisen. */ ok(aufCrew.length === 2 && aufAgentur.length === 2, `je zwei von drei Marken sind sichtbar (${aufCrew.length} / ${aufAgentur.length})`); } melde("\n=== 3. Ohne Adresse wird nicht gefiltert ==="); { /* Auf 127.0.0.1 gibt es kein Haus -- dort muss alles sichtbar bleiben, sonst waeren alle Pruefungen des Hauses still gruen und blind. Genau davor warnt crew-adresse.js dreimal. */ const alles = await titelAuf("127.0.0.1:" + PORT); ok(alles !== null && alles.length === 3, `auf einer Pruefadresse stehen alle drei Marken (${alles ? alles.length : "keine Antwort"})`); } melde(""); melde(`${geprueft} geprueft, ${fehler} Fehler`); if (!geprueft) { melde("ABBRUCH: nichts geprueft -- das ist ein Fehler, kein Erfolg."); process.exit(3); } process.exit(fehler ? 1 : 0);