Files
dogfather-universe/server/pruef-anruf-klingelt.mjs
DogFatherGitandClaude Opus 5 73f9b24793 Benachrichtigungen wecken das Telefon wieder: Urgency je Art
Diene hat gemeldet: "Die Benachrichtigungen werden nicht angezeigt,
wenn neue Nachrichten reinkommen. Erst, wenn man die App oeffnet."

Alles Messbare war gruen: Der Service Worker zeigt die Meldung bei
geschlossener App (pruef-push-zu, 13 Pruefungen), jede Anmeldung im
Haus stand auf fehler = 0, der Push-Dienst quittierte mit 201. Nur
den Kopf auf der Leitung hat nie jemand gemessen:

    Urgency: normal

Beide Dienste behandeln das ausdruecklich als "darf warten".
Android/FCM haelt solche Meldungen zurueck, solange das Telefon
doest, und stellt sie zu, wenn es aufwacht -- typischerweise beim
Entsperren oder Oeffnen der App. Apple/APNs nennt es "verzoegert,
gebuendelt oder gedrosselt". Das ist Dienes Satz, Wort fuer Wort,
und es stand als Vorgabe im eigenen Quelltext.

zuletzt_ok konnte das nie zeigen: Es beweist, dass der DIENST
angenommen hat, nicht dass das GERAET etwas angezeigt hat.

Bis hierher hing die Dringlichkeit an der Ruheregel (dringend =
regel === "nie"), mit der Begruendung, "darf das nachts stoeren?"
und "darf das warten?" haetten dieselbe Antwort. Haben sie nicht:
Ein Chat um 3 Uhr soll schweigen, um 14 Uhr aber nicht vierzig
Minuten liegen bleiben. Jetzt zwei Felder -- in DERSELBEN Zeile
derselben Artenliste, damit sie nicht auseinanderlaufen koennen.

10 von 19 Arten sind eilig (Anruf, Chat, Erwaehnung, Support, Hilfe,
Termin, Wecker, zweimal Live, Probe). Die anderen neun duerfen
warten -- waere alles eilig, waere nichts mehr eilig.

DIE FALLE BEIM BEHEBEN: dringend steuerte auch die Haltbarkeit
(TTL 150). Ein einfach auf "dringend" gestellter Chat waere nach
150 s verfallen -- wer sein Telefon drei Minuten aus hat, haette die
Nachricht GAR nicht mehr bekommen. Aus "zu spaet" waere "nie"
geworden. Deshalb sind eilig und kurzlebig getrennt; kurzlebig
bleibt genau beim Anruf und der Probe.

Der alte Name kracht jetzt, statt still "normal" zu liefern.

pruef-push-eilig.mjs (neu, 13 Pruefungen): die Entscheidung je Art
unabhaengig notiert statt aus der Artenliste abgelesen, jede Art
muss eine Entscheidung haben, und der Kopf wird durch
benachrichtige() hindurch an einem nachgebauten Push-Dienst
gemessen. Gegenprobe gelaufen: ohne das Feld meldet sie
"chat_nachricht geht mit Urgency: normal hinaus", 4 Fehler.
Die Uhr ist dort eine Eingabe, keine Annahme -- die Zeitzone wird so
gewaehlt, dass der Lauf immer auf 12 Uhr faellt, sonst waere die
Pruefung nachts rot ohne Befund.

pruef-anruf-klingelt 25 -> 26. Dabei aufgefallen: Eine ihrer
Pruefungen war gruen, obwohl die Zeile aus dem Code verschwunden war
-- sie stand nur noch in einem Kommentar, der die alte Fassung
zitiert. Quelltextpruefungen lesen dort jetzt ohne Kommentare.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-03 12:58:01 +02:00

295 lines
13 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* =====================================================================
pruef-anruf-klingelt.mjs — Klingelt es auch wirklich? (19.09.2026)
Filipe: „irgendwas klappt mit dem telefonieren nicht."
---------------------------------------------------------------------
WIE DER FEHLER GEFUNDEN WURDE — UND WARUM DAS DER WICHTIGE TEIL IST
Ich habe zuerst die Vermittlung verdächtigt: coturn geprüft, STUN
gemessen, eine echte Zuteilung von außen gemacht, den TCP-Weg
nachgerüstet. Alles richtig und alles nötig — aber es war NICHT die
Ursache.
Die Ursache stand im Protokoll des Servers, und sie stand dort die
ganze Zeit:
24× anruf_start, jeder nach 7–39 Sekunden beendet
im Chat danach jedes Mal: „📞 Verpasster Anruf"
die Gegenseite schrieb: „Hab keinen Anrufeingang gehabt
und keine Benachrichtigung"
Es ging nie um Ton oder Verbindung. Es hat bei der anderen Person
schlicht nicht geklingelt.
Das ist dieselbe Lehre wie am 06.09. („kommt keine Mail an" — erst
fragen, OB gesendet wurde) und am 14.09. (zwei Tage Audiowege
verfolgt, während die CPU bei 98 % stand): **Erst das Protokoll
lesen, dann suchen.** Eine halbe Minute in der richtigen Tabelle
hätte Stunden gespart.
---------------------------------------------------------------------
WAS WIRKLICH DEFEKT WAR
Die Benachrichtigung wurde zugestellt — `zuletzt_ok` am Gerät stand
auf genau die Anrufminute. Sie hat nur nicht geklingelt:
1. `Urgency: "normal"` stand FEST im Transport, für jede Meldung.
Auf Android entscheidet dieser Kopf, ob sofort zugestellt wird
oder bis zum nächsten Aufwachen gewartet. Im Stromsparmodus
werden daraus Minuten — bei einem Anruf, der 120 Sekunden
klingelt, ist das ein verpasster Anruf.
2. Im Service Worker war `renotify: false` und `tag: d.art`. Eine
zweite Meldung mit derselben Kennung ersetzt die erste LAUTLOS.
Wer ein zweites Mal anruft, WEIL nicht abgehoben wurde, löst
damit gar keinen Ton mehr aus.
3. Kein `vibrate`, kein `requireInteraction`. Ein stiller Kasten,
der von selbst verschwindet — in einer Hosentasche unsichtbar.
Für „Aufgabe überfällig" ist all das genau richtig. Für ein
klingelndes Telefon ist es das Gegenteil. Deshalb unterscheidet das
Haus jetzt zwischen beidem.
AUFRUF: node server/pruef-anruf-klingelt.mjs
Läuft in unter einer Sekunde, ohne Server, ohne Netz.
===================================================================== */
import { readFileSync } from "node:fs";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
const REPO = join(dirname(fileURLToPath(import.meta.url)), "..");
const lies = (p) => readFileSync(join(REPO, p), "utf8");
/* ---- NUR DER CODE, OHNE DIE ERZAEHLUNG DRUMHERUM (03.10.2026) -------
Diese Datei prueft Quelltext mit Mustern. Das ist schnell und
braucht keinen Server -- hat aber eine Luecke, die heute
aufgefallen ist: Die Pruefung "und zwar an derselben Bedingung wie
die Nachtruhe" war gruen, OBWOHL die Zeile aus dem Code
verschwunden war. Sie stand nur noch in einem Kommentar, der die
alte Fassung zitiert -- und ein Muster sieht den Unterschied
nicht.
Das ist die dritte Sorte gruener Haken: nicht uebersprungen, nicht
falsch geprueft, sondern am Beleg vorbei. Deshalb werden
Kommentare vorher entfernt. Was hier geprueft wird, muss laufender
Code sein. */
const nurCode = (t) => t
.replace(/\/\*[\s\S]*?\*\//g, " ")
.replace(/(^|[^:])\/\/.*/g, "$1");
let ok = 0, fehl = 0;
const pruef = (name, bedingung, zusatz = "") => {
if (bedingung) { ok++; console.log(` OK ${name}`); }
else { fehl++; console.log(` FEHL ${name}${zusatz ? " — " + zusatz : ""}`); }
};
console.log("\n=== Klingelt ein Anruf auch wirklich? ===\n");
/* =====================================================================
1. DER SERVICE WORKER — die Anzeige auf dem Gerät
===================================================================== */
const sw = lies("workspace/sw.js");
pruef("der Dienst unterscheidet einen Anruf von anderen Meldungen",
/istAnruf\s*=\s*d\.art\s*===\s*"anruf"/.test(sw));
pruef("ein Anruf bleibt stehen, bis jemand reagiert",
/requireInteraction:\s*istAnruf/.test(sw),
"ohne requireInteraction verschwindet er von selbst");
pruef("ein zweiter Anruf meldet sich hörbar (renotify)",
/renotify:\s*istAnruf/.test(sw),
"mit renotify:false ersetzt der zweite den ersten LAUTLOS — genau der Fehler");
pruef("das Handy vibriert beim Anruf",
/vibrate:\s*istAnruf\s*\?/.test(sw));
pruef("jeder Anruf bekommt eine eigene Kennung",
/tag:\s*istAnruf\s*\?\s*\("anruf-"/.test(sw),
"eine gemeinsame Kennung lässt den zweiten Anruf den ersten still ersetzen");
/* DIE ANDEREN MELDUNGEN BLEIBEN, WIE SIE WAREN. Das ist kein
Nebensatz: Würden alle Benachrichtigungen stehenbleiben und
vibrieren, schaltet sie nach drei Tagen jemand ab -- und dann
klingelt auch der Anruf nie wieder. */
pruef("und für alles andere bleibt es ruhig",
/\(d\.art \|\| "workspace"\)/.test(sw) && !/requireInteraction:\s*true\b/.test(sw));
/* =====================================================================
2. DER VERSAND — Dringlichkeit und Haltbarkeit
===================================================================== */
const krypto = nurCode(lies("server/workspace-push-krypto.js"));
const versand = nurCode(lies("server/workspace-push.js"));
pruef("der Transport kennt überhaupt eine Dringlichkeit",
/eilig\s*=\s*false/.test(krypto),
"vorher stand Urgency fest auf \"normal\"");
pruef("ein eiliger Push geht mit Urgency: high hinaus",
/Urgency:\s*eilig\s*\?\s*"high"\s*:\s*"normal"/.test(krypto));
pruef("und verfällt, wenn der Anruf vorbei ist",
/TTL:\s*String\(kurzlebig\s*\?\s*Math\.min\(ttl,\s*\d+\)/.test(krypto),
"sonst ploppt ein Anruf von vor einer Stunde nachträglich auf");
pruef("der Anruf wird eilig UND kurzlebig verschickt",
/schicken\(a,\s*inhalt,\s*paar,\s*ABSENDER,\s*\{\s*eilig,\s*kurzlebig\s*\}\)/.test(versand));
/* ---- ZWEI FRAGEN, ZWEI FELDER (geändert 03.10.2026) -----------------
Hier stand bis heute das Gegenteil: „Darf das nachts stören?" und
„darf das warten?" hätten dieselbe Antwort, zwei getrennte
Bedingungen wären die, die auseinanderlaufen. Die Vorhersage war
richtig, nur in die andere Richtung — sie laufen auseinander, WEIL
es zwei Fragen sind:
Chat um 3 Uhr nachts -> nicht stören (Ruhezeit greift)
Chat um 14 Uhr -> nicht warten (eilig)
Genau diese Gleichsetzung hat Dienes Meldung verursacht: Weil der
Chat nicht nachts stören darf, bekam er auch tagsüber
`Urgency: normal` — und Android legt das beiseite, bis jemand die
App öffnet. Das Zusammenlegen war die Ursache, nicht der Schutz.
Auseinanderlaufen können die beiden trotzdem nicht: Sie stehen als
`eilig` und `ruhe` in DERSELBEN Zeile derselben Artenliste. */
pruef("der Anruf ist eilig und kurzlebig, beides in einer Zeile",
/\{\s*schluessel:\s*"anruf",\s*eilig:\s*true,[^}]*ruhe:\s*"nie"/.test(versand));
pruef("und die Dringlichkeit wird aus der Artenliste abgeleitet",
/const eilig = eiligRegel\(art\);/.test(versand),
"nicht aus einer zweiten Aufzählung daneben");
/* =====================================================================
3. DER WEG DORTHIN — wird beim Anruf überhaupt geweckt?
===================================================================== */
const anruf = lies("server/workspace-anruf.js");
const chat = lies("server/workspace-chat.js");
pruef("ein Anruf weckt alle außer dem Anrufer",
/const wecken = leute\.map\(\(t\) => t\.id\)\.filter\(\(id\) => id !== req\.person\.id\)/.test(anruf));
pruef("und die Art „anruf\" geht bis zur Benachrichtigung durch",
/chatEreignis\(raumId,\s*\{[\s\S]*?art:\s*"klingelt"[\s\S]*?\},\s*wecken,\s*"anruf"\)/.test(anruf));
/* `.size` UND NICHT `.length` (berichtigt 19.09.2026).
Hier stand die alte Form -- und sie war grün, obwohl die Zeile
nichts tat: `zuschauer` hält Mengen, und eine Menge hat kein
`length`. Der Ausdruck war immer `undefined`, also immer falsch,
also wurde nie übersprungen: Wer die Seite offen hatte, bekam
zusätzlich eine Meldung aufs Handy.
Die Prüfung hat den alten Wortlaut bestätigt statt sein Verhalten.
Genau davor warnt die Hausregel vom 01.09.: Ein grüner Haken sagt
nur, dass die Bedingung erfüllt war -- nicht, dass sie das Richtige
geprüft hat. */
/* ==== UND SEIT DEM 30.09.2026 IST ES EINE ANDERE FRAGE ============
Diene im Support: „Die Benachrichtigungen werden nicht angezeigt,
wenn neue Nachrichten reinkommen. Erst, wenn man die App öffnet."
Nachgestellt (pruef-abzeichen, Abschnitt 8): Der Server hat die
Meldung unterdrückt, solange eine VERBINDUNG offen war. Ein Handy
mit der App im Hintergrund hält sie weiter — also galt Diene als
anwesend, während sein Bildschirm schwarz war.
Die Absicht der Zeile darüber bleibt richtig und wird weiter
geprüft: Wer WIRKLICH hinsieht, bekommt keine zweite Meldung.
Gefragt wird nur nicht mehr nach der Verbindung, sondern nach
`siehtZu()` — und das verlangt zusätzlich eine frische Sichtmeldung
der Seite.
UND DIESE PRÜFUNG HAT DEN FEHLER MITGETRAGEN. Sie hielt den
WORTLAUT fest, nicht das Verhalten — genau das, wovor ihr eigener
Kommentar drei Zeilen weiter oben warnt. Deshalb steht hier jetzt
BEIDES: dass gefragt wird, und dass die Frage die richtige ist. */
pruef("wer wirklich hinsieht, bekommt KEINE zusätzliche Meldung",
/if \(siehtZu\(personId\)\) continue;/.test(chat),
"sonst meldet es sich doppelt — der schnellste Weg, dass jemand alles abschaltet");
pruef("und „hinsehen\" heißt mehr als „eine Verbindung offen haben\"",
/function siehtZu[\s\S]{0,400}?sichtbarSeit\.get\(id\)[\s\S]{0,200}?SICHT_FRIST/.test(chat),
"sonst wäre es wieder die alte Frage, nur mit neuem Namen");
/* =====================================================================
DIE GEGENPROBEN
Zu jeder Prüfung, die „in Ordnung" sagen kann, gehört der Beweis,
dass sie auch „nicht in Ordnung" sagen kann.
===================================================================== */
console.log("\n --- Gegenproben (jede MUSS anschlagen) ---");
pruef("Gegenprobe: das alte renotify:false würde auffallen",
!/renotify:\s*istAnruf/.test(' renotify: false,'));
pruef("Gegenprobe: ein festes Urgency würde auffallen",
!/Urgency:\s*dringend\s*\?/.test('headers: { ...kopf, TTL: String(ttl), Urgency: "normal" }'));
pruef("Gegenprobe: eine gemeinsame Kennung würde auffallen",
!/tag:\s*istAnruf\s*\?\s*\("anruf-"/.test(' tag: d.art || "workspace",'));
/* =====================================================================
4. DER ZWEITE WEG — selbst nachfragen (19.09.2026)
Filipe: „wenn vanvan rangeht und redet klingelt es immer noch bei
mir weiter."
Im Protokoll nachgemessen: Sie WAR im Gespräch (24 Sekunden, mit
eigenem `anruf_ende`). Das Ereignis „dabei" wurde also verschickt
und kam beim Anrufer nicht an.
Geprüft und ausgeschlossen: Raumzugehörigkeit, Ereignis-Verkabelung,
Tonsteuerung, Service Worker. Es geht auf einem Weg verloren, der
von hier aus nicht messbar ist.
Deshalb wird die Ursache nicht weiter gesucht, sondern die
ABHÄNGIGKEIT beseitigt: Solange es klingelt, fragt der Anrufer
selbst nach. Der Ereignisstrom bleibt der schnellere erste Weg --
dies ist das Netz darunter.
===================================================================== */
console.log("\n --- Der zweite Weg ---");
const anrufJs = lies("workspace/assets/js/anruf.js");
pruef("der Anrufer fragt selbst nach, ob schon jemand dran ist",
/function nachfrageStarten\(raumId\)/.test(anrufJs),
"ohne das hängt ein klingelndes Telefon an einem einzigen Kanal");
pruef("und zwar ab dem Moment des Wählens",
/klingelWache\(KLINGELT_MS\);[\s\S]{0,160}nachfrageStarten\(raumId\)/.test(anrufJs));
pruef("sie hält an, sobald die Verbindung steht",
/if \(anruf\.beginn\) return nachfrageStoppen\(\)/.test(anrufJs));
/* SIE MUSS AN JEDEM ENDE AUFHOEREN. Eine Schleife, die weiterläuft,
nachdem das Gespräch vorbei ist, fragt den Server alle zwei Sekunden
nach einem Anruf, den es nicht mehr gibt -- und das auf jedem Gerät,
das je telefoniert hat. */
for (const [wo, muster] of [
["beim Auflegen", /async function auflegen\(melden\) \{[\s\S]{0,160}nachfrageStoppen\(\)/],
["wenn die Verbindung steht", /nachfrageStoppen\(\);\s*\/\/ und die Nachfrage/],
["wenn das Ereignis doch ankommt", /d\.art === 'dabei'[\s\S]{0,260}nachfrageStoppen\(\)/],
]) {
pruef(`die Nachfrage hört auf: ${wo}`, muster.test(anrufJs));
}
/* =====================================================================
5. DAS PROTOKOLL MUSS DIE FRAGE BEANTWORTEN KOENNEN
Bis heute standen dort nur Anfang und Ende. Die wichtigste Frage bei
der Suche -- „ist sie ueberhaupt rangegangen?" -- war nur ueber
einen Umweg zu beantworten. Das ist eine Schlussfolgerung, keine
Auskunft.
===================================================================== */
pruef("das Rangehen steht im Protokoll",
/protokolliere\("anruf_dabei"/.test(lies("server/workspace-anruf.js")),
"sonst lässt sich später nicht sagen, OB jemand rangegangen ist");
console.log(`\n ${ok} bestanden, ${fehl} fehlgeschlagen\n`);
process.exit(fehl ? 1 : 0);