B9: In einen Kategorie-Kanal gehoert Team Dogi -- und alle davon

VanVans Befund, von Filipe weitergegeben:
  * "Patrick und BananaStift (stifti) aus der Erwaehnen-Liste
     entfernen -- sie stehen dort als SCOUT."
  * "Die Modis zum Erwaehnen hinzufuegen."
  * "Die Modis sehen den Chat Moderation auch gar nicht."

AN DER LIVE-DATENBANK NACHGEMESSEN, bevor etwas gebaut wurde:

    Raum 3  kanal  Chat-Moderation   admin, hand, SCOUT, SCOUT, linke
    Raum 7  kanal  Der Treff         admin, hand, 4x modi, linke, 3x gast
    Raum 8  gruppe Dogi und Modis    admin, hand, 4x modi, linke
    Raum 9  gruppe Abmeldungen       admin, hand, 4x modi, linke

Damit waren alle drei Punkte EIN Befund. Die Erwaehnen-Liste im
Browser zeigt genau `offen.teilnehmer`, also die Teilnehmer des Raums
(teilnehmerVon) -- eine zweite Quelle gibt es nicht. Standen dort zwei
Scouts und kein Modi, dann schlug sie Scouts vor, und die Modis sahen
den Kanal gar nicht. An der Erwaehnung selbst war nichts kaputt.

DIE REGEL

Ein Kategorie-Kanal gehoert Team Dogi: admin + TEAM_DOGI_ROLLEN
(hand, linke, modi), abgeleitet aus der einen Liste des Hauses. Der
Abgleich beim Blick in die Gespraechsliste heilt jetzt in BEIDE
Richtungen:

  hinein  jeder aktive Mensch aus Team Dogi, der im Raum NOCH NIE eine
          Zeile hatte
  hinaus  jeder aktive Teilnehmer, dessen Rolle nicht dazugehoert
          (raus_am, kein DELETE -- der Verlauf bleibt lesbar)

"NOCH NIE EINE ZEILE" ist der wichtige Teil: Wer ueber die
Mitglieder-Route bewusst herausgenommen wurde, hat eine Zeile mit
raus_am und wird NICHT zurueckgeholt. Ein Abgleich, der eine
Entscheidung von Hand beim naechsten Seitenaufruf ueberschreibt,
macht die Route wertlos -- dieselbe Ueberlegung wie bei
treffAngleichen(). Der Treff bleibt ausgenommen: Dort gehoert die
Community dazu.

Dazu dieselbe Schranke an beiden Schreibwegen (Kanal anlegen und
umbesetzen). `darfSchreibenMit` beantwortet eine ANDERE Frage -- "darf
ich diesen Menschen ueberhaupt anschreiben" -- und sagt bei einem
Scout zu Recht ja. Genau diese Luecke hat die zwei Scouts in den
Moderations-Kanal gebracht.

WARUM NICHT "NUR DIE ZUSTAENDIGEN"

Weil es die Zuordnung nicht gibt: `kategorienFuer` gibt JEDEM aus Team
Dogi ALLE Kategorien, eine Tabelle Person -> Kategorie existiert
nirgends. Eine Regel "nur die Zustaendigen" waere eine Liste, die
niemand pflegt. Wer einen engeren Kanal will, nimmt jemanden heraus --
und das haelt.

PRUEFUNGEN

  pruef-kanal-besetzung.mjs (NEU, 16/0): baut die gemeldete Lage nach
  (Scout drin, kein Modi), laesst den Abgleich darueberlaufen und
  misst beide Richtungen. Vier Gegenproben: der von Hand Entfernte
  bleibt draussen; ein Scout kommt auch ueber die Mitglieder-Route
  nicht hinein; der Gast bleibt im Treff; und ein wieder
  hereingeschmuggelter Scout fliegt beim naechsten Abgleich erneut
  hinaus -- die Regel wirkt dauerhaft, nicht einmalig.

  pruef-chat-kanaele.mjs (79 mit 3 Fehlern -> 81/0): Die Zeile "wer
  nicht drin ist, sieht ihn nicht" hat den alten Zuschnitt
  festgeschrieben -- sie prueft jetzt die schaerfere Grenze: wer
  HERAUSGENOMMEN wurde, bleibt draussen, auch nach dem naechsten
  Abgleich. Dieser Weg war bis heute ungeprueft.

Mitgelaufen und gruen: pruef-chat, pruef-treffchat (110/0).

Vor dem Ausliefern gesichert: workspace-vor-kanalregel-20260922-231409.db
(946 KB, integrity_check ok, 17 Personen, 37 Teilnehmerzeilen,
111 Nachrichten).

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-22 23:14:38 +02:00
co-authored by Claude Opus 5
parent 803750edbc
commit 95da1345fc
3 changed files with 473 additions and 16 deletions
+44 -4
View File
@@ -313,12 +313,48 @@ console.log("\n=== Wer sieht ihn ===");
const beiEins = await raeumeVon(eins.keks);
ok(beiEins.some((r) => r.id === kanalId), "wer hineingesetzt wurde, sieht ihn auch");
/* GEGENPROBE: Der dritte ist NICHT drin. Er darf ihn weder in der
Liste haben noch ueber die Nummer erreichen. */
/* =================================================================
GEGENPROBE: WER DRAUSSEN IST, BLEIBT DRAUSSEN
=================================================================
HIER STAND ETWAS ANDERES (bis 22.09.2026 abends): "Der dritte ist
NICHT drin, weil ihn niemand eingetragen hat."
Das galt, solange ein Kategorie-Kanal nur die enthielt, die man
von Hand hineingesetzt hatte. Genau daran ist VanVan haengen
geblieben: Im Moderations-Kanal standen zwei SCOUTS und kein
einziger Modi -- die Modis sahen den Kanal gar nicht und konnten
dort niemanden erwaehnen.
Filipe: „Die Modis zum Erwaehnen hinzufuegen." /
„Die Modis sehen den Chat Moderation auch gar nicht."
Seither gilt: Ein Kategorie-Kanal gehoert dem ganzen Team Dogi.
Es gibt auch keine Handhabe fuer etwas anderes -- `kategorienFuer`
gibt JEDEM aus Team Dogi ALLE Kategorien; eine Zuordnung Person ->
Kategorie existiert nirgends. Eine Regel "nur die Zustaendigen"
waere also eine Liste, die niemand pflegt.
DIE GRENZE GIBT ES WEITERHIN, sie faengt nur an einer anderen
Stelle an: Wer ueber die Mitglieder-Route HERAUSGENOMMEN wurde,
bleibt draussen -- auch ueber die Nummer. Das ist die schaerfere
Messung: Vorher wurde jemand geprueft, den nie jemand eingetragen
hat; jetzt einer, der drin WAR. Der Weg dorthin (raus_am, und der
Abgleich holt ihn nicht zurueck) war bis heute ungeprueft. */
const raus = await rufe(dogi.keks, `/workspace/api/chat/kanal/${kanalId}/mitglieder`,
{ mit: [idEins, idZwei] }, "PUT");
ok(raus.status === 200, `der dritte wird herausgenommen (${raus.status})`);
const beiDrei = await raeumeVon(drei.keks);
ok(!beiDrei.some((r) => r.id === kanalId), "wer nicht drin ist, sieht ihn nicht");
ok(!beiDrei.some((r) => r.id === kanalId), "wer herausgenommen wurde, sieht ihn nicht mehr");
const versuch = await rufe(drei.keks, `/workspace/api/chat/raeume/${kanalId}/nachrichten`);
ok(versuch.status === 404, `und kommt auch ueber die Nummer nicht hinein (${versuch.status})`);
/* UND DER ABGLEICH HOLT IHN NICHT ZURUECK. Ohne diese Zeile waere
die Sperre oben nur ein Augenblick: Der naechste Blick in die
Gespraechsliste stellt sie wieder her -- oder eben nicht. */
await raeumeVon(dogi.keks);
const nochDraussen = await raeumeVon(drei.keks);
ok(!nochDraussen.some((r) => r.id === kanalId),
"und der naechste Abgleich holt ihn nicht zurueck");
const wahl = await holen(dogi.keks, "/workspace/api/chat/raeume");
ok(wahl.darf_kanal === true, "DogFather bekommt das Ja zum Anlegen");
@@ -378,11 +414,15 @@ console.log("\n=== Und was NICHT offensteht ===");
======================================================================= */
console.log("\n=== Wer ist im Kanal ===");
{
/* Vorher steht der dritte draussen (Abschnitt 2 hat ihn
herausgenommen) und der zweite drin. Diese Runde dreht genau das
um: der dritte kommt zurueck, der zweite geht. Die Zahlen sind
damit nicht geraten, sondern die Folge des Standes davor. */
const a = await rufe(dogi.keks, `/workspace/api/chat/kanal/${kanalId}/mitglieder`,
{ mit: [idEins, idDrei] }, "PUT");
const antwort = await a.json();
ok(a.status === 200 && antwort.dazu === 1 && antwort.raus === 1,
`einer kommt dazu, einer geht (${antwort.dazu}/${antwort.raus})`);
`der Herausgenommene kommt zurueck, ein anderer geht (${antwort.dazu}/${antwort.raus})`);
const beiDrei = (await holen(drei.keks, "/workspace/api/chat/raeume")).raeume || [];
ok(beiDrei.some((r) => r.id === kanalId), "der Neue sieht den Kanal jetzt");