Der Bauplan ist damit vollstaendig umgesetzt.
ETAPPE 3 -- SCOUT, MANAGER UND SPICY MEDIA.
Woertlich aus Kapitel 07 bis 09, zusammen 84 Karten. Die Zahlen des
Bauplans stimmen auf die Karte genau: 18/21/22/23, verteilt 5/6/7,
5/7/9, 6/9/7, 5/5/13.
EINE ZWEIDEUTIGKEIT IM PDF, GEMESSEN STATT GERATEN. Beim Manager
nennt die Merke-Spalte "61, 58, 51 feste Punkte", und Layout- und
Raw-Auszug ordnen sie verschiedenen Kacheln zu. Nachgezaehlt im Code:
LIVE 69, Content-Ideen 61, Community 58, Technik 51. Damit ist die
zeilenweise Lesart bewiesen richtig -- die Layout-Lesart haette "61"
der LIVE-Analyse gegeben, die 69 hat.
Die Zuordnungspruefung laeuft jetzt fuer alle vier Rollen, und die
erwartete Kartenzahl wird AUS DER KACHELLISTE abgeleitet, nicht aus
der Abnahmeliste abgeschrieben. Dazu eine Gegenprobe, dass sich die
vier Fassungen wirklich unterscheiden -- sonst waeren alle Zeilen
gruen, solange die Kachellisten zufaellig passen.
ETAPPE 4 -- DIE PFLEGE, fuer DogFather UND Spicy.
Bearbeitet wird an Ort und Stelle: Stufe und die drei Zeilen je Karte,
Titel und Text je Listenzeile, Leitgedanke je Rolle. Fehlende Karten
werden benannt und lassen sich mit einem Griff anlegen -- das ist der
Fall "Fuer diese Kachel fehlt ein Eintrag" aus Kapitel 11. Dazu "Wer
ist wie weit": eine Arbeitslage, keine Ueberwachung.
KEINE ZWEITE ROLLENAUSWAHL. Die Vorschau gibt es schon -- sie heisst
"Meine Sicht" und steht in der Kopfleiste. Geprueft wird fuers Pflegen
`req.person` und nicht `req.sicht`: Wer durch fremde Augen sieht, soll
nicht aus Versehen in fremdem Namen aendern.
DIE MARKE "NEU" HAT EIN ENDE: 14 Tage ODER bis die Person die Seite
nach der Aenderung geoeffnet hat. Der Bauplan sagt dazu nichts, und
ohne Ende waere nach einem halben Jahr alles neu.
ETAPPE 5 -- DIE SUCHE.
Die Karten stehen in der Kopfleisten-Suche, aber nur die der EIGENEN
Rolle: Faende ein Creator die Manager-Karte, stuende dort "Zuteilen,
freigeben" neben einer Kachel, die er nicht hat. Der Treffer traegt
den Zusatz hinter dem Fragezeichen, sonst landen fuenf Karten auf
derselben Seite.
ETAPPE 6 -- BEGRUESSUNG UND RUNDGANG.
Fuenf Schritte ueber die Zentrale, im letzten leuchten nur die
Muss-Kacheln. EIGENE DATEI (`rundgang.js`) und nicht ein Stueck
start.js: Er laeuft auf der meistbenutzten Seite des Hauses, und wenn
hier etwas schiefgeht, darf davon nichts anderes betroffen sein.
Keine gerechnete Koordinate, kein Loch in einer Abdeckung -- die Seite
wird leiser, das Ziel bleibt hell. Faellt das Skript aus, ist beim
naechsten Laden alles normal.
DER ROLLENWECHSEL IST KEINE SONDERREGEL, sondern der Schluessel:
`(Person, Rolle)`. Die neue Rolle hat schlicht noch keine Zeile, also
startet die Einweisung von selbst noch einmal. Eine Spalte
"zuruecksetzen", an die jemand denken muesste, waere die Stelle, an
der es vergessen wird.
EIN FUND AUS DER MESSUNG: Gemerkt wurde zuerst erst am ENDE des
Rundgangs. Wer "Los geht's" drueckte und dann wegging, bekam die
Begruessung bei jedem Aufruf wieder -- fuer immer. Jetzt wird beim
ZEIGEN gemerkt; das ist die Tatsache, um die es geht.
ETAPPE 7 -- DIE ABNAHME.
Dabei fiel auf, dass der Rollen-Rundgang SPICY MEDIA gar nicht kannte:
fuenf Durchgaenge, aber zweimal Manager, zweimal Scout und Spicy nie.
Ihr gehoert die Gruppe "Rund um das Team" samt Team-Lage -- diese
Kachel ist in keinem Durchgang je geoeffnet worden. Jetzt laeuft sie
mit: 452 statt 406 Pruefungen.
MITGENOMMEN, WEIL ES ROT WAR: `pruef-eventkarte` und
`pruef-haus-luecke` bildeten ihr Tagesdatum aus UTC. Zwischen 00:00
und 02:00 deutscher Zeit liegt UTC im Vortag -- ein naechtlicher Lauf
haette an einem Datum gesucht, das er selbst nicht geschrieben hat.
Beide benutzen jetzt `helfer-zeit`. `pruef-struktur` ist damit gruen.
Gemessen (alle gruen, kein einziger Befund)
pruef-anleitung 125 Pruefungen (vorher 70)
pruef-rollen 452 Pruefungen (vorher 406) -- mit Spicy Media
pruef-struktur gruen (vorher rot)
pruef-rechtetafel, pruef-css-klassen, pruef-kachel-universum,
pruef-suchfeld, pruef-kachelraster, pruef-start-ansicht,
pruef-sicht, pruef-eventkarte alle gruen
mess-anleitung Rundgang fuenf Schritte, im letzten genau 5
Muss-Kacheln hervorgehoben; beim zweiten
Aufruf keine Begruessung mehr; kein
Querschieben bei 412 und 1440 px
Sicherung: ~/sicherungen/workspace-vor-anleitung-e37-20261006-1536.db
OFFEN, NICHT VON MIR UND NICHT AUS DIESEM BAUPLAN: pruef-haus-luecke
endet mit Rueckgabewert 3 ("konnte nicht nachsehen") -- eine der
beiden Adressen liefert das Anschlagbrett nicht. Nachgemessen: Das war
vor dieser Arbeit genauso.
Co-Authored-By: Claude Opus 5 <[email protected]>
236 lines
11 KiB
JavaScript
236 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";
|
|
import { heuteLokal } from "./helfer-zeit.mjs";
|
|
|
|
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");
|
|
/* ORTSZEIT statt UTC -- siehe helfer-zeit.mjs. Nachts zwischen
|
|
00:00 und 02:00 liegt UTC einen Tag zurueck, und dann sucht diese
|
|
Pruefung an einem Datum, das sie selbst nicht geschrieben hat. */
|
|
const heute = heuteLokal();
|
|
/* 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);
|