Ein Husky statt der Krone -- und Spicy sieht DogFather, aber nur ihn

DER HUSKY (screen 1)

Filipe: "die krone bei dogfather durch einen husky ersetzen, wie mein
logo." Bei 24 Pixeln entscheidet die SILHOUETTE, nicht das Detail. Ein
Husky erkennt man an dreierlei, und mehr passt auch nicht hinein: den
spitzen aufrechten Ohren, dem breiten Kopf, der nach unten schmal
zulaeuft, und der Gesichtsmaske. Fell oder Zunge waeren bei dieser
Groesse Matsch -- genau deshalb hat die Krone davor funktioniert.

UND ALLE ROLLENSYMBOLE SIND JETZT KOERPER

Sie waren reine Konturen in einer Farbe -- daneben auf derselben Seite
die Kachelzeichen mit drei Lichtern. Jetzt tragen sie eine gefuellte
Flaeche in ihrer Rollenfarbe, die Zeichnung hell darauf, und Augen und
Nase eigens gesetzt. Beim gewaehlten Knopf leuchtet die Flaeche
staerker -- der einzige Unterschied, den es braucht: mehr Licht auf
demselben Gegenstand.

SPICY SIEHT DOGFATHER, ABER NICHT DEN ZWEITEN ADMIN (screen 2)

Filipe: "die rolle spicy soll auch die rolle dogfather sehen, aber nur
dogfather und nicht vanvan."

NACHGEMESSEN AM ECHTEN SYSTEM, nicht angenommen: In der Datenbank
tragen BEIDE die Rolle `admin` -- id 1 "Dogfather", id 4 "VanVan". Es
gibt kein Feld, das den einen vom anderen unterscheidet.

Der Unterschied, den es wirklich gibt, ist das Alter: DogFather ist der
erste Zugang des Hauses. Deshalb zaehlt die kleinste Nummer unter den
Admins -- eine Eigenschaft, die feststeht und nicht am Namen haengt.
Die Schwachstelle steht im Code, damit sie niemand sucht: Wuerde Zugang
1 je geloescht, rueckte der naechste nach; dann gehoert ein
ausdrueckliches Merkmal in die Tabelle.

Die Entscheidung faellt im SERVER, nicht in der Oberflaeche. Dort stand
vorher ein Filter, der den ganzen Abschnitt wegnahm -- zwei Regeln fuer
dieselbe Frage laufen auseinander, und eine ausgeblendete Zeile hat
noch nie etwas geschuetzt.

MEINE EIGENEN PRUEFUNGEN VON HEUTE MITTAG FIELEN DABEI

Richtig so: Sie pruefen "DogFather steht NICHT darin", und das gilt
nicht mehr. Umgeschrieben -- und dabei kam der eigentliche Prueffall
dazu: ein ZWEITER Admin-Zugang im Testbestand. Ohne ihn waere die Regel
gar nicht pruefbar, bei einem einzigen Admin ist jede Antwort richtig.
pruef-spicy von 57 auf 60.

NOCH OFFEN aus derselben Nachricht: die Terminarten (BigMatch,
Turniere, Special-Live) und der Umbau der Begruessungskachel nach dem
Vorbild von VanVans Werktisch.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-07 18:14:54 +02:00
co-authored by Claude Opus 5
parent 09a0c17237
commit 28a260fab2
23 changed files with 326 additions and 206 deletions
+25 -5
View File
@@ -213,6 +213,11 @@ for (const z of ausgabe().split(String.fromCharCode(10))) if (z.includes("worksp
} catch { erfunden = true; }
ok(erfunden, "die Schranke beisst noch -- eine erfundene Rolle wird abgelehnt");
const idSpicy = anlegen(s, "Agentur", "spicy", "CODE-SPICY-001");
/* EIN ZWEITER ADMIN-ZUGANG. Ohne ihn waere die Regel "Spicy Media
sieht DogFather, aber nicht den zweiten Admin" gar nicht pruefbar
-- bei einem einzigen Admin ist jede Antwort richtig. Am echten
System gibt es zwei (id 1 "Dogfather", id 4 "VanVan"). */
anlegen(s, "VanVan", "admin", "CODE-VAN-000001");
ok(Number.isInteger(idSpicy) && idSpicy > 0, "'spicy' laesst sich jetzt anlegen");
s.close();
}
@@ -275,9 +280,22 @@ const ruf = async (art, weg, keks, koerper) => {
einer Aussage werden drei, und die dritte ist die wichtigste:
Ueberblick ist nicht Verwaltung. */
ok(status === 200, `die Personenliste ist fuer sie offen (HTTP ${status})`);
const listeRollen = (daten?.personen || []).map((x) => x.rolle);
ok(listeRollen.length > 0 && !listeRollen.includes("admin"),
`und DogFather steht nicht darin (${listeRollen.join(", ") || "leer"})`);
/* AM 07.09.2026 PRAEZISIERT. Erst hiess es "DogFather steht nicht
darin", dann: "die rolle spicy soll auch die rolle dogfather sehen,
aber nur dogfather und nicht vanvan."
Beide Zugaenge tragen dieselbe Rolle `admin`; unterschieden werden
sie ueber das ALTER -- DogFather ist der erste Zugang des Hauses,
also die kleinste Nummer. Genau das wird hier gemessen, und der
zweite Admin ist der Prueffall: Ohne ihn waere jede Antwort
richtig. */
const admins = (daten?.personen || []).filter((x) => x.rolle === "admin");
ok(admins.length === 1,
`genau EIN Admin-Zugang steht in ihrer Liste (${admins.length})`);
ok(admins[0]?.id === idDogi,
`und zwar der erste, DogFather (${admins[0]?.name || "keiner"})`);
ok(!(daten?.personen || []).some((x) => x.name === "VanVan"),
"der zweite Admin-Zugang nicht");
const prot = await ruf("GET", "/workspace/api/verwaltung/protokoll?anzahl=5", keksSpicy);
ok(prot.status === 404, `das Protokoll bleibt zu (HTTP ${prot.status})`);
const loeschen = await ruf("DELETE", `/workspace/api/verwaltung/personen/${idLuna}`, keksSpicy);
@@ -507,8 +525,10 @@ console.log("\n=== Die Anmeldeseite ===");
const abschnitteSpicy = await seite.$$eval(".gruppe__name, .gruppe__kopf",
(k) => k.map((x) => x.textContent.trim()).filter(Boolean));
const alsText = abschnitteSpicy.join(" | ");
ok(!/DogFather/.test(alsText),
`bei ihr fehlt der Abschnitt DogFather (${alsText.slice(0, 70) || "keine Abschnitte"})`);
ok(/DogFather/.test(alsText),
`bei ihr steht der Abschnitt DogFather (${alsText.slice(0, 70) || "keine Abschnitte"})`);
ok(/Manager|Creator|Scout/.test(alsText),
"und die uebrigen Abschnitte ebenfalls -- die Liste ist nicht leer");
await ctx.close();
/* GEGENPROBE BEI DOGFATHER: Bei ihm steht der Abschnitt "Spicy Media"
+24 -1
View File
@@ -139,7 +139,30 @@ personenRouter.get("/workspace/api/verwaltung/personen", (req, res) => {
nicht in der Abfrage: Die Abfrage ist lang und wird von mehreren
Feldern gelesen; eine zusaetzliche Bedingung mittendrin waere die
Stelle, an der beim naechsten Umbau jemand danebengreift. */
/* FUER SPICY MEDIA: DOGFATHER JA, DER ZWEITE ADMIN-ZUGANG NEIN.
Filipe: "die rolle spicy soll auch die rolle dogfather sehen,
aber nur dogfather sehen und nicht vanvan."
In der Datenbank tragen BEIDE die Rolle `admin` -- nachgemessen
am laufenden System: id 1 "Dogfather", id 4 "VanVan". Es gibt
kein Feld, das den einen vom anderen unterscheidet.
Der Unterschied, den es WIRKLICH gibt, ist das Alter: DogFather
ist der erste Zugang des Hauses. Deshalb zaehlt hier die
kleinste Nummer unter den Admins -- eine Eigenschaft, die
feststeht und nicht am Namen haengt.
DIE SCHWACHSTELLE STEHT HIER, damit sie niemand sucht: Wuerde
Zugang 1 je geloescht, rueckte der naechste Admin nach. Sollte
das eintreten, gehoert stattdessen ein ausdrueckliches Merkmal
in die Tabelle -- ein `haupt`-Feld. Solange es genau zwei
Admin-Zugaenge gibt und der erste DogFather ist, ist die
Nummer die ehrlichste Antwort ohne Datenbankumbau. */
const ohne = req.ohneDogFather === true;
const hauptAdmin = ohne
? (db().prepare("SELECT MIN(id) AS id FROM personen WHERE rolle = 'admin'").get()?.id || 0)
: 0;
res.json({
personen: db().prepare(`
SELECT p.id, p.name, p.rolle, p.aktiv, p.erstellt, p.letzter_login,
@@ -162,7 +185,7 @@ personenRouter.get("/workspace/api/verwaltung/personen", (req, res) => {
LEFT JOIN betreuung b ON b.creator_id = p.id
LEFT JOIN scout_zuteilung sz ON sz.scout_id = p.id
ORDER BY ${ROLLEN_SORTIERUNG.replace("rolle", "p.rolle")}, p.aktiv DESC, p.name`)
.all().filter((z) => !(ohne && z.rolle === "admin")),
.all().filter((z) => !(ohne && z.rolle === "admin" && z.id !== hauptAdmin)),
/* Wer ueberhaupt als zustaendig eingetragen werden kann.
Bis zum 31.08.2026 waren das nur Scouts. In der Auswahl stand