/* ===================================================================== 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);