Zwoelf rote Pruefungen -- und kein einziger Fehler im Programm

Der Rundumcheck geht weiter. Diese zwoelf standen im Nachtlauf als rot
und waren es nicht: Jede hat den Umbau vom 24.09. (die Haustrennung),
den vom 25.09. (die zugetragenen Punkte) oder eine Umbenennung
mitbekommen -- nur ihre Erwartung nicht.

DAS IST KEIN TROST. Ein Fehlalarm zeigt auf eine echte Stelle und nennt
eine plausible Zahl; man sucht danach an richtigem Code. Und zwoelf rote
Zeilen, die immer rot sind, nehmen dem ganzen Satz die Stimme.

=== DIESELBE WURZEL, SECHSMAL: DAS FALSCHE HAUS ===

Seit dem 24.09. antwortet der Server auf der Agenturadresse nicht mehr
ueber Team-Dogi-Leute -- und genau das taten diese Pruefungen:

  pruef-nachwuchs      legte einen Modi ueber die Agenturadresse an
                       (400). Einundzwanzig Meldungen hingen an diesem
                       einen Aufruf. 82 Zeilen auf crew. umgestellt,
                       aus „Mara, Managerin" wurde die linke Hand --
                       im Teamhaus ist sie das, was gemeint war.
                       230 -> 262 Pruefungen, 0 Fehler.
  pruef-schritt        dasselbe beim Aufgabenbrett (404/400).
  pruef-treff-werkzeuge fragte die Einladungsliste eines Modis auf der
                       Agenturadresse ab -- dort ist er nicht.
  pruef-rechte-umstellen benutzte `wissen.html` als zweite Probeseite.
                       Die ist laut Rechtetafel fuer alle offen und auf
                       crew. trotzdem zu: Dort kommt man nur auf Seiten,
                       fuer die es eine Kachel gibt. Jetzt material.html.
  pruef-personen-formular verglich eine Erwartung FUER die Agentur mit
                       einer Messung OHNE Adresse (127.0.0.1) -- vier
                       gegen acht Rollen. Beides war richtig, nur nicht
                       dasselbe.
  pruef-treff          suchte „Support" zwischen den Brettern. Er steht
                       in „Fuer dich", wo er hingehoert -- eine kaputte
                       Seite zu melden ist kein Aushang. Die Liste stand
                       ZWEIMAL in der Datei; nachgezogen wurde eine.
                       Jetzt eine, von beiden benutzt.

=== ZWEIMAL: EINE FESTE ZAHL, DIE DIE SEITE UEBERHOLT HAT ===

  pruef-community-sicht erlaubte „3 bis 8 Seiten". Es sind zwoelf, und
                       jede gehoert dorthin. Statt einer Spanne steht
                       jetzt die LISTE da: Kommt eine dazu, wird die
                       Zeile rot und nennt sie beim Namen. Eine Spanne
                       haette elf statt zwoelf stillschweigend
                       durchgelassen -- in beide Richtungen.
  pruef-nachwuchs      schaltete EINE Person ab und erwartete, dass die
                       Ampel danach schweigt. Der Kommentar darueber
                       sagt selbst: „Die Schwelle wird nicht
                       abgeschrieben, sondern aus dem Verhalten
                       abgeleitet" -- eine feste Anzahl abzuschalten ist
                       aber eine Abschrift in anderer Waehrung. Jetzt
                       wird abgeschaltet, BIS es kippt.

=== UND VIERMAL EIN WERKZEUG, DAS STUMPF WAR ===

  pruef-scout-zuteilung suchte `.person__zeile` -- eine Klasse, die es
                       nicht mehr gibt. `querySelectorAll` liefert dafuer
                       ein leeres Feld, `.some()` darauf ist immer
                       false: Die Pruefung war nicht rot, weil etwas
                       fehlte, sondern weil sie nichts ansehen konnte.
                       Jetzt `.person`, und die ANZAHL der gefundenen
                       Karten steht in einer eigenen Bedingung.
  pruef-loeschen       suchte `::before` in einem Fenster von 600
                       Zeichen ab dem Selektor. Das misst die Laenge des
                       KOMMENTARS: Am 25.09. kam eine Begruendung von
                       zwanzig Zeilen dazu, und die Regel rutschte
                       hinaus. Jetzt wird nach dem Selektor gesucht.
  pruef-portnummern    hielt jedes `listen(` fuer einen Serverstart --
                       auch das blosse Nachsehen, ob eine Nummer frei
                       ist. Damit mahnte sie ausgerechnet die Datei an,
                       die das Vergeben der Nummern prueft. Jetzt zaehlt
                       der Import von `index.js`; dazu vier Gegenproben,
                       damit die engere Fassung nicht zum blinden Fleck
                       wird.
  pruef-spicy          stellte eine Person auf „manager" und dann
                       weiter. An Managern aendert seit dem 22.09. nur
                       DogFather etwas -- die Pruefung hatte sich selbst
                       die Tuer zugezogen. Jetzt kommt „manager" zuletzt,
                       und dass danach nichts mehr geht, ist eine eigene
                       Zeile. Aus dem Stolperstein wird eine Aussage.

=== EINER WAR FAST EIN BEFUND ===

  pruef-alle-sehen-es  meldete „highlight: DogFather 0, Community 0".
                       Das Brett hat als einziges keinen Katalog (dort
                       stehen echte Hoehepunkte, keine Vorlagen), und an
                       einem leeren Brett ist „sehen beide dasselbe?"
                       nicht zu messen: null gleich null waere auch dann
                       wahr, wenn die Community gar nichts duerfte.
                       Die Pruefung legt dort jetzt selbst einen Eintrag
                       an -- und musste dabei zweimal lernen: Der Eintrag
                       gehoert VOR den Katalog (danach ist die
                       Schreibbremse ausgeloest, 429), und er muss
                       FREIGEGEBEN werden, sonst sieht die Community ihn
                       zu Recht nicht. Beides steht jetzt als Aussage in
                       der Pruefung.

GEMESSEN: alle zwoelf gruen -- 43, 10, 31, 262, 43, 15, 56, 69, 37, 85,
73 und 80 Pruefungen, zusammen 804, kein Fehler.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-25 22:54:21 +02:00
co-authored by Claude Opus 5
parent 3b73e1f42e
commit 1ba9d07af2
13 changed files with 545 additions and 124 deletions
+2
View File
@@ -125,3 +125,5 @@ tools/.nachtlauf-*
tools/.sammellauf*
tools/.nachpruef*
tools/.handy-detail.txt
tools/.rest*
+77 -1
View File
@@ -128,12 +128,72 @@ async function liste(bereich, rolle) {
return { code: a.code, n: (j?.eintraege || []).length };
}
/* Der Katalog wird schon fuer Abschnitt 0 gebraucht -- er steht
deshalb vor ihm und nicht mehr in Abschnitt 1. */
const KATALOG_VORAB = { ...ARBEIT_START, ...TREFF_START };
const KATALOG = KATALOG_VORAB;
/* =======================================================================
0. EIN BRETT OHNE KATALOG BEKOMMT SEINEN EINTRAG -- UND ZWAR ZUERST
=======================================================================
„highlight" hat als einziges Treff-Brett keinen Katalog: Dort stehen
echte Hoehepunkte, keine Vorlagen. Abschnitt 2 misst aber, ob die
Community DASSELBE sieht wie DogFather -- und an einem leeren Brett
ist das nicht zu messen (null gleich null waere auch dann wahr, wenn
die Community gar nichts duerfte).
WARUM GANZ AM ANFANG: Es gibt eine Schreibbremse -- rund fuenfzig
Beitraege je Stunde. Der Katalog unten legt 110 an; danach ist sie
ausgeloest, und der Versuch, hier noch etwas zu schreiben, bekommt
429. Genau so ist es im ersten Anlauf passiert. Die Bremse ist
richtig und bleibt; die Pruefung stellt sich davor statt dahinter.
UEBER DIE ECHTE ROUTE, nicht ueber die Datenbank: So ist der Weg,
den auch ein Mensch nimmt, gleich mitgeprueft.
======================================================================= */
{
/* NUR DIE BRETTER OHNE KATALOG. Der erste Anlauf fragte „ist es
leer?" -- und vor dem Katalog sind ALLE leer. Er legte damit
ueberall einen Eintrag an, und bei „ansteht" stimmte danach die
Zahl nicht mehr (5 gegen 4). Gefragt wird deshalb, was wirklich
gemeint ist: Gibt es fuer dieses Brett eine Vorlage? */
const ohneKatalog = TREFF_BRETTER.filter((b) => !KATALOG_VORAB[b]);
ok(ohneKatalog.length >= 1,
`${ohneKatalog.length} Treff-Brett(er) ohne Katalog: ${ohneKatalog.join(", ") || "keines"}`);
for (const b of ohneKatalog) {
const art = Object.keys(BEREICHE[b]?.arten || {})[0];
const neu = await roh(`/workspace/api/bereich/${b}`,
{ cookie: kekse.admin, origin: `https://${CREW}` },
{ art, titel: "Damit dieses Brett etwas zeigt",
text: "Ein Eintrag fuer die Messung -- dieses Brett hat keine Vorlagen." });
ok(neu.code === 201, `${b}: bekommt einen Eintrag (${neu.code})`);
/* UND ER MUSS FREIGEGEBEN WERDEN. Auf den Freigabe-Brettern sieht
die Community nur, was jemand aus dem Team durchgelassen hat --
beim Katalog passiert das automatisch (workspace-bereiche.js),
beim gewoehnlichen Schreiben nicht. Ohne diesen Schritt maesse
Abschnitt 2 „DogFather 1, Community 0" und haette recht: Der
Eintrag ist wirklich nicht frei. */
if (TREFF_FREIGABE_BRETTER.includes(b)) {
let id = null;
try { id = JSON.parse(neu.text)?.eintrag?.id ?? JSON.parse(neu.text)?.id; }
catch { /* steht unten als Fehler */ }
const frei = id
? await roh(`/workspace/api/treff/eintrag/${id}/freigeben`,
{ cookie: kekse.admin, origin: `https://${CREW}` }, {}, "PUT")
: { code: 0 };
ok(frei.code === 200, ` und wird freigegeben (${frei.code})`);
}
}
}
/* =======================================================================
1. DOGFATHER FÜLLT ALLES
======================================================================= */
melde("");
melde("=== 1. Der Vorschlagskasten ===");
const KATALOG = { ...ARBEIT_START, ...TREFF_START };
const gefuellt = [];
{
let summe = 0;
@@ -160,6 +220,22 @@ const gefuellt = [];
melde("");
melde("=== 2. Was die Community sieht ===");
{
/* ==== EIN LEERES BRETT BEWEIST NICHTS (25.09.2026) ============
„highlight" hat als einziges Treff-Brett keinen Katalog -- dort
stehen echte Hoehepunkte und keine Vorlagen, und das ist richtig
so. Die Folge war aber: null Eintraege bei DogFather, null bei
der Community, und die Bedingung `a.n > 0` wurde rot. Die
Pruefung meldete einen Fehler, wo schlicht nichts zu sehen war.
GEMESSEN WERDEN SOLL DIE GLEICHHEIT, nicht der Katalog. Also legt
die Pruefung dort selbst einen Eintrag an -- ueber die echte
Route, mit der auch DogFather schreibt. Damit hat jedes Brett
etwas, und die Frage „sieht die Community dasselbe?" laesst sich
ueberall stellen.
KEIN TESTDATENSATZ IM SINNE DES VERBOTS: Diese Pruefung laeuft
auf ihrer eigenen Wegwerf-Datenbank in einem mktemp-Ordner. */
for (const b of TREFF_BRETTER) {
const a = await liste(b, "admin");
const g = await liste(b, "gast");
+37 -2
View File
@@ -79,8 +79,43 @@ const verboten = alleSeiten.filter((d) => !darfSeite(GAST, `/workspace/${d}`));
melde("");
melde("=== 1. Was ueberhaupt offensteht ===");
ok(erlaubt.length >= 3 && erlaubt.length <= 8,
`ein Mitglied darf ${erlaubt.length} Seiten: ${erlaubt.join(", ")}`);
/* ==== EINE LISTE STATT EINER SPANNE (25.09.2026) ===================
Hier stand `>= 3 && <= 8`. Eine Spanne sagt nichts darueber, WELCHE
Seiten offen sind -- und genau das ist die Frage. Sie war ausserdem
seit dem 24.09. rot: Mit dem Support sind es zwoelf geworden, und
jede einzelne davon gehoert dorthin (nachgesehen, nicht vermutet --
dieselben zwoelf stehen auf der Kachelwand eines Mitglieds).
AB JETZT WIRD DIE LISTE FESTGEHALTEN. Kommt eine Seite dazu, wird
diese Zeile rot und nennt sie beim Namen; wer sie hinzugefuegt hat,
traegt sie hier nach und hat damit BESTAETIGT, dass die Community
sie sehen soll. Verschwindet eine, faellt es genauso auf. Eine
Spanne kann beides nicht: Sie haette elf statt zwoelf stillschweigend
durchgehen lassen -- und zwar in beide Richtungen.
DAS IST DIESELBE BAUART wie die festgelegten Kachelfarben: nicht
„ungefaehr so viele", sondern „genau diese, und hier steht warum". */
const COMMUNITY_SEITEN = [
"bereich.html", // die Bretter (Highlights, Anschlag, Wunschliste ...)
"bewerben.html", // Mitmachen
"chat.html", // der Treff
"hilfe.html", // Regeln & Hilfe
"material.html", // Dogi-Media
"start.html", // die Wand
"steckbrief.html", // der eigene Steckbrief
"support.html", // etwas ist kaputt (seit 24.09. fuer alle)
"treff-regeln.html", // die Regeln des Rudels
"unsere-seiten.html", // Draussen
"unterstuetzen.html", // Unterstuetzen
"willkommen.html", // die Begruessung
];
const zuViel = erlaubt.filter((s) => !COMMUNITY_SEITEN.includes(s));
const fehltDa = COMMUNITY_SEITEN.filter((s) => !erlaubt.includes(s));
ok(zuViel.length === 0 && fehltDa.length === 0,
`ein Mitglied darf genau die ${COMMUNITY_SEITEN.length} vorgesehenen Seiten`
+ (zuViel.length ? ` -- ZU VIEL: ${zuViel.join(", ")}` : "")
+ (fehltDa.length ? ` -- ES FEHLT: ${fehltDa.join(", ")}` : ""));
ok(verboten.length >= 15,
`und ${verboten.length} nicht -- so viele Wege gibt es, ins Leere zu zeigen`);
+12 -3
View File
@@ -349,9 +349,18 @@ console.log("--- Der Loeschknopf in der Stilvorlage ---");
ok(!!notfall && !notfall.includes("color:"),
"sie setzt KEINE eigene Schriftfarbe -- die Markierung sitzt im Punkt"
+ " davor, so wie beim Namen in der Blase");
ok(!!notfall && /::before/.test(
chatCss.slice(chatCss.indexOf('.chat-nachricht__weg-knopf[data-notfall="ja"]'),
chatCss.indexOf('.chat-nachricht__weg-knopf[data-notfall="ja"]') + 600)),
/* ==== NACH DEM SELEKTOR SUCHEN, NICHT IN 600 ZEICHEN (25.09.2026)
Hier wurde ab der Fundstelle ein Fenster von 600 Zeichen
abgesucht. Das misst in Wahrheit, wie lang der KOMMENTAR
dazwischen ist: Am 25.09. kam eine Begruendung von zwanzig Zeilen
dazu (aus dem Punkt wurde ein Papierkorb), und das `::before`
rutschte aus dem Fenster. Die Pruefung meldete „der Punkt ist
nicht da" fuer etwas, das zweiundzwanzig Zeilen tiefer steht.
Gesucht wird jetzt nach dem SELEKTOR selbst. Der ist da oder
nicht; wie viel Text davor steht, geht die Frage nichts an. */
ok(chatCss.includes('.chat-nachricht__weg-knopf[data-notfall="ja"]::before'),
"der Punkt ist wirklich da (::before)");
/* GEGENPROBE: Die Suche muss eine Schriftfarbe auch FINDEN koennen.
+174 -94
View File
@@ -58,7 +58,18 @@ let offen = 0;
const unklar = (t) => { offen++; console.log(" -- " + t); };
const CREW = "crew.dogfather-universe.com";
const HAUS = "workspace.dogfather-universe.com";
/* ==== DIESE PRUEFUNG WOHNT BEI TEAM DOGI (25.09.2026) ==============
Nachwuchs, Entwicklung, Talente, die 68 Punkte -- alles davon
gehoert ins Teamhaus. Gefragt wurde trotzdem ueber die
AGENTURadresse, und seit der Trennung vom 24.09. antwortet der
Server dort richtigerweise mit 400: Einen Modi legt man dort
nicht an. Einundzwanzig Meldungen hingen an diesem einen
Aufruf -- alle Folge, keine Ursache.
Die Agenturadresse bleibt als Name stehen; gebraucht wird sie
nur noch dort, wo ausdruecklich das andere Haus gemeint ist. */
const AGENTUR = "workspace.dogfather-universe.com";
const { DatabaseSync } = await import("node:sqlite");
const { scryptSync, randomBytes, createHmac } = await import("node:crypto");
@@ -84,7 +95,12 @@ const idDogi = anlegen("Filipe", "admin", "CODE-DOGI-0001");
const idHand = anlegen("Rieke", "hand", "CODE-HAND-0001");
const idModi = anlegen("Frida", "modi", "CODE-MODI-0001");
anlegen("Nina", "modi", "CODE-MODI-0002");
anlegen("Mara", "manager", "CODE-MANA-0001");
/* MARA IST DIE LINKE HAND UND KEIN MANAGER (25.09.2026).
Diese Pruefung misst das Teamhaus; ein Manager taucht dort seit
der Haustrennung nicht mehr auf und konnte deshalb auch nichts
mehr beweisen. Als linke Hand steht sie da, wo sie gebraucht
wird: in der Rangfolge unter der rechten Hand. */
anlegen("Mara", "linke", "CODE-LINK-0001");
/* Ein Kandidat im Talente-Brett -- ohne ihn waere jede Zeile ueber den
Trichter ein gruener Haken ueber der Null. */
@@ -116,7 +132,7 @@ async function anmelden(rolle, code, host) {
const r = await roh("/workspace/api/anmelden", host, {}, { rolle, code });
return [].concat(r.kopf?.["set-cookie"] || []).map((c) => c.split(";")[0]).join("; ");
}
const kDogi = await anmelden("admin", "CODE-DOGI-0001", HAUS);
const kDogi = await anmelden("admin", "CODE-DOGI-0001", CREW);
const kHand = await anmelden("hand", "CODE-HAND-0001", CREW);
const kModi = await anmelden("modi", "CODE-MODI-0001", CREW);
ok(!!kDogi && !!kHand && !!kModi, "DogFather, rechte Hand und ein Modi sind angemeldet");
@@ -176,7 +192,7 @@ melde("=== 2. Block 5 ===");
{
const selbst = kat.ENTWICKLUNG_SELBST[0].schluessel;
const karte = await roh(`/workspace/api/entwicklung/person/${idModi}`, HAUS, { cookie: kDogi });
const karte = await roh(`/workspace/api/entwicklung/person/${idModi}`, CREW, { cookie: kDogi });
const bloecke = json(karte).bloecke || [];
const alleSchluessel = bloecke.flatMap((b) => (b.punkte || []).map((p) => p.schluessel));
ok(karte.code === 200 && alleSchluessel.length > 0,
@@ -188,9 +204,9 @@ melde("=== 2. Block 5 ===");
/* Und schreiben geht auch nicht -- mit derselben Antwort wie ein
erfundener Schluessel, damit man an der Antwort nichts ablesen kann. */
const schreib = await roh(`/workspace/api/entwicklung/person/${idModi}/${selbst}`, HAUS,
const schreib = await roh(`/workspace/api/entwicklung/person/${idModi}/${selbst}`, CREW,
{ cookie: kDogi }, { stufe: "laeuft" }, "PUT");
const erfunden = await roh(`/workspace/api/entwicklung/person/${idModi}/e-gibt-es-nicht`, HAUS,
const erfunden = await roh(`/workspace/api/entwicklung/person/${idModi}/e-gibt-es-nicht`, CREW,
{ cookie: kDogi }, { stufe: "laeuft" }, "PUT");
ok(schreib.code === 404, `DogFather kann dort auch nichts setzen (${schreib.code})`);
ok(schreib.code === erfunden.code,
@@ -209,7 +225,7 @@ melde("=== 2. Block 5 ===");
ok(setzen.code === 200, `und kann sie beantworten (${setzen.code})`);
/* Und danach steht sie immer noch nicht in DogFathers Karte. */
const nachher = await roh(`/workspace/api/entwicklung/person/${idModi}`, HAUS, { cookie: kDogi });
const nachher = await roh(`/workspace/api/entwicklung/person/${idModi}`, CREW, { cookie: kDogi });
ok(!nachher.text.includes(selbst),
"auch nach der Antwort steht sie nicht in DogFathers Karte");
@@ -238,7 +254,7 @@ melde("=== 3. Die Ampel ===");
Die Schwelle wird nicht abgeschrieben, sondern aus dem Verhalten
abgeleitet: Erst mit vollem Team messen, dann eine Person
abschalten und noch einmal. Was dazwischen kippt, IST die Grenze. */
const voll = json(await roh("/workspace/api/entwicklung/ampel", HAUS, { cookie: kDogi }));
const voll = json(await roh("/workspace/api/entwicklung/ampel", CREW, { cookie: kDogi }));
ok(voll.team >= 3 && voll.sagbar === true,
`bei ${voll.team} Personen sagt die Ampel etwas (${voll.lage})`);
ok(typeof voll.geantwortet === "number" && voll.geantwortet >= 1,
@@ -250,14 +266,39 @@ melde("=== 3. Die Ampel ===");
ok(!/Frida|Nina|Rieke|Filipe/.test(JSON.stringify(voll)),
"und in der ganzen Antwort steht kein einziger Name");
/* Jetzt eine Person abschalten -- das Team faellt unter die Schwelle. */
const dd = new DatabaseSync(process.env.WORKSPACE_DB);
dd.prepare("UPDATE personen SET aktiv = 0 WHERE name = 'Nina'").run();
dd.close();
/* ==== DIE SCHWELLE WIRD GESUCHT, NICHT ANGENOMMEN (25.09.2026) ==
const klein = json(await roh("/workspace/api/entwicklung/ampel", HAUS, { cookie: kDogi }));
ok(klein.team === voll.team - 1 && klein.sagbar === false,
`bei ${klein.team} Personen schweigt sie`);
Hier wurde EINE Person abgeschaltet und erwartet, dass die Ampel
danach schweigt. Das stimmte, solange das Team genau so gross
war, wie es an dem Tag war -- und ging kaputt, als eine Rolle
dazukam (aus dem Manager im anderen Haus wurde die linke Hand in
diesem). Die Pruefung meldete „bei 3 Personen schweigt sie" und
meinte: sie tut es nicht.
Der Kommentar darueber sagt es selbst: „Die Schwelle wird nicht
abgeschrieben, sondern aus dem Verhalten abgeleitet." Eine feste
Anzahl abzuschalten IST aber eine Abschrift -- nur in einer
anderen Waehrung. Jetzt wird abgeschaltet, BIS es kippt, und
hoechstens so oft, wie es Leute gibt.
WAS DAMIT GEMESSEN WIRD, bleibt dasselbe und wird sogar
schaerfer: dass es ueberhaupt kippt (eine Ampel, die immer redet,
waere ein Bruch des Versprechens an die Leute), dass sie dabei
einen Grund nennt, und dass in keiner der beiden Antworten ein
Name steht. */
const namen = ["Nina", "Mara", "Frida", "Rieke"];
let klein = voll, abgeschaltet = 0;
for (const wen of namen) {
const dd = new DatabaseSync(process.env.WORKSPACE_DB);
dd.prepare("UPDATE personen SET aktiv = 0 WHERE name = ?").run(wen);
dd.close();
abgeschaltet++;
klein = json(await roh("/workspace/api/entwicklung/ampel", CREW, { cookie: kDogi }));
if (klein.sagbar === false) break;
}
ok(klein.sagbar === false && klein.team < voll.team,
`nach ${abgeschaltet} Abschaltung(en) schweigt sie `
+ `(${voll.team} -> ${klein.team} Personen)`);
ok(/Name/.test(klein.grund || ""),
"und sagt, warum – ein stilles Nichts sieht aus wie ein Fehler");
ok(!/Frida|Nina|Rieke|Filipe/.test(JSON.stringify(klein)),
@@ -283,7 +324,7 @@ melde("=== 4. Zwei Augenpaare ===");
{ cookie: kHand }, { stufe: "laeuft", anlass: "Gestern im Chat gesehen." }, "PUT");
ok(gesetzt.code === 200, `die rechte Hand setzt einen Punkt (${gesetzt.code})`);
const vorher = await roh(`/workspace/api/entwicklung/person/${idModi}`, HAUS, { cookie: kDogi });
const vorher = await roh(`/workspace/api/entwicklung/person/${idModi}`, CREW, { cookie: kDogi });
const p1 = (json(vorher).bloecke || []).flatMap((b) => b.punkte || [])
.find((p) => p.schluessel === punkt);
ok(p1 && p1.andere === null,
@@ -293,12 +334,12 @@ melde("=== 4. Zwei Augenpaare ===");
ok(!vorher.text.includes("Gestern im Chat gesehen"),
"und ihr Anlasstext wird gar nicht erst ausgeliefert");
const eigen = await roh(`/workspace/api/entwicklung/person/${idModi}/${punkt}`, HAUS,
const eigen = await roh(`/workspace/api/entwicklung/person/${idModi}/${punkt}`, CREW,
{ cookie: kDogi }, { stufe: "waechst" }, "PUT");
ok(eigen.code === 200, `DogFather setzt selbst (${eigen.code})`);
/* GEGENPROBE: jetzt kommt er. */
const nachher = await roh(`/workspace/api/entwicklung/person/${idModi}`, HAUS, { cookie: kDogi });
const nachher = await roh(`/workspace/api/entwicklung/person/${idModi}`, CREW, { cookie: kDogi });
const p2 = (json(nachher).bloecke || []).flatMap((b) => b.punkte || [])
.find((p) => p.schluessel === punkt);
ok(p2 && p2.andere?.length === 1 && p2.andere[0].stufe === "laeuft",
@@ -319,20 +360,20 @@ melde("");
melde("=== 5. Kein Klick ohne Anlass ===");
{
const punkt = kat.ENTWICKLUNG_FREMD[1].schluessel;
const ohne = await roh(`/workspace/api/entwicklung/person/${idModi}/${punkt}`, HAUS,
const ohne = await roh(`/workspace/api/entwicklung/person/${idModi}/${punkt}`, CREW,
{ cookie: kDogi }, { stufe: "hakt", anlass: "" }, "PUT");
ok(ohne.code === 400, `„Da hakt es" ohne Anlass wird abgelehnt (${ohne.code})`);
ok(/gemerkt|Satz/.test(json(ohne).fehler || ""),
"und die Absage sagt, was fehlt, statt nur nein");
const mit = await roh(`/workspace/api/entwicklung/person/${idModi}/${punkt}`, HAUS,
const mit = await roh(`/workspace/api/entwicklung/person/${idModi}/${punkt}`, CREW,
{ cookie: kDogi }, { stufe: "hakt", anlass: "Am Samstag zweimal nicht reagiert." }, "PUT");
ok(mit.code === 200, `mit Anlass geht es durch (${mit.code})`);
/* GEGENPROBE: „Läuft" braucht keinen — sonst wären es 28 Pflichtfelder
je Person, und „so einfach wie möglich" war die halbe Aufgabe. */
const laeuft = await roh(`/workspace/api/entwicklung/person/${idModi}/${kat.ENTWICKLUNG_FREMD[2].schluessel}`,
HAUS, { cookie: kDogi }, { stufe: "laeuft" }, "PUT");
CREW, { cookie: kDogi }, { stufe: "laeuft" }, "PUT");
ok(laeuft.code === 200, `„Läuft" geht ohne Anlass (${laeuft.code})`);
}
@@ -351,7 +392,7 @@ melde("=== 6. Verblassen ===");
new Date(Date.now() - (modul.VERBLASST_NACH_TAGEN + 10) * 86400000).toISOString());
dd.close();
const r = await roh(`/workspace/api/entwicklung/person/${idModi}`, HAUS, { cookie: kDogi });
const r = await roh(`/workspace/api/entwicklung/person/${idModi}`, CREW, { cookie: kDogi });
const punkte = (json(r).bloecke || []).flatMap((b) => b.punkte || []);
const alterPunkt = punkte.find((p) => p.schluessel === alt);
ok(alterPunkt?.meins?.verblasst === true,
@@ -363,7 +404,7 @@ melde("=== 6. Verblassen ===");
ok(frisch?.meins?.verblasst === false,
"einer von heute nicht");
const lage = json(await roh("/workspace/api/entwicklung/lage", HAUS, { cookie: kDogi }));
const lage = json(await roh("/workspace/api/entwicklung/lage", CREW, { cookie: kDogi }));
const zeile = (lage.personen || []).find((p) => p.id === idModi);
ok(zeile?.veraltet === 1, `und die Übersicht zählt ihn (${zeile?.veraltet})`);
ok(zeile?.abweichungen === 1, `samt der Abweichung (${zeile?.abweichungen})`);
@@ -375,7 +416,7 @@ melde("=== 6. Verblassen ===");
melde("");
melde("=== 7. Talente ===");
{
const lage = await roh("/workspace/api/talente/lage", HAUS, { cookie: kDogi });
const lage = await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi });
const r = json(lage);
ok(lage.code === 200 && (r.kandidaten || []).length === 1,
`DogFather sieht den Trichter (${(r.kandidaten || []).length} Kandidat)`);
@@ -403,31 +444,31 @@ melde("=== 7. Talente ===");
/* Merkmale: ein Klick setzt, der zweite nimmt zurück. */
const m = tal.TALENT_ALLE[0].schluessel;
const w = tal.TALENT_WARNZEICHEN[0].schluessel;
const eins = await roh(`/workspace/api/talente/${idTalent}/merkmal/${m}`, HAUS,
const eins = await roh(`/workspace/api/talente/${idTalent}/merkmal/${m}`, CREW,
{ cookie: kDogi }, {}, "PUT");
ok(eins.code === 200 && json(eins).gesetzt === true, "ein Merkmal wird gesetzt");
const zwei = await roh(`/workspace/api/talente/${idTalent}/merkmal/${m}`, HAUS,
const zwei = await roh(`/workspace/api/talente/${idTalent}/merkmal/${m}`, CREW,
{ cookie: kDogi }, {}, "PUT");
ok(zwei.code === 200 && json(zwei).gesetzt === false, "und der zweite Klick nimmt es zurück");
await roh(`/workspace/api/talente/${idTalent}/merkmal/${m}`, HAUS, { cookie: kDogi }, {}, "PUT");
await roh(`/workspace/api/talente/${idTalent}/merkmal/${w}`, HAUS, { cookie: kDogi }, {}, "PUT");
await roh(`/workspace/api/talente/${idTalent}/merkmal/${m}`, CREW, { cookie: kDogi }, {}, "PUT");
await roh(`/workspace/api/talente/${idTalent}/merkmal/${w}`, CREW, { cookie: kDogi }, {}, "PUT");
const nach = json(await roh("/workspace/api/talente/lage", HAUS, { cookie: kDogi })).kandidaten[0];
const nach = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi })).kandidaten[0];
ok(nach.merkmale.length === 1 && nach.warnzeichen.length === 1,
`Merkmale und Warnzeichen stehen getrennt (${nach.merkmale.length} / ${nach.warnzeichen.length})`);
ok(!nach.merkmale.includes(w),
"ein Warnzeichen landet nicht zwischen den Merkmalen");
/* Die Stufe wandert -- und die Standzeit beginnt von vorn. */
const weiter = await roh(`/workspace/api/talente/${idTalent}/stufe`, HAUS,
const weiter = await roh(`/workspace/api/talente/${idTalent}/stufe`, CREW,
{ cookie: kDogi }, { stufe: "beobachtet" }, "PUT");
ok(weiter.code === 200, `ein Schritt weiter im Trichter (${weiter.code})`);
const nach2 = json(await roh("/workspace/api/talente/lage", HAUS, { cookie: kDogi })).kandidaten[0];
const nach2 = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi })).kandidaten[0];
ok(nach2.stufe === "beobachtet" && nach2.tage === 0,
`Stufe „${nach2.stufe_name}", Standzeit beginnt bei ${nach2.tage}`);
ok(nach2.liegt === false, "und liegt damit auch nicht");
const erfunden = await roh(`/workspace/api/talente/${idTalent}/stufe`, HAUS,
const erfunden = await roh(`/workspace/api/talente/${idTalent}/stufe`, CREW,
{ cookie: kDogi }, { stufe: "chef" }, "PUT");
ok(erfunden.code === 400, `eine erfundene Stufe wird abgelehnt (${erfunden.code})`);
}
@@ -444,7 +485,7 @@ melde("=== 8. Standzeit ===");
.run(new Date(Date.now() - (stufe.frist + 11) * 86400000).toISOString(), idTalent);
dd.close();
const r = json(await roh("/workspace/api/talente/lage", HAUS, { cookie: kDogi }));
const r = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi }));
const k = r.kandidaten[0];
ok(k.liegt === true && k.tage === stufe.frist + 11,
`nach ${k.tage} Tagen auf einer Stufe mit Frist ${stufe.frist} gilt die Karte als liegengeblieben`);
@@ -457,7 +498,7 @@ melde("=== 8. Standzeit ===");
dd2.prepare("UPDATE talent_stufe SET seit = ? WHERE eintrag_id = ?")
.run(new Date(Date.now() - (stufe.frist - 1) * 86400000).toISOString(), idTalent);
dd2.close();
const r2 = json(await roh("/workspace/api/talente/lage", HAUS, { cookie: kDogi }));
const r2 = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi }));
ok(r2.kandidaten[0].liegt === false,
`einen Tag vor der Frist noch nicht (${r2.kandidaten[0].tage} Tage)`);
}
@@ -500,7 +541,7 @@ melde("=== 9. Der Vorlagentext ===");
ok(/\b(fuer)/i.test("das ist fuer dich"), "und der Sucher findet so einen auch wirklich");
/* UND ER KOMMT UEBER DIE LEITUNG AN, nicht nur aus dem Modul. */
const lage = json(await roh("/workspace/api/talente/lage", HAUS, { cookie: kDogi }));
const lage = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi }));
const k = (lage.kandidaten || [])[0];
ok(typeof k?.ansprechtext === "string" && k.ansprechtext.length > 100,
"die Karte bringt ihren Text mit");
@@ -579,7 +620,7 @@ melde("=== 10. Die eigene Karte ===");
ok(ihre.hat_karte === true, `auch die rechte Hand hat eine eigene Karte (${JSON.stringify(ihre).slice(0, 90)})`);
ok(ihre.beurteiler === 1, `ueber sie urteilt genau einer (${ihre.beurteiler})`);
const chef = json(await roh("/workspace/api/entwicklung/meine-karte", HAUS, { cookie: kDogi }));
const chef = json(await roh("/workspace/api/entwicklung/meine-karte", CREW, { cookie: kDogi }));
ok(chef.hat_karte === false, "DogFather selbst hat keine -- ueber ihn urteilt hier niemand");
}
@@ -646,9 +687,33 @@ melde("=== 11. Probe → Zugang ===");
/* UND DAS PROTOKOLL BLEIBT BEI DOGFATHER. Dort stehen die
Einstellungsaenderungen des ganzen Hauses. */
/* ==== INS PROTOKOLL SIEHT SIE SEHR WOHL (25.09.2026) ===========
Hier stand „und ins Protokoll sieht sie nicht", geprueft mit
404/403. Das war richtig, bis Filipe es umgedreht hat: „sie soll
die gleichen rechte haben wie dogfather, das einzige was sie
nicht kann ist die dogfather rolle oder leute anfassen." Fuer das
Protokoll laesst dieser Satz keinen Rest -- und
workspace-personen.js oeffnet den Weg seither ausdruecklich
(`protokollWeg`, nur GET, ohne Nummer im Pfad).
ZUM ZWEITEN MAL DIESELBE STELLE. Am 23.09. wurde hier schon
einmal aus „darf nicht" ein „darf" -- damals beim Anlegen. Eine
Pruefung, die eine Erlaubnis festhaelt, veraltet genau dann, wenn
jemand die Erlaubnis erweitert; das ist kein Fehler der Pruefung,
sondern ihr Zweck. Gemeldet werden muss es trotzdem, sonst steht
sie rot herum und niemand liest sie mehr.
GEPRUEFT WIRD JETZT BEIDES: dass sie hineinsieht -- und dass es
wirklich das Protokoll ist und nicht eine leere Antwort, die
zufaellig 200 heisst. */
const handProtokoll = await roh("/workspace/api/verwaltung/protokoll", CREW, { cookie: kHand });
ok(handProtokoll.code === 404 || handProtokoll.code === 403,
`und ins Protokoll sieht sie nicht (${handProtokoll.code})`);
ok(handProtokoll.code === 200,
`und ins Protokoll sieht sie sehr wohl (${handProtokoll.code})`);
const handZeilen = json(handProtokoll).eintraege;
ok(Array.isArray(handZeilen),
` und bekommt wirklich Eintraege (${Array.isArray(handZeilen)
? handZeilen.length + " Stueck" : typeof handZeilen})`);
/* EIN MODI DARF WEITERHIN GAR NICHTS DAVON. Das ist die eigentliche
Aussage dieses Blocks -- und sie ist unveraendert. */
@@ -658,7 +723,7 @@ melde("=== 11. Probe → Zugang ===");
/* GEGENPROBE: DogFather kann es -- sonst waere "404" oben auch dann
gruen, wenn der Weg gar nicht existiert. */
const neu = await roh("/workspace/api/verwaltung/personen", HAUS, { cookie: kDogi },
const neu = await roh("/workspace/api/verwaltung/personen", CREW, { cookie: kDogi },
{ name: "Kessi", rolle: "modi" });
ok(neu.code === 201, `DogFather legt den Zugang an (${neu.code})`);
const person = json(neu);
@@ -668,16 +733,16 @@ melde("=== 11. Probe → Zugang ===");
/* UND DANACH IST ER WEG. Nirgends abrufbar -- auch nicht fuer den,
der ihn angelegt hat. */
const liste = await roh("/workspace/api/verwaltung/personen", HAUS, { cookie: kDogi });
const liste = await roh("/workspace/api/verwaltung/personen", CREW, { cookie: kDogi });
ok(liste.code === 200 && !liste.text.includes(person.code),
"danach steht er in keiner Liste mehr");
/* Erst jetzt wandert die Karte -- in derselben Reihenfolge wie in der
Oberflaeche: erst der Mensch, dann der Haken. */
const stufe = await roh(`/workspace/api/talente/${idTalent}/stufe`, HAUS,
const stufe = await roh(`/workspace/api/talente/${idTalent}/stufe`, CREW,
{ cookie: kDogi }, { stufe: "imteam" }, "PUT");
ok(stufe.code === 200, `und die Karte geht auf "Im Team" (${stufe.code})`);
const lage = json(await roh("/workspace/api/talente/lage", HAUS, { cookie: kDogi }));
const lage = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi }));
const k = (lage.kandidaten || []).find((x) => x.id === idTalent);
ok(k?.stufe === "imteam" && k?.knopf === null,
"dahinter kommt kein weiterer Knopf -- der Trichter ist zu Ende");
@@ -727,7 +792,7 @@ melde("=== 12. Die Reihenfolge ===");
"die Community steht nicht in der Rangfolge — sie faellt ans Ende");
/* ---- Die Liste aus dem Bildschirmfoto ---- */
const lage = json(await roh("/workspace/api/entwicklung/lage", HAUS, { cookie: kDogi }));
const lage = json(await roh("/workspace/api/entwicklung/lage", CREW, { cookie: kDogi }));
const namen = (lage.personen || []).map((p) => p.name);
ok(namen.length >= 3, `${namen.length} Menschen in „Wen gehst du durch?“ (${namen.join(", ")})`);
ok(namen[0] === "Rieke", `die rechte Hand steht ganz links (${namen[0]})`);
@@ -739,7 +804,7 @@ melde("=== 12. Die Reihenfolge ===");
`alphabetisch stuende dort ${alphabetisch[0]} — die Reihenfolge ist also keine Namensfolge`);
/* ---- Personen & Zugaenge: unter DogFather, ueber allen anderen ---- */
const liste = json(await roh("/workspace/api/verwaltung/personen", HAUS, { cookie: kDogi }));
const liste = json(await roh("/workspace/api/verwaltung/personen", CREW, { cookie: kDogi }));
const alle = (liste.personen || []).map((p) => ({ name: p.name, rolle: p.rolle }));
ok(alle.length >= 4, `${alle.length} Menschen in „Personen & Zugaenge“`);
const platz = (rolle) => alle.findIndex((p) => p.rolle === rolle);
@@ -751,8 +816,23 @@ melde("=== 12. Die Reihenfolge ===");
const darueber = alle.filter((p, i) => i < vonHand).map((p) => p.rolle);
ok(darueber.every((r) => r === "admin" || r === "spicy"),
`ueber ihr steht nur die Leitung (${darueber.join(", ") || "niemand"})`);
ok(darunter.includes("manager") && darunter.includes("modi"),
`unter ihr stehen Manager und Modis (${[...new Set(darunter)].join(", ")})`);
/* ==== IM TEAMHAUS STEHEN TEAMROLLEN (25.09.2026) ================
Hier stand `darunter.includes("manager")`. Seit der Haustrennung
vom 24.09. liefert die Personenliste nur noch das Haus, aus dem
gefragt wird -- und diese Pruefung fragt aus dem Teamhaus. Ein
Manager kommt dort nicht mehr vor, und das ist die Absicht:
„Eine Aenderung an einem der beiden Haeuser darf im anderen
nichts bewirken."
Gemessen wird deshalb, was hier wirklich unter der rechten Hand
steht: die linke Hand und die Modis. Die Aussage bleibt dieselbe
-- nach Rang sortiert, die Leitung oben -- nur eben mit den
Leuten, die es in diesem Haus gibt. */
ok(darunter.includes("linke") && darunter.includes("modi"),
`unter ihr stehen die linke Hand und die Modis (${[...new Set(darunter)].join(", ")})`);
ok(!darunter.includes("manager") && !darueber.includes("manager"),
"und KEIN Manager -- der gehoert ins andere Haus (Gegenprobe zur Trennung)");
/* ---- Und dieselbe Regel im Haus nebenan ---- */
const crew = json(await roh("/workspace/api/entwicklung/lage", CREW, { cookie: kHand }));
@@ -838,11 +918,11 @@ melde("=== 13. Die Probezeit ===");
waere "vor der Probe steht keine Liste" nicht falsch, sondern
unpruefbar. Beim ersten Anlauf war genau das der Grund fuer vier
rote Zeilen: meine Annahme, nicht der Code. */
const neuerTalent = await roh("/workspace/api/bereich/talente", HAUS, { cookie: kDogi },
const neuerTalent = await roh("/workspace/api/bereich/talente", CREW, { cookie: kDogi },
{ art: "ruhig", titel: "Jonas aus dem Chat", text: "Faellt seit kurzem auf." });
ok(neuerTalent.code === 201 || neuerTalent.code === 200,
`ein frischer Kandidat fuer diesen Abschnitt (${neuerTalent.code})`);
const lage = json(await roh("/workspace/api/talente/lage", HAUS, { cookie: kDogi }));
const lage = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi }));
const kandidat = (lage.kandidaten || []).find((k) => k.titel === "Jonas aus dem Chat");
ok(!!kandidat, `er steht im Trichter (${kandidat?.stufe})`);
@@ -853,7 +933,7 @@ melde("=== 13. Die Probezeit ===");
`vor der Probe steht dort keine Liste (${kandidat.probe ? "doch eine" : "keine"})`);
{
const r = await roh(`/workspace/api/talente/${kandidat.id}/probe/pr-erste-schicht`,
HAUS, { cookie: kDogi }, {}, "PUT");
CREW, { cookie: kDogi }, {}, "PUT");
ok(r.code === 409,
`und abhaken geht nicht, solange die Probe nicht laeuft (${r.code})`);
}
@@ -871,7 +951,7 @@ melde("=== 13. Die Probezeit ===");
while (stufe !== "probe" && schutz++ < 6) {
const naechste = tal.talentNaechste(stufe)?.schluessel;
if (!naechste) break;
const r = await roh(`/workspace/api/talente/${kandidat.id}/stufe`, HAUS,
const r = await roh(`/workspace/api/talente/${kandidat.id}/stufe`, CREW,
{ cookie: kDogi },
naechste === "probe"
? { stufe: naechste, buddy_id: idModi, erste_schicht: morgen }
@@ -884,7 +964,7 @@ melde("=== 13. Die Probezeit ===");
}
{
const k = json(await roh("/workspace/api/talente/lage", HAUS, { cookie: kDogi }))
const k = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi }))
.kandidaten.find((x) => x.id === kandidat.id);
ok(!!k.probe, "jetzt steht die Liste an der Karte");
ok(k.probe.gesamt === probeplan.PROBE_ALLE.length && k.probe.getan === 0,
@@ -899,9 +979,9 @@ melde("=== 13. Die Probezeit ===");
/* ---- Abhaken, und WER es war ---- */
{
const r = await roh(`/workspace/api/talente/${kandidat.id}/probe/pr-erste-schicht`,
HAUS, { cookie: kDogi }, {}, "PUT");
CREW, { cookie: kDogi }, {}, "PUT");
ok(r.code === 200 && json(r).fertig === true, `abhaken geht (${r.code})`);
const k = json(await roh("/workspace/api/talente/lage", HAUS, { cookie: kDogi }))
const k = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi }))
.kandidaten.find((x) => x.id === kandidat.id);
const s = k.probe.schritte.find((x) => x.schluessel === "pr-erste-schicht");
ok(s.fertig === true && s.von === "Filipe",
@@ -911,25 +991,25 @@ melde("=== 13. Die Probezeit ===");
{
/* EIN KNOPF, ZWEI RICHTUNGEN -- wie bei den Merkmalen. */
const r = await roh(`/workspace/api/talente/${kandidat.id}/probe/pr-erste-schicht`,
HAUS, { cookie: kDogi }, {}, "PUT");
CREW, { cookie: kDogi }, {}, "PUT");
ok(r.code === 200 && json(r).fertig === false, "ein zweiter Klick nimmt zurueck");
const k = json(await roh("/workspace/api/talente/lage", HAUS, { cookie: kDogi }))
const k = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi }))
.kandidaten.find((x) => x.id === kandidat.id);
ok(k.probe.getan === 0, "und der Stand geht mit zurueck");
}
{
const r = await roh(`/workspace/api/talente/${kandidat.id}/probe/voellig-erfunden`,
HAUS, { cookie: kDogi }, {}, "PUT");
CREW, { cookie: kDogi }, {}, "PUT");
ok(r.code === 404, `ein erfundener Schritt wird abgewiesen (${r.code})`);
}
/* ---- Und die Frage, wegen der das alles da ist ---- */
{
for (const s of ["pr-allein-schicht", "pr-team-gefragt", "pr-er-will-noch"]) {
await roh(`/workspace/api/talente/${kandidat.id}/probe/${s}`, HAUS,
await roh(`/workspace/api/talente/${kandidat.id}/probe/${s}`, CREW,
{ cookie: kDogi }, {}, "PUT");
}
const k = json(await roh("/workspace/api/talente/lage", HAUS, { cookie: kDogi }))
const k = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi }))
.kandidaten.find((x) => x.id === kandidat.id);
ok(k.probe.entscheidbar === true,
`mit den drei noetigen Schritten ist es entscheidbar (${k.probe.getan} von ${k.probe.gesamt})`);
@@ -940,9 +1020,9 @@ melde("=== 13. Die Probezeit ===");
/* ---- Wer was gesehen hat ---- */
{
const m = tal.TALENT_MERKMALE[tal.TALENT_GRUPPEN[0].schluessel][0].schluessel;
await roh(`/workspace/api/talente/${kandidat.id}/merkmal/${m}`, HAUS,
await roh(`/workspace/api/talente/${kandidat.id}/merkmal/${m}`, CREW,
{ cookie: kDogi }, {}, "PUT");
const k = json(await roh("/workspace/api/talente/lage", HAUS, { cookie: kDogi }))
const k = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi }))
.kandidaten.find((x) => x.id === kandidat.id);
ok(k.gesehen_von?.[m] === "Filipe",
`an einem Merkmal steht, wer es gesehen hat (${k.gesehen_von?.[m]})`);
@@ -985,7 +1065,7 @@ melde("=== 14. Der Ausgang ===");
/* Jonas steht aus Abschnitt 13 in der Probe -- genau der interessante
Fall: Wer in der Probe aussteigt, hat eine andere Geschichte als
wer nach dem ersten Hinsehen aussteigt. */
const vorher = json(await roh("/workspace/api/talente/lage", HAUS, { cookie: kDogi }));
const vorher = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi }));
const jonas = (vorher.kandidaten || []).find((k) => k.titel === "Jonas aus dem Chat");
ok(!!jonas && jonas.stufe === "probe",
`Jonas steht in der Arbeitsliste, Stufe ${jonas?.stufe}`);
@@ -993,7 +1073,7 @@ melde("=== 14. Der Ausgang ===");
/* ---- Der Satz ist Pflicht ---- */
{
const r = await roh(`/workspace/api/talente/${jonas.id}/beenden`, HAUS,
const r = await roh(`/workspace/api/talente/${jonas.id}/beenden`, CREW,
{ cookie: kDogi }, { grund: "" }, "PUT");
ok(r.code === 400, `ohne Satz geht es nicht (${r.code})`);
ok((json(r).fehler || "").length > 20,
@@ -1005,7 +1085,7 @@ melde("=== 14. Der Ausgang ===");
auffallen. Ohne diese Zeile wuesste man nur, dass ein LEERES Feld
abgewiesen wird, und das ist eine andere, viel schwaechere
Aussage. */
const r = await roh(`/workspace/api/talente/${jonas.id}/beenden`, HAUS,
const r = await roh(`/workspace/api/talente/${jonas.id}/beenden`, CREW,
{ cookie: kDogi }, { grund: "passt nix" }, "PUT");
ok(r.code === 400, `neun Zeichen sind noch kein Satz (${r.code})`);
}
@@ -1013,12 +1093,12 @@ melde("=== 14. Der Ausgang ===");
/* ---- Beenden ---- */
const GRUND = "Hat sich nach der zweiten Schicht nicht mehr gemeldet.";
{
const r = await roh(`/workspace/api/talente/${jonas.id}/beenden`, HAUS,
const r = await roh(`/workspace/api/talente/${jonas.id}/beenden`, CREW,
{ cookie: kDogi }, { grund: GRUND }, "PUT");
ok(r.code === 200 && json(r).beendet === true, `mit Satz geht es (${r.code})`);
}
{
const l = json(await roh("/workspace/api/talente/lage", HAUS, { cookie: kDogi }));
const l = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi }));
ok(!(l.kandidaten || []).some((k) => k.id === jonas.id),
"er steht nicht mehr in der Arbeitsliste");
const b = (l.beendete || []).find((k) => k.id === jonas.id);
@@ -1040,10 +1120,10 @@ melde("=== 14. Der Ausgang ===");
/* ---- Wieder aufnehmen ---- */
{
const r = await roh(`/workspace/api/talente/${jonas.id}/wieder`, HAUS,
const r = await roh(`/workspace/api/talente/${jonas.id}/wieder`, CREW,
{ cookie: kDogi }, null, "PUT");
ok(r.code === 200, `wieder aufnehmen geht (${r.code})`);
const l = json(await roh("/workspace/api/talente/lage", HAUS, { cookie: kDogi }));
const l = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi }));
const k = (l.kandidaten || []).find((x) => x.id === jonas.id);
ok(!!k, "er steht wieder in der Arbeitsliste");
ok(k?.stufe === "aufgefallen",
@@ -1060,7 +1140,7 @@ melde("=== 14. Der Ausgang ===");
"aus der Sammlung der Beendeten ist er heraus");
}
{
const r = await roh(`/workspace/api/talente/${jonas.id}/wieder`, HAUS,
const r = await roh(`/workspace/api/talente/${jonas.id}/wieder`, CREW,
{ cookie: kDogi }, null, "PUT");
ok(r.code === 409, `wer laeuft, kann nicht wieder aufgenommen werden (${r.code})`);
}
@@ -1073,22 +1153,22 @@ melde("=== 14. Der Ausgang ===");
und den ZUGANG offengelassen -- und das ist schlimmer als gar kein
Ausgang, weil man glaubt, gehandelt zu haben. */
{
const l = json(await roh("/workspace/api/talente/lage", HAUS, { cookie: kDogi }));
const l = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi }));
const drin = (l.kandidaten || []).find((k) => k.stufe === "imteam");
ok(!!drin, `es gibt einen im Team zum Gegenprobieren (${drin?.titel})`);
const r = await roh(`/workspace/api/talente/${drin.id}/beenden`, HAUS,
const r = await roh(`/workspace/api/talente/${drin.id}/beenden`, CREW,
{ cookie: kDogi }, { grund: GRUND }, "PUT");
ok(r.code === 409, `wer im Team ist, wird nicht hier beendet (${r.code})`);
ok((json(r).fehler || "").includes("Zug"),
`und es steht dabei, wo stattdessen (${(json(r).fehler || "").slice(0, 40)}...)`);
const l2 = json(await roh("/workspace/api/talente/lage", HAUS, { cookie: kDogi }));
const l2 = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi }));
ok((l2.kandidaten || []).some((k) => k.id === drin.id),
"er steht unveraendert in der Liste -- nichts halb passiert");
}
/* ---- Und die Gegenproben ---- */
{
const r = await roh("/workspace/api/talente/999999/beenden", HAUS,
const r = await roh("/workspace/api/talente/999999/beenden", CREW,
{ cookie: kDogi }, { grund: GRUND }, "PUT");
ok(r.code === 404, `eine erfundene Nummer wird abgewiesen (${r.code})`);
}
@@ -1288,11 +1368,11 @@ melde("=== 16. Vom Talent zur Person mit Aufgaben ===");
/* Ein eigener, frischer Kandidat. Der aus Abschnitt 13/14 ist durch
den Ausgang und wieder hinein gelaufen -- an ihm waere „hat noch
keine Person" nicht falsch, sondern unpruefbar. */
const neuer = await roh("/workspace/api/bereich/talente", HAUS, { cookie: kDogi },
const neuer = await roh("/workspace/api/bereich/talente", CREW, { cookie: kDogi },
{ art: "ruhig", titel: "Marek aus dem Stream", text: "Faellt auf." });
ok(neuer.code === 201 || neuer.code === 200, `ein frischer Kandidat (${neuer.code})`);
const finde = async (titel) => (json(await roh("/workspace/api/talente/lage", HAUS,
const finde = async (titel) => (json(await roh("/workspace/api/talente/lage", CREW,
{ cookie: kDogi })).kandidaten || []).find((k) => k.titel === titel);
let k = await finde("Marek aus dem Stream");
@@ -1301,7 +1381,7 @@ melde("=== 16. Vom Talent zur Person mit Aufgaben ===");
/* ---- Die Nummer wird geprueft, nicht geglaubt ---- */
{
const r = await roh(`/workspace/api/talente/${k.id}/stufe`, HAUS, { cookie: kDogi },
const r = await roh(`/workspace/api/talente/${k.id}/stufe`, CREW, { cookie: kDogi },
{ stufe: "beobachtet", person_id: 999999 }, "PUT");
ok(r.code === 400, `eine erfundene Zugangsnummer wird abgewiesen (${r.code})`);
/* GEGENPROBE zur Wirkung: Die Stufe darf sich dabei NICHT geaendert
@@ -1314,13 +1394,13 @@ melde("=== 16. Vom Talent zur Person mit Aufgaben ===");
/* ---- Der Weg bis ins Team ---- */
{
const morgen = tagLokal(1);
await roh(`/workspace/api/talente/${k.id}/stufe`, HAUS, { cookie: kDogi },
await roh(`/workspace/api/talente/${k.id}/stufe`, CREW, { cookie: kDogi },
{ stufe: "beobachtet" }, "PUT");
await roh(`/workspace/api/talente/${k.id}/stufe`, HAUS, { cookie: kDogi },
await roh(`/workspace/api/talente/${k.id}/stufe`, CREW, { cookie: kDogi },
{ stufe: "angesprochen" }, "PUT");
const hand = json(await roh("/workspace/api/talente/lage", HAUS, { cookie: kDogi }))
const hand = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi }))
.buddys?.[0];
await roh(`/workspace/api/talente/${k.id}/stufe`, HAUS, { cookie: kDogi },
await roh(`/workspace/api/talente/${k.id}/stufe`, CREW, { cookie: kDogi },
/* Die Felder heissen `buddy_id` und `erste_schicht` -- beim
ersten Anlauf stand hier `buddy`/`datum`, und die Route wies
den Uebergang ab. Der Fehler lag in dieser Datei, nicht im
@@ -1333,13 +1413,13 @@ melde("=== 16. Vom Talent zur Person mit Aufgaben ===");
/* ---- Der Zugang entsteht und wird VERKNUEPFT ---- */
let personId = null;
{
const p = await roh("/workspace/api/verwaltung/personen", HAUS, { cookie: kDogi },
const p = await roh("/workspace/api/verwaltung/personen", CREW, { cookie: kDogi },
{ name: "Marek", rolle: "modi" });
ok(p.code === 200 || p.code === 201, `der Zugang entsteht (${p.code})`);
personId = json(p).id;
ok(!!personId, `und hat eine Nummer (${personId})`);
const r = await roh(`/workspace/api/talente/${k.id}/stufe`, HAUS, { cookie: kDogi },
const r = await roh(`/workspace/api/talente/${k.id}/stufe`, CREW, { cookie: kDogi },
{ stufe: "imteam", person_id: personId }, "PUT");
ok(r.code === 200, `die Karte geht ins Team (${r.code})`);
@@ -1366,7 +1446,7 @@ melde("=== 16. Vom Talent zur Person mit Aufgaben ===");
Aufgefallen ist das erst an der GEGENPROBE -- mit ausgebautem
COALESCE blieb sie gruen, obwohl sie genau das absichern soll.
Eine Gegenprobe ist deshalb keine Kuer: Sie prueft die Pruefung. */
const r = await roh(`/workspace/api/talente/${k.id}/stufe`, HAUS, { cookie: kDogi },
const r = await roh(`/workspace/api/talente/${k.id}/stufe`, CREW, { cookie: kDogi },
{ stufe: "angesprochen" }, "PUT");
ok(r.code === 200, `ein Schritt zurueck geht durch (${r.code})`);
const zurueck = await finde("Marek aus dem Stream");
@@ -1374,13 +1454,13 @@ melde("=== 16. Vom Talent zur Person mit Aufgaben ===");
`und er steht wirklich dort (${zurueck.stufe})`);
ok(zurueck.person?.id === personId,
"der Schritt zurueck nimmt ihr den Menschen nicht weg");
await roh(`/workspace/api/talente/${k.id}/stufe`, HAUS, { cookie: kDogi },
await roh(`/workspace/api/talente/${k.id}/stufe`, CREW, { cookie: kDogi },
{ stufe: "imteam" }, "PUT");
}
/* ---- Womit faengt er an? ---- */
{
const lage2 = json(await roh("/workspace/api/talente/lage", HAUS, { cookie: kDogi }));
const lage2 = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi }));
ok(Array.isArray(lage2.start_kategorien) && lage2.start_kategorien.length > 0,
`es gibt ${lage2.start_kategorien?.length} Bereiche zum Anfangen`);
/* JEDER BEREICH HAT AUCH WIRKLICH AUFGABEN. Ein Knopf, der „0
@@ -1394,14 +1474,14 @@ melde("=== 16. Vom Talent zur Person mit Aufgaben ===");
const vorher = lage2.schon_vergeben?.[personId] || 0;
ok(vorher === 0, `er hat noch nichts (${vorher})`);
const r = await roh("/workspace/api/vorlagen/uebernehmen", HAUS, { cookie: kDogi },
const r = await roh("/workspace/api/vorlagen/uebernehmen", CREW, { cookie: kDogi },
{ art: "katalog_alle", gruppe: g.wert, stufe: "neu", person_id: personId });
ok(r.code === 200, `die Aufgaben lassen sich geben (${r.code})`);
ok(json(r).angelegt === g.anzahl,
`und es sind genau so viele, wie der Knopf versprochen hat `
+ `(${json(r).angelegt} von ${g.anzahl})`);
const lage3 = json(await roh("/workspace/api/talente/lage", HAUS, { cookie: kDogi }));
const lage3 = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi }));
ok((lage3.schon_vergeben?.[personId] || 0) === g.anzahl,
`die Karte zeigt jetzt ${lage3.schon_vergeben?.[personId]} offene`);
}
@@ -1638,23 +1718,23 @@ melde("=== 17. Der Anschluss im Browser ===");
melde("");
melde("=== 19. Wer sich kuemmert ===");
{
const neuer = await roh("/workspace/api/bereich/talente", HAUS, { cookie: kDogi },
const neuer = await roh("/workspace/api/bereich/talente", CREW, { cookie: kDogi },
{ art: "ruhig", titel: "Sami aus dem Chat", text: "Faellt auf." });
ok(neuer.code === 201 || neuer.code === 200, `ein Kandidat dafuer (${neuer.code})`);
const finde = async (keks, haus) => (json(await roh("/workspace/api/talente/lage",
haus, { cookie: keks })).kandidaten || []).find((k) => k.titel === "Sami aus dem Chat");
let k = await finde(kDogi, HAUS);
let k = await finde(kDogi, CREW);
ok(k?.zustaendig === null,
`am Anfang kuemmert sich niemand (${JSON.stringify(k?.zustaendig)})`);
/* ---- Uebernehmen ---- */
{
const r = await roh(`/workspace/api/talente/${k.id}/kuemmern`, HAUS,
const r = await roh(`/workspace/api/talente/${k.id}/kuemmern`, CREW,
{ cookie: kDogi }, null, "PUT");
ok(r.code === 200 && json(r).zustaendig === true, `uebernehmen geht (${r.code})`);
k = await finde(kDogi, HAUS);
k = await finde(kDogi, CREW);
ok(k.zustaendig?.name === "Filipe", `und es steht dabei, wer (${k.zustaendig?.name})`);
ok(k.zustaendig?.tage === 0, `seit wann (${k.zustaendig?.tage} Tage)`);
}
@@ -1668,16 +1748,16 @@ melde("=== 19. Wer sich kuemmert ===");
ok((json(r).fehler || "").includes("Filipe"),
`und erfaehrt, wer sich kuemmert ("${json(r).fehler}")`);
/* GEGENPROBE ZUR WIRKUNG: nichts halb passiert. */
k = await finde(kDogi, HAUS);
k = await finde(kDogi, CREW);
ok(k.zustaendig?.name === "Filipe", "der Zustaendige bleibt unveraendert");
}
/* ---- Abgeben, dann kann der andere ---- */
{
const r = await roh(`/workspace/api/talente/${k.id}/kuemmern`, HAUS,
const r = await roh(`/workspace/api/talente/${k.id}/kuemmern`, CREW,
{ cookie: kDogi }, null, "PUT");
ok(r.code === 200 && json(r).zustaendig === false, `derselbe Knopf gibt ab (${r.code})`);
k = await finde(kDogi, HAUS);
k = await finde(kDogi, CREW);
ok(k.zustaendig === null, "danach kuemmert sich wieder niemand");
const r2 = await roh(`/workspace/api/talente/${k.id}/kuemmern`, CREW,
@@ -1694,7 +1774,7 @@ melde("=== 19. Wer sich kuemmert ===");
ok(r.code === 404, `ein Modi kann sich nicht zustaendig erklaeren (${r.code})`);
}
{
const r = await roh("/workspace/api/talente/999999/kuemmern", HAUS,
const r = await roh("/workspace/api/talente/999999/kuemmern", CREW,
{ cookie: kDogi }, null, "PUT");
ok(r.code === 404, `eine erfundene Nummer wird abgewiesen (${r.code})`);
}
+19 -1
View File
@@ -183,7 +183,25 @@ const mass = await dogi.evaluate(() => {
daran ist es am 17.09. schon einmal gescheitert ("wieso kann ich
immer noch keine community rolle erstellen?"). */
const w = await import("./workspace.js");
const sollKarten = [...w.darfAnlegen({ rolle: "admin", haus: "agentur" })].sort();
/* ==== DASSELBE HAUS AUF BEIDEN SEITEN DER RECHNUNG (25.09.2026) =====
Hier stand `haus: "agentur"`. Gemessen wird aber ueber
`http://127.0.0.1` -- und dort kennt der Server kein Haus, liefert
also ALLE anlegbaren Rollen (acht statt vier). Die Pruefung verglich
damit eine Erwartung fuer die Agenturadresse mit einer Messung ohne
Adresse und meldete: „alle 4 vergebbaren Rollen stehen zur Wahl (8)".
BEIDES WAR RICHTIG, nur nicht dasselbe. Der Server tut genau, was er
soll: `darfAnlegen` schneidet auf das Haus zu, sobald es eines gibt
(workspace.js, seit dem 24.09.) -- und laesst alles zu, wo keines
ist. Auf dem echten Server gibt es immer eines; die 127.0.0.1 ist
ein Zustand, den nur Pruefungen herstellen.
Die Erwartung fragt deshalb jetzt OHNE Haus -- genauso, wie die
Messung laeuft. Dass die Einschraenkung je Haus wirklich greift,
misst `pruef-haus-trennung` an echten Hostnamen; das ist ihre
Aufgabe und nicht diese. */
const sollKarten = [...w.darfAnlegen({ rolle: "admin" })].sort();
ok(mass.karten === sollKarten.length,
`alle ${sollKarten.length} vergebbaren Rollen stehen zur Wahl (${mass.karten})`);
ok(JSON.stringify([...mass.kartenRollen].sort()) === JSON.stringify(sollKarten),
+49 -1
View File
@@ -40,7 +40,26 @@ for (const f of dateien) {
Nummern als Beleg, und ein Beleg ist kein Eintrag. */
const code = s.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
if (/portMussFreiSein\(\s*\d/.test(code)) vonHand.push(f);
const startetServer = /listen\(|process\.env\.PORT\s*=/.test(code);
/* ==== WER STARTET WIRKLICH EINEN SERVER? (25.09.2026) ==========
Hier stand `listen(` -- und das trifft auch den, der nur
NACHSIEHT, ob eine Nummer frei ist: `pruef-ports.mjs` macht einen
Port auf, schliesst ihn sofort wieder und startet dabei gar
nichts. Sie wurde deshalb angemahnt, den Waechter nicht zu
benutzen, den sie selbst prueft.
DAS ECHTE MERKMAL IST DAS HAUS: Wer `index.js` importiert oder
`process.env.PORT` setzt, laesst den Workspace laufen und braucht
eine eigene, eindeutige Nummer. Wer nur `createServer().listen()`
aufruft, um das Betriebssystem zu fragen, braucht sie nicht --
und darf es auch gar nicht, sonst verschoebe sich die ganze
Nummernfolge.
DIE GEGENPROBE UNTEN BLEIBT GUELTIG und wird sogar schaerfer: Sie
baut eine Datei, die `index.js` importiert und keinen Waechter
hat -- also genau den Fall, um den es geht. */
const startetServer = /import\(\s*["'`]\.\/index\.js["'`]\s*\)/.test(code)
|| /process\.env\.PORT\s*=/.test(code);
if (startetServer && !/eigenerPort\(\s*import\.meta/.test(code)) ohneWaechter.push(f);
}
ok(dateien.length > 100, `${dateien.length} Pruefdateien angesehen`);
@@ -49,6 +68,35 @@ ok(vonHand.length === 0,
ok(ohneWaechter.length === 0,
`jede, die einen Server startet, leitet ihre Nummer ab (${ohneWaechter.slice(0, 4).join(", ") || "—"})`);
/* ==== KANN DIESE ERKENNUNG AUCH NEIN SAGEN? (25.09.2026) ============
Die Zeile darueber meldet „alles in Ordnung", solange sie NICHTS
findet -- und genau so sieht auch eine Erkennung aus, die nichts
mehr findet, weil man sie zu eng gefasst hat. Am 25.09. wurde sie
enger gefasst (vorher schlug jedes `listen(` an, auch das blosse
Nachsehen, ob ein Port frei ist). Ohne diese Probe waere aus der
Verbesserung ein blinder Fleck geworden.
Gebaut wird der Fall im Speicher, nicht als Datei: Eine echte Datei
im Ordner wuerde die Nummernfolge aller anderen verschieben -- und
das ist genau das, wovor diese Pruefung schuetzt. */
{
const wieErkannt = (code) =>
(/import\(\s*["'`]\.\/index\.js["'`]\s*\)/.test(code)
|| /process\.env\.PORT\s*=/.test(code))
&& !/eigenerPort\(\s*import\.meta/.test(code);
ok(wieErkannt('await import("./index.js");'),
"Gegenprobe: ein Serverstart ohne Waechter faellt auf");
ok(wieErkannt('process.env.PORT = "5000";'),
" und eine von Hand gesetzte Nummer ebenfalls");
ok(!wieErkannt('const PORT = await eigenerPort(import.meta, "x");\n'
+ 'process.env.PORT = `${PORT}`;\nawait import("./index.js");'),
" wer den Waechter benutzt, faellt NICHT auf");
ok(!wieErkannt('const s = createServer(); s.listen(port, "127.0.0.1");'),
" und wer nur nachsieht, ob ein Port frei ist, auch nicht");
}
console.log("\n=== 2. Die abgeleiteten Nummern sind eindeutig ===");
const belegt = new Map();
let doppelt = 0;
+22 -3
View File
@@ -77,6 +77,24 @@ anlegen("Frida", "modi", "CODE-MODI-0001");
anlegen("Mara", "manager", "CODE-MANA-0001");
d.close();
/* ==== DIE ZWEITE PROBESEITE MUSS ES IM TEAMHAUS GEBEN (25.09.2026)
Hier stand `/workspace/wissen.html`. Die Seite ist laut Rechtetafel
fuer alle offen -- auf der CREW-Adresse ist sie es trotzdem nicht,
und das ist richtig so: Dort kommt man nur auf Seiten, fuer die es
auch eine Kachel gibt (`gehoertAufDieseAdresse`), und Wissen ist
eine Sache der Agentur. Der Server leitete den Modi also auf
start.html um, und die Pruefung meldete „beide Seiten sind wieder
offen (200/302)".
`material.html` steht auf der Crew-Wand als „Dogi-Media" und ist
fuer jede Rolle offen -- damit misst die Zeile wieder das, was sie
messen soll: dass eine ZURUECKGENOMMENE Sperre auch wirklich wieder
aufgeht. Dass die Seite VOR dem Verstellen offen ist, wird
ausdruecklich geprueft; sonst waere das „wieder offen" am Ende eine
Aussage ueber eine Seite, die nie zu war. */
const ZWEITE = "/workspace/material.html";
function roh(pfad, host, kopf = {}, koerper = null, methode = null) {
if (methode === "DELETE") koerper = null;
return new Promise((fertig, schief) => {
@@ -210,7 +228,7 @@ melde("=== 3. Zurück ===");
await roh("/workspace/api/rechte/feld", HAUS, { cookie: kDogi },
{ pfad: PROBE, rolle: "modi", erlaubt: false }, "PUT");
await roh("/workspace/api/rechte/feld", HAUS, { cookie: kDogi },
{ pfad: "/workspace/wissen.html", rolle: "modi", erlaubt: false }, "PUT");
{ pfad: ZWEITE, rolle: "modi", erlaubt: false }, "PUT");
const vorAllem = json(await roh("/workspace/api/rechte/tafel", HAUS, { cookie: kDogi }));
ok(vorAllem.abweichungen === 2, `zwei Felder verstellt (${vorAllem.abweichungen})`);
@@ -224,9 +242,10 @@ melde("=== 3. Zurück ===");
/* Und das Verhalten stimmt auch wieder -- die Zahl allein wäre nur
eine Behauptung. */
const chat = await roh(PROBE, CREW, { cookie: kModi });
const wissen = await roh("/workspace/wissen.html", CREW, { cookie: kModi });
const wissen = await roh(ZWEITE, CREW, { cookie: kModi });
ok(chat.code === 200 && wissen.code === 200,
`beide Seiten sind für den Modi wieder offen (${chat.code}/${wissen.code})`);
`beide Seiten sind für den Modi wieder offen (${chat.code}/${wissen.code}`
+ `${wissen.code === 302 ? " -> " + (wissen.kopf?.location || "?") : ""})`);
}
/* =======================================================================
+39 -5
View File
@@ -57,7 +57,18 @@ const melde = (t) => console.log(t);
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
const CREW = "crew.dogfather-universe.com";
const HAUS = "workspace.dogfather-universe.com";
/* ==== DIESE PRUEFUNG WOHNT BEI TEAM DOGI (25.09.2026) ==============
Gemessen werden Modis, ihre Aufgaben und ihre Entwicklung -- alles
davon gehoert ins Teamhaus. Gefragt wurde trotzdem ueber die
AGENTURadresse, und seit der Trennung vom 24.09.2026 antwortet der
Server dort richtigerweise mit 400 oder 404: Einen Modi gibt es dort
nicht. Die Folge waren Meldungen, die alle an einem einzigen Aufruf
hingen -- Folge, keine Ursache.
Die Agenturadresse bleibt unter ihrem Namen stehen; gebraucht wird
sie nur noch dort, wo ausdruecklich das andere Haus gemeint ist. */
const AGENTUR = "workspace.dogfather-universe.com";
const { DatabaseSync } = await import("node:sqlite");
const { scryptSync, randomBytes, createHmac } = await import("node:crypto");
@@ -107,7 +118,7 @@ async function anmelden(rolle, code, host) {
const r = await ruf("/workspace/api/anmelden", host, "POST", {}, { rolle, code });
return [].concat(r.kopf?.["set-cookie"] || []).map((c) => c.split(";")[0]).join("; ");
}
const kDogi = await anmelden("admin", "CODE-DOGI-0001", HAUS);
const kDogi = await anmelden("admin", "CODE-DOGI-0001", CREW);
const kHand = await anmelden("hand", "CODE-HAND-0001", CREW);
const kFrida = await anmelden("modi", "CODE-MODI-0001", CREW);
ok(!!kDogi && !!kHand && !!kFrida, "DogFather, die rechte Hand und ein Modi sind angemeldet");
@@ -125,7 +136,30 @@ const SELBST = kat.ENTWICKLUNG_SELBST[0].schluessel;
bekam daraufhin 401, und WEIL sie nichts setzen konnte, ging ein
zweiter Test ("aus der Beobachtung eines anderen wird kein Schritt")
aus dem falschen Grund durch. Ein grüner Haken über einer Leere. */
const wo = (keks) => (keks === kDogi ? HAUS : CREW);
const wo = (keks) => (keks === kDogi ? CREW : CREW);
/* ==== ZUERST ZUTRAGEN, DANN MESSEN (25.09.2026) ====================
Filipe: „es sollen nur die menge angezeigt werden die wir zutragen
und erledigte dan auch nur die die erledigt wurden von den
zugetragenen."
Seither ist das GANZE der Kachel das, was einem Menschen zugetragen
ist -- nicht mehr der ganze Katalog. Diese Pruefung setzte
Einschaetzungen auf Punkte, die Frida nie bekommen hatte; die
Uebersicht zaehlte sie zu Recht nicht mit, und die Meldung lautete
„die Kachel zaehlt sofort mit (0 -> 0)".
DIE PRUEFUNG MISST JETZT DIESELBE LAGE WIE VORHER -- nur mit dem
Schritt davor, den es seit gestern gibt. Die drei Punkte, um die es
unten geht, werden Frida zugetragen; alles andere bleibt, wie es
war. Ueber die ECHTE Route und nicht ueber die Datenbank: So ist
der Weg, den auch DogFather nimmt, gleich mitgeprueft. */
{
const a = await ruf(`/workspace/api/entwicklung/person/${idFrida}/punkte`,
CREW, "PUT", { cookie: kDogi }, { punkte: [P1, P2] });
ok(a.code === 200, `die zwei Punkte sind Frida zugetragen (${a.code})`);
}
const setzen = (keks, punkt, stufe, anlass) =>
ruf(`/workspace/api/entwicklung/person/${idFrida}/${punkt}`, wo(keks),
"PUT", { cookie: keks }, { stufe, anlass });
@@ -182,7 +216,7 @@ melde("=== 2. Eine echte Aufgabe ===");
"der Anlass steht in der Beschreibung");
/* UND SIE STEHT WIRKLICH AUF DEM BRETT, nicht nur in der Tabelle. */
const brett = json(await ruf("/workspace/api/aufgaben", HAUS, "GET", { cookie: kDogi }));
const brett = json(await ruf("/workspace/api/aufgaben", CREW, "GET", { cookie: kDogi }));
const drauf = (brett.aufgaben || []).find((x) => x.id === a.id);
ok(!!drauf, "und sie taucht auf dem Aufgabenbrett auf");
@@ -272,7 +306,7 @@ melde("=== 6. Der Kreis ===");
const d = oeffne();
const a = d.prepare("SELECT id FROM aufgaben WHERE aus_punkt = ?").get(P1);
d.close();
const fertig = await ruf(`/workspace/api/aufgaben/${a.id}`, HAUS, "PATCH",
const fertig = await ruf(`/workspace/api/aufgaben/${a.id}`, CREW, "PATCH",
{ cookie: kDogi }, { status: "erledigt" });
ok(fertig.code === 200, `die Aufgabe wird erledigt (${fertig.code})`);
+32 -5
View File
@@ -339,7 +339,27 @@ const r = await bDogi.seite.evaluate(() => ({
anzahl: s.querySelectorAll("option").length,
})),
/* Die Zeilen sollen es auch in Worten sagen. */
texte: [...document.querySelectorAll(".person__zeile")].map((z) => z.textContent),
/* ==== DIE ZEILE HEISST `.person` (25.09.2026) ==================
Hier stand `.person__zeile` -- eine Klasse, die es im Haus nicht
(mehr) gibt. `querySelectorAll` liefert dafuer ein LEERES Feld,
und `.some()` darauf ist immer false: Die zwei Zeilen darunter
waren also nicht rot, weil etwas fehlte, sondern weil sie nichts
ansehen konnten. Genau die Sorte stiller Aussetzer, vor der die
Hausregel warnt -- nur andersherum: nicht still gruen, sondern
still rot, was fast schlimmer ist, weil man die Ursache im
Programm sucht.
Gemessen heisst die Karte `.person`, der Bezug darin
`.person__bezug` und die Zahl `.person__wert`. Damit das nicht
wieder unbemerkt veraltet, wird die ANZAHL der gefundenen Karten
unten ausdruecklich geprueft. */
texte: [...document.querySelectorAll(".person")].map((z) => z.textContent),
bezuege: [...document.querySelectorAll(".person__bezug")].map((z) => z.textContent.trim()),
/* Zahl und Wort stehen in zwei Kaestchen -- zusammengesetzt ergibt
das „1 Scout", nicht „1Scout". */
werte: [...document.querySelectorAll(".person__wert")].map((z) =>
[...z.children].map((k) => k.textContent.trim()).join(" ")),
ueber: document.documentElement.scrollWidth - document.documentElement.clientWidth,
}));
ok(r.personen === 7,
@@ -354,10 +374,17 @@ ok(r.wahlen[0]?.anzahl >= 4,
`Manager UND DogFather stehen zur Wahl (${r.wahlen[0]?.anzahl} Eintraege)`);
ok(r.wahlen.some((w) => w.wert === String(idNina)),
"die bestehende Zuteilung ist vorausgewaehlt");
ok(r.texte.some((t) => /gehört zu Nina/.test(t)),
"und steht auch in Worten in der Zeile");
ok(r.texte.some((t) => /1 Scout zugeteilt/.test(t)),
"beim Manager steht die Zahl seiner Scouts");
/* DIE ZAHL GEHOERT IN DIE BEDINGUNG. Faende der Selektor eines Tages
nichts mehr, waeren die zwei Zeilen darunter nicht rot, weil etwas
fehlt, sondern weil nichts angesehen wurde -- und man suchte im
Programm. */
ok(r.texte.length >= 7,
`${r.texte.length} Personenkarten angesehen -- der Selektor trifft noch`);
ok((r.bezuege || []).some((t) => /gehört zu Nina/.test(t)),
`und es steht auch in Worten da (${(r.bezuege || []).join(" | ") || "nichts"})`);
ok((r.werte || []).some((t) => /^1 Scout$/.test(t)),
`beim Manager steht die Zahl seiner Scouts `
+ `(${(r.werte || []).filter((x) => /Scout/.test(x)).join(" | ") || "nichts"})`);
ok(r.ueber <= 0, `nichts steht ueber (${r.ueber}px)`);
ok(bDogi.konsole.length === 0,
`keine Fehler in der Konsole${bDogi.konsole.length ? " -> " + bDogi.konsole.join(" | ") : ""}`);
+45 -6
View File
@@ -347,24 +347,63 @@ const ruf = async (art, weg, keks, koerper) => {
/* Die vier Kategorien, die Filipe genannt hat -- einzeln, nicht als
"irgendeine geht". Waere nur eine davon offen, saehe ein "geht"
genauso aus. */
for (const r of ["scout", "manager", "spicy"]) {
genauso aus.
==== UND MANAGER KOMMT ZULETZT (25.09.2026) ====================
Hier stand die Reihenfolge scout, manager, spicy -- und ab dem
zweiten Schritt war die Person eine MANAGERIN. An denen aendert
seit dem 22.09. nur DogFather etwas (`nurDogFatherBeiLeitung`),
also antwortete der Server ab da mit 403, und zwei Zeilen wurden
rot. Der Server hatte recht; die Pruefung hatte sich selbst die
Tuer zugezogen.
Jetzt stehen die drei frei waehlbaren zuerst, und „manager" ist
der letzte Schritt -- danach ist die Person geschuetzt, und
genau DAS wird in der Zeile darunter ausdruecklich gemessen.
Aus einem Stolperstein wird damit eine Aussage. */
for (const r of ["scout", "spicy", "creator", "manager"]) {
const a = await zu(keksSpicy, r);
ok(a.status === 200 && a.daten?.rolle === r,
`Spicy Media stellt auf "${r}" um (HTTP ${a.status})`);
}
const zurueck = await zu(keksSpicy, "creator");
ok(zurueck.status === 200, `und wieder zurueck auf Creator (HTTP ${zurueck.status})`);
/* UND JETZT IST SIE GESCHUETZT. Ohne diese Zeile waere die
Reihenfolge oben nur eine Bequemlichkeit; so ist sie der Beleg
fuer die Schranke. */
const nachManager = await zu(keksSpicy, "creator");
ok(nachManager.status === 403,
`an einer Managerin aendert Spicy Media nichts mehr (HTTP ${nachManager.status})`);
/* DogFather darf es -- sonst waere „403" oben auch dann gruen, wenn
der Weg gar nicht mehr existiert. */
const zurueck = await zu(keksDogi, "creator");
ok(zurueck.status === 200,
`DogFather stellt sie wieder auf Creator (HTTP ${zurueck.status})`);
/* DIE EINZIGE, DIE NICHT GEHT. */
const hoch = await zu(keksSpicy, "admin");
ok(hoch.status === 403,
/* 403 ODER 400 -- dieselbe Ueberlegung wie zwei Zeilen tiefer: Die
Aussage ist „DogFather laesst sich nicht vergeben", nicht „der
Server nimmt dafuer genau diesen Weg". Seit „admin" aus der Liste
der vergebbaren Rollen heraus ist, antwortet er mit 400
(„Unbekannte Rolle") statt mit 403 -- die Tuer ist damit sogar
eine Schicht frueher zu. */
ok(hoch.status === 403 || hoch.status === 400,
`DogFather laesst sich nicht vergeben (HTTP ${hoch.status}, ${hoch.daten?.fehler || ""})`);
/* AUCH NICHT VON DOGFATHER SELBST. Das ist der Satz "das ist die
einzige die man nicht auswaehlen kann" -- ohne Ausnahme. */
const vonDogi = await zu(keksDogi, "admin");
ok(vonDogi.status === 403,
/* 403 ODER 400 -- beides heisst „geht nicht", und welches davon
kommt, haengt daran, an welcher Schranke es scheitert: an der
Rechtefrage oder an der Liste der vergebbaren Rollen, in der
„admin" gar nicht steht. Die Aussage ist, dass DogFather sich
nicht verdoppeln laesst -- nicht, welchen der beiden Wege der
Server dafuer nimmt. Auf eine Zahl festgelegt war diese Zeile
seit dem Umbau der Rollenliste rot, ohne dass etwas offen
gewesen waere. */
ok(vonDogi.status === 403 || vonDogi.status === 400,
`auch DogFather kann sie nicht vergeben (HTTP ${vonDogi.status})`);
/* UND AN DOGFATHER SELBST AENDERT SIE NICHTS. Die Pruefungen oben
+15 -1
View File
@@ -467,11 +467,25 @@ melde("=== 7b. Keine Arbeitsliste, aber die Verwaltung ===");
} catch { return []; }
};
/* ==== GEFRAGT WIRD IM TEAMHAUS (25.09.2026) ====================
Hier stand `HAUS` -- die Agenturadresse. Gesucht wird aber nach
„Frida", einem Modi, und die gibt es dort seit der Trennung vom
24.09. nicht mehr. Der Server antwortete richtig (eine Liste ohne
Frida), und die Pruefung meldete „filtert nicht einfach alles
weg". Beim Chat-Partner war die Liste sogar ganz leer -- auf der
Agenturadresse hat DogFather keine Team-Leute zum Schreiben.
Die Aussage bleibt dieselbe und wird sogar schaerfer: Im Teamhaus
MUSS Frida in beiden Listen stehen, und die Community MUSS
draussen bleiben. Der Kommentar zwanzig Zeilen weiter unten sagt
es fuer die Verwaltung bereits so: „Auf der Team-Adresse, denn
dort wohnt der Treff jetzt." */
for (const [was, weg] of [
["die Einladungsliste", "/workspace/api/einladbar"],
["die Chat-Partner", "/workspace/api/chat/partner"],
]) {
const a = await roh(weg, HAUS, { cookie: kDogi });
const a = await roh(weg, CREW, { cookie: kDogi });
const liste = namen(a);
/* DIE ZAHL GEHÖRT IN DIE BEDINGUNG: Wäre die Liste leer, wäre
"Kessi steht nicht drin" trivial wahr. */
+22 -2
View File
@@ -102,6 +102,23 @@ const rein = d.prepare(
"INSERT INTO eintraege (bereich, art, titel, text, datum, erstellt, erstellt_von)"
+ " VALUES (?,?,?,?,?,?,?)");
const BRETTER = ["anschlag", "ansteht", "treff", "wunsch", "highlight", "regeln", "mitmachen"];
/* ==== WAS DER EINZELNEN PERSON GEHOERT, IST KEIN BRETT ==============
„Vertraulich melden", „Mein Steckbrief" und seit dem 24.09. auch
„Support" stehen in der Gruppe „Fuer dich" -- nicht zwischen den
Brettern. Eine vertrauliche Meldung oder eine kaputte Seite ist eine
Sache zwischen einem Menschen und der Leitung; zwischen den
Aushaengen saehe sie aus wie ein oeffentlicher Anschlag.
EINE LISTE, ZWEI BENUTZER. Bis heute stand sie zweimal in dieser
Datei -- einmal bei den Brettern der Community, einmal beim
Vergleich mit dem Modi. Als Support dazukam, wurde die eine
nachgezogen und die andere nicht, und die Pruefung meldete „FEHLT:
Support" fuer etwas, das genau dort steht, wo es hingehoert. Zwei
Listen fuer dieselbe Sache sind zwei Gelegenheiten, eine davon zu
vergessen. */
const PERSOENLICH = ["Vertraulich melden", "Mein Steckbrief", "Support"];
for (const b of BRETTER) rein.run(b, Object.keys((await import("./workspace-bereiche.js")).BEREICHE[b].arten)[0],
`CREW-PROBE-${b.toUpperCase()}`, "", jetzt.slice(0, 10), jetzt, idDogi);
/* Und einer im Haus, den die Community NICHT sehen darf. */
@@ -371,7 +388,7 @@ let namen = [];
Steckbrief" gehoeren der einzelnen Person, nicht dem Raum. Sie
zwischen die Bretter zu stellen hiesse, dass eine vertrauliche
Meldung aussieht wie ein oeffentliches Brett. */
const PERSOENLICH = ["Vertraulich melden", "Mein Steckbrief"];
/* Siehe PERSOENLICH ganz oben -- eine Liste fuer beide Stellen. */
const bretter = (j.bereiche || []).filter((k) => !PERSOENLICH.includes(k.name));
ok(bretter.every((k) => k.gruppe === "Community"),
`die ${bretter.length} Bretter stehen in der Gruppe „Community“`);
@@ -417,7 +434,10 @@ let namen = [];
war. Jetzt steht die Ausnahme ausdruecklich da -- und sie ist
eine ECHTE Pruefung: Taucht "Vertraulich melden" eines Tages bei
einem Modi auf, wird diese Zeile rot. */
const NUR_COMMUNITY = ["Vertraulich melden", "Mein Steckbrief"];
/* DIESELBE LISTE WIE OBEN. Hier stand eine zweite, und sie kannte
„Support" nicht -- die Pruefung meldete deshalb, dem Modi fehle
ein Brett, das gar keines ist. */
const NUR_COMMUNITY = PERSOENLICH;
/* Verglichen wird die GRUPPE "Community" auf beiden Seiten -- die
zwei persoenlichen Kacheln stehen dort ohnehin nicht drin. */
const sollteSehen = namen.filter((n) => !NUR_COMMUNITY.includes(n));