/* ===================================================================== WAS DOGFATHER EINTRÄGT, SEHEN AUCH DIE ANDEREN (17.09.2026) Filipe: „die community und alle anderen sollen die neuen sachen in allen kategorien auch sehen bitte. in allen die sie sehen soll auch jede änderung mit gehen, immer nur in denen die ihnen erlaubt ist zu sehen." --------------------------------------------------------------------- ZWEI FEHLER, DIE NIEMAND GEMELDET HÄTTE Beide sahen auf DogFathers eigenem Bildschirm vollkommen richtig aus. Das ist das Tückische daran: Ein leeres Brett sieht nicht nach Fehler aus, sondern nach „da ist eben noch nichts". 1. FREIGABE. `ansteht` und `highlight` zeigen der Community nur, was freigegeben ist. Ein aus dem Vorschlagskasten übernommener Eintrag hatte keine Freigabe -- gemessen sah die Community 0 von 4 und 0 von 3, während im Team alle drei Rollen alles sahen. 2. TEAM_DOGI_ROLLEN ist {hand, modi} -- OHNE "admin". Die Sichtbarkeitsregel für Team Dogi lautete damit wörtlich „Einträge von hand oder modi", und alles, was DogFather in Live, Technik, Community, Ideen, Angebote oder Rückmeldung schrieb, war für sein eigenes Team unsichtbar. --------------------------------------------------------------------- UND DIE ANDERE HÄLFTE DES SATZES WIRD AUCH GEPRÜFT „immer nur in denen die ihnen erlaubt ist zu sehen." Eine Prüfung, die nur zählt, ob alle alles sehen, wäre grün, wenn man sämtliche Schranken entfernte. Deshalb steht in Abschnitt 4 die Gegenprobe: Was zu ist, muss zu bleiben. Aufrufen mit: node server/pruef-alle-sehen-es.mjs ===================================================================== */ import { mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { request as httpAnfrage } from "node:http"; import { eigenerPort } from "./helfer-port.mjs"; const PORT = await eigenerPort(import.meta, "pruef-alle-sehen-es"); const ordner = mkdtempSync(join(tmpdir(), "ws-sicht-")); process.env.WORKSPACE_DB = join(ordner, "workspace.db"); process.env.PORT = String(PORT); process.env.SITE_ACCESS_SECRET = "lokaler-test"; process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; 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 }); }; import { notbremse } from "./helfer-notbremse.mjs"; await import("./index.js"); notbremse(180_000, "pruef-alle-sehen-es"); await new Promise((r) => setTimeout(r, 700)); const CREW = "crew.dogfather-universe.com"; let fehler = 0, geprueft = 0; const melde = (t) => console.log(t); const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; }; const { DatabaseSync } = await import("node:sqlite"); const { scryptSync, randomBytes, createHmac } = await import("node:crypto"); const { TREFF_BRETTER } = await import("./workspace.js"); const { TREFF_FREIGABE_BRETTER } = await import("./workspace-treff.js"); const { BEREICHE } = await import("./workspace-bereiche.js"); const { ARBEIT_START } = await import("./workspace-arbeit-start.js"); const { TREFF_START } = await import("./workspace-treff-start.js"); const d = new DatabaseSync(process.env.WORKSPACE_DB); const jetzt = new Date().toISOString(); const sch = randomBytes(32).toString("hex"); d.prepare("INSERT INTO einstellungen (schluessel, wert, geaendert, von)" + " VALUES (?,?,?,NULL) ON CONFLICT(schluessel) DO NOTHING") .run("code_kennung_schluessel", sch, jetzt); const ROLLEN = ["admin", "hand", "modi", "gast"]; const codes = {}; for (const r of ROLLEN) { const code = `CODE-${r.toUpperCase()}-0001`; 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, code_kennung, aktiv, erstellt)" + " VALUES (?,?,?,?,?,?,1,?)") .run(r, r, hash, salt, 32768, createHmac("sha256", sch).update(code).digest("hex"), jetzt); codes[r] = code; } d.close(); function roh(pfad, kopf = {}, koerper = null, methode = null) { return new Promise((fertig, schief) => { const a = httpAnfrage({ host: "127.0.0.1", port: PORT, path: pfad, method: methode || (koerper ? "POST" : "GET"), headers: { Host: CREW, ...(koerper ? { "Content-Type": "application/json" } : {}), ...kopf }, }, (antwort) => { let text = ""; antwort.on("data", (s) => { text += s; }); antwort.on("end", () => fertig({ code: antwort.statusCode, text })); }); a.on("error", schief); if (koerper) a.write(JSON.stringify(koerper)); a.end(); }); } const kekse = {}; for (const r of ROLLEN) { const antwort = await new Promise((fertig) => { const q = httpAnfrage({ host: "127.0.0.1", port: PORT, path: "/workspace/api/anmelden", method: "POST", headers: { Host: CREW, "Content-Type": "application/json" }, }, (w) => { let t = ""; w.on("data", (x) => { t += x; }); w.on("end", () => fertig(w)); }); q.write(JSON.stringify(r === "gast" ? { rolle: r, code: codes[r], alter_ok: true } : { rolle: r, code: codes[r] })); q.end(); }); kekse[r] = [].concat(antwort.headers["set-cookie"] || []).map((c) => c.split(";")[0]).join("; "); } async function liste(bereich, rolle) { const a = await roh(`/workspace/api/bereich/${bereich}`, { cookie: kekse[rolle] }); let j = null; try { j = JSON.parse(a.text); } catch { /* keine JSON-Antwort */ } return { code: a.code, n: (j?.eintraege || []).length }; } /* ======================================================================= 1. DOGFATHER FÜLLT ALLES ======================================================================= */ melde(""); melde("=== 1. Der Vorschlagskasten ==="); const KATALOG = { ...ARBEIT_START, ...TREFF_START }; const gefuellt = []; { let summe = 0; for (const b of Object.keys(BEREICHE)) { const a = await roh(`/workspace/api/bereich/${b}/start`, { cookie: kekse.admin, origin: `https://${CREW}` }, {}); let j = null; try { j = JSON.parse(a.text); } catch { /* keine JSON-Antwort */ } if (a.code === 201 && j?.angelegt) { gefuellt.push(b); summe += j.angelegt; } } /* DIE ZAHL STEHT IN DER BEDINGUNG, nicht nur im Text. Ohne das wäre „alle sehen gleich viel" auch dann grün, wenn es null wäre. */ ok(summe > 50, `DogFather legt ${summe} fertige Einträge in ${gefuellt.length} Kategorien an`); ok(gefuellt.length >= 15, `und zwar in fast allen (${gefuellt.length} von ${Object.keys(BEREICHE).length})`); /* Die beiden personenbezogenen bleiben mit Absicht leer. */ ok(!KATALOG.talente && !KATALOG.entwicklung, "„talente\" und „entwicklung\" haben bewusst keinen Katalog – dort stünden erfundene Menschen"); } /* ======================================================================= 2. UND DIE COMMUNITY SIEHT ES AUCH ← Fehler 1 ======================================================================= */ melde(""); melde("=== 2. Was die Community sieht ==="); { for (const b of TREFF_BRETTER) { const a = await liste(b, "admin"); const g = await liste(b, "gast"); const noetig = TREFF_FREIGABE_BRETTER.includes(b); ok(a.n > 0 && g.n === a.n, `${b.padEnd(10)} DogFather ${a.n}, Community ${g.n}` + (noetig ? " (Brett mit Freigabe – genau hier war der Fehler)" : "")); } } /* ======================================================================= 3. UND SEIN TEAM AUCH ← Fehler 2 ======================================================================= */ melde(""); melde("=== 3. Was das Team sieht ==="); { let geprueftHier = 0; for (const b of gefuellt) { const a = await liste(b, "admin"); if (!a.n) continue; for (const r of ["hand", "modi"]) { const x = await liste(b, r); /* 404 heißt: Diese Rolle darf den Bereich gar nicht -- das ist kein Fehler, sondern die andere Hälfte von Filipes Satz. Sie wird in Abschnitt 4 geprüft, hier übersprungen. */ if (x.code === 404) continue; geprueftHier++; ok(x.n === a.n, `${b.padEnd(12)} ${r.padEnd(5)} sieht ${x.n} von ${a.n}`); } } ok(geprueftHier >= 10, `es wurden ${geprueftHier} Kombinationen wirklich geprüft – nicht alle übersprungen`); } /* ======================================================================= 4. ABER NUR DA, WO SIE DÜRFEN ← die Gegenprobe ======================================================================= */ melde(""); melde("=== 4. Und was zu bleibt ==="); { /* OHNE DIESEN ABSCHNITT wäre die ganze Prüfung grün, wenn man jede Schranke entfernte. „Alle sehen alles" ist nicht das Ziel -- Filipe sagt ausdrücklich „immer nur in denen die ihnen erlaubt ist zu sehen". */ const zu = []; for (const b of Object.keys(BEREICHE)) { for (const r of ["hand", "modi", "gast"]) { const x = await liste(b, r); if (x.code === 404) zu.push(`${b}/${r}`); } } ok(zu.length >= 10, `${zu.length} Bereich-Rollen-Paare bleiben verschlossen (404)`); /* Und die Community kommt an keinen einzigen Arbeitsbereich. */ const arbeit = Object.keys(BEREICHE).filter((b) => !TREFF_BRETTER.includes(b)); const offenFuerGast = []; for (const b of arbeit) { const x = await liste(b, "gast"); if (x.code !== 404) offenFuerGast.push(`${b} (${x.code})`); } ok(offenFuerGast.length === 0, offenFuerGast.length ? `die Community kommt an: ${offenFuerGast.join(", ")}` : `die Community sieht keinen der ${arbeit.length} Arbeitsbereiche`); } /* ======================================================================= 5. UND DIE REGEL STEHT AN EINER STELLE ======================================================================= */ melde(""); melde("=== 5. Eine Quelle, nicht zwei ==="); { const { readFileSync } = await import("node:fs"); const quelle = readFileSync(new URL("./workspace-bereiche.js", import.meta.url), "utf8") .replace(/\/\*[\s\S]*?\*\//g, " ").replace(/\/\/[^\r\n]*/g, " "); ok(/\[\.\.\.TEAM_DOGI_ROLLEN,\s*"admin"\]/.test(quelle), "die Team-Sicht schließt DogFather ein"); /* UND TEAM_DOGI_ROLLEN SELBST BLEIBT UNBERÜHRT. Stünde "admin" dort drin, wäre er stillschweigend ins andere Haus umgezogen -- diese Menge entscheidet auch darüber. */ const { TEAM_DOGI_ROLLEN } = await import("./workspace.js"); ok(!TEAM_DOGI_ROLLEN.has("admin"), `TEAM_DOGI_ROLLEN bleibt {${[...TEAM_DOGI_ROLLEN].join(", ")}} – sonst zöge DogFather ins Haus der Crew`); const treff = readFileSync(new URL("./workspace-bereiche.js", import.meta.url), "utf8"); ok(/INSERT OR IGNORE INTO treff_freigaben/.test(treff), "und ein übernommener Vorschlag wird gleich freigegeben"); } melde(""); melde(`${geprueft} Prüfungen, ${fehler} Fehler`); melde(fehler ? "NICHT IN ORDNUNG" : "ALLES IN ORDNUNG"); try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Aufraeumrest */ } process.exit(fehler ? 1 : 0);