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`. -->
-
-
+
+
-
+