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 <[email protected]>
232 lines
11 KiB
JavaScript
232 lines
11 KiB
JavaScript
/* =====================================================================
|
|
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);
|