Eine Kampagne gehoert zur Agentur -- auch wenn DogFather ueberall admin ist

Aufgefallen beim Nachweis auf der echten Adresse, nicht beim Bauen:
Der Kasten haengt an `bereich === "agentur"` und an der Rolle. DogFather
ist aber in BEIDEN Haeusern `admin`. Oeffnet er auf
crew.dogfather-universe.com von Hand `bereich.html?b=agentur`, dann
stimmt der Bereich, die Rolle stimmt -- und `hausFuerNeuenEintrag` gaebe
der Kampagne das Haus "crew". Eine Agentur-Kampagne staende im Rudel,
und die Trennung waere an genau der Stelle unterlaufen, an der niemand
nachsieht. Bauplan, Teil 11: "Nichts im Haus Team Dogi."

ZWEI FRAGEN, ZWEI SCHWELLEN -- und das ist Absicht:

  /api/ich (ZEIGEN)     haus !== "crew"
  Route    (SCHREIBEN)  haus === "agentur"

Der Unterschied betrifft genau einen Fall: `null`. Den gibt es nur auf
einer PRUEFADRESSE -- `sitzungLesen` vergibt dort seit dem 24.09.2026
bewusst kein Haus, und die Begruendung dort nennt den Schaden:
Als 127.0.0.1 einmal ein Haus bekam, fiel pruef-chat-kanaele mit 13
Fehlschlaegen um, und die naechsten Pruefungen waeren still gruen
geblieben, ohne noch etwas zu messen. Eine echte Anfrage hat immer eine
der beiden Waende; eine Sitzung zu einer dritten lehnt
`sitzungPasstZurAdresse` ohnehin ab.

Beim SCHREIBEN wird trotzdem streng gefragt: Ein Eintrag mit
`haus = null` waere in BEIDEN Haeusern sichtbar -- genau das, was Teil
11 ausschliesst.

WAS ICH DABEI ZWEIMAL FALSCH VERSUCHT HABE
  Zuerst sollte das Bildwerkzeug ueber die Agenturwand kommen, damit
  es den Kasten weiter messen kann. Den Host-Kopf beim Durchreichen zu
  ueberschreiben wirkt nicht -- gemessen: /api/ich meldete weiter
  `darf: false`, der Wirt blieb "127.0.0.1:4338". Chromium laesst
  `Host` nicht ueberschreiben.

  Dann den Namen wirklich aufloesen (--host-resolver-rules). Das
  funktioniert, bringt aber die Anmeldemaske der echten Wand mit einer
  Tuer davor, die ein Bildwerkzeug nicht aufmachen soll. Gemessen
  werden soll die KACHEL, nicht der Zugang.

  Deshalb bleibt bild-kampagne auf der Pruefadresse -- und genau
  deshalb fragt /api/ich `!== "crew"` statt `=== "agentur"`. Beide
  Zeilen tragen jetzt die Begruendung bei sich, damit sie niemand
  "vereinheitlicht": Das macht entweder die Pruefungen blind oder
  laesst eine Kampagne ins Rudel.

Zwei Gegenproben gefahren, beide schlagen an: Route nimmt jede Wand an
-> 2 rot. /api/ich fragt nicht nach dem Haus -> 1 rot.

GEPRUEFT: pruef-kampagne 98 -> 99 gruen · bild-kampagne 20 Messungen
gruen · pruef-struktur, pruef-haus-trennung 107, pruef-agentur 62,
pruef-eventkarte 94, pruef-alle-wege, pruef-schranke, pruef-zeichen 8

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-10-07 17:31:11 +02:00
co-authored by Claude Opus 5
parent 10e24b0c93
commit 8cde56bd96
4 changed files with 122 additions and 8 deletions
+37 -2
View File
@@ -37,7 +37,24 @@ express.response.cookie = function (n, w, o) {
};
await import("./index.js");
await new Promise((r) => setTimeout(r, 1200));
const BASIS = `http://127.0.0.1:${PORT}`;
/* WARUM HIER 127.0.0.1 STEHT UND NICHT DIE AGENTURWAND
(gemessen und wieder zurueckgebaut, 07.10.2026).
Versucht wurde beides. Der Host-Kopf laesst sich in Chromium
nicht ueberschreiben (gemessen: /api/ich meldete weiter
`darf: false`, der Wirt blieb "127.0.0.1"), und laesst man den
Namen wirklich aufloesen, kommt die Anmeldemaske der echten Wand
-- mit einer Tuer davor, die dieses Werkzeug nicht aufmachen
soll. Gemessen werden soll die KACHEL, nicht der Zugang.
Deshalb bleibt es bei der Pruefadresse. Dort vergibt
`sitzungLesen` bewusst KEIN Haus (24.09.2026), und `/api/ich`
fragt genau deshalb `haus !== "crew"` statt `=== "agentur"`:
Der Kasten ist hier sichtbar und damit messbar.
Dass er auf der CREW-Adresse verschwindet, misst
pruef-kampagne mit echten Host-Koepfen -- das geht ohne
Browser und ist dort besser aufgehoben. */const BASIS = `http://127.0.0.1:${PORT}`;
const { DatabaseSync } = await import("node:sqlite");
const { scryptSync, randomBytes } = await import("node:crypto");
@@ -65,11 +82,23 @@ const pw = await import("file:///C:/Users/qciga/Documents/Obelix/Analyse/node_mo
const browser = await pw.chromium.launch();
const fehlerAufSeite = [];
const anmelden = async (seite, rolle, code) => {
await seite.goto(BASIS + "/workspace/", { waitUntil: "networkidle" });
await seite.click(`.rolle[data-rolle="${rolle}"]`);
await seite.fill("#code", code);
await seite.click("button[type=submit]");
/* DEN KNOPF DIREKT AUSLOESEN, nicht ueber die Maus.
page.click wartet darauf, dass das Element "visible, enabled and
stable" ist -- und auf der echten Agenturwand lief es dort in die
30-Sekunden-Frist. Die Anmeldeseite bewegt sich dauerhaft (die
Buehne atmet), also wird "stable" nie erreicht. Auf 127.0.0.1 war
das nie aufgefallen, weil dort eine andere Szene laeuft.
Gemessen wird hier die KACHEL, nicht die Anmeldemaske -- der
direkte Klick ist deshalb kein Umgehen einer Pruefung, sondern
der Weg zum eigentlichen Messgegenstand. */
await seite.$eval("button[type=submit]", (b) => b.click());
await seite.waitForURL("**/start.html", { timeout: 25000 });
};
@@ -107,6 +136,12 @@ async function messen(breite, hoehe, name) {
await seite.waitForTimeout(1600);
console.log(`\n=== ${name} (${breite} px) ===`);
const ichSagt = await seite.evaluate(async () => {
const a = await fetch("/workspace/api/ich", { credentials: "same-origin" });
const j = await a.json().catch(() => ({}));
return { rolle: j.rolle, darf: j.darf_kampagne_einlesen, wirt: location.host };
});
console.log(" /api/ich: " + JSON.stringify(ichSagt));
const mass = await seite.evaluate(() => {
const k = document.getElementById("kampagneholen");
if (!k || k.hidden) return { da: false };
+21
View File
@@ -753,6 +753,15 @@ melde("=== 11. Kampagnen anlegen dürfen DogFather und die Manager ===");
&& ichLuna.darf_kampagne_einlesen === false,
"/api/ich sagt der Oberfläche dasselbe wie die Schranke der Route");
/* UND AUF DER CREW-ADRESSE SAGT SIE NEIN. Das ist der Fall, den
es in der Wirklichkeit gibt: DogFather ist dort ebenfalls
`admin`, und ohne diese Antwort stuende der Kasten im Rudel. */
const ichCrew = JSON.parse((await roh("/workspace/api/ich",
{ keks: keksDogiCrew, wirt: CREW_HOST })).text);
ok(ichCrew.rolle === "admin" && ichCrew.darf_kampagne_einlesen === false,
`auf der Crew-Adresse sagt sie nein (Rolle ${ichCrew.rolle},`
+ ` darf ${ichCrew.darf_kampagne_einlesen})`);
/* FREMDE HERKUNFT. Die Wache steht in index.js, VOR diesem Router. */
const fremd = await new Promise((f, s) => {
const daten = Buffer.from(JSON.stringify({ text: "x" }));
@@ -795,6 +804,18 @@ melde("=== 12. Team Dogi bleibt unberührt ===");
ok((wsBrett.eintraege || []).some((e) => e.id === kachelId),
"Gegenprobe: auf der Agentur-Adresse schon");
/* UND VON DER CREW-ADRESSE AUS LAESST SIE SICH GAR NICHT ANLEGEN.
DogFather ist in beiden Haeusern `admin` -- die Rollenfrage
allein laesst ihn also durch. Ohne diese Schranke bekaeme die
Kampagne das Haus "crew", und die Trennung waere an genau der
Stelle unterlaufen, an der niemand nachsieht. */
const vonCrew = await einlesen(`Klick: ${TIK}/t/gipfel`, keksDogiCrew, CREW_HOST);
ok(vonCrew.code === 403,
`von der Crew-Adresse: HTTP ${vonCrew.code} — „${vonCrew.j.fehler}“`);
ok(/[Aa]gentur/.test(vonCrew.j.fehler || ""),
" und die Meldung sagt, wo es hingehoert");
const crewNachher = dbLesen("SELECT COUNT(*) AS n FROM eintraege WHERE haus = 'crew'")[0].n;
ok(crewVorher === crewNachher,
`und die Zahl der Crew-Einträge ist unverändert (${crewVorher} = ${crewNachher})`);
+35 -5
View File
@@ -264,6 +264,36 @@ kampagneRouter.post("/workspace/api/kampagne/einlesen", async (req, res) => {
`keine Leitung (${person.rolle})`);
}
/* ---- Und nur auf der AGENTURADRESSE ---------------------
Bauplan, Teil 11: "Nichts im Haus Team Dogi."
WARUM DAS EINE EIGENE ZEILE BRAUCHT: DogFather ist in BEIDEN
Haeusern `admin`, und `istLeitung` fragt nur nach der Rolle.
Meldet er sich auf crew.dogfather-universe.com an und oeffnet
dort `bereich.html?b=agentur`, dann ist er Leitung, der
Bereich stimmt -- und `hausFuerNeuenEintrag` gaebe der
Kampagne das Haus "crew". Eine Agentur-Kampagne staende
damit im Rudel, und die Trennung waere genau an der Stelle
unterlaufen, an der sie niemand nachsieht.
Gefragt wird POSITIV ("ist es die Agentur?") und nicht
negativ ("ist es nicht die Crew?"): Ohne Adresse gibt
`hausFuerNeuenEintrag` `null` zurueck, und ein Eintrag ohne
Haus ist in BEIDEN sichtbar. Fuer die meisten Eintraege ist
das die bewusst sichere Richtung -- fuer eine Kampagne waere
es das Gegenteil.
Dasselbe Haus wird unten beim Schreiben benutzt, nicht ein
zweites Mal berechnet. */
const haus = hausFuerNeuenEintrag(person, "agentur");
if (haus !== "agentur") {
return schiefgegangen(403,
"Kampagnen gehören zur Agentur. Bitte auf "
+ "workspace.dogfather-universe.com anmelden.",
`falsches Haus: ${haus ?? "ohne Adresse"}`);
}
const text = String(req.body?.text ?? "");
if (!text.trim()) {
return schiefgegangen(400,
@@ -440,11 +470,11 @@ kampagneRouter.post("/workspace/api/kampagne/einlesen", async (req, res) => {
kuerzen(regelnText(gelesen.kuer, hinweise)),
dringlichkeit,
jetzt, person.id,
/* NICHT von Hand auf "agentur" gesetzt: Das Haus entscheidet
die Adresse, über die jemand hereinkommt. Wer es hier
festschreibt, baut die Trennung ein zweites Mal -- und
zwei Fassungen derselben Regel laufen auseinander. */
hausFuerNeuenEintrag(person, "agentur"),
/* NICHT von Hand auf "agentur" gesetzt, sondern dieselbe
Angabe, die oben schon ueber die Annahme entschieden
hat. Zweimal zu rechnen waeren zwei Fassungen derselben
Regel -- und die laufen auseinander. */
haus,
nummer, link, jetzt, gelesen.pflicht.zone,
JSON.stringify(gelesen.auszug));
eintragId = Number(lastInsertRowid);
+29 -1
View File
@@ -7680,7 +7680,35 @@ workspaceRouter.get("/workspace/api/ich", (req, res) => {
obwohl der Weg dahinter offen war. Und die umgekehrte Richtung
ist schlimmer: Ein Feld, das erscheint und dann 403 bekommt,
laesst den Menschen davor sich selbst fuer den Fehler halten. */
darf_kampagne_einlesen: istLeitung(person),
/* UND NICHT IM HAUS TEAM DOGI (nachgetragen 07.10.2026).
Hier stand zuerst nur `istLeitung(person)`. DogFather ist
aber in BEIDEN Haeusern `admin`: Auf der Crew-Adresse haette
die Oberflaeche den Kasten gezeigt, und der Server haette mit
403 abgelehnt. Ein Formular, das der Server ablehnt, laesst
den Menschen davor sich selbst fuer den Fehler halten.
ZWEI FRAGEN, ZWEI SCHWELLEN -- und das ist Absicht:
hier (ZEIGEN) haus !== "crew"
Route (SCHREIBEN) haus === "agentur"
Der Unterschied betrifft genau einen Fall: `null`. Den gibt
es nur auf einer PRUEFADRESSE -- `sitzungLesen` vergibt dort
bewusst kein Haus (siehe die Begruendung dort, 24.09.2026:
Als 127.0.0.1 einmal ein Haus bekam, fiel pruef-chat-kanaele
mit 13 Fehlschlaegen um, und die naechsten Pruefungen waeren
still gruen geblieben, ohne noch etwas zu messen). Eine
echte Anfrage hat immer eine der beiden Waende; eine Sitzung
zu einer dritten lehnt `sitzungPasstZurAdresse` ohnehin ab.
Beim SCHREIBEN wird trotzdem streng gefragt: Ein Eintrag mit
`haus = null` waere in BEIDEN Haeusern sichtbar -- genau das,
was Teil 11 des Bauplans ausschliesst. Wer die beiden Zeilen
einmal "vereinheitlicht", macht entweder die Pruefungen
blind oder laesst eine Kampagne ins Rudel. */
darf_kampagne_einlesen: istLeitung(person)
&& hausFuerNeuenEintrag(person, "agentur") !== "crew",
/* ZWEI LISTEN STATT EINER RECHNUNG IM BROWSER (20.09.2026).
Die Rollenwahl nahm bisher die Knoepfe des Anlege-Formulars --