Files
dogfather-universe/server/pruef-portnummern.mjs
T
DogFatherGitandClaude Opus 5 1ba9d07af2 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]>
2026-09-25 22:54:21 +02:00

192 lines
9.3 KiB
JavaScript

/* =====================================================================
pruef-portnummern.mjs — Zwei Pruefungen, eine Nummer
WAS HIER SCHIEFGING (20.09.2026 gemessen):
164 Pruefdateien, 129 verschiedene Nummern -- NEUNZEHN waren doppelt
vergeben, 4186 und 4188 und 4196 und 4198 sogar dreifach. Niemand
hatte das gewollt; jede neue Pruefung wurde von einer vorhandenen
abgeschrieben, und die Nummer kam mit.
WAS DAS ANRICHTET: Der Waechter bricht ab, statt gegen einen fremden
Server zu messen -- so weit, so richtig. Aber zwei Pruefungen mit
derselben Nummer koennen nie gleichzeitig laufen, und ein
liegengebliebener Prozess der einen laesst die andere mit einer
Meldung abbrechen, die wie ein Befund aussieht. Genau das ist am
20.09. zweimal passiert, beide Male mit Fehlersuche an der falschen
Stelle.
WAS DIESE PRUEFUNG SICHERT: Dass die Nummer ABGELEITET bleibt. Eine
von Hand eingetragene Zahl ist der Anfang der naechsten Doppelung --
sie faellt nur niemandem auf, bis zwei Laeufe kollidieren.
===================================================================== */
import { readdirSync, readFileSync } from "node:fs";
import { eigenerPort, portNummer } from "./helfer-port.mjs";
let ok_ = 0, fehl = 0;
const ok = (b, text) => { if (b) { ok_++; console.log(" ok " + text); }
else { fehl++; console.log(" FEHL " + text); } };
const ORDNER = new URL(".", import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, "$1");
const dateien = readdirSync(ORDNER)
.filter((f) => f.startsWith("pruef-") && f.endsWith(".mjs")).sort();
console.log("\n=== 1. Niemand traegt eine Nummer von Hand ein ===");
const vonHand = [];
const ohneWaechter = [];
for (const f of dateien) {
const s = readFileSync(ORDNER + "/" + f, "utf8");
/* Kommentare zaehlen nicht: In helfer-port.mjs und hier stehen alte
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);
/* ==== 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`);
ok(vonHand.length === 0,
`keine traegt eine feste Nummer im Waechter (${vonHand.slice(0, 4).join(", ") || "—"})`);
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;
dateien.forEach((f, i) => {
for (let n = 0; n < 2; n++) {
const p = portNummer(i * 2 + n);
if (belegt.has(p)) { doppelt++; console.log(` ${p}: ${belegt.get(p)} und ${f}`); }
belegt.set(p, f);
}
});
ok(belegt.size === dateien.length * 2 && doppelt === 0,
`${belegt.size} Nummern fuer ${dateien.length} Dateien, ${doppelt} doppelt`);
console.log("\n=== 2b. Keine davon ist eine, die `fetch` verweigert ===");
/* DIE TEUERSTE HALBE STUNDE DIESES ABENDS. Nach der Umstellung bekam
pruef-chat-kanaele die 5060 und stuerzte ab -- obwohl der Server
nachweislich lief und die Anmeldung ueber `http.request` geklappt
hatte. 5060 ist SIP und steht auf der "bad ports"-Liste der
Fetch-Spezifikation, die Node uebernommen hat: `fetch` baut dorthin
keine Verbindung auf.
Die Meldung lautet "fetch failed" -- genau wie bei einem Server, der
nicht laeuft. Wer sie liest, sucht am Server. Deshalb steht sie
jetzt hier als eigene Pruefung: Die naechste Datei, die in so eine
Nummer rutscht, soll es HIER erfahren und nicht in einer
Fehlersuche. */
const BOESE = [1, 7, 9, 11, 13, 15, 17, 19, 20, 21, 22, 23, 25, 37, 42, 43, 53,
69, 77, 79, 87, 95, 101, 102, 103, 104, 109, 110, 111, 113, 115, 117, 119,
123, 135, 137, 138, 139, 143, 161, 179, 389, 427, 465, 512, 513, 514, 515,
526, 530, 531, 532, 540, 548, 554, 556, 563, 587, 601, 636, 989, 990, 993,
995, 1719, 1720, 1723, 2049, 3659, 4045, 4190, 5060, 5061, 6000, 6566, 6665,
6666, 6667, 6668, 6669, 6679, 6697, 10080];
const getroffen = [...belegt.keys()].filter((x) => BOESE.includes(x));
ok(getroffen.length === 0,
`keine der ${belegt.size} Nummern steht auf der Sperrliste (${getroffen.join(", ") || "—"})`);
/* GEGENPROBE AM ECHTEN VERHALTEN, nicht an der Liste: Ein Zuhoerer auf
einer gesperrten Nummer muss fuer `fetch` unerreichbar sein, einer
daneben erreichbar. Ohne das waere die Liste oben nur eine
Behauptung ueber Node -- und Behauptungen altern. */
{
const { createServer } = await import("node:http");
const probe = async (nummer) => {
const dienst = createServer((q, a) => { a.writeHead(200); a.end("da"); });
await new Promise((r) => dienst.listen(nummer, "127.0.0.1", r));
let grund = "erreichbar";
try { await fetch(`http://127.0.0.1:${nummer}/`); }
catch (f) { grund = f?.cause?.message || f?.message || "?"; }
await new Promise((r) => dienst.close(r));
return grund;
};
const gesperrt = await probe(5060);
const erlaubt = await probe(portNummer(0));
ok(/bad port/i.test(gesperrt),
`eine gesperrte Nummer ist fuer fetch tot, obwohl jemand lauscht (5060: ${gesperrt})`);
ok(erlaubt === "erreichbar",
`eine abgeleitete dagegen nicht (${portNummer(0)}: ${erlaubt})`);
}
console.log("\n=== 3. Die Ableitung stimmt wirklich (gemessen, nicht gerechnet) ===");
/* NICHT die Formel gegen sich selbst pruefen -- das waere eine
Bestaetigung ohne Aussage. Gefragt wird die Funktion, die die
Pruefungen auch benutzen, und verglichen wird mit der Stelle im
Alphabet. */
const ersteDatei = dateien[0];
const meine = dateien.indexOf("pruef-portnummern.mjs");
const p0 = await eigenerPort(import.meta, "pruef-portnummern (Selbsttest)", 0);
ok(p0 === portNummer(meine * 2),
`meine eigene Nummer ist ${p0} — Stelle ${meine} im Alphabet, erste Datei ${ersteDatei}`);
const p1 = await eigenerPort(import.meta, "pruef-portnummern (Selbsttest)", 1);
ok(p1 === p0 + 1, `und die zweite ist ${p1}, also genau eine daneben`);
console.log("\n=== 4. Gegenprobe: kann sie auch NICHT IN ORDNUNG sagen? ===");
/* Eine Pruefung, die immer bestaetigt, bestaetigt nichts. */
let drittesAusgangGriff = false;
try {
/* Eine Datei, die es nicht gibt -- die Ableitung darf dann NICHT
raten, sondern muss abbrechen. */
await eigenerPort({ url: new URL("./gibt-es-nicht.mjs", import.meta.url).href }, "Gegenprobe");
} catch (f) {
drittesAusgangGriff = /steht nicht in der Liste/.test(f?.message || "");
}
ok(drittesAusgangGriff, "eine unbekannte Datei bekommt keine geratene Nummer, sondern einen Abbruch");
let zuHoheNummer = false;
try { await eigenerPort(import.meta, "Gegenprobe", 7); }
catch (f) { zuHoheNummer = /Nummern je Datei/.test(f?.message || ""); }
ok(zuHoheNummer, "und eine dritte Nummer je Datei gibt es nicht");
console.log(`\n${ok_} Pruefungen, ${fehl} Fehler`);
console.log(fehl ? "NICHT IN ORDNUNG\n" : "ALLES IN ORDNUNG\n");
process.exit(fehl ? 1 : 0);