From 5d9c8cd37764f5e4f3e5ed36aed900cf34be1537 Mon Sep 17 00:00:00 2001 From: Dogfather Date: Sat, 3 Oct 2026 23:33:03 +0200 Subject: [PATCH] Haustrennung: kein Eintrag mehr aus dem anderen Haus Filipe, 24.09.2026: "wenn ich bei der einen was mache soll nichts bei der anderen passieren." -- und heute: "ja los". GEMESSEN, BEVOR ETWAS ANGEFASST WURDE (jedes Brett x beide Adressen x vier Rollen): AGENTUR-Adresse DogFather 18 Bretter Manager 6 Bretter Creator 6 Bretter Modi kommt nicht rein (401) CREW-Adresse DogFather 18 Bretter Modi 13 Bretter Manager kommt nicht rein (401) Creator kommt nicht rein (401) Die Anmeldung war also dicht. 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. Nachgemessen ist der Durchgriff AELTER als der gestrige Eventkarten- Umbau -- zweimal gemessen, mit und ohne ihn, gleiches Ergebnis. RIEGEL 0 in sichtbarEintrag(): Wer auf einer Adresse angemeldet ist, sieht nur Eintraege dieses Hauses. Er haengt den uebrigen Riegeln UM, statt in jeden Ausgang geschrieben zu werden -- die Funktion hat drei Rueckgabepunkte, und der naechste waere sonst wieder offen. Nachher, dieselbe Messung: DogFather sieht auf der Agenturadresse nur Agentur-Eintraege, auf der Crew-Adresse nur die des Rudels. Manager, Creator und Modi unveraendert. WAS DABEI SCHIEFGING UND WIE ES AUFFIEL 1. Die erste Fassung liess bei `haus IS NULL` den BEREICH entscheiden. pruef-haus-trennung.mjs wurde sofort rot: "Lunas Live vom Montag" verschwand von der Agenturadresse. `live`, `technik` und `community` tragen BEIDES -- die Kacheln von Team Dogi und die Creator-Akten. Eine Regel, die jedem Brett genau ein Haus zuweist, kann das nicht. Jetzt bleibt ein Eintrag ohne Haus sichtbar: ein Eintrag zu viel faellt auf, ein fehlender nicht. 2. Damit NULL kein Dauerloch ist: FUENF von ACHT Stellen, die Eintraege anlegen, setzten `haus` gar nicht (workspace-video.js, -content.js, -bewerbung.js, -treff.js, -vorlagen.js). Nachgetragen. 3. Und `person.haus` war dafuer der falsche Massstab: Legt DogFather ueber die Agenturadresse ein Highlight an, gehoert es trotzdem dem Rudel -- sonst sieht die Community es nie. pruef-treff.mjs hat das gefunden (2 Fehler). Neu: hausFuerNeuenEintrag() -- bei den sieben Brettern des Rudels entscheidet das BRETT, sonst die Adresse. NEU: pruef-haus-luecke.mjs (12 Pruefungen). Sie sucht die Einfuege-Stellen im Quelltext und wird rot, sobald eine neunte dazukommt, die `haus` vergisst -- mit Gegenprobe, dass das Suchmuster eine solche Stelle auch wirklich erkennt. Ein Kommentar daneben haette es nicht verhindert; das steht so schon im Projektgedaechtnis. NEBENBEI: In bereich.js stand seit gestern `|| "Agentur-Events"` als Rueckfall fuer das Etikett der Vorschau. pruef-treff.mjs verbietet das zu Recht -- wie ein Brett heisst, haengt am Haus, und der Server sagt es. Der Rueckfall ist weg; fehlt die Angabe, steht lieber kein Etikett da als ein falsches. pruef-treff.mjs nachgezogen (85 -> 86 Pruefungen, nicht weniger): Die Zusage "DogFather sieht den Beitrag" wird jetzt auf der Crew-Adresse geprueft, mit Gegenprobe fuer die Agenturadresse. GEPRUEFT, alle gruen: pruef-haus-luecke 12 pruef-haus-trennung 100 pruef-crew-adresse 169 pruef-treff 86 pruef-eventkarte 77 pruef-agentur 62 pruef-haus-seiten 38 pruef-eintrag-bild 24 pruef-vorlagen 24 pruef-video, -content, -bewerbung, -bereiche-lesend: in Ordnung Co-Authored-By: Claude Opus 5 --- server/pruef-haus-luecke.mjs | 231 ++++++++++++++++++++++++++++++++ server/pruef-treff.mjs | 26 +++- server/workspace-bereiche.js | 130 ++++++++++++++++-- server/workspace-bewerbung.js | 7 +- server/workspace-content.js | 7 +- server/workspace-treff.js | 8 +- server/workspace-video.js | 8 +- server/workspace-vorlagen.js | 7 +- server/workspace.js | 43 ++++++ workspace/anruf-probe.html | 10 +- workspace/app.webmanifest | 8 +- workspace/assets/js/bereich.js | 15 ++- workspace/aufgaben.html | 38 +++--- workspace/automation.html | 32 ++--- workspace/befinden.html | 34 ++--- workspace/bereich.html | 44 +++--- workspace/bewerben.html | 38 +++--- workspace/bewerbungen.html | 36 ++--- workspace/buehne.html | 6 +- workspace/calls.html | 34 ++--- workspace/chat.html | 40 +++--- workspace/content.html | 34 ++--- workspace/crew-index.html | 12 +- workspace/crew.webmanifest | 8 +- workspace/dateien.html | 34 ++--- workspace/entwicklung.html | 36 ++--- workspace/hilfe.html | 30 ++--- workspace/index.html | 10 +- workspace/kalender.html | 34 ++--- workspace/leistung.html | 32 ++--- workspace/manager-ziele.html | 28 ++-- workspace/material.html | 32 ++--- workspace/notizen.html | 28 ++-- workspace/personen.html | 32 ++--- workspace/profil.html | 36 ++--- workspace/reaktion.html | 38 +++--- workspace/rechte.html | 34 ++--- workspace/report.html | 36 ++--- workspace/scouting.html | 32 ++--- workspace/start.html | 32 ++--- workspace/startcheck.html | 34 ++--- workspace/steckbrief.html | 36 ++--- workspace/support.html | 28 ++-- workspace/tafel.html | 6 +- workspace/talente.html | 36 ++--- workspace/team.html | 32 ++--- workspace/teamlage.html | 34 ++--- workspace/teilen.html | 30 ++--- workspace/treff-moderation.html | 32 ++--- workspace/treff-regeln.html | 32 ++--- workspace/uebersicht.html | 32 ++--- workspace/unsere-seiten.html | 30 ++--- workspace/unterstuetzen.html | 32 ++--- workspace/werdegang.html | 32 ++--- workspace/willkommen.html | 30 ++--- workspace/wissen.html | 34 ++--- 56 files changed, 1140 insertions(+), 710 deletions(-) create mode 100644 server/pruef-haus-luecke.mjs diff --git a/server/pruef-haus-luecke.mjs b/server/pruef-haus-luecke.mjs new file mode 100644 index 00000000..959fa1ec --- /dev/null +++ b/server/pruef-haus-luecke.mjs @@ -0,0 +1,231 @@ +/* ===================================================================== + 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); diff --git a/server/pruef-treff.mjs b/server/pruef-treff.mjs index 33075d09..316625bc 100644 --- a/server/pruef-treff.mjs +++ b/server/pruef-treff.mjs @@ -892,8 +892,30 @@ melde("=== 6. Schreiben ==="); text: "Ansage", datum: heuteLokal() }); ok(brettDesTeams.code === 403, `aufs Anschlagbrett schreibt nur das Team (${brettDesTeams.code})`); - const beiDogi = await brett("treff", HAUS, kDogi); - ok(beiDogi.titel.includes("GAST-SCHREIBT-8899"), "DogFather sieht den Beitrag"); + /* ==== AUF DER CREW-ADRESSE, NICHT AUF DER AGENTURADRESSE ======== + (nachgezogen 03.10.2026) + + Hier stand `brett("treff", HAUS, kDogi)` -- DogFather liest das + Rudel-Brett ueber die AGENTURADRESSE. Das war richtig, solange + "getrennt wird das AUSSEHEN, nicht der Bestand" galt. Filipe hat + das am 24.09.2026 umgedreht: "wenn ich bei der einen was mache + soll nichts bei der anderen passieren." + + Seit Riegel 0 in sichtbarEintrag() gibt es auf der Agenturadresse + keine Eintraege von Team Dogi mehr -- auch nicht fuer DogFather. + `treff` ist ein Brett des Rudels; dass dort auf workspace. nichts + steht, ist das Ziel und kein Fehler. + + DIE ZUSAGE BLEIBT, sie wird nur am richtigen Ort geprueft -- und + bekommt eine GEGENPROBE dazu. Damit sagt diese Stelle jetzt mehr + als vorher: nicht nur "er sieht es", sondern "er sieht es DORT + und nur dort". */ + const beiDogi = await brett("treff", CREW, kDogi); + ok(beiDogi.titel.includes("GAST-SCHREIBT-8899"), + "DogFather sieht den Beitrag auf der Crew-Adresse"); + const beiDogiAgentur = await brett("treff", HAUS, kDogi); + ok(!beiDogiAgentur.titel.includes("GAST-SCHREIBT-8899"), + `und auf der Agenturadresse nicht (${beiDogiAgentur.titel.join(" | ") || "leer"})`); const beiModi = await brett("treff", CREW, kModi); ok(beiModi.titel.includes("GAST-SCHREIBT-8899"), "der Modi auch"); const beiCrea = await brett("treff", HAUS, kCrea); diff --git a/server/workspace-bereiche.js b/server/workspace-bereiche.js index 7f06405d..8b1dc55c 100644 --- a/server/workspace-bereiche.js +++ b/server/workspace-bereiche.js @@ -86,6 +86,7 @@ import { heuteLokal, gehoertAufDieseAdresse, bereicheFuer, kanaeleFuer, KANAELE, + hausFuerNeuenEintrag, } from "./workspace.js"; import { nachrichtSchicken } from "./workspace-chat.js"; import { @@ -1282,7 +1283,119 @@ function ohneVertrauliche(regel, person, praefix) { auch Fragen zu Aufgaben, Dateien und Terminen; ein Bereichsname hat dort nichts zu suchen. Der Kommentar eine Bildschirmseite weiter oben warnt woertlich davor. */ + +/* ===================================================================== + RIEGEL 0: DAS HAUS DER ADRESSE (03.10.2026) + + Filipe, 24.09.2026: "bevor du das aber machst will ich dass du + zuerst die komplette site vn der workspace seite trennst. da soll + nichts verknuepft sein. ... vermerk dass ab jetzt jeder umbau immer + getrennt abgefuehrt wird auf den zwei seiten." + + WAS GEMESSEN WURDE (03.10.2026, server/_mess-haeuser.mjs, jedes + Brett x beide Adressen x vier Rollen): + + AGENTUR-Adresse DogFather 18 Bretter + Manager 6 Bretter + Creator 6 Bretter + Modi kommt nicht rein (401) + CREW-Adresse DogFather 18 Bretter + Modi 13 Bretter + Manager kommt nicht rein (401) + Creator kommt nicht rein (401) + + Die Anmeldung ist also dicht -- eine Agenturrolle kommt nicht auf + die Crew-Adresse und umgekehrt. Durch greift genau EINE Rolle: + DogFather. Er ist die einzige in BEIDE_HAEUSER_ROLLEN, und die + Riegel darunter fragen nach der ROLLE, nicht nach der Adresse. + Auf der Crew-Adresse stand damit das Brett der Agentur samt Inhalt. + + WARUM ES BISHER NICHT GRIFF, obwohl es die Spalte `eintraege.haus` + seit dem 24.09. gibt: Sie wird beim ANLEGEN gefuellt und beim LESEN + nie gefragt. Gefiltert wurde ausschliesslich ueber die ROLLEN der + beteiligten Personen (ohneTeamDogi / ohneAgentur) -- und ein Eintrag, + an dem nur DogFather haengt, faellt durch beide Netze, weil er in + beiden Haeusern wohnt. Bei 20 von 26 Eintraegen ist er der Ersteller. + + DER RIEGEL STEHT GANZ VORNE, vor der Aussenrolle: Er gilt damit fuer + JEDEN Rueckgabeweg dieser Funktion, auch fuer den, den jemand als + naechstes einbaut. Dieselbe Ueberlegung wie bei "WARUM DER KLICK + GLEICH HIER DRANHAENGT" in bereich.js -- eine Funktion mit drei + Ausgaengen braucht ihre Regel am Eingang, nicht an jedem Ausgang. + + OHNE ADRESSE KEINE EINSCHRAENKUNG. `person.haus` setzt sitzungLesen() + aus dem Host-Kopf; bei einer Pruefung auf 127.0.0.1 steht dort + nichts. Dann greift dieser Riegel nicht -- sonst waeren alle + Pruefungen des Hauses still gruen und blind, genau die Falle, vor der + crew-adresse.js dreimal warnt. + + UND NULL IST KEIN LOCH. Fuenf von acht Stellen, die Eintraege + anlegen, setzen `haus` gar nicht (gemessen: workspace-video.js, + workspace-content.js, workspace-bewerbung.js, workspace-treff.js, + workspace-vorlagen.js). Ein Filter, der nur `haus = ?` prueft, + wuerde deren Eintraege fuer BEIDE Haeuser verbergen -- Daten + verschwinden still, der schlimmere Ausgang. Deshalb entscheidet bei + NULL der BEREICH, und zwar nach genau derselben Regel, nach der der + naechtliche Nachtrag ihn setzt (eintraegeNachHaus in workspace.js). + Zwei Fassungen derselben Regel waeren die, die auslaeuft. + ===================================================================== */ + +/** Die Bedingung "nur Eintraege dieses Hauses" -- oder null, wenn die + * Frage sich nicht stellt (keine Adresse, also keine Haustrennung). + * + * ==== WARUM NULL SICHTBAR BLEIBT (03.10.2026) ======================= + * + * Die erste Fassung hat bei `haus IS NULL` den BEREICH entscheiden + * lassen -- nach derselben Regel, nach der eintraegeNachHaus() das + * Haus nachtraegt (MODI_BEREICHE_ERLAUBT). Das war falsch, und + * pruef-haus-trennung.mjs hat es sofort gefunden: + * + * FEHL auf workspace. steht der Eintrag da + * + * Es ging um "Lunas Live vom Montag" auf dem Brett `live`. Die + * Ableitung stuft `live` als Crew-Brett ein (Team Dogi hat eine + * Kachel darauf) -- aber dasselbe Brett traegt auch die + * Creator-Akten der Agentur. `live`, `technik` und `community` + * gehoeren BEIDEN Haeusern, und eine Regel, die jedem Brett genau + * ein Haus zuweist, kann das nicht abbilden. + * + * Sie ist als VERTEILREGEL trotzdem richtig: Wenn ein neuer Eintrag + * ein Haus braucht, ist "wer hat die Kachel" die beste Auskunft, die + * es gibt. Als AUSSCHLUSSREGEL beim Lesen ist sie es nicht -- dort + * nimmt sie etwas weg, statt etwas zuzuordnen, und ein Irrtum kostet + * Sichtbarkeit statt nur Ordnung. + * + * Deshalb: Wer ein Haus hat, wird danach gefiltert. Wer keins hat, + * bleibt sichtbar. Das ist die sichere Richtung -- ein Eintrag zu + * viel faellt auf, ein fehlender nicht. + * + * DAMIT DARAUS KEIN DAUERLOCH WIRD, zwei Dinge: Die fuenf Stellen, + * die `haus` bisher nicht gesetzt haben, tun es jetzt (gemessen am + * 03.10.2026: workspace-video.js, workspace-content.js, + * workspace-bewerbung.js, workspace-treff.js, workspace-vorlagen.js), + * und pruef-haus-luecke.mjs wird rot, sobald eine sechste dazukommt, + * die es vergisst. Was trotzdem ohne Haus entsteht, traegt + * eintraegeNachHaus() beim naechsten Start nach. */ +function nurDiesesHaus(person, praefix = "e") { + const haus = person?.haus; + if (haus !== "crew" && haus !== "agentur") return null; + return { + wo: `(${praefix}.haus = ? OR ${praefix}.haus IS NULL)`, + werte: [haus], + }; +} + export function sichtbarEintrag(person, praefix = "e") { + /* RIEGEL 0 -- das Haus der Adresse. Begruendung direkt darueber. + Er wird den uebrigen Riegeln UMGEHANGEN statt in jeden Ausgang + geschrieben: Diese Funktion hat drei Rueckgabepunkte, und der + naechste, den jemand einbaut, waere sonst wieder offen. */ + const haus = nurDiesesHaus(person, praefix); + const mitHaus = (r) => { + if (!r || !haus) return r; + return { wo: `(${r.wo}) AND ${haus.wo}`, werte: [...r.werte, ...haus.werte] }; + }; + /* RIEGEL 1 -- die Aussenrolle. Eine eigene, vollstaendige Regel: genau die Bretter des Treffs, sonst nichts. */ if (AUSSEN_ROLLEN.has(person?.rolle)) { @@ -1298,19 +1411,19 @@ export function sichtbarEintrag(person, praefix = "e") { weiterhin geliefert. Genau so ist am 10.09. ein Modi-Eintrag durch die Suche gekommen, obwohl das Brett zu war. */ const frei = freigabeBedingung(person, praefix); - if (!frei) return grund; - return { wo: `(${grund.wo}) AND ${frei.wo}`, werte: [...grund.werte, ...frei.werte] }; + if (!frei) return mitHaus(grund); + return mitHaus({ wo: `(${grund.wo}) AND ${frei.wo}`, werte: [...grund.werte, ...frei.werte] }); } const regel = sichtbarRoh2(person, praefix); /* RIEGEL 2 -- wer nicht zum Treff gehoert, sieht seine Bretter nicht. Auch DogFather geht hier durch; er steht in TREFF_ROLLEN und faellt deshalb nicht heraus. */ - if (!regel || TREFF_ROLLEN.has(person?.rolle)) return regel; + if (!regel || TREFF_ROLLEN.has(person?.rolle)) return mitHaus(regel); const platz = TREFF_BRETTER.map(() => "?").join(", "); - return { + return mitHaus({ wo: `(${regel.wo}) AND ${praefix}.bereich NOT IN (${platz})`, werte: [...regel.werte, ...TREFF_BRETTER], - }; + }); } function sichtbarRoh2(person, praefix = "e") { @@ -2289,7 +2402,8 @@ bereicheRouter.post("/workspace/api/bereich/:bereich/start", gleicheHerkunft, (r /* Der TAG in Ortszeit, der ZEITSTEMPEL in UTC -- das ist kein Widerspruch: Ein Zeitpunkt ist ueberall derselbe, ein Kalendertag nicht. */ - heuteLokal(), jetzt(), req.person.id, req.person.haus || null); + heuteLokal(), jetzt(), req.person.id, + hausFuerNeuenEintrag(req.person, bereich)); /* UND SOFORT FREIGEBEN (17.09.2026) -- der Fehler, den Filipe auf seinem Bildschirm gesehen hat. @@ -2410,7 +2524,7 @@ bereicheRouter.post("/workspace/api/bereich/:bereich/:id/daraus", „Vorbei" und war zugeklappt -- waehrend beim Wunsch „Daraus wurde ein Termin" zu lesen war. */ heuteLokal(), jetzt(), req.person.id, quelle.id, - req.person.haus || null); + hausFuerNeuenEintrag(req.person, nach)); protokolliere("bereich_daraus", { personId: req.person.id, rolle: req.person.rolle, ip: echteIp(req), @@ -2683,7 +2797,7 @@ bereicheRouter.post("/workspace/api/bereich/:bereich", gleicheHerkunft, (req, re Fall, und der soll der Normalfall sein. */ aus.nur_leitung ?? 0, /* Das Haus aus der Adresse -- letzte Spalte, letzter Wert. */ - req.person.haus || null); + hausFuerNeuenEintrag(req.person, bereich)); /* DASSELBE VON HAND WIE ÜBER DEN VIDEOWEG (30.09.2026). Wer ein Highlight selbst anlegt und es auch freigeben dürfte, braucht diff --git a/server/workspace-bewerbung.js b/server/workspace-bewerbung.js index 47b33e9b..1f61c0a1 100644 --- a/server/workspace-bewerbung.js +++ b/server/workspace-bewerbung.js @@ -46,6 +46,7 @@ import express from "express"; import { db, sitzungLesen, protokolliere, echteIp, istDogFather, heuteLokal, ROLLEN_NAME, + hausFuerNeuenEintrag, } from "./workspace.js"; import { BEWERBUNG_FRAGEN, BEWERBUNG_GRUPPEN, BEWERBUNG_WEGE, BEWERBUNG_ZUSTAENDE, @@ -543,13 +544,13 @@ bewerbungRouter.put("/workspace/api/bewerbung/:id/weg", express.json({ limit: "8 const einsatz = antworten.get("womit") || null; const { lastInsertRowid } = db().prepare(` INSERT INTO eintraege - (bereich, art, titel, text, datum, status, erstellt, erstellt_von, einsatz) - VALUES ('talente', 'empfohlen', ?, ?, ?, 'offen', ?, ?, ?)`) + (bereich, art, titel, text, datum, status, erstellt, erstellt_von, einsatz, haus) + VALUES ('talente', 'empfohlen', ?, ?, ?, 'offen', ?, ?, ?, ?)`) .run(b.nenn_name, `Hat sich selbst gemeldet (Anfrage #${id}).` + (antworten.get("warum") ? `\n\nWarum: ${antworten.get("warum")}` : ""), /* Der TAG in Ortszeit (01.10.2026), der Zeitstempel in UTC. */ - heuteLokal(), t, req.person.id, einsatz); + heuteLokal(), t, req.person.id, einsatz, hausFuerNeuenEintrag(req.person, "talente")); eintragId = Number(lastInsertRowid); /* AUF „ANGESPROCHEN" und nicht auf „aufgefallen": Die ersten diff --git a/server/workspace-content.js b/server/workspace-content.js index 466063d0..fd980346 100644 --- a/server/workspace-content.js +++ b/server/workspace-content.js @@ -28,6 +28,7 @@ import { db, protokolliere, echteIp, sitzungLesen, betreutWo, darfCreator, betreuteIds, istLeitung, istDogFather, siehtAlles, tagLokal, heuteLokal, + hausFuerNeuenEintrag, } from "./workspace.js"; export const contentRouter = express.Router(); @@ -413,10 +414,10 @@ contentRouter.post("/workspace/api/content/:id/uebernehmen", gleicheHerkunft, (r } else { const r = db().prepare(` - INSERT INTO eintraege (bereich, art, titel, text, datum, status, creator_id, erstellt, erstellt_von) - VALUES ('live','vorbereitung',?,?,?,'offen',?,?,?)`).run( + INSERT INTO eintraege (bereich, art, titel, text, datum, status, creator_id, erstellt, erstellt_von, haus) + VALUES ('live','vorbereitung',?,?,?,'offen',?,?,?,?)`).run( e.titel.slice(0, 160), beschreibung, e.geplant ? e.geplant.slice(0, 10) : heute(), - e.creator_id, jetzt(), req.person.id); + e.creator_id, jetzt(), req.person.id, hausFuerNeuenEintrag(req.person, "live")); neu = { art: "LIVE-Thema", id: Number(r.lastInsertRowid), wohin: "bereich.html?b=live" }; } diff --git a/server/workspace-treff.js b/server/workspace-treff.js index b9b1e1de..77b7e525 100644 --- a/server/workspace-treff.js +++ b/server/workspace-treff.js @@ -76,6 +76,7 @@ import { db, sitzungLesen, protokolliere, echteIp, TREFF_BRETTER, TREFF_ROLLEN, ROLLEN_NAME, istHand, heuteLokal, + hausFuerNeuenEintrag, } from "./workspace.js"; /* Die Tabellen und die Sperrfrage stehen in einem Blatt ohne eigene Importe -- workspace.js braucht beides ebenfalls, und ein Importkreis @@ -1019,13 +1020,14 @@ treffRouter.post("/workspace/api/treff/mitmachen/:id/uebernehmen", gleicheHerkun const nun = jetzt(); const r = db().prepare(`INSERT INTO eintraege (bereich, art, titel, text, datum, dringlichkeit, status, creator_id, - erstellt, erstellt_von, einsatz) - VALUES ('talente','empfohlen',?,?,?,'mittel','offen',?,?,?,?)`) + erstellt, erstellt_von, einsatz, haus) + VALUES ('talente','empfohlen',?,?,?,'mittel','offen',?,?,?,?,?)`) .run( `${wer?.name ?? "Jemand"} hat sich im Rudel gemeldet`, `Aus „Mitmachen“: ${String(e.titel ?? "")}\n\n${String(e.text ?? "")}`.trim(), /* Der TAG in Ortszeit (01.10.2026), der Zeitstempel in UTC. */ - heuteLokal(), e.erstellt_von, nun, req.person.id, "Das Rudel"); + heuteLokal(), e.erstellt_von, nun, req.person.id, "Das Rudel", + hausFuerNeuenEintrag(req.person, "talente")); protokolliere("treff_talent", { personId: req.person.id, ip: echteIp(req), detail: `aus Eintrag ${id}` }); res.json({ ok: true, talent_id: Number(r.lastInsertRowid) }); diff --git a/server/workspace-video.js b/server/workspace-video.js index 563cd87c..d8ca7dbf 100644 --- a/server/workspace-video.js +++ b/server/workspace-video.js @@ -48,6 +48,7 @@ import { db, protokolliere, echteIp, sitzungLesen, DATEN_ORDNER, KANAELE, kanalVonHandle, TREFF_BRETTER, heuteLokal, + hausFuerNeuenEintrag, } from "./workspace.js"; import { darfSchreiben, sofortFreigeben } from "./workspace-treff.js"; import { sichtbar } from "./workspace-bereiche.js"; @@ -281,13 +282,14 @@ videoRouter.post("/workspace/api/bereich/:bereich/video", async (req, res) => { const { lastInsertRowid } = db().prepare(` INSERT INTO eintraege (bereich, art, titel, datum, status, erstellt, erstellt_von, - quelle_url, quelle_kanal, quelle_geholt_am, aus_eintrag_id) - VALUES (?,?,?,?, 'offen', ?,?,?,?,?,?)`) + quelle_url, quelle_kanal, quelle_geholt_am, aus_eintrag_id, haus) + VALUES (?,?,?,?, 'offen', ?,?,?,?,?,?,?)`) /* Der TAG in Ortszeit (01.10.2026), der Zeitstempel daneben in UTC -- ein Zeitpunkt ist ueberall derselbe, ein Kalendertag nicht. */ .run(bereich, arten[bereich] || "clip", auskunft.titel, heuteLokal(), - jetzt, req.person.id, url, ablage, jetzt, ausQuelle ? aus : null); + jetzt, req.person.id, url, ablage, jetzt, ausQuelle ? aus : null, + hausFuerNeuenEintrag(req.person, bereich)); eintragId = Number(lastInsertRowid); } diff --git a/server/workspace-vorlagen.js b/server/workspace-vorlagen.js index b1ca6f88..1ac5cc0b 100644 --- a/server/workspace-vorlagen.js +++ b/server/workspace-vorlagen.js @@ -56,6 +56,7 @@ import { KATALOG_EMPFAENGER, KATALOG_EMPFAENGER_SQL, heuteLokal, + hausFuerNeuenEintrag, } from "./workspace.js"; /* Dieselbe Meldung wie beim anderen Bewerbungsweg -- eine Stelle, @@ -2212,14 +2213,14 @@ vorlagenRouter.post("/workspace/api/vorlagen/uebernehmen", gleicheHerkunft, const einfuegen = (bereich, unterart, titel, text, zusatz = {}) => { db().prepare(`INSERT INTO eintraege (bereich, art, titel, text, datum, status, creator_id, hook, format, - erstellt, erstellt_von) - VALUES (?,?,?,?,?,?,?,?,?,?,?)`) + erstellt, erstellt_von, haus) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?)`) /* Der TAG in Ortszeit (01.10.2026), der Zeitstempel weiter unten in UTC. Aus dem Startkatalog angelegte Eintraege trugen sonst nachts das Datum von gestern. */ .run(bereich, unterart, titel, text, heuteLokal(), "offen", creatorId, zusatz.hook || null, zusatz.format || null, - jetzt(), req.person.id); + jetzt(), req.person.id, hausFuerNeuenEintrag(req.person, bereich)); angelegt.push(titel); }; diff --git a/server/workspace.js b/server/workspace.js index f2c86640..eb72131d 100644 --- a/server/workspace.js +++ b/server/workspace.js @@ -2296,6 +2296,49 @@ const BRETTER_UEBER_SEITEN = { /** Bretter mit eigener Kachel. Diese darf jeder aus dem Team, der die * Kachel sieht -- sie sind daraus abgeleitet, nicht abgeschrieben. */ +/** In welches Haus gehoert ein NEU angelegter Eintrag? + * + * ==== WARUM NICHT EINFACH `person.haus` (03.10.2026) =============== + * + * Genau das stand an allen acht Einfuege-Stellen -- und + * pruef-treff.mjs hat gezeigt, dass es falsch ist: + * + * FEHL die Community sieht es SOFORT -- ohne zweiten Klick + * FEHL DogFather sieht den Beitrag + * + * Beide Male legt DogFather ueber die AGENTURADRESSE einen Beitrag + * auf einem Brett des Rudels an (`highlight`, `anschlag`). Mit + * `person.haus` bekam der Beitrag das Haus "agentur" -- und war + * damit fuer die Community auf crew. unsichtbar. Auf einem Brett, + * das es nur dort gibt. + * + * Der richtige Massstab steht schon im Kommentar von + * eintraegeNachHaus() weiter unten: "Das Haus eines Eintrags -- aus + * seinem BEREICH, nicht aus dem Ersteller. `regeln`, `anschlag`, + * `highlight` sind Bretter von Team Dogi, `agentur` gehoert der + * Agentur; das steht fest, unabhaengig davon, wer gerade + * hineingeschrieben hat." + * + * ABER NICHT FUER JEDES BRETT. `live`, `technik` und `community` + * tragen BEIDES: die Kacheln von Team Dogi und die Creator-Akten der + * Agentur. Dort ist die Adresse, auf der jemand schreibt, die beste + * Auskunft, die es gibt -- und die einzige. + * + * Also: Wo das Brett eindeutig ist, entscheidet das Brett. Wo nicht, + * die Adresse. TREFF_BRETTER ist die gepflegte Liste der eindeutigen + * Bretter des Rudels; `agentur` ist das Gegenstueck auf der anderen + * Seite und steht in BEREICHE, nicht hier. + */ +export function hausFuerNeuenEintrag(person, bereich) { + /* Die sieben Bretter des Rudels gibt es nur bei Team Dogi -- egal, + von welcher Adresse aus jemand hineinschreibt. */ + if (TREFF_BRETTER.includes(String(bereich))) return "crew"; + /* Sonst entscheidet die Adresse. Ohne Adresse (Pruefungen, + 127.0.0.1) bleibt es offen -- und ein Eintrag ohne Haus ist in + beiden sichtbar, die bewusst sichere Richtung. */ + return person?.haus || null; +} + export const MODI_BEREICHE_ERLAUBT = new Set([ ...HAND_MIT_TREFF .map((k) => (/bereich\.html\?b=([a-z]+)/.exec(k.ziel) || [])[1]) diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html index e667a25e..ce6bd5b8 100644 --- a/workspace/anruf-probe.html +++ b/workspace/anruf-probe.html @@ -16,8 +16,8 @@ verdeckt. Gefunden hat es pruef-struktur. Richtig sind `gate.css` (die Farbwerte des Hauses) und `haus.css`. --> - - + + - +