Filipe: „fuer die modis, rechte und linke hand und dogfather eine
kachel hinzufuegst, sie soll: Notizen, heissen. ich will dass du das
auch wie ein notizblock erstellst. ich will dass es uebelst geil und
einzigartig ist."
=================================================================
TEIL 1: DIE FARBE -- UND WAS DABEI AUFFIEL
=================================================================
Fuer die neue Kachel braucht es einen Ton. Beim Suchen fiel auf, dass
tools/kachelton-entzerren.mjs zwoelf der 45 Farben fuer FREI hielt.
Sie sind es nicht. bereicheFuer() gibt fuer die fuenf Rollen des
Agenturhauses null zurueck -- das heisst „nimm die Liste aus dem
Browser", und die steht in workspace/assets/js/bereiche.js. Dort
stehen die Toene 1 bis 22 und 44: Dashboard, Zahlen, Team-Lage,
Scout-Pipeline. Kacheln, die jeden Tag jemand ansieht.
pruef-kachelfarben hatte denselben blinden Fleck. Sie meldete seit
Wochen „33 benutzte Toene" und war gruen. Was sie dadurch NICHT sah:
* Der Farblauf von gestern Nacht hat zwoelf dieser Kacheln
verschoben und drei davon unter die Buntheitsgrenze gedrueckt.
Gruen geblieben.
* Ton 7, 16 und 22 lagen SEIT JEHER unter der Grenze (0,112 / 0,116
/ 0,068). Nie gemeldet.
Das ist die Sorte gruener Haken, vor der die Hausregel warnt: Er sagt
nur, dass die Bedingung erfuellt war -- nicht, dass sie das Richtige
angesehen hat.
BEHOBEN, und zwar an der Wurzel: Die REGELN (Kontrast, Buntheit)
gelten jetzt fuer jeden Ton, der irgendwo an einer Kachel steht --
gelesen aus denselben fuenf Dateien, die auch pruef-kachel-universum
liest. Dazu die Namen der Kacheln, damit „Ton 1 traegt Dashboard"
ueberhaupt pruefbar ist.
DIE PALETTE WURDE NEU GERECHNET, mit dem dritten Verfahren an einem
Tag -- die ersten zwei stehen als Fehler im Kopf des Werkzeugs:
1. Alle 45 neu verteilt. Lief ueber zwei ausdrueckliche Wuensche
hinweg (#ff1a1a, #a8d8ff).
2. Nur die Kollisionen, aber immer nur EINEN der beiden bewegt.
Ergebnis: Ton 1 sprang vom Tuerkis ins Altrosa, 0,197 weit.
3. BEIDE duerfen sich bewegen, und zwar beide nur ein bisschen.
Zwei Toene, die 0,02 auseinanderstehen und 0,09 brauchen, teilen
sich das -- jeder rueckt 0,045, und beide bleiben, was sie waren.
kleinster Abstand 0,0154 -> 0,0905
zu blass 4 -> 0
sichtbar veraendert 7 Kacheln (ueber 0,05)
kaum zu sehen 28 (13 zwischen 0,02 und 0,05, 15 darunter)
EINE AUSNAHME MIT ZAHL, keine mit Achselzucken: Ton 1 (Dashboard)
bleibt acht Tausendstel unter der Buntheitsgrenze. Gemessen ueber ALLE
16,7 Millionen sRGB-Farben ist die naechste, die alle Regeln haelt,
#7a75c8 -- ein Blauviolett, 0,113 entfernt. Tuerkis erreicht in sRGB
schlicht keine hoehere Buntheit, und das schmale Band teilen sich
schon sieben Toene. Eine Kachel, die ihre Farbfamilie behaelt, ist die
bessere Antwort auf „richtig geil und speziell" als eine, die acht
Tausendstel bunter ist und niemand wiedererkennt. blassBis sagt jetzt
bei jeder Ausnahme, WIE WEIT sie reicht -- vorher hiess blassErlaubt
schlicht „hier wird weggesehen".
DIE NEUE FARBE IST GRUEN UND WOLLTE BERNSTEIN SEIN. Gesucht war die
Farbe von Papier. Gemessen gibt es sie nicht mehr: Der beste Bernstein
im ganzen Farbraum haelt 0,0799 Abstand -- unter der Hausgrenze. Und
Platz schaffen hilft nicht: Setzt man ihn fest, muss ein warmer Ton
das Band verlassen (Ton 45 waere 0,212 weit ins Magenta gewandert).
Die groesste wirklich freie Luecke liegt im Gruen, bei 0,0917. Ein
linierter Block in Gruen ist Papier, seit es Papier gibt.
=================================================================
TEIL 2: DER BLOCK
=================================================================
DREI ENTSCHEIDUNGEN, AUS DENEN DER REST FOLGT:
1. ES GIBT KEINEN SPEICHERN-KNOPF. Nirgends. Wer einen Block
aufschlaegt und lostippt, drueckt hinterher nicht auf „sichern";
er klappt ihn zu. Gesichert wird 800 ms nach dem letzten
Tastendruck. Geht das nicht, bleibt der Text im Browser liegen und
wird nachgereicht -- und der Fuss sagt ehrlich „noch nicht
gesichert", statt einen Verlust zu melden, den man gerade nicht
verhindern kann.
2. DAS BLATT IST EIN SCHREIBFELD MIT EINER DECKSCHICHT. Das Feld
traegt Text und Cursor und ist unsichtbar; darueber zeichnet eine
zweite Schicht denselben Text noch einmal -- mit Kaestchen,
Ueberschriften, Strichen und Links.
DARAUS FOLGT EINE EISERNE REGEL, und sie steht dreimal im Code:
KEINE Auszeichnung darf die BREITE eines Zeichens aendern. Kein
Fettdruck, keine andere Groesse, keine Sperrung. Erlaubt sind nur
Farbe, Flaeche, Rahmen und Durchstreichen. Ein einziges
font-weight: 700 verschoebe den Umbruch, und ab der zweiten Zeile
stuende der sichtbare Text neben dem Cursor.
pruef-notizen misst das am echten Umbruch: eine Probe mit allen
Auszeichnungen und einer Zeile, die umbrechen MUSS. Sind beide
Schichten verschieden hoch, sitzt der Umbruch woanders. Mit
Gegenprobe -- Fettdruck auf der Deckschicht bricht die Deckung um
genau eine Zeile (30 px), und die Zeile wird rot.
3. DIE TASTATUR FUEHRT DIE LISTE FORT, NICHT EINE LEISTE. Ein
Spiegelstrich und Enter macht den naechsten Punkt, ein leeres
Kaestchen und Enter das naechste Kaestchen -- und ein GESETZTER
Haken wird dabei nicht mitgenommen, die naechste Aufgabe ist ja
noch nicht erledigt. Zweimal Enter beendet die Liste. Ein Klick
aufs Kaestchen hakt ab. Es gibt keine Werkzeugleiste, weil man beim
Schreiben nie den Stift wechselt.
UND: NIEMAND SIEHT DIE NOTIZEN EINES ANDEREN. Auch DogFather nicht.
Es gibt in workspace-notizen.js keinen siehtAlles()-Zweig, keinen
Umschalter, keine fremde Liste -- jede Abfrage hat person_id = ? fest
eingebaut. Die Kachel steht deshalb in „Fuer dich" und nicht in
„Taeglich": Die Gruppe sagt die wichtigste Eigenschaft, bevor man sie
anfasst.
NUR AUF DER TEAM-ADRESSE. Auf der Agenturadresse ist die Seite ein
404 -- auch fuer DogFather. Das ist dieselbe Trennung wie bei Chat,
Kalender und Aufgaben, und sie steht an zwei Stellen: in
GEHOERT_ZU_ADRESSE (die Datei) und in der Schnittstelle (die Daten).
Die Seite ist nur HTML; die Daten sind die Sache.
Dazu: sechs Papierfarben, Anheften, Suche mit Hervorhebung im Text,
Papierkorb mit dreissig Tagen und einem Eintrag in der
Aufbewahrungsliste (ohne den waere die Frist ein Satz in einem
Kommentar -- genau das ist dem Support am 24.09. passiert).
=================================================================
EIN FUND BEIM BAUEN, DER ALLEN GEHOERT
=================================================================
DELETE /workspace/api/notizen/:id gab es schon -- in
workspace-aufgaben.js, fuer die Notizen AN einer Aufgabe. Weil jener
Router frueher eingehaengt ist, hat er jeden Loeschversuch des Blocks
abgefangen und mit 404 beantwortet.
Von aussen sah das aus wie ein Fehler im neuen Modul: Anlegen ging,
Aendern ging, Loeschen nicht. Man sucht dann im eigenen Code, und dort
ist nichts. Express meldet so etwas nicht -- es nimmt die erste Route,
die passt, und schweigt ueber die zweite.
Der Block liegt jetzt unter /workspace/api/notizblock. Und
pruef-notizen geht seitdem den Routenbaum von express durch und meldet
jedes Paar aus Methode und Pfad, das zweimal vergeben ist. Gemessen:
307 Wege, keine Dublette. Die Pruefung gilt fuers ganze Haus, auch
wenn sie in dieser Datei steht -- hier ist sie gefunden worden.
Nebenbei berichtigt: pruef-crew-adresse verlangte von jedem Eintrag
der Haustafel HTTP 200 mit Inhalt. Das stimmte, solange dort nur
oeffentliche Dateien standen. notizen.html ist die erste Seite hinter
der Anmeldung -- sie antwortet mit 302, und das ist richtig. Gefragt
wird jetzt, was die Tafel wirklich meint: GIBT es das hier.
GEMESSEN, alles nach den Aenderungen:
pruef-notizen 76 Punkte, 0 Fehler (neu)
pruef-kachelfarben 31 Punkte, 0 Fehler (vorher 26)
pruef-kachel-universum 13 Punkte, 0 Fehler
pruef-crew-adresse 157 Punkte, 0 Fehler
pruef-buehne 230 Punkte, 0 Fehler -- notizen.html neu in
der Liste, schlechtester Kontrast 6,73:1,
also 50 % ueber der Grenze
pruef-rechtetafel, -haus-seiten, -struktur, -css-klassen,
-jeder-hat-eine-seite, -workspace-seiten, -kachelraster,
-rueckmeldung alle 0 Fehler
Co-Authored-By: Claude Opus 5 <[email protected]>
905 lines
47 KiB
JavaScript
905 lines
47 KiB
JavaScript
/* =====================================================================
|
|
DIE EIGENE ADRESSE DER MODI-APP
|
|
|
|
Geprueft wird nicht "die neue Adresse geht", sondern die vier Fragen,
|
|
an denen dieser Umbau scheitern kann:
|
|
|
|
1. KOMMT EIN MODI AUF crew. HEREIN -- und sonst niemand?
|
|
2. IST DER WORKSPACE UNVERAENDERT? Derselbe Dienst bedient beide
|
|
Adressen. Wer nicht auf den Hostnamen prueft, sperrt mit einer
|
|
einzigen Zeile alle Creator, Scouts und Manager aus, sofort nach
|
|
dem Neustart und ohne Vorwarnung.
|
|
3. BLEIBEN DIE PRUEFADRESSEN UNBERUEHRT? Waere die Regel "alles
|
|
ausser crew", dann wuerden ab sofort sieben andere Pruefdateien
|
|
messen, dass ein Modi nirgends hereinkommt -- gruen, weil sie
|
|
nichts mehr finden. Ein Lauf, der nichts mehr prueft, ist
|
|
schlimmer als ein roter.
|
|
4. HEISST DIE APP AUF DEM HANDY DES MODIS ANDERS? Sonst war der
|
|
ganze Umzug umsonst.
|
|
|
|
MIT GEGENPROBE. Ganz unten bekommt der Modi in der Datenbank eine
|
|
andere Rolle -- danach MUSS ihn dieselbe Sitzung auf crew. nicht mehr
|
|
hereinlassen. Ohne diesen Teil waere nicht bewiesen, dass die
|
|
Pruefung ueberhaupt anschlagen kann.
|
|
===================================================================== */
|
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { request } from "node:http";
|
|
|
|
const ordner = mkdtempSync(join(tmpdir(), "ws-crew-"));
|
|
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
|
|
/* DER PORT MUSS MEINER SEIN (nachgeruestet 19.09.2026).
|
|
Ohne diese Zeile startet der eigene Server bei belegtem Port
|
|
STILL nicht -- und alles Folgende misst gegen einen fremden
|
|
Stand. Das ist der dritte Ausgang: belegt ist etwas anderes
|
|
als kaputt. */
|
|
const PORT = await eigenerPort(import.meta, "pruef-crew-adresse");
|
|
process.env.PORT = `${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 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 }); };
|
|
import { notbremse } from "./helfer-notbremse.mjs";
|
|
import { eigenerPort } from "./helfer-port.mjs";
|
|
await import("./index.js");
|
|
notbremse(180_000, "pruef-crew-adresse");
|
|
await new Promise((r) => setTimeout(r, 700));
|
|
|
|
let fehler = 0;
|
|
let geprueft = 0;
|
|
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
|
|
let offen = 0;
|
|
const unklar = (t) => { offen++; console.log(" -- " + t); };
|
|
|
|
/* ---------------------------------------------------------------------
|
|
Ein Aufruf mit frei waehlbarem Host-Kopf.
|
|
|
|
Bewusst node:http und nicht fetch: `Host` gilt in der Fetch-Norm als
|
|
verbotener Kopf, und ob undici das durchlaesst, ist eine Annahme, auf
|
|
die diese ganze Pruefung nicht gebaut sein soll. Hier wird der Kopf
|
|
nachweislich gesetzt.
|
|
------------------------------------------------------------------ */
|
|
function hole(pfad, { host, methode = "GET", rumpf = null, keks = "" } = {}) {
|
|
return new Promise((fertig, schief) => {
|
|
const kopf = { Host: host };
|
|
if (rumpf) { kopf["Content-Type"] = "application/json"; kopf["Content-Length"] = Buffer.byteLength(rumpf); }
|
|
if (keks) kopf.Cookie = keks;
|
|
const a = request({ host: "127.0.0.1", port: PORT, path: pfad, method: methode, headers: kopf },
|
|
(antwort) => {
|
|
let text = "";
|
|
antwort.setEncoding("utf8");
|
|
antwort.on("data", (t) => { text += t; });
|
|
antwort.on("end", () => fertig({
|
|
status: antwort.statusCode,
|
|
kopf: antwort.headers,
|
|
text,
|
|
keks: (antwort.headers["set-cookie"] || [])
|
|
.map((z) => z.split(";")[0]).join("; "),
|
|
}));
|
|
});
|
|
a.on("error", schief);
|
|
a.setTimeout(15_000, () => { a.destroy(new Error("Zeitueberschreitung")); });
|
|
if (rumpf) a.write(rumpf);
|
|
a.end();
|
|
});
|
|
}
|
|
|
|
const anmelden = (host, rolle, code) =>
|
|
hole("/workspace/api/anmelden", { host, methode: "POST", rumpf: JSON.stringify({ rolle, code }) });
|
|
|
|
/* ---------- Personen anlegen ---------------------------------------- */
|
|
const { DatabaseSync } = await import("node:sqlite");
|
|
const { scryptSync, randomBytes, createHmac } = await import("node:crypto");
|
|
const d = new DatabaseSync(process.env.WORKSPACE_DB);
|
|
const jetzt = new Date().toISOString();
|
|
|
|
const schluessel = randomBytes(32).toString("hex");
|
|
d.prepare("INSERT INTO einstellungen (schluessel, wert, geaendert, von)"
|
|
+ " VALUES (?,?,?,NULL) ON CONFLICT(schluessel) DO NOTHING")
|
|
.run("code_kennung_schluessel", schluessel, jetzt);
|
|
const kennung = (code) => createHmac("sha256", schluessel).update(code).digest("hex");
|
|
|
|
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, code_kennung, aktiv, erstellt)"
|
|
+ " VALUES (?,?,?,?,?,?,1,?)")
|
|
.run(name, rolle, hash, salt, 32768, kennung(code), jetzt);
|
|
return d.prepare("SELECT last_insert_rowid() AS id").get().id;
|
|
}
|
|
|
|
anlegen("Filipe", "admin", "CODE-DOGI-0001");
|
|
anlegen("Schulle", "manager", "CODE-MAN-0001");
|
|
anlegen("Rieke", "hand", "CODE-HAND-0001");
|
|
const idModi = anlegen("Marina", "modi", "CODE-MODI-0001");
|
|
|
|
const CREW = "crew.dogfather-universe.com";
|
|
const WS = "workspace.dogfather-universe.com";
|
|
const HAUPT = "dogfather-universe.com";
|
|
const LOKAL = `127.0.0.1:${PORT}`;
|
|
|
|
/* =====================================================================
|
|
TEIL A -- DIE REGELN, OHNE SERVER
|
|
|
|
Sie stehen als eigene Funktionen in crew-adresse.js, damit genau das
|
|
hier moeglich ist: die Entscheidung pruefen, nicht ihre Auswirkung.
|
|
===================================================================== */
|
|
console.log("\n--- Die Regeln selbst ---");
|
|
const { istCrewAdresse, istOhneModiAdresse, istWorkspacePfad, sitzungPasstZurAdresse }
|
|
= await import("./crew-adresse.js");
|
|
|
|
ok(istCrewAdresse(CREW) === true, "crew.dogfather-universe.com ist die Modi-Adresse");
|
|
ok(istCrewAdresse("CREW.Dogfather-Universe.COM") === true, "Gross/klein macht keinen Unterschied");
|
|
ok(istCrewAdresse(CREW + ":443") === true, "ein Port angehaengt macht keinen Unterschied");
|
|
ok(istCrewAdresse(WS) === false, "workspace. ist NICHT die Modi-Adresse");
|
|
ok(istCrewAdresse(HAUPT) === false, "die Hauptseite ist NICHT die Modi-Adresse");
|
|
ok(istCrewAdresse("crew.dogfather-universe.com.boese.de") === false,
|
|
"eine fremde Domain mit dem Namen darin zaehlt nicht");
|
|
ok(istCrewAdresse("") === false && istCrewAdresse(null) === false,
|
|
"leer und fehlend sind nicht die Modi-Adresse");
|
|
|
|
ok(istOhneModiAdresse(WS) === true, "auf workspace. haben Modis nichts verloren");
|
|
ok(istOhneModiAdresse(HAUPT) === true, "auf der Hauptseite ebenfalls nicht");
|
|
ok(istOhneModiAdresse("www." + HAUPT) === true, "auf www. ebenfalls nicht");
|
|
ok(istOhneModiAdresse(CREW) === false, "crew. gehoert NICHT dazu (Falle 1)");
|
|
ok(istOhneModiAdresse(LOKAL) === false, "127.0.0.1 gehoert NICHT dazu (Falle 2)");
|
|
ok(istOhneModiAdresse("localhost:4100") === false, "localhost gehoert NICHT dazu (Falle 2)");
|
|
|
|
/* Die beiden Mengen duerfen sich nirgends beruehren. Ein Muster statt
|
|
einer Liste haette hier sofort angeschlagen. */
|
|
const alleAdressen = [CREW, WS, HAUPT, "www." + HAUPT, LOKAL, "localhost:4100", "git." + HAUPT];
|
|
ok(alleAdressen.every((h) => !(istCrewAdresse(h) && istOhneModiAdresse(h))),
|
|
"keine Adresse liegt in beiden Mengen");
|
|
|
|
ok(istWorkspacePfad("/workspace") === true, "/workspace gehoert dazu");
|
|
ok(istWorkspacePfad("/workspace/start.html") === true, "/workspace/start.html gehoert dazu");
|
|
ok(istWorkspacePfad("/workspaceXYZ") === false, "/workspaceXYZ gehoert NICHT dazu (Falle 3)");
|
|
ok(istWorkspacePfad("/workspace-alt") === false, "/workspace-alt gehoert NICHT dazu (Falle 3)");
|
|
ok(istWorkspacePfad("/assets/img/x.png") === false, "/assets liegt ausserhalb");
|
|
|
|
/* SEIT DEM 10.09.2026 DREI ROLLEN AUF crew. -- Filipe: "3 rollen.
|
|
dogfather. rechte hand und modis."
|
|
|
|
DogFather steht damit auf BEIDEN Adressen. Das ist keine Luecke in
|
|
der Trennung, sondern ihr Sinn: Er ist die einzige Person, die in
|
|
beiden Welten arbeitet. Die rechte Hand und die Modis dagegen gibt
|
|
es nur hier -- und auf workspace. gar nicht. */
|
|
const ROLLEN = ["admin", "spicy", "manager", "scout", "creator", "hand", "modi"];
|
|
const TEAM = new Set(["hand", "modi"]);
|
|
let passtWahr = 0, passtFalsch = 0;
|
|
for (const r of ROLLEN) {
|
|
const aufCrew = sitzungPasstZurAdresse(r, CREW);
|
|
const aufWs = sitzungPasstZurAdresse(r, WS);
|
|
const aufLokal = sitzungPasstZurAdresse(r, LOKAL);
|
|
const darfCrew = TEAM.has(r) || r === "admin";
|
|
ok(aufCrew === darfCrew, `crew.: ${r} ${darfCrew ? "darf" : "darf nicht"}`);
|
|
ok(aufWs === !TEAM.has(r), `workspace.: ${r} ${TEAM.has(r) ? "darf nicht" : "darf"}`);
|
|
ok(aufLokal === true, `127.0.0.1: ${r} unveraendert erlaubt`);
|
|
for (const w of [aufCrew, aufWs, aufLokal]) { if (w) passtWahr++; else passtFalsch++; }
|
|
}
|
|
/* Die Gegenprobe zur Regel selbst: Eine Funktion, die immer `true`
|
|
liefert, haette alle 18 Zeilen darueber ebenfalls teilweise bestanden.
|
|
Beide Antworten muessen vorkommen. */
|
|
ok(passtWahr > 0 && passtFalsch > 0,
|
|
`die Regel unterscheidet wirklich (${passtWahr}x ja, ${passtFalsch}x nein)`);
|
|
|
|
/* =====================================================================
|
|
TEIL B -- AM LAUFENDEN SERVER
|
|
===================================================================== */
|
|
console.log("\n--- Anmeldung je Adresse ---");
|
|
|
|
const modiAufCrew = await anmelden(CREW, "modi", "CODE-MODI-0001");
|
|
ok(modiAufCrew.status === 200, "crew.: der Modi kommt herein");
|
|
let zielModi = {};
|
|
try { zielModi = JSON.parse(modiAufCrew.text); } catch { /* bleibt leer */ }
|
|
ok(zielModi.weiter === "/workspace/start.html", "crew.: er landet auf seiner Startseite");
|
|
ok(modiAufCrew.keks.includes("dfw_sitzung"), "crew.: er bekommt eine Sitzung");
|
|
|
|
/* Der Code des Modis wird bewusst mit der Kachel 'creator' geschickt --
|
|
genau so kommt er auch aus der Zugangswand, denn eine Modi-Kachel
|
|
gibt es nicht. */
|
|
const modiOhneKachel = await anmelden(CREW, "creator", "CODE-MODI-0001");
|
|
ok(modiOhneKachel.status === 200, "crew.: die gewaehlte Kachel ist egal (stiller Zugang)");
|
|
|
|
const managerAufCrew = await anmelden(CREW, "manager", "CODE-MAN-0001");
|
|
ok(managerAufCrew.status === 401, "crew.: ein Manager mit gueltigem Code kommt NICHT herein");
|
|
ok(/ungueltig/.test(managerAufCrew.text),
|
|
"crew.: er bekommt dieselbe Antwort wie bei einem Tippfehler");
|
|
ok(!managerAufCrew.keks.includes("dfw_sitzung"), "crew.: und keine Sitzung");
|
|
|
|
const adminAufCrew = await anmelden(CREW, "admin", "CODE-DOGI-0001");
|
|
ok(adminAufCrew.status === 200 && adminAufCrew.keks.includes("dfw_sitzung"),
|
|
"crew.: DogFather kommt herein -- er arbeitet in beiden Welten");
|
|
|
|
const handAufCrew = await anmelden(CREW, "hand", "CODE-HAND-0001");
|
|
ok(handAufCrew.status === 200 && handAufCrew.keks.includes("dfw_sitzung"),
|
|
"crew.: die rechte Hand kommt herein");
|
|
|
|
/* Und sie kommt NICHT auf die andere Adresse -- genau wie ein Modi,
|
|
und mit genau derselben Antwort wie ein erfundener Code. */
|
|
const handAufWs = await anmelden(WS, "creator", "CODE-HAND-0001");
|
|
const erfunden2 = await anmelden(WS, "creator", "AUCH-DIESEN-NICHT-000");
|
|
ok(handAufWs.status === 401 && handAufWs.text === erfunden2.text,
|
|
`workspace.: die rechte Hand wird abgewiesen wie ein erfundener Code (${handAufWs.status})`);
|
|
ok(!handAufWs.keks.includes("dfw_sitzung"),
|
|
"workspace.: und bekommt dort keine Sitzung");
|
|
|
|
/* Eine Kachel, die es auf crew. nicht gibt, ist dort so gut wie
|
|
erfunden -- auch mit einem gueltigen Code dahinter. */
|
|
const spicyAufCrew = await anmelden(CREW, "spicy", "CODE-DOGI-0001");
|
|
ok(spicyAufCrew.status === 401, "crew.: eine fremde Kachel gibt es dort nicht");
|
|
|
|
/* =====================================================================
|
|
DIE ALTE WAND SCHWEIGT (Entscheidung Filipe, 10.09.2026)
|
|
|
|
Vorher zeigte sie einem Modi den Weg zur neuen Adresse. Seine
|
|
Entscheidung: "gar nichts -- Code stimmt nicht."
|
|
|
|
GEPRUEFT WIRD NICHT DER STATUSCODE, SONDERN DIE UNUNTERSCHEIDBARKEIT.
|
|
Eine 401 waere leicht zu erfuellen und truege trotzdem eine Spur, wenn
|
|
der Rumpf, die Koepfe oder die Dauer anders aussaehen als bei einem
|
|
erfundenen Code. Deshalb wird beides nebeneinandergelegt und
|
|
verglichen -- Zeichen fuer Zeichen. */
|
|
const erfunden = await anmelden(WS, "creator", "DIESEN-CODE-GIBT-ES-NICHT");
|
|
const modiAufWs = await anmelden(WS, "creator", "CODE-MODI-0001");
|
|
ok(modiAufWs.status === erfunden.status && modiAufWs.status === 401,
|
|
`workspace.: der Modi-Code wird abgewiesen wie ein erfundener (${modiAufWs.status})`);
|
|
ok(modiAufWs.text === erfunden.text,
|
|
`workspace.: und die Antwort ist Zeichen fuer Zeichen dieselbe (${modiAufWs.text.slice(0, 40)})`);
|
|
ok(!modiAufWs.keks.includes("dfw_sitzung"), "workspace.: keine Sitzung");
|
|
ok(!/crew\./.test(modiAufWs.text), "workspace.: die neue Adresse wird nicht genannt");
|
|
|
|
/* DER WICHTIGSTE PUNKT DIESER DATEI. Derselbe Dienst bedient beide
|
|
Adressen; ein Denkfehler hier sperrt das ganze Team aus. */
|
|
const managerAufWs = await anmelden(WS, "manager", "CODE-MAN-0001");
|
|
ok(managerAufWs.status === 200 && managerAufWs.keks.includes("dfw_sitzung"),
|
|
"workspace.: der Manager kommt herein wie immer -- der Workspace ist unberuehrt");
|
|
|
|
const managerLokal = await anmelden(LOKAL, "manager", "CODE-MAN-0001");
|
|
const modiLokal = await anmelden(LOKAL, "creator", "CODE-MODI-0001");
|
|
ok(managerLokal.status === 200 && managerLokal.keks.includes("dfw_sitzung"),
|
|
"127.0.0.1: der Manager kommt herein (Pruefadressen unberuehrt)");
|
|
ok(modiLokal.status === 200 && modiLokal.keks.includes("dfw_sitzung"),
|
|
"127.0.0.1: der Modi ebenfalls -- die sieben anderen Modi-Pruefungen messen weiter");
|
|
|
|
/* ---------------------------------------------------------------------
|
|
Eine Sitzung gehoert zu genau einer Adresse.
|
|
------------------------------------------------------------------ */
|
|
console.log("\n--- Eine Sitzung, eine Adresse ---");
|
|
const keksModi = modiAufCrew.keks;
|
|
const keksManager = managerAufWs.keks;
|
|
|
|
const modiDaheim = await hole("/workspace/api/ich", { host: CREW, keks: keksModi });
|
|
ok(modiDaheim.status === 200, "crew.: der angemeldete Modi wird erkannt");
|
|
ok(/Marina/.test(modiDaheim.text), "crew.: und zwar als er selbst");
|
|
|
|
const modiFremd = await hole("/workspace/api/ich", { host: WS, keks: keksModi });
|
|
ok(modiFremd.status === 401, "workspace.: dieselbe Sitzung zaehlt dort nicht");
|
|
|
|
const managerDaheim = await hole("/workspace/api/ich", { host: WS, keks: keksManager });
|
|
ok(managerDaheim.status === 200, "workspace.: der Manager wird erkannt");
|
|
|
|
const managerFremd = await hole("/workspace/api/ich", { host: CREW, keks: keksManager });
|
|
ok(managerFremd.status === 401, "crew.: seine Sitzung zaehlt dort nicht");
|
|
|
|
/* Auch die Seitenschranke, nicht nur die Schnittstelle: Eine Seite, die
|
|
ausgeliefert wird, waere ein Loch, selbst wenn die Daten dahinter
|
|
leer blieben. */
|
|
const seiteFremd = await hole("/workspace/start.html", { host: CREW, keks: keksManager });
|
|
ok(seiteFremd.status === 302, "crew.: die Startseite schickt den Manager zur Zugangswand");
|
|
const seiteDaheim = await hole("/workspace/start.html", { host: CREW, keks: keksModi });
|
|
ok(seiteDaheim.status === 200, "crew.: dem Modi liefert sie die Seite");
|
|
|
|
/* ---------------------------------------------------------------------
|
|
Die Weiche: was es auf crew. ueberhaupt gibt.
|
|
------------------------------------------------------------------ */
|
|
console.log("\n--- Die Weiche ---");
|
|
const wurzel = await hole("/", { host: CREW });
|
|
ok(wurzel.status === 302 && wurzel.kopf.location === "/workspace/",
|
|
"crew.: die Hauptseite gibt es dort nicht, es geht zur Zugangswand");
|
|
const wurzelWs = await hole("/", { host: HAUPT });
|
|
ok(wurzelWs.status === 200, "die Hauptseite selbst ist unveraendert erreichbar");
|
|
|
|
const fastWorkspace = await hole("/workspaceXYZ", { host: CREW });
|
|
ok(fastWorkspace.status === 302, "crew.: /workspaceXYZ gehoert nicht dazu (Falle 3)");
|
|
|
|
const robots = await hole("/workspace/", { host: CREW });
|
|
ok(String(robots.kopf["x-robots-tag"] || "").includes("noindex"),
|
|
"crew.: nicht indizieren -- die Adresse soll in keiner Suchmaschine stehen");
|
|
const robotsWs = await hole("/workspace/", { host: WS });
|
|
ok(!robotsWs.kopf["x-robots-tag"],
|
|
"workspace.: dort steht der Kopf NICHT (Gegenprobe zur Weiche)");
|
|
|
|
/* ---------------------------------------------------------------------
|
|
Die App heisst anders.
|
|
------------------------------------------------------------------ */
|
|
console.log("\n--- Zwei Apps, zwei Namen ---");
|
|
const manifestCrew = await hole("/workspace/app.webmanifest", { host: CREW });
|
|
const manifestWs = await hole("/workspace/app.webmanifest", { host: WS });
|
|
let mc = {}, mw = {};
|
|
try { mc = JSON.parse(manifestCrew.text); } catch { /* bleibt leer */ }
|
|
try { mw = JSON.parse(manifestWs.text); } catch { /* bleibt leer */ }
|
|
ok(manifestCrew.status === 200 && manifestWs.status === 200,
|
|
"beide Adressen liefern ein Manifest");
|
|
/* DER NAME GILT SEIT DEM 11.09.2026 FUER BEIDE (Entscheidung Filipe).
|
|
|
|
Er hiess "Team Dogi". Seit der Treff hier wohnt, wuerde ein Mitglied
|
|
der Community diesen Namen auf seinem Startbildschirm haben -- den
|
|
Namen eines Bereichs, zu dem es nicht gehoert. Das Manifest gilt je
|
|
ADRESSE und kann sich nicht nach der Rolle richten; also einer, der
|
|
fuer beide stimmt.
|
|
|
|
GEPRUEFT WIRD NICHT NUR DER NAME, SONDERN DER UNTERSCHIED. Ein
|
|
fester Name allein waere beim naechsten Umbenennen wieder rot, ohne
|
|
dass etwas kaputt ist. Dass die beiden Adressen sich UNTERSCHEIDEN,
|
|
ist die Eigenschaft, um die es geht -- sonst waere es eine App. */
|
|
ok(mc.name === "DogFather Universe",
|
|
`crew.: die App heisst "DogFather Universe" (gelesen: ${mc.name || "?"})`);
|
|
ok(mc.name && mw.name && mc.name !== mw.name,
|
|
`und anders als auf der Agenturadresse ("${mw.name}")`);
|
|
ok(mw.name === "Creator Workspace — Spicy & Dogi",
|
|
`workspace.: dort heisst sie unveraendert (gelesen: ${mw.name || "?"})`);
|
|
ok(Array.isArray(mc.icons) && mc.icons.length > 0
|
|
&& mc.icons.every((s) => /crew-/.test(s.src)),
|
|
`crew.: alle ${(mc.icons || []).length} Symbole sind die eigenen`);
|
|
ok(Array.isArray(mw.icons) && mw.icons.length > 0
|
|
&& mw.icons.every((s) => /workspace-/.test(s.src)),
|
|
"workspace.: dort weiterhin die alten");
|
|
ok(mc.start_url === "/workspace/start.html" && mc.scope === "/workspace/",
|
|
"crew.: Bereich und Startseite stimmen");
|
|
|
|
/* ---------------------------------------------------------------------
|
|
UND DAS SYMBOL AUF DEM IPHONE (22.09.2026)
|
|
---------------------------------------------------------------------
|
|
Die Zeilen darueber pruefen das MANIFEST -- daraus nimmt Android sein
|
|
Symbol. iOS Safari nimmt es von dort NICHT, sondern aus
|
|
`<link rel="apple-touch-icon">`, und die Zeile steht in jeder HTML
|
|
auf workspace-180.png.
|
|
|
|
Bis zum 22.09.2026 war das Manifest deshalb richtig und der
|
|
Startbildschirm eines Modis trotzdem falsch -- aber nur auf dem
|
|
iPhone. Genau die Sorte Fehler, die beim Ansehen nie auffaellt: Man
|
|
braeuchte beide Geraete nebeneinander.
|
|
|
|
Gemessen wird der AUSGELIEFERTE INHALT, nicht der Pfad. Eine
|
|
Umleitung, die auf eine Datei zeigt, die es nicht gibt, waere sonst
|
|
gruen. */
|
|
console.log("\n--- Das Symbol auf dem Startbildschirm ---");
|
|
{
|
|
const pfad = "/assets/img/app-symbole/workspace-180.png";
|
|
const aufCrew = await hole(pfad, { host: CREW });
|
|
const aufWs = await hole(pfad, { host: WS });
|
|
const echtCrew = await hole("/assets/img/app-symbole/crew-180.png", { host: CREW });
|
|
|
|
/* DIE ZAHL GEHOERT IN DIE BEDINGUNG: Waeren beide Antworten leer,
|
|
waeren sie auch gleich -- und der Vergleich unten trivial gruen. */
|
|
ok(aufCrew.status === 200 && aufWs.status === 200 && echtCrew.status === 200
|
|
&& aufCrew.text.length > 1000,
|
|
`alle drei Symbole werden ausgeliefert (${aufCrew.text.length} Byte)`);
|
|
ok(aufCrew.text === echtCrew.text,
|
|
"crew.: hinter apple-touch-icon steckt wirklich das Crew-Zeichen");
|
|
/* GEGENPROBE. Ohne sie wuesste niemand, ob die Umleitung ueberhaupt
|
|
etwas tut -- waeren beide Dateien identisch, waere die Zeile
|
|
darueber auch ohne jede Umleitung gruen. */
|
|
ok(aufCrew.text !== aufWs.text,
|
|
"workspace.: dort unveraendert das Agenturzeichen — die Umleitung greift also");
|
|
|
|
/* UND SIE GREIFT NUR, WO ES EINE CREW-FASSUNG GIBT. workspace-1024.png
|
|
ist das TikTok-Symbol und hat keine; wuerde es mit umgebogen,
|
|
liefe die Anfrage auf eine Datei, die es nicht gibt. */
|
|
const tiktok = await hole("/assets/img/app-symbole/workspace-1024.png", { host: CREW });
|
|
ok(tiktok.status === 200 && tiktok.text.length > 1000,
|
|
`und Groessen ohne Crew-Fassung bleiben unangetastet (1024: ${tiktok.status})`);
|
|
}
|
|
|
|
/* Die Symboldateien muessen es auch wirklich geben -- ein Manifest, das
|
|
auf nichts zeigt, faellt sonst erst auf dem Startbildschirm auf. */
|
|
let symboleDa = 0;
|
|
for (const s of mc.icons || []) {
|
|
const pfad = String(s.src).split("?")[0];
|
|
const a = await hole(pfad, { host: CREW });
|
|
if (a.status === 200) symboleDa++;
|
|
}
|
|
ok((mc.icons || []).length > 0 && symboleDa === (mc.icons || []).length,
|
|
`crew.: alle ${symboleDa} Symbole werden auch ausgeliefert`);
|
|
|
|
/* =====================================================================
|
|
DIE ZUGANGSWAND
|
|
|
|
Filipe am 10.09.2026: "alles soll auf dogfather und die modis
|
|
perfektionniert werden auf der neuen modi seite." Bis dahin stand auf
|
|
crew. die gewohnte Wand -- Spicy-Media-Logo, "Creator Workspace",
|
|
fuenf Rollenkacheln. Fuer einen Modi war davon nichts richtig.
|
|
|
|
Zwei Dinge werden hier gemessen, und das zweite ist das wichtigere:
|
|
dass die ALTE Wand unangetastet blieb.
|
|
===================================================================== */
|
|
console.log("");
|
|
console.log("--- Zwei Waende ---");
|
|
|
|
const wandCrew = await hole("/workspace/", { host: CREW });
|
|
const wandWs = await hole("/workspace/", { host: WS });
|
|
|
|
ok(wandCrew.status === 200, "crew.: die Zugangswand kommt (keine Weiterleitungsschleife)");
|
|
ok(wandWs.status === 200, "workspace.: die Zugangswand kommt");
|
|
|
|
ok(/Team Dogi|Team Dogi/.test(wandCrew.text), "crew.: dort steht Team Dogi");
|
|
ok(/DogFather/.test(wandCrew.text), "crew.: und DogFather daneben");
|
|
ok(!/Spicy/i.test(wandCrew.text), "crew.: von Spicy Media steht dort nichts mehr");
|
|
ok(!/Creator/i.test(wandCrew.text), "crew.: und vom Creator Workspace auch nicht");
|
|
/* DER SATZ AUF DER WAND MUSS FUER BEIDE STIMMEN (11.09.2026).
|
|
|
|
Hier stand `/die Modis/` -- und der Satz lautete "Privater Bereich
|
|
fuer DogFather und die Modis". Seit die Community durch dieselbe Wand
|
|
kommt, liest sie ihn auch, und fuer sie ist er schlicht falsch.
|
|
|
|
UND DIE ALTE PRUEFUNG WAERE TROTZDEM GRUEN GEBLIEBEN: Sie durchsuchte
|
|
den QUELLTEXT, und dort steht "die Modis" weiterhin in einem
|
|
Kommentar. Sie haette also bestaetigt, dass ein Satz dasteht, den es
|
|
auf dem Bildschirm gar nicht mehr gibt. Gemessen wird deshalb die
|
|
sichtbare Unterzeile, nicht die Datei. */
|
|
{
|
|
const unter = /<p class="unterzeile">([^<]*)<\/p>/.exec(wandCrew.text)?.[1] || "";
|
|
/* Sucht seit dem 22.09.2026 nach "Rudel": Der Treff heisst so.
|
|
Ein regulaerer Ausdruck ist keine Zeichenkette -- die
|
|
Umbenennung ist an ihm vorbeigelaufen, und er haette ab sofort
|
|
nach einem Wort gesucht, das die Seite nicht mehr sagt. */
|
|
ok(/Team\s*Dogi/.test(unter) && /Rudel/.test(unter),
|
|
`crew.: die Unterzeile nennt Team Dogi und das Rudel ("${unter.trim()}")`);
|
|
ok(!/Arbeitsbereich/.test(unter),
|
|
"und behauptet nicht mehr, das hier sei nur zum Arbeiten");
|
|
}
|
|
|
|
/* DIE KACHELREIHE HAT SEIT DEM 21.09.2026 FUENF EINTRAEGE.
|
|
|
|
Die Geschichte dieser Zeile ist lehrreich und steht deshalb hier:
|
|
Erst gab es keine Kachelreihe (eine Sorte Zugang, eine Reihe waere
|
|
eine Huerde gewesen). Dann drei ("3 rollen. dogfather. rechte hand
|
|
und modis"). Dann vier, weil der Treff hier eingezogen ist. Jetzt
|
|
fuenf -- die linke Hand ist dazugekommen.
|
|
|
|
GEPRUEFT WIRD NICHT DIE ZAHL, SONDERN WELCHE. Eine Zahl war schon
|
|
zweimal rot, ohne dass etwas kaputt war. Die NAMEN dagegen sagen
|
|
etwas: Steht dort eines Tages "manager", ist das ein Befund und kein
|
|
Umbau.
|
|
|
|
NACHGETRAGEN AM 22.09.2026, und der Rueckstand gehoert hierher:
|
|
Die Karte "Linke Hand" kam am 21.09. auf die Wand (Commit 21793b6),
|
|
diese Erwartung blieb bei vier. Die Pruefung war seitdem rot -- an
|
|
einer Stelle, an der NICHTS kaputt war. Eine rote Pruefung, die rot
|
|
bleibt, gewoehnt einem das Hinsehen ab; die Zeile darunter ("so
|
|
viele, wie es Rollen auf der Wand gibt") war die ganze Zeit gruen
|
|
und hat den Fehler damit sogar verdeckt.
|
|
|
|
Warum die Liste trotzdem stehen bleibt und nicht abgeleitet wird:
|
|
Die REIHENFOLGE ist eine Entscheidung (DogFather, rechte Hand, linke
|
|
Hand, Modis, Gaeste) und steht sonst nirgends. Abzuleiten hiesse nur,
|
|
die Liste woanders hinzuschreiben. Die Zahl daneben ist die
|
|
abgeleitete Haelfte -- zwei Fragen, zwei Zeilen. */
|
|
const rollenAufWand = [...wandCrew.text.matchAll(/data-rolle="([a-z]+)"/g)].map((m) => m[1]);
|
|
const ERWARTETE_REIHE = ["admin", "hand", "linke", "modi", "gast"];
|
|
ok(JSON.stringify(rollenAufWand) === JSON.stringify(ERWARTETE_REIHE),
|
|
`crew.: die ${ERWARTETE_REIHE.length} Kacheln in dieser Reihenfolge `
|
|
+ `(${rollenAufWand.join(", ")})`);
|
|
const kachelnCrew = (wandCrew.text.match(/class="rolle"/g) || []).length;
|
|
const kachelnWs = (wandWs.text.match(/class="rolle"/g) || []).length;
|
|
ok(kachelnCrew === rollenAufWand.length,
|
|
`crew.: ${kachelnCrew} Rollenkacheln -- so viele, wie es Rollen auf der Wand gibt`);
|
|
for (const [rolle, name] of [["admin", "DogFather"], ["hand", "Rechte Hand"], ["modi", "Modi"]]) {
|
|
ok(wandCrew.text.includes(`data-rolle="${rolle}"`) && wandCrew.text.includes(`>${name}<`),
|
|
`crew.: die Kachel "${name}" steht da`);
|
|
}
|
|
/* WELCHES ZEICHEN AUF WELCHER KACHEL
|
|
---------------------------------------------------------------------
|
|
HIER STAND BIS ZUM 22.09.2026 DAS GEGENTEIL, und beide Fassungen
|
|
geben eine Anweisung von Filipe wieder:
|
|
|
|
10.09.: "bei rechte hand soll der husky sein und bei modis soll
|
|
die pfote sein" -> DogFather und rechte Hand gleich.
|
|
22.09.: "bei dogi soll kein husky sein sondern eine krone. die
|
|
auch extrem auffaellt damit man den unterschied extrem
|
|
erkennt." -> DogFather eigenes Zeichen.
|
|
|
|
Dazwischen ist die LINKE Hand dazugekommen. Damit trugen DREI von
|
|
fuenf Kacheln denselben Husky -- und drei gleiche Zeichen
|
|
untereinander sind kein Zeichen mehr, sondern Tapete.
|
|
|
|
WAS JETZT GEPRUEFT WIRD, ist deshalb nicht mehr eine einzelne
|
|
Zuordnung, sondern die Eigenschaft, um die es geht: Jede Kachel
|
|
traegt ein Zeichen, und das von DogFather kommt kein zweites Mal
|
|
vor. Das haelt auch, wenn die Zeichen neu gezeichnet werden oder
|
|
eine sechste Rolle dazukommt -- eine abgeschriebene Paarliste
|
|
haette beim naechsten Umbau wieder danebengelegen.
|
|
|
|
Die zwei Haende teilen sich weiterhin eines, und das ist Absicht:
|
|
Sie haben denselben Untertitel ("vertritt und koordiniert") und
|
|
lesen sich als Paar. Geprueft wird deshalb nicht "alle
|
|
verschieden", sondern "DogFather ist einmalig". */
|
|
{
|
|
const zeichenVon = (rolle) => {
|
|
const stelle = wandCrew.text.indexOf(`data-rolle="${rolle}"`);
|
|
if (stelle < 0) return null;
|
|
return (/href="(#r-[a-z-]+)"/.exec(wandCrew.text.slice(stelle, stelle + 400)) || [])[1] || null;
|
|
};
|
|
const zeichen = Object.fromEntries(ERWARTETE_REIHE.map((r) => [r, zeichenVon(r)]));
|
|
const ohne = ERWARTETE_REIHE.filter((r) => !zeichen[r]);
|
|
ok(ohne.length === 0 && ERWARTETE_REIHE.length > 0,
|
|
ohne.length ? `crew.: ohne Zeichen: ${ohne.join(", ")}`
|
|
: `crew.: jede der ${ERWARTETE_REIHE.length} Kacheln traegt ein Zeichen`);
|
|
|
|
const zAdmin = zeichen.admin;
|
|
const wieOft = Object.values(zeichen).filter((z) => z === zAdmin).length;
|
|
ok(!!zAdmin && wieOft === 1,
|
|
`crew.: DogFathers Zeichen (${zAdmin}) kommt genau einmal vor (${wieOft})`);
|
|
ok(zAdmin === "#r-krone", `crew.: und es ist die Krone (${zAdmin})`);
|
|
|
|
/* Das Zeichen muss es auch GEBEN. Ein `use` auf eine Kennung, die
|
|
nirgends steht, zeichnet nichts -- und zwar lautlos: keine
|
|
Fehlermeldung, kein leeres Kaestchen, einfach nichts. Genau so
|
|
saehe eine vertippte Kennung aus. */
|
|
ok(wandCrew.text.includes('id="r-krone"'),
|
|
"crew.: und die Krone ist auf der Seite auch definiert");
|
|
|
|
/* GEGENPROBE: Die Messung muss einen Verstoss auch sehen koennen.
|
|
Zwei erfundene Verteilungen, eine richtig, eine mit doppeltem
|
|
DogFather-Zeichen. */
|
|
const zaehl = (v) => Object.values(v).filter((z) => z === v.admin).length;
|
|
ok(zaehl({ admin: "#r-krone", hand: "#r-husky", modi: "#r-pfote" }) === 1
|
|
&& zaehl({ admin: "#r-husky", hand: "#r-husky", modi: "#r-pfote" }) === 2,
|
|
"crew.: Gegenprobe -- ein doppelt vergebenes Zeichen wuerde auffallen");
|
|
|
|
const zModi = zeichen.modi;
|
|
ok(!!zModi && zModi !== zAdmin, `crew.: der Modi ein eigenes (${zModi})`);
|
|
ok(zeichen.hand === zeichen.linke && !!zeichen.hand,
|
|
`crew.: die zwei Haende lesen sich als Paar (${zeichen.hand} / ${zeichen.linke})`);
|
|
}
|
|
|
|
/* Die Namen kommen aus den Kacheln, nicht aus einer Liste in gate.js --
|
|
sonst stuenden sie in einer Datei, die jeder bekommt. */
|
|
ok(!/Rechte Hand/.test(wandWs.text), "workspace.: dort steht die dritte Rolle nicht");
|
|
ok(kachelnWs === 5, `workspace.: dort weiterhin fuenf (gezaehlt: ${kachelnWs})`);
|
|
ok(/SpicyMedia/.test(wandWs.text), "workspace.: die alte Wand ist unveraendert");
|
|
|
|
/* Auch der ausgeschriebene Weg. Ihn zu vergessen waere nicht
|
|
aufgefallen -- im Alltag tippt ihn niemand. */
|
|
const wandCrewLang = await hole("/workspace/index.html", { host: CREW });
|
|
ok(wandCrewLang.status === 200 && /Team Dogi|Team Dogi/.test(wandCrewLang.text),
|
|
"crew.: auch /workspace/index.html fuehrt zur neuen Wand");
|
|
|
|
/* ---------------------------------------------------------------------
|
|
DIE ZWEITE WAND GIBT ES NUR AUF crew.
|
|
|
|
Sie liegt als Datei im selben Ordner wie alles andere. Ohne Sperre
|
|
koennte sie jeder unter ihrem Dateinamen aufrufen -- auch auf
|
|
workspace. -- und saehe dort eine Anmeldeseite, die es dort nicht
|
|
geben soll.
|
|
------------------------------------------------------------------ */
|
|
/* ZWEI NUMMERN, EINE AUSSAGE. Auf workspace. antwortet die Sperre mit
|
|
404 "gibt es nicht". Auf der Hauptseite und www. kommt schon der
|
|
Umzugs-Zwischenschritt vom 06.09.2026 davor und beantwortet den
|
|
GANZEN Pfad /workspace mit 410 "gab es, ist absichtlich weg".
|
|
|
|
Erwartet werden deshalb beide -- was zaehlt, ist: kein Inhalt und
|
|
keine 200. Auf eine der zwei Nummern zu bestehen hiesse, eine
|
|
fremde, aeltere Entscheidung nachtraeglich zu einem Fehler zu
|
|
erklaeren. */
|
|
for (const [name, host] of [["workspace.", WS], ["die Hauptseite", HAUPT], ["www.", "www." + HAUPT]]) {
|
|
const a = await hole("/workspace/crew-index.html", { host });
|
|
ok(a.status === 404 || a.status === 410,
|
|
`${name}: die zweite Wand gibt es dort nicht (${a.status})`);
|
|
ok(!/Team Dogi/.test(a.text), `${name}: und ihr Inhalt kommt auch nicht durch`);
|
|
}
|
|
/* Doppelte Schraegstriche haben diese Schranke im Haus schon einmal
|
|
umgangen (04.09.2026, //workspace/start.html kam mit 200). */
|
|
const schraeg = await hole("/workspace//crew-index.html", { host: WS });
|
|
ok(schraeg.status === 404 || !/Team Dogi/.test(schraeg.text),
|
|
`workspace.: auch mit doppeltem Schraegstrich nicht (${schraeg.status})`);
|
|
|
|
/* GEGENPROBE: Auf crew. und auf den Pruefadressen bleibt sie erreichbar
|
|
-- sonst haette ich sie fuer alle gesperrt, auch fuer die Pruefung,
|
|
die sie im Browser ansieht. Die waere dann gruen und wertlos. */
|
|
const dortSchon = await hole("/workspace/crew-index.html", { host: CREW });
|
|
ok(dortSchon.status === 200, "crew.: dort gibt es sie natuerlich");
|
|
const lokalSchon = await hole("/workspace/crew-index.html", { host: LOKAL });
|
|
ok(lokalSchon.status === 200, "127.0.0.1: und auf den Pruefadressen auch");
|
|
|
|
/* ---------------------------------------------------------------------
|
|
JEDER EINTRAG DER TAFEL, NICHT NUR DIE WAND (21.09.2026)
|
|
|
|
Der Block darueber prueft crew-index.html namentlich. Am 21.09. kam
|
|
ein zweiter Eintrag dazu -- crew-haus.css, das ganze Farbhaus von
|
|
Team Dogi. Es stand bis dahin NICHT unter der Sperre: 27 806 Byte,
|
|
HTTP 200, von der Agenturadresse, ohne jede Anmeldung. Im Kopf der
|
|
Datei stand seit dem 10.09. das Gegenteil.
|
|
|
|
WAERE DIESER BLOCK EINE ZWEITE NAMENTLICHE ABSCHRIFT, stuende beim
|
|
dritten Eintrag dasselbe Loch wieder offen. Er geht deshalb die
|
|
Tafel durch -- dieselbe Liste, die der Server durchsetzt. Ein neuer
|
|
Eintrag wird damit automatisch in beide Richtungen gemessen, ohne
|
|
dass jemand hier etwas nachtraegt.
|
|
|
|
UND IN BEIDE RICHTUNGEN heisst: nicht nur "draussen gesperrt",
|
|
sondern auch "drinnen erreichbar". Eine Sperre, die zu viel sperrt,
|
|
nimmt der zweiten Adresse ihr Aussehen -- und das faellt erst auf,
|
|
wenn jemand hinsieht.
|
|
------------------------------------------------------------------ */
|
|
const { GEHOERT_ZU_ADRESSE } = await import("./crew-adresse.js");
|
|
ok(GEHOERT_ZU_ADRESSE.length >= 2,
|
|
`die Tafel hat ${GEHOERT_ZU_ADRESSE.length} Eintraege`);
|
|
|
|
for (const eintrag of GEHOERT_ZU_ADRESSE) {
|
|
const weg = eintrag.datei;
|
|
const draussen = await hole(weg, { host: WS });
|
|
ok(draussen.status === 404 || draussen.status === 410,
|
|
`workspace.: ${weg} gibt es dort nicht (${draussen.status})`);
|
|
|
|
/* DIE ZAHL ALLEIN REICHT NICHT. Ein 404 mit Inhalt waere kein
|
|
Schutz; gemessen wird deshalb auch, dass nichts Nennenswertes
|
|
mitkommt. */
|
|
ok((draussen.text || "").length < 200,
|
|
`workspace.: und es kommt auch kein Inhalt mit (${(draussen.text || "").length} Zeichen)`);
|
|
|
|
/* ==== DRINNEN HEISST NICHT IMMER 200 (26.09.2026) ==============
|
|
|
|
Hier stand „muss 200 sein, und mehr als 200 Zeichen lang".
|
|
Das stimmte, solange in der Tafel nur OEFFENTLICHE Dateien
|
|
standen -- die Eingangswand und eine Stilvorlage.
|
|
|
|
Mit notizen.html steht dort zum ersten Mal eine Seite HINTER
|
|
der Anmeldung. Ohne Sitzung antwortet sie mit 302 auf die
|
|
Anmeldung, und das ist richtig so. Die Pruefung meldete
|
|
trotzdem einen Fehler -- nicht am Haus, sondern an ihrer
|
|
eigenen, zu engen Erwartung.
|
|
|
|
Die Frage dieser Tafel ist nicht „liefert es Inhalt aus?",
|
|
sondern „GIBT es das hier?". Genau das wird jetzt gemessen:
|
|
drinnen alles ausser 404/410, draussen nur 404/410. Beide
|
|
Richtungen bleiben, nur die Innenseite ist nicht mehr auf eine
|
|
einzige Zahl festgelegt. */
|
|
const drinnen = await hole(weg, { host: CREW });
|
|
ok(drinnen.status !== 404 && drinnen.status !== 410,
|
|
`crew.: ${weg} gibt es dort (${drinnen.status})`);
|
|
/* Und eine offene Datei kommt dort auch wirklich an. Bei einer
|
|
Seite hinter der Anmeldung waere „mehr als 200 Zeichen" eine
|
|
Aussage ueber die Weiterleitung, nicht ueber die Seite. */
|
|
if (drinnen.status === 200) {
|
|
ok((drinnen.text || "").length > 200,
|
|
`crew.: und zwar mit Inhalt (${(drinnen.text || "").length} Zeichen)`);
|
|
} else {
|
|
ok(drinnen.status === 302 || drinnen.status === 401 || drinnen.status === 403,
|
|
`crew.: und schickt ohne Anmeldung zur Anmeldung (${drinnen.status})`);
|
|
}
|
|
}
|
|
|
|
/* DIE UMBIEGUNG DARF DIE SPERRE NICHT MITNEHMEN.
|
|
|
|
`haus.css` wird auf crew. intern auf crew-haus.css umgebogen. Die
|
|
Tafel wird ganz oben in crewWeiche() gefragt, die Umbiegung passiert
|
|
weiter unten -- deshalb geht das gut. Stuende die Tafel eines Tages
|
|
NACH der Umbiegung, waere auf crew. jede Seite ohne ihr Farbhaus, und
|
|
niemand saehe eine Fehlermeldung: Eine Seite ohne Stilvorlage laedt,
|
|
sie sieht nur falsch aus. */
|
|
const umgebogen = await hole("/workspace/assets/css/haus.css", { host: CREW });
|
|
ok(umgebogen.status === 200 && (umgebogen.text || "").length > 200,
|
|
`crew.: haus.css wird weiterhin umgebogen (${umgebogen.status}, `
|
|
+ `${(umgebogen.text || "").length} Zeichen)`);
|
|
const leer = await hole("/workspace/assets/css/haus.css", { host: WS });
|
|
ok(leer.status === 200,
|
|
`workspace.: haus.css kommt dort weiterhin an (${leer.status})`);
|
|
ok((leer.text || "").length < (umgebogen.text || "").length,
|
|
`und ist dort die kleinere, leere Fassung `
|
|
+ `(${(leer.text || "").length} gegen ${(umgebogen.text || "").length} Zeichen)`);
|
|
|
|
/* ---------------------------------------------------------------------
|
|
WAS gate.js AUS DEM HTML BRAUCHT -- auf BEIDEN Seiten.
|
|
|
|
Es gibt jetzt zwei Wanddateien und ein gemeinsames Skript. Wer nur
|
|
eine anfasst, macht die andere kaputt, und zwar lautlos: Ein
|
|
fehlendes Element ist im Browser ein `null`, der Fehler steht in der
|
|
Konsole und sonst nirgends. Deshalb wird beides gegen dieselbe Liste
|
|
gehalten.
|
|
------------------------------------------------------------------ */
|
|
const GEBRAUCHT = ["formular", "code", "auge", "fehler", "hinweis", "knopf", "fuer"];
|
|
for (const [name, wand] of [["crew.", wandCrew], ["workspace.", wandWs]]) {
|
|
const fehlend = GEBRAUCHT.filter((id) => !wand.text.includes(`id="${id}"`));
|
|
ok(fehlend.length === 0,
|
|
`${name} die Wand hat alle ${GEBRAUCHT.length} Elemente, die gate.js sucht`
|
|
+ (fehlend.length ? ` -- es fehlen: ${fehlend.join(", ")}` : ""));
|
|
ok(wand.text.includes("knopf__text") && wand.text.includes("buehne__bild"),
|
|
`${name} auch die beiden Klassen, die gate.js anspricht`);
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------
|
|
Die Buehne.
|
|
------------------------------------------------------------------ */
|
|
ok(/crew-gate-\d+\.webp/.test(wandCrew.text), "crew.: die eigene Buehne ist eingebunden");
|
|
ok(!/buehne\/gate-\d+\.webp/.test(wandCrew.text), "crew.: die alte Buehne nicht mehr");
|
|
ok(/buehne\/gate-\d+\.webp/.test(wandWs.text), "workspace.: dort weiterhin die alte");
|
|
ok(/crew-32\.png/.test(wandCrew.text) && /crew-180\.png/.test(wandCrew.text),
|
|
"crew.: eigenes Zeichen im Reiter und auf dem Startbildschirm");
|
|
|
|
/* Ein Verweis beweist nicht, dass die Datei da ist. */
|
|
const buehnen = [...new Set(wandCrew.text.match(/assets\/img\/buehne\/crew-gate-[a-z0-9-]+\.webp/g) || [])];
|
|
let buehnenDa = 0;
|
|
for (const b of buehnen) {
|
|
const a = await hole("/workspace/" + b, { host: CREW });
|
|
if (a.status === 200) buehnenDa++;
|
|
}
|
|
ok(buehnen.length >= 8 && buehnenDa === buehnen.length,
|
|
`crew.: alle ${buehnenDa} von ${buehnen.length} Buehnenbildern werden ausgeliefert`);
|
|
|
|
/* =====================================================================
|
|
DIE BUEHNEN INNEN (10.09.2026)
|
|
|
|
Filipe: "auch die hintergrund bilder vom crew espace sollen anderes
|
|
aussehen, der community angepasst wie die zugangscode seite".
|
|
|
|
DIE SZENENNAMEN WERDEN AUS start.css GELESEN, nicht hier
|
|
abgeschrieben. Eine Liste an zwei Stellen ist eine Liste, die
|
|
auseinanderlaeuft -- und dann faellt eine neue Szene ohne eigenes
|
|
Motiv niemandem auf: Auf der Modi-Seite stuende ploetzlich wieder
|
|
ein fremdes Logo im Hintergrund, und zwar genau auf der Seite, an
|
|
die niemand gedacht hat.
|
|
===================================================================== */
|
|
console.log("");
|
|
console.log("--- Die Buehnen innen ---");
|
|
{
|
|
const { readFileSync } = await import("node:fs");
|
|
const { dirname, join } = await import("node:path");
|
|
const { fileURLToPath } = await import("node:url");
|
|
const css = readFileSync(
|
|
join(dirname(fileURLToPath(import.meta.url)), "..", "workspace", "assets", "css", "start.css"),
|
|
"utf8");
|
|
const namen = [...new Set((css.match(/buehne-[a-z]+-(?:uhd|breit|schmal)\.webp/g) || []))];
|
|
|
|
/* Die Zahl gehoert in die Bedingung: Faende das Muster nichts --
|
|
umbenannte Dateien, anderer Ordner --, waere alles darunter gruen,
|
|
ohne dass ein einziges Bild angesehen wurde. */
|
|
ok(namen.length >= 5, `${namen.length} Buehnenbilder in start.css gefunden`);
|
|
|
|
let eigene = 0, alte = 0;
|
|
const fehlend = [];
|
|
for (const name of namen) {
|
|
const aufCrew = await hole(`/workspace/assets/img/${name}`, { host: CREW });
|
|
const aufWs = await hole(`/workspace/assets/img/${name}`, { host: WS });
|
|
if (aufCrew.status === 200) eigene++; else fehlend.push(name);
|
|
if (aufWs.status === 200) alte++;
|
|
}
|
|
ok(eigene === namen.length,
|
|
`crew.: fuer alle ${namen.length} gibt es ein eigenes Motiv`
|
|
+ (fehlend.length ? ` -- es fehlt: ${fehlend.slice(0, 3).join(", ")}` : ""));
|
|
ok(alte === namen.length, `workspace.: dort liegen unveraendert die alten (${alte})`);
|
|
|
|
/* DAS ZEICHEN IN DER KOPFLEISTE. Es kommt aus dem CSS als
|
|
Hintergrundbild -- auf der Modi-Adresse stand dort die Chili von
|
|
Spicy Media, auf jeder Seite. */
|
|
const zeichenCrew = await hole("/workspace/assets/img/marke-spicy.webp", { host: CREW });
|
|
const zeichenWs = await hole("/workspace/assets/img/marke-spicy.webp", { host: WS });
|
|
const husky = await hole("/workspace/assets/img/marke-husky.webp", { host: WS });
|
|
ok(zeichenCrew.status === 200 && husky.status === 200
|
|
&& zeichenCrew.text === husky.text,
|
|
"crew.: oben links kommt der Husky, nicht die Chili");
|
|
ok(zeichenWs.status === 200 && zeichenWs.text !== husky.text,
|
|
"workspace.: dort bleibt es die Chili (Gegenprobe)");
|
|
|
|
/* UND SIE MUESSEN VERSCHIEDEN SEIN. Beide Adressen antworten mit 200
|
|
-- das sagt noch nicht, dass es zwei verschiedene Bilder sind. Ohne
|
|
diese Zeile waere die Pruefung auch dann gruen, wenn die Weiche gar
|
|
nicht greift. */
|
|
const probe = namen.find((n) => n.endsWith("-breit.webp")) || namen[0];
|
|
const a1 = await hole(`/workspace/assets/img/${probe}`, { host: CREW });
|
|
const a2 = await hole(`/workspace/assets/img/${probe}`, { host: WS });
|
|
ok(a1.text.length > 0 && a2.text.length > 0 && a1.text !== a2.text,
|
|
`und es sind zwei verschiedene Bilder (${probe}: `
|
|
+ `${Math.round(a1.text.length / 1024)} KB gegen ${Math.round(a2.text.length / 1024)} KB)`);
|
|
}
|
|
|
|
/* =====================================================================
|
|
DAS HAUS (10.09.2026)
|
|
|
|
Filipe: "ich will doch dass der eingang hier getrennt ist von der
|
|
workspace seite … aber es soll trotzdem so bleiben dass ich die
|
|
daten hier und da sehe."
|
|
|
|
Getrennt wird das AUSSEHEN, nicht der Bestand. Jede Seite laedt als
|
|
letzte Stilvorlage `haus.css`; auf der Agenturadresse ist die leer,
|
|
auf crew. biegt die Weiche sie auf `crew-haus.css` um.
|
|
|
|
GEPRUEFT WIRD NICHT NUR "beide antworten mit 200". Zwei gueltige
|
|
Antworten sagen nichts darueber, ob es zwei VERSCHIEDENE Dateien
|
|
sind -- genau daran waere die Pruefung der Buehnen weiter oben schon
|
|
einmal fast vorbeigelaufen. Deshalb wird der Inhalt verglichen, und
|
|
zusaetzlich der Griff `--haus-name` gelesen: Er unterscheidet "die
|
|
leere Fassung ist angekommen" von "gar nichts ist angekommen".
|
|
===================================================================== */
|
|
console.log("");
|
|
console.log("--- Welches Haus kommt an? ---");
|
|
{
|
|
const hausCrew = await hole("/workspace/assets/css/haus.css", { host: CREW });
|
|
const hausWs = await hole("/workspace/assets/css/haus.css", { host: WS });
|
|
ok(hausCrew.status === 200 && hausWs.status === 200,
|
|
`beide Adressen liefern ein Haus (${hausCrew.status}/${hausWs.status})`);
|
|
ok(hausCrew.text !== hausWs.text,
|
|
`und es sind zwei verschiedene (${hausCrew.text.length} gegen ${hausWs.text.length} Zeichen)`);
|
|
ok(/--haus-name:\s*"crew"/.test(hausCrew.text),
|
|
"auf crew. steht das Haus von Team Dogi darin");
|
|
ok(/--haus-name:\s*"workspace"/.test(hausWs.text),
|
|
"auf workspace. das der Agentur (Gegenprobe)");
|
|
|
|
/* DIE FASSUNGSNUMMER MUSS MITREISEN. Jede Seite ruft die Datei mit
|
|
`?v=…` auf. Wer beim Umbiegen nur den Pfad setzt, wirft sie weg --
|
|
und der Browser behaelt die Datei dann laenger, als er soll.
|
|
Aufgefallen waere das erst beim naechsten Farbwechsel, Wochen
|
|
spaeter, bei einem einzelnen Menschen. */
|
|
const mitNummer = await hole("/workspace/assets/css/haus.css?v=202609102225", { host: CREW });
|
|
ok(mitNummer.status === 200 && /--haus-name:\s*"crew"/.test(mitNummer.text),
|
|
`mit Fassungsnummer kommt dasselbe an (${mitNummer.status})`);
|
|
|
|
/* HIER STAND BIS ZUM 21.09.2026 DAS GEGENTEIL -- und die Aenderung
|
|
ist eine Entscheidung von Filipe, keine stille Korrektur.
|
|
|
|
Der alte Satz lautete: "Die Datei des anderen Hauses ist auf
|
|
beiden Adressen direkt abrufbar -- sie ist kein Geheimnis, sie ist
|
|
nur nicht die Vorgabe." Dazu die Sorge, eine Sperre koenne die
|
|
Pruefung darueber blind machen.
|
|
|
|
WAS SICH GEAENDERT HAT, IST NICHT DIE MEINUNG, SONDERN DIE
|
|
MESSUNG. Nachgemessen am 21.09.: 27 806 Byte, HTTP 200, von der
|
|
Agenturadresse, OHNE jede Anmeldung -- also nicht nur fuer
|
|
Kollegen, sondern fuer jeden im Netz. Darin das ganze Farbhaus von
|
|
Team Dogi samt seiner Kommentare, und in denen zweimal die Adresse
|
|
der zweiten Wand im Klartext. Die Adresse ist das eigentlich
|
|
Verborgene: Die Rollennamen stehen auf jener Wand ohnehin offen,
|
|
WO sie liegt dagegen nicht.
|
|
|
|
Im Kopf der Datei selbst stand seit dem 10.09. das Gegenteil
|
|
("wird NUR auf crew... ausgeliefert"). Zwei Stellen im Haus haben
|
|
sich also widersprochen, und die durchgesetzte war die offene.
|
|
|
|
DIE ALTE SORGE IST BEANTWORTET, nicht uebergangen: Der Block
|
|
weiter oben misst jeden Eintrag der Tafel in BEIDE Richtungen und
|
|
prueft zusaetzlich, dass die Umbiegung von `haus.css` auf crew.
|
|
weiterhin greift. Genau das war der Fall, vor dem der alte
|
|
Kommentar gewarnt hat -- er ist jetzt gemessen statt vermieden. */
|
|
const direkt = await hole("/workspace/assets/css/crew-haus.css", { host: WS });
|
|
ok(direkt.status === 404 || direkt.status === 410,
|
|
`crew-haus.css ist von der Agenturadresse nicht mehr abrufbar (${direkt.status})`);
|
|
}
|
|
|
|
/* =====================================================================
|
|
DIE GEGENPROBE
|
|
|
|
Bis hierhin koennte alles auch dann gruen sein, wenn die Regel gar
|
|
nicht auf die Rolle sieht. Deshalb wird sie jetzt ausgehebelt: Der
|
|
Modi bekommt in der Datenbank die Rolle 'manager'. Seine Sitzung
|
|
bleibt dieselbe -- aber auf crew. darf sie ab jetzt NICHT mehr
|
|
gelten.
|
|
===================================================================== */
|
|
console.log("\n--- Gegenprobe: die Regel ausgehebelt ---");
|
|
d.prepare("UPDATE personen SET rolle = 'manager' WHERE id = ?").run(idModi);
|
|
|
|
const nachher = await hole("/workspace/api/ich", { host: CREW, keks: keksModi });
|
|
ok(nachher.status === 401,
|
|
"aus dem Modi wurde ein Manager -- seine Sitzung gilt auf crew. nicht mehr");
|
|
const nachherWs = await hole("/workspace/api/ich", { host: WS, keks: keksModi });
|
|
ok(nachherWs.status === 200,
|
|
"und dieselbe Sitzung gilt jetzt auf workspace. -- die Regel sieht wirklich auf die Rolle");
|
|
|
|
d.prepare("UPDATE personen SET rolle = 'modi' WHERE id = ?").run(idModi);
|
|
const zurueck = await hole("/workspace/api/ich", { host: CREW, keks: keksModi });
|
|
ok(zurueck.status === 200, "zurueckgedreht gilt sie wieder");
|
|
|
|
/* ---------- Ende ------------------------------------------------------ */
|
|
d.close();
|
|
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Windows haelt die Datei manchmal */ }
|
|
|
|
console.log(`\n${fehler === 0 ? "BESTANDEN" : "FEHLGESCHLAGEN"}`
|
|
+ ` -- ${geprueft} geprueft, ${fehler} Fehler, ${offen} nicht nachsehbar`);
|
|
process.exit(fehler === 0 ? 0 : 1);
|