From e349de00405fc868d1318feea9e149d5dd9037b6 Mon Sep 17 00:00:00 2001 From: Dogfather Date: Sat, 3 Oct 2026 13:03:29 +0200 Subject: [PATCH] Sendeprotokoll fuer Benachrichtigungen: "ich bekomme nichts" ist jetzt beantwortbar Beim Nachsehen zu Dienes Meldung ist aufgefallen, dass ueber den Versand selbst nichts festgehalten wird. Im Protokoll standen nur push_angemeldet und push_abgemeldet -- kein Wort darueber, ob je etwas verschickt wurde, an wie viele Geraete, und warum nicht. Von neun Aufrufstellen wertete genau eine den Rueckgabewert aus; die anderen acht warfen ihn weg. Sagt jemand "ich bekomme nichts", liess sich also nicht nachsehen, OB gesendet wurde -- dieselbe Falle wie am 06.09. in VanVans Shop, wo zwei Stunden in die Zustellung ermittelt wurden, bevor jemand fragte, ob die Mails abgeschickt waren. Sie waren es, alle, nachweisbar in einer Abfrage. zuletzt_ok reicht dafuer nicht: Es sagt, wann zuletzt irgendetwas ankam -- nicht was, nicht an wen sonst, und nichts ueber die Faelle, in denen gar nicht erst gesendet wurde. Genau die (abgeschaltet, Ruhezeit, kein Geraet) sind die haeufigste Antwort auf die Frage. Neue Tabelle push_versand: Zeit, Person, Art, Grund, Geraete, zugestellt. Festgehalten wird JEDER Ausgang, auch der, bei dem nichts hinausging -- ein Protokoll, das nur Erfolge kennt, kann die Frage nicht beantworten, fuer die es angelegt wurde. Das Protokoll sitzt als Huelle um den Versand, nicht in ihm: Sieben Ausgaenge einzeln zu protokollieren waere eine Liste zum Pflegen, und der achte, den jemand naechstes Jahr einbaut, umginge sie still -- so sind am 11.09. die Spalten beim Tabellenumbau verschwunden. So steht ein neuer Ausgang ohne Zutun mit drin. Aufraeumen nach 30 Tagen, neben dem bestehenden Aufraeumen von push_verschickt. Gemessen statt geschaetzt: rund 16 Chat-Nachrichten am Tag an bis zu acht angemeldete Geraete -- ein paar tausend Zeilen im Monat. Das Schreiben kann den Versand nicht aufhalten (try), meldet sich aber, wenn es scheitert: ein Protokoll, das heimlich nichts schreibt, ist schlimmer als keins. pruef-push-eilig 13 -> 20. Darunter die Gegenprobe, dass auch das NICHT-Senden protokolliert wird, und dass ein Fehlschlag nicht als zugestellt gilt. Datenbank vorher gesichert und die Sicherung geprueft (integrity_check, 20 Personen, 10 Anmeldungen lesbar). Co-Authored-By: Claude Opus 5 --- server/pruef-push-eilig.mjs | 48 +++++++++++++++++++++++++++++ server/workspace-push.js | 60 +++++++++++++++++++++++++++++++++++-- server/workspace.js | 40 +++++++++++++++++++++++++ 3 files changed, 146 insertions(+), 2 deletions(-) diff --git a/server/pruef-push-eilig.mjs b/server/pruef-push-eilig.mjs index d6272945..28780f93 100644 --- a/server/pruef-push-eilig.mjs +++ b/server/pruef-push-eilig.mjs @@ -244,6 +244,54 @@ const kopfVon = async (art) => { `anruf verfaellt nach ${anruf.kopf.ttl} s -- eilig UND kurzlebig, beides zugleich`); } +/* ===================================================================== + Das Sendeprotokoll -- damit "ich bekomme nichts" beantwortbar wird + ===================================================================== */ +console.log("\n=== Was hinausging, steht hinterher da ==="); +{ + const p = new DatabaseSync(process.env.WORKSPACE_DB); + const zaehle = (wo = "1=1", ...w) => + p.prepare(`SELECT COUNT(*) AS n FROM push_versand WHERE ${wo}`).get(...w).n; + + /* Drei Meldungen sind oben wirklich hinausgegangen. */ + const geschickt = zaehle("grund = 'ok'"); + ok(geschickt >= 3, `${geschickt} zugestellte Meldungen stehen im Protokoll (mindestens 3)`); + + const mitGeraet = zaehle("grund = 'ok' AND geraete >= 1 AND zugestellt >= 1"); + ok(mitGeraet === geschickt, + `bei allen ${mitGeraet} steht, an wie viele Geraete (nicht nur DASS)`); + + const arten = p.prepare( + "SELECT DISTINCT art FROM push_versand ORDER BY art").all().map((r) => r.art); + ok(arten.includes("chat_nachricht") && arten.includes("anruf"), + `die Art steht dabei: ${arten.join(", ")}`); + + /* DER EIGENTLICHE ZWECK: auch das NICHT-Senden muss dastehen. + "Abgeschaltet", "Ruhezeit", "kein Geraet" sind die haeufigsten + Antworten auf "ich bekomme nichts" -- ein Protokoll, das nur + Erfolge kennt, kann die Frage nicht beantworten, fuer die es + angelegt wurde. */ + const vorher = zaehle(); + const nichts = await push.benachrichtige(personId, "gibtsnicht", { + titel: "x", text: "x", ziel: "/workspace/start.html", + }); + const nachher = zaehle(); + ok(nichts.verschickt === 0 && nichts.grund === "unbekannte_art", + `es ging nichts hinaus (${nichts.grund})`); + ok(nachher === vorher + 1, + `und genau DAS steht jetzt auch da (${vorher} -> ${nachher})`); + ok(zaehle("grund = 'unbekannte_art'") === 1, + "mit dem Grund, nicht nur als leere Zeile"); + + /* Gegenprobe: Koennte diese Messung ueberhaupt "nein" sagen? Eine + Art, die es nicht gibt, darf nicht als zugestellt gelten -- sonst + waere jede Zeile oben wertlos. */ + ok(zaehle("grund = 'ok' AND art = 'gibtsnicht'") === 0, + "Gegenprobe: der Fehlschlag steht NICHT als zugestellt drin"); + + p.close(); +} + /* ===================================================================== Gegenprobe: der alte Name darf nicht still durchrutschen ===================================================================== */ diff --git a/server/workspace-push.js b/server/workspace-push.js index 573ca6fe..b154e6a9 100644 --- a/server/workspace-push.js +++ b/server/workspace-push.js @@ -614,7 +614,48 @@ export function hausFuerMeldung(personId, ziel) { } } -export async function benachrichtige(personId, art, { titel, text, ziel, merkmal, zahl }) { +/** Verschickt eine Meldung -- und schreibt auf, was dabei herauskam. + * + * WARUM DAS PROTOKOLL AUSSEN SITZT (03.10.2026). + * + * `versenden` hat sieben Ausgaenge: unbekannte_art, abgeschaltet, + * ruhezeit, schon_geschickt, keine_geraete, nicht_zugestellt, ok. + * Haette jeder davon seine eigene Protokollzeile, waere das eine + * Liste zum Pflegen -- und der achte Ausgang, den jemand naechstes + * Jahr einbaut, wuerde sie stillschweigend umgehen. Genau so sind + * die Spalten beim Tabellenumbau am 11.09. verlorengegangen. + * + * Hier wird deshalb nichts aufgezaehlt: Was `versenden` auch immer + * zurueckgibt, wird festgehalten. Ein neuer Ausgang steht ohne + * Zutun mit drin. + * + * UND WARUM UEBERHAUPT: Als Diene meldete, sie bekomme nichts, + * konnte niemand nachsehen, ob je etwas hinausging. Die Frage + * "warum kommt es nicht an?" setzt voraus, dass "wurde es + * abgeschickt?" schon beantwortet ist -- im Shop hat das Ueberspringen + * dieser Reihenfolge am 06.09. zwei Stunden gekostet. + * + * Das Schreiben darf den Versand nie aufhalten: Eine Meldung, die + * ankommt, ist wichtiger als die Notiz darueber. Deshalb steht es + * in einem try -- aber mit Ausgabe, denn ein Protokoll, das + * heimlich nichts schreibt, ist schlimmer als keins. */ +export async function benachrichtige(personId, art, sache) { + const e = await versenden(personId, art, sache); + try { + db().prepare( + "INSERT INTO push_versand (zeit, person_id, art, grund, geraete, zugestellt) " + + "VALUES (?,?,?,?,?,?)") + .run(jetzt(), personId, art, e.grund || "unbekannt", + e.geraete || 0, e.verschickt || 0); + } catch (fehler) { + console.error("[push] Versand nicht protokolliert:", fehler?.message || fehler); + } + return e; +} + +/* Der eigentliche Versand. Umhuellt von `benachrichtige` -- siehe + dort, warum das Protokoll aussen sitzt und nicht hier drin. */ +async function versenden(personId, art, { titel, text, ziel, merkmal, zahl }) { if (!ARTEN_SCHLUESSEL.has(art)) return { verschickt: 0, grund: "unbekannte_art" }; if (!willHaben(personId, art)) return { verschickt: 0, grund: "abgeschaltet" }; @@ -715,7 +756,7 @@ export async function benachrichtige(personId, art, { titel, text, ziel, merkmal Nachricht aufmachen zu muessen -- derselbe Gedanke wie beim Ziel. */ return { verschickt: raus, grund: raus ? "ok" : "nicht_zugestellt", - ziel: zielEcht, haus }; + geraete: anmeldungen.length, ziel: zielEcht, haus }; } /* --------------------------------------------------------------------- @@ -1057,6 +1098,21 @@ export async function laufen() { mehr, und die Tabelle soll nicht ewig wachsen. */ const grenze = new Date(Date.now() - 60 * 86400_000).toISOString(); d.prepare("DELETE FROM push_verschickt WHERE zeit < ?").run(grenze); + + /* Das Sendeprotokoll ebenso -- 30 Tage. + + Die Frist ist kuerzer als oben, weil die beiden Tabellen + verschiedene Fragen beantworten: push_verschickt verhindert + Doppelmeldungen und muss deshalb so lange halten wie die Sache + selbst. Das Protokoll beantwortet "kam letzte Woche etwas an?" + -- wer danach noch fragt, fragt nach etwas anderem. + + Gemessen statt geschaetzt: rund 16 Chat-Nachrichten am Tag an + bis zu acht angemeldete Geraete, also hoechstens ein paar + tausend Zeilen im Monat. Die Tabelle bleibt klein genug, dass + niemand sie je bemerkt. */ + d.prepare("DELETE FROM push_versand WHERE zeit < ?") + .run(new Date(Date.now() - 30 * 86400_000).toISOString()); } catch (fehler) { console.error("[push] Lauf:", fehler?.message); } diff --git a/server/workspace.js b/server/workspace.js index 5a5ab76c..fb9d809f 100644 --- a/server/workspace.js +++ b/server/workspace.js @@ -5216,6 +5216,46 @@ export function db() { ); CREATE INDEX IF NOT EXISTS idx_push_verschickt_zeit ON push_verschickt (zeit); + /* WAS HINAUSGING -- das Sendeprotokoll (03.10.2026). + + ACHTUNG: In diesem Block duerfen keine schraegen Anfuehrungs- + striche stehen, er liegt in einem Template-Literal. Zweimal + hat genau das den Serverstart zerlegt. + + WARUM ES DAS BRAUCHT. Diene meldete "ich bekomme nichts", und + niemand konnte nachsehen, OB etwas hinausging. Es gab nur + push_angemeldet und push_abgemeldet im Protokoll -- kein + Wort darueber, ob je eine Meldung verschickt wurde, an wie + viele Geraete, und was der Dienst geantwortet hat. + + Das ist dieselbe Falle wie am 06.09. in VanVans Shop: Zwei + Stunden Suche nach dem Grund, warum eine Mail nicht ankommt, + bevor jemand fragte, ob sie ueberhaupt abgeschickt wurde. Sie + war es, alle, nachweisbar. Eine einzige Abfrage haette am + Anfang stehen muessen statt am Ende. + + zuletzt_ok in push_anmeldungen reicht dafuer NICHT: Es sagt + nur, wann zuletzt IRGENDETWAS ankam, nicht was, nicht an wen + sonst, und vor allem nichts ueber die Faelle, in denen gar + nicht erst gesendet wurde -- abgeschaltet, Ruhezeit, kein + Geraet. Genau die sind die haeufigste Antwort auf "ich + bekomme nichts". + + Deshalb wird JEDER Ausgang festgehalten, auch der, bei dem + nichts hinausging. Ein Protokoll, das nur Erfolge kennt, kann + die Frage nicht beantworten, fuer die es angelegt wurde. */ + CREATE TABLE IF NOT EXISTS push_versand ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + zeit TEXT NOT NULL, + person_id INTEGER NOT NULL REFERENCES personen(id) ON DELETE CASCADE, + art TEXT NOT NULL, + grund TEXT NOT NULL, + geraete INTEGER NOT NULL DEFAULT 0, + zugestellt INTEGER NOT NULL DEFAULT 0 + ); + CREATE INDEX IF NOT EXISTS idx_push_versand_zeit ON push_versand (zeit); + CREATE INDEX IF NOT EXISTS idx_push_versand_person ON push_versand (person_id, zeit); + /* Rueckmeldungen zu einer Aufgabe (Konzept: "Aufgaben & Feedback"). Ohne sie endet jede Rueckfrage ausserhalb des Systems -- in WhatsApp, und damit ausserhalb dessen, was spaeter noch