Die Rolle "Spicy Media" -- und drei Fehler, die nur ihre Pruefung fand

DIE PERSONENTABELLE WURDE NEU GEBAUT. Eine Rolle ist ein erlaubter Wert
in einer Spalte, und der steckt in einem CHECK -- den kann SQLite nicht
aendern. Auf `personen` zeigen ZWEIUNDFUENFZIG Fremdschluessel.

Deshalb wird der Bauplan NICHT abgeschrieben, sondern gelesen: Der
CREATE-Text kommt aus sqlite_master, darin wird ausschliesslich die
Rollenliste ersetzt, und die Kopierliste kommt aus PRAGMA table_info.
Die beiden aelteren Umstellungen schreiben ihre Spalten von Hand ab --
`personen` hat seit damals SIEBEN dazubekommen (bild, ueber_mich,
tiktok ...). Wer hier abschreibt, verliert alle Profilbilder.

ZWEI FRAGEN, DIE MAN AUSEINANDERHALTEN MUSS:
  siehtAlles()   = DogFather ODER Spicy Media -> Listen, Uebersichten
  istDogFather() = nur DogFather              -> loeschen, Rollen, Codes
Es waere weniger Arbeit gewesen, istDogFather() um "spicy" zu erweitern
-- und genau das waere der Fehler: Spicy Media koennte dann DogFather
loeschen.

DER CHAT BRAUCHTE NICHTS. Er haengt allein an der Teilnehmerliste und
kennt kein "das Management sieht alles". Spicy Media sieht fremde
Gespraeche nicht, weil es dafuer keinen Weg gibt -- nicht, weil eine
Abfrage es verbietet.

DREI ECHTE FEHLER, alle von pruef-spicy gefunden, keiner vorher sichtbar:

1. MEIN EIGENER KOMMENTAR STAND IM BAUPLAN. SQLite hebt den CREATE-Text
   woertlich auf, samt Kommentaren. Ich hatte "'spicy' steht HIER mit
   drin" hineingeschrieben -- und die Erkennung suchte genau dieses Wort
   im ganzen Text. Ergebnis: Die Umstellung hielt die Tabelle fuer
   erledigt, obwohl die CHECK-Regel noch die alte war. Jetzt wird die
   Regel herausgeschnitten und NUR darin gesucht; der Kommentar steht
   ausserhalb des SQL.

2. DER SICHERUNGSNAME HATTE NUR MINUTEN. `VACUUM INTO` weigert sich, eine
   vorhandene Datei zu ueberschreiben -- zu Recht. Zwei Umstellungen in
   derselben Minute wollten in dieselbe Datei, die zweite scheiterte, und
   weil ohne Sicherung nicht umgestellt wird, blieb sie aus. Es sah nach
   "lief" aus (die Datei lag ja da) und war keine. Jetzt mit Sekunden.

3. EINE FRISCHE DATENBANK LEGTE DIE ALTE ROLLENLISTE AN und stellte beim
   allerersten Start sofort um -- Tabelle neu bauen, Sicherung schreiben,
   fuer nichts. Ein Bauplan, der sofort umgebaut werden muss, ist der
   falsche.

Und ein vierter in der Pruefung selbst: Der Chat-Aufbau benutzte einen
falschen Weg, das Gespraech entstand gar nicht -- "Spicy Media sieht 0
Gespraeche" war trotzdem gruen, weil es keine gab. Jetzt ist das Anlegen
selbst eine Pruefung, und eine Gegenprobe zeigt, dass Max es sehr wohl
sieht.

DAZU: Manager sehen die Kachel "Personen & Zugaenge" -- die SEITE geht
auf, die Schnittstellen nicht. Alles unter /verwaltung haengt weiter an
`nurAdmin` und antwortet 404; sie sehen genau den Teil, fuer den es
einen Weg gibt. Spicy Media legt Creator UND Manager an (zweite enge
Tuer, Rolle steht auch dort nicht im Aufruf; ein Manager kommt durch
sie nicht). Der Rollentext des Managers stimmte nicht mehr -- er legt
jetzt Creator an.

pruef-spicy: 36 Pruefungen, alle gruen. Ausserdem gruen: personen-liste
(33), creator-anlegen (29), sicht (48), css-klassen (15), alle-wege (19),
haerte (20), manager-sicht (43).

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-07 04:56:07 +02:00
co-authored by Claude Opus 5
parent 4bb5afc942
commit 68bc116c36
39 changed files with 983 additions and 247 deletions
+234 -23
View File
@@ -57,17 +57,19 @@ const COOKIE = "dfw_sitzung";
const SITZUNG_STUNDEN = 12;
const VERSUCHE_MAX = 8; // pro IP
const VERSUCHE_FENSTER_MIN = 10;
const ROLLEN = new Set(["admin", "manager", "scout", "creator"]);
const ROLLEN = new Set(["spicy", "admin", "manager", "scout", "creator"]);
/* Die Reihenfolge, in der Rollen ueberall erscheinen: DogFather zuerst,
dann Manager, dann Scout, dann Creator. Steht hier einmal, damit keine
Liste eine eigene Reihenfolge erfindet. */
export const ROLLEN_REIHE = ["admin", "manager", "scout", "creator"];
/* Die Reihenfolge, in der Rollen ueberall erscheinen: Spicy Media
zuerst, dann DogFather, Manager, Scout, Creator. Steht hier einmal,
damit keine Liste eine eigene Reihenfolge erfindet. */
export const ROLLEN_REIHE = ["spicy", "admin", "manager", "scout", "creator"];
/* Als SQL-Ausdruck fuer ORDER BY. "ORDER BY rolle" waere alphabetisch
(admin, creator, manager, scout) -- also fast genau falsch herum. */
(admin, creator, manager, scout, spicy) -- also fast genau falsch
herum. */
export const ROLLEN_SORTIERUNG =
"CASE rolle WHEN 'admin' THEN 0 WHEN 'manager' THEN 1 WHEN 'scout' THEN 2 ELSE 3 END";
"CASE rolle WHEN 'spicy' THEN 0 WHEN 'admin' THEN 1 WHEN 'manager' THEN 2"
+ " WHEN 'scout' THEN 3 ELSE 4 END";
/* LEITUNG = DogFather und Manager. Ein Manager darf alles, was
DogFather darf -- mit genau zwei Ausnahmen, die in
@@ -75,10 +77,44 @@ export const ROLLEN_SORTIERUNG =
Leitung veraendern. Sonst koennte er sich selbst zum DogFather machen
oder den echten aussperren. "Nur DogFather hat alle endgueltigen
Rechte" heisst genau das. */
const LEITUNG = new Set(["admin", "manager"]);
const LEITUNG = new Set(["spicy", "admin", "manager"]);
export const istLeitung = (person) => !!person && LEITUNG.has(person.rolle);
export const istDogFather = (person) => !!person && person.rolle === "admin";
/* =======================================================================
SPICY MEDIA (07.09.2026)
Wunsch Filipe: "eine neue rolle ... die den namen traegt, spicy media,
und soll die gleichen rechte haben wie dogfather ausser, bei
automationen sollen die nicht sehen und die kategorie personen und
zugaenge sollen die nur leute hinzufuegen koennen ... aber die sollen
meine privaten chats und so nicht sehen."
ZWEI FRAGEN, DIE MAN AUSEINANDERHALTEN MUSS -- und genau daran haette
man diesen Umbau kaputtmachen koennen:
WER SIEHT ALLES? -> siehtAlles() = DogFather ODER Spicy Media
WER ENTSCHEIDET? -> istDogFather() = nur DogFather
Die erste Frage stellt sich bei Listen, Uebersichten und Auswertungen:
Spicy Media soll alle Manager, Scouts und Creator sehen. Die zweite
bei allem Endgueltigen: loeschen, Rollen aendern, Codes neu setzen,
Sicherungen, die KI abschalten. Da bleibt DogFather allein.
Es waere viel weniger Arbeit gewesen, istDogFather() einfach um
"spicy" zu erweitern -- und genau das waere der Fehler: Spicy Media
koennte dann DogFather loeschen. Zwei Namen, zwei Bedeutungen, und an
jeder Stelle steht sichtbar, welche gemeint ist.
DER CHAT BRAUCHTE NICHTS. Er haengt ausschliesslich an der
Teilnehmerliste (chat_teilnehmer) und kennt kein "das Management sieht
alles". Spicy Media sieht fremde Gespraeche also nicht, weil es dafuer
gar keinen Weg gibt -- nicht, weil eine Abfrage es verbietet.
======================================================================= */
export const istSpicy = (person) => !!person && person.rolle === "spicy";
export const siehtAlles = (person) => !!person
&& (person.rolle === "admin" || person.rolle === "spicy");
/* Der Rollenschluessel bleibt "admin" -- er steckt in der CHECK-Regel der
Datenbank, in jeder Sitzung und in jeder Rechteabfrage. Umbenannt wird
nur, was man LIEST. Diese Zuordnung ist die einzige Stelle dafuer:
@@ -89,6 +125,7 @@ export const istDogFather = (person) => !!person && person.rolle === "admin";
Rolle "admin" weiterhin "das Management". Gemeint ist dieselbe Rolle,
die in der Oberflaeche "DogFather" heisst. */
export const ROLLEN_NAME = {
spicy: "Spicy Media",
admin: "DogFather",
manager: "Manager",
scout: "Scout",
@@ -120,7 +157,21 @@ let _dbFehler = null;
waehrend laufender Schreibvorgaenge).
===================================================================== */
function umstellungen(d) {
const jetztStempel = new Date().toISOString().slice(0, 16).replace(/[-:T]/g, "");
/* MIT SEKUNDEN, nicht nur mit Minuten (07.09.2026).
`VACUUM INTO` weigert sich, eine vorhandene Datei zu ueberschreiben
("output file already exists") -- zu Recht, eine Sicherung darf
nichts wegwerfen. Der Stempel hatte aber nur Minutenaufloesung:
Zwei Umstellungen innerhalb derselben Minute wollten in dieselbe
Datei, die zweite scheiterte, und weil ohne Sicherung nicht
umgestellt wird, blieb sie einfach aus.
Das ist genau die unangenehme Sorte Fehler: Es sieht nach "die
Umstellung lief" aus (die Datei liegt ja da) und war doch keine.
Gefunden von pruef-spicy, das den Server zweimal in derselben
Minute startet -- im Betrieb genuegt dafuer ein Neustart im
falschen Moment. */
const jetztStempel = new Date().toISOString().slice(0, 19).replace(/[-:T]/g, "");
/* ---- Zeitpunkt des Hochladens in der Bibliothek ----
Eine neue Spalte laesst SQLite anstandslos anhaengen -- anders als
@@ -635,6 +686,131 @@ function umstellungen(d) {
d.exec("PRAGMA foreign_keys = ON");
}
}
/* =====================================================================
ROLLE "spicy" (Spicy Media) FREISCHALTEN — 07.09.2026
Wunsch Filipe: "ich will dass ueber dogfather auch eine kategorie,
eine neue rolle entsteht die den namen traegt, spicy media."
---------------------------------------------------------------------
DAS IST DER RISKANTESTE UMBAU IM GANZEN HAUS
Eine Rolle ist ein erlaubter Wert in einer Spalte, und der steckt in
SQLite in einem CHECK. Ein CHECK laesst sich nicht aendern -- die
Tabelle muss neu gebaut werden. Bei `eintraege` war das schon
unangenehm; hier geht es um `personen`, und auf die zeigen
ZWEIUNDFUENFZIG Fremdschluessel aus einem Dutzend Tabellen. Jede
Sitzung, jede Aufgabe, jeder Termin, jede Datei haengt daran.
DESHALB WIRD DER BAUPLAN NICHT ABGESCHRIEBEN, SONDERN GELESEN.
Die beiden Umstellungen darueber schreiben ihre Spaltenliste von
Hand ab. Das ging gut, solange die Liste stimmte -- aber `personen`
hat seit damals SIEBEN Spalten dazubekommen (bild, ueber_mich,
tiktok, instagram, youtube, twitch und weitere, siehe den
Spalten-Nachtrag oben). Wer hier eine Liste abschreibt, verliert
beim naechsten Mal genau die Spalte, die jemand letzte Woche
ergaenzt hat -- samt allen Profilbildern.
Also: Der vorhandene CREATE-Text wird aus sqlite_master geholt und
darin AUSSCHLIESSLICH die Rollenliste ersetzt. Alles andere -- jede
Spalte, jeder Typ, jede Vorgabe -- bleibt woertlich stehen. Und die
Kopierliste kommt aus PRAGMA table_info, also aus der Tabelle
selbst.
WENN DIE ERSETZUNG NICHT GREIFT, WIRD NICHT UMGESTELLT. Ein
`replace`, das nichts findet, gibt den Text unveraendert zurueck --
die Umstellung liefe dann durch, baute dieselbe Tabelle noch einmal
und meldete Erfolg. Genau die Sorte gruener Haken, die nichts
geprueft hat. Deshalb wird danach nachgesehen, ob 'spicy' wirklich
drinsteht.
===================================================================== */
const rollenPlan = d.prepare(
"SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'personen'").get()?.sql || "";
/* GEPRUEFT WIRD DIE REGEL, NICHT DER TEXT (07.09.2026).
Hier stand `rollenPlan.includes("'spicy'")`. Das ist eine Suche im
ganzen gespeicherten Bauplan -- und SQLite hebt den woertlich auf,
mitsamt allen Kommentaren darin. Ein erklaerender Satz mit dem Wort
'spicy' genuegte, und die Umstellung hielt die Tabelle fuer schon
umgestellt, obwohl die CHECK-Regel noch die alte war. Beim Bauen
genau so passiert.
Jetzt wird die Regel selbst herausgeschnitten und NUR darin gesucht.
Findet sich keine, gibt es nichts umzustellen. */
const regel = rollenPlan.match(/rolle\s+IN\s*\(([^)]*)\)/i)?.[1] || "";
if (rollenPlan && regel && !regel.includes("'spicy'")) {
const sicherung = `${DB_PFAD}.vor-spicy-${jetztStempel}`;
try {
d.exec(`VACUUM INTO '${sicherung.replace(/'/g, "''")}'`);
console.log("[workspace] Sicherung vor der Umstellung:", sicherung);
} catch (fehler) {
console.error("[workspace] Sicherung fehlgeschlagen, Umstellung abgebrochen:", fehler?.message);
return;
}
/* Nur die Rollenliste ersetzen -- und pruefen, dass es geklappt hat. */
const neuerPlan = rollenPlan
/* `IF NOT EXISTS` MUSS MIT INS MUSTER (07.09.2026, gefunden von
pruef-spicy). SQLite hebt den CREATE-Text WOERTLICH auf -- die
Tabelle wurde mit `CREATE TABLE IF NOT EXISTS personen` angelegt,
also steht das auch in sqlite_master. Ohne diese drei Woerter im
Muster griff die Ersetzung nicht, der Bauplan blieb unveraendert,
die Schutzabfrage darunter schlug an -- und die Umstellung waere
auf dem echten Server NIE gelaufen. Sie haette es gesagt (das ist
der Wert der Abfrage), aber sie waere nie gelaufen. */
.replace(/CREATE TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?["'`]?personen["'`]?/i,
"CREATE TABLE personen_neu")
.replace(/rolle\s+IN\s*\([^)]*\)/i, "rolle IN ('spicy','admin','manager','scout','creator')");
if (!neuerPlan.includes("'spicy'") || !neuerPlan.includes("personen_neu")) {
console.error("[workspace] Umstellung abgebrochen: Der Bauplan liess sich nicht "
+ "umschreiben. Steht die CHECK-Regel noch so da wie erwartet?");
return;
}
/* Die Spaltenliste kommt aus der Tabelle, nicht aus dem Gedaechtnis. */
const spalten = d.prepare("PRAGMA table_info(personen)").all().map((z) => z.name);
if (!spalten.length) {
console.error("[workspace] Umstellung abgebrochen: keine Spalten gefunden.");
return;
}
const liste = spalten.map((n) => `"${n}"`).join(", ");
d.exec("PRAGMA foreign_keys = OFF");
try {
const vorher = d.prepare("SELECT COUNT(*) AS n FROM personen").get().n;
d.exec("BEGIN");
d.exec(neuerPlan);
d.exec(`INSERT INTO personen_neu (${liste}) SELECT ${liste} FROM personen;`);
const nachher = d.prepare("SELECT COUNT(*) AS n FROM personen_neu").get().n;
/* Die Zaehlung steht INNERHALB der Transaktion -- stimmt sie nicht,
wird zurueckgerollt und die alte Tabelle bleibt unberuehrt. */
if (nachher !== vorher) {
d.exec("ROLLBACK");
console.error(`[workspace] Umstellung abgebrochen: ${vorher} Personen vorher, `
+ `${nachher} nachher. Sicherung: ${sicherung}`);
} else {
d.exec("DROP TABLE personen;");
d.exec("ALTER TABLE personen_neu RENAME TO personen;");
d.exec("COMMIT");
const kaputt = d.prepare("PRAGMA foreign_key_check").all();
if (kaputt.length) {
console.error("[workspace] ACHTUNG: nach der Umstellung", kaputt.length,
"verwaiste Verweise. Sicherung liegt unter", sicherung);
} else {
console.log(`[workspace] Rolle 'spicy' freigeschaltet, ${nachher} Personen, `
+ `${spalten.length} Spalten uebernommen, Verweise geprueft.`);
}
}
} catch (fehler) {
try { d.exec("ROLLBACK"); } catch { /* schon zurueckgerollt */ }
console.error("[workspace] Umstellung fehlgeschlagen:", fehler?.message,
"-- Sicherung:", sicherung);
} finally {
d.exec("PRAGMA foreign_keys = ON");
}
}
}
export function db() {
@@ -648,10 +824,23 @@ export function db() {
PRAGMA journal_mode = WAL;
PRAGMA foreign_keys = ON;
/* ROLLENLISTE: 'spicy' steht hier mit drin, nicht nur in der
Umstellung (07.09.2026, gefunden von pruef-spicy). Vorher legte
eine frische Datenbank die alte Liste an, und die Umstellung
lief beim allerersten Start sofort hinterher -- Tabelle neu
bauen, Sicherung schreiben, umbenennen, fuer nichts. Ein
Bauplan, der sofort umgebaut werden muss, ist der falsche.
KEIN KOMMENTAR INNERHALB DIESES CREATE-TEXTES. SQLite hebt ihn
woertlich in sqlite_master auf -- samt Kommentaren. Ein Satz mit
dem Wort 'spicy' darin haette bedeutet, dass die Umstellung die
Tabelle fuer bereits umgestellt haelt, obwohl die CHECK-Regel
noch die alte ist. Genau das ist beim Bauen passiert: Der
Bauplan enthielt das Wort, die Regel nicht. */
CREATE TABLE IF NOT EXISTS personen (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
rolle TEXT NOT NULL CHECK (rolle IN ('admin','manager','scout','creator')),
rolle TEXT NOT NULL CHECK (rolle IN ('spicy','admin','manager','scout','creator')),
code_hash TEXT NOT NULL,
code_salt TEXT NOT NULL,
code_n INTEGER NOT NULL,
@@ -1456,27 +1645,49 @@ const GESCHUETZT = {
Lesezeichen hat, waere weiterhin hineingekommen. Beides gehoert
zusammen: bereiche.js zeigt sie nur noch DogFather, diese Zeile
laesst nur ihn hinein. */
"/workspace/leistung.html": ["admin"],
"/workspace/leistung.html": ["spicy", "admin"],
/* NUR DogFather (02.09.2026). Die Rollenauswahl verspricht das seit
jeher ("Manager -- dieselben Rechte, ausser der
Personenverwaltung"), die Schranke hielt sich nur nicht daran.
Wunsch: "die manager sollen diese kategorien garnicht sehen". */
"/workspace/personen.html": ["admin"],
"/workspace/profil.html": ["admin", "manager", "scout", "creator"],
/* PERSONEN & ZUGAENGE: jetzt auch fuer Manager und Spicy Media
(07.09.2026).
Filipe: "die manager sehen immer noch nicht die kachel personen &
zugangscode, wo sie dan die neuen creator hinzufuegen koennen ...
und die sollen in der kategorie wo die jetzt sehen werden auch noch
das mit den hinzufuegen sehen, alles andere auf der seite sollen die
weiterhin nicht sehen."
DIE SEITE OEFFNET SICH, DIE SCHNITTSTELLEN NICHT. Das ist der ganze
Trick und der Grund, warum das hier ungefaehrlich ist: Alles unter
/workspace/api/verwaltung haengt weiterhin an `nurAdmin` -- Rollen
aendern, Codes neu setzen, sperren, loeschen, Protokoll lesen
bleiben zu und antworten mit 404. Wer die Seite oeffnet, bekommt
also genau die Teile zu sehen, fuer die es auch einen Weg gibt.
Eine Seite ist kein Schutz, sie ist ein Weg. Der Schutz steht in den
Schnittstellen, und der ist unveraendert. */
"/workspace/personen.html": ["spicy", "admin", "manager"],
"/workspace/profil.html": ["spicy", "admin", "manager", "scout", "creator"],
/* Der eigene Steckbrief -- jede Rolle hat einen. Ein Creator wird
dort nicht hingeschickt (er sieht seinen auf profil.html), darf die
Seite aber aufrufen: Sie zeigt ihm dasselbe, nur ohne die Akte. */
"/workspace/steckbrief.html": ["admin", "manager", "scout", "creator"],
"/workspace/steckbrief.html": ["spicy", "admin", "manager", "scout", "creator"],
"/workspace/kalender.html": null,
"/workspace/dateien.html": null,
"/workspace/bereich.html": ["admin", "manager", "scout", "creator"],
"/workspace/report.html": ["admin", "manager", "scout", "creator"],
"/workspace/scouting.html": ["admin", "manager", "scout"],
"/workspace/bereich.html": ["spicy", "admin", "manager", "scout", "creator"],
"/workspace/report.html": ["spicy", "admin", "manager", "scout", "creator"],
"/workspace/scouting.html": ["spicy", "admin", "manager", "scout"],
"/workspace/calls.html": null,
"/workspace/startcheck.html": null,
"/workspace/wissen.html": null,
/* Ebenfalls nur DogFather: Was von allein passiert, bestimmt, was
allen anderen zugeschoben wird. */
/* AUTOMATIONEN BLEIBEN BEI DogFather (07.09.2026). Ausdruecklich:
"bei automationen sollen die nicht sehen." Dahinter liegen
Sicherungen, die KI und der Zustand des Servers -- das ist Betrieb,
nicht Betreuung. */
"/workspace/automation.html": ["admin"],
};
@@ -1870,7 +2081,7 @@ export function scoutZuteilungSetzen(scoutId, managerId, akteur = null) {
* null = alle (nur DogFather). */
export function sichtbareCreatorIds(person) {
if (!person) return [];
if (istDogFather(person)) return null;
if (siehtAlles(person)) return null;
if (person.rolle === "creator") return [person.id];
return betreuteIds(person); // Manager: eigene + die seiner Scouts
}
@@ -1883,7 +2094,7 @@ export function sichtbareCreatorIds(person) {
* seine Scouts dazu -- er führt sie, er muss sie eintragen können. */
export function sichtbarePersonenIds(person) {
if (!person) return [];
if (istDogFather(person)) return null;
if (siehtAlles(person)) return null;
const creator = sichtbareCreatorIds(person) || [];
const scouts = person.rolle === "manager" ? scoutsVon(person.id) : [];
/* DOGFATHER IST FUER ALLE SICHTBAR (07.09.2026, Wunsch Filipe:
@@ -1968,7 +2179,7 @@ export function betreuerIds(person) {
* null = alle (nur DogFather), wie überall in dieser Datei. */
export function einladbareIds(person) {
if (!person) return [];
if (istDogFather(person)) return null;
if (siehtAlles(person)) return null;
const sichtbar = sichtbarePersonenIds(person) || [];
/* DogFather gehört immer dazu: Er ist der Einzige, mit dem jede
Rolle zu tun hat, und ihn nicht einladen zu können wäre der erste
@@ -2001,7 +2212,7 @@ export function einladbareIds(person) {
* null = alle (nur DogFather). */
export function schreibbareIds(person) {
if (!person) return [];
if (istDogFather(person)) return null;
if (siehtAlles(person)) return null;
const basis = new Set(einladbareIds(person) || []);
@@ -2118,7 +2329,7 @@ export const externSql = (personSpalte, externSpalte) =>
spaeter auseinandergelaufen -- und dann haette eine Wiederholung
jemandem etwas gezeigt, was der einzelne Termin ihm verbirgt. */
export function termineSichtbar(person, praefix = "t") {
if (istDogFather(person)) return { wo: "1=1", werte: [] };
if (siehtAlles(person)) return { wo: "1=1", werte: [] };
const p = praefix;
/* MITGEZAEHLT WIRD AUCH DIE TEILNEHMERLISTE (05.09.2026).
@@ -2236,7 +2447,7 @@ export function darfCreator(person, creatorId) {
Ein Manager faellt jetzt in dieselbe Zeile wie ein Scout -- die
Kette "eigene plus die meiner Scouts" steckt in betreuteIds. */
if (istDogFather(person)) return true;
if (siehtAlles(person)) return true;
if (person.rolle === "creator") return person.id === Number(creatorId);
return betreuteIds(person).includes(Number(creatorId));
}