From 73facb7ce929d470c1b595aab41efd86b4b484c9 Mon Sep 17 00:00:00 2001 From: Dogfather Date: Tue, 22 Sep 2026 23:36:02 +0200 Subject: [PATCH] Nachtrag zu B4: die Aufraeumung lief nie -- .changes an der falschen Stelle WAS PASSIERT IST Der Commit 60802170 ist um 23:33 ausgeliefert worden. Im Protokoll des Servers stand danach: [workspace] undefined zurueckgenommene Nachrichten endgueltig entfernt. [workspace] 4 blaue Herzen auf babyblau umgestellt Die zweite Zeile stimmt. Die erste ist der Befund: `undefined` statt einer Zahl. Nachgesehen in der Live-Datenbank -- die fuenf zurueckgenommenen Zeilen waren noch da. URSACHE: `d.prepare("DELETE ...").changes` statt `.run().changes` -- also die Eigenschaft der vorbereiteten ANWEISUNG, nicht die des Laufs. Die Anweisung wurde vorbereitet und nie ausgefuehrt. Nichts stuerzte ab, nichts war rot, kein Fehler im Protokoll. NEBENFOLGE, die es schlimmer gemacht haette: Die Sicherung wird VOR dem Loeschen geschrieben. Da nie geloescht wurde, blieb die Bedingung erfuellt -- bei JEDEM Neustart waere eine weitere Sicherungsdatei entstanden. Genau das, wovor der Kommentar zwei Zeilen darueber warnt. Ein Kommentar, der vor einem Fehler warnt, verhindert ihn nicht. WAS JETZT ANDERS IST Behoben, und die Umstellung ist eine exportierte Funktion geworden: `loeschspurenAufraeumen(d, dbPfad)`. Nicht aus Ordnungsliebe -- eine Umstellung, die tief im Hochfahren steckt, kann keine Pruefung aufrufen. Eine Funktion schon. pruef-loeschen.mjs (20 -> 25): stellt den alten Zustand her (Text leer, weg_am gesetzt), ruft die Funktion und misst * dass sie eine ZAHL meldet (`typeof === "number"`) -- genau hier stand undefined, und 0 sieht in einer Meldung fast so aus, * dass die Zeile wirklich verschwindet, * und dass beim zweiten Aufruf nichts mehr passiert und nichts gesichert wird. Ohne den ersten dieser drei Haken waere der Fehler auch beim naechsten Mal durchgegangen: Eine Meldung mit `undefined` ist gruen, solange niemand sie liest. Co-Authored-By: Claude Opus 5 --- server/pruef-loeschen.mjs | 54 +++++++++++++++ server/workspace.js | 135 ++++++++++++++++++++++---------------- 2 files changed, 134 insertions(+), 55 deletions(-) diff --git a/server/pruef-loeschen.mjs b/server/pruef-loeschen.mjs index 0daf8e04..c9706b0e 100644 --- a/server/pruef-loeschen.mjs +++ b/server/pruef-loeschen.mjs @@ -286,6 +286,60 @@ ok(/Nachricht zurückgenommen/.test(chatJs), "Gegenprobe: der Satz kommt in den Kommentaren durchaus vor —" + " die Suche oben unterscheidet also wirklich"); +/* ===================================================================== + 6. DIE UMSTELLUNG RAEUMT WIRKLICH AUF + + ENTSTANDEN AUS EINEM FEHLER, DER SCHON LIVE WAR (22.09.2026, 23:33). + + In der ersten Fassung stand `d.prepare("DELETE ...").changes` statt + `.run().changes` -- die Eigenschaft der vorbereiteten ANWEISUNG, + nicht die des Laufs. Die Anweisung wurde vorbereitet und nie + ausgefuehrt. + + NICHTS STUERZTE AB, NICHTS WAR ROT. Der einzige Hinweis war ein Wort + im Protokoll: "undefined zurueckgenommene Nachrichten endgueltig + entfernt". Haette dort niemand hingesehen, waeren die fuenf Zeilen + liegen geblieben -- und weil die Sicherung VOR dem Loeschen + geschrieben wird, waere bei jedem Neustart eine weitere + Sicherungsdatei entstanden. + + DESHALB WIRD HIER DIE ZAHL GEPRUEFT, nicht nur das Ergebnis. `0` + und `undefined` sehen in einer Meldung fast gleich aus; in einer + Bedingung nicht. + ===================================================================== */ +console.log(""); +console.log("--- Die Umstellung fuer alte Zeilen ---"); + +{ + const { loeschspurenAufraeumen } = await import("./workspace.js"); + const idAlt = await schreiben(keksModi, "Aus einer alten Fassung"); + /* Den alten Zustand von Hand herstellen: Text leer, weg_am gesetzt. + Genau so sahen die fuenf Zeilen in der echten Datenbank aus. */ + d.prepare("UPDATE chat_nachrichten SET text = '', weg_am = ? WHERE id = ?") + .run(new Date().toISOString(), idAlt); + const davor = d.prepare( + "SELECT COUNT(*) AS n FROM chat_nachrichten WHERE weg_am IS NOT NULL AND text = ''").get().n; + ok(davor === 1, `eine alte zurueckgenommene Zeile steht da (${davor})`); + + /* OHNE PFAD: Dann schreibt die Funktion keine Sicherung -- hier wird + das Aufraeumen gemessen, nicht das Sichern. */ + const erg = loeschspurenAufraeumen(d, null); + ok(typeof erg.entfernt === "number", + `sie meldet eine ZAHL (${typeof erg.entfernt}: ${erg.entfernt})` + + " — genau hier stand vorher undefined"); + ok(erg.entfernt === 1, `und zwar die richtige (${erg.entfernt})`); + const danach = d.prepare( + "SELECT COUNT(*) AS n FROM chat_nachrichten WHERE weg_am IS NOT NULL AND text = ''").get().n; + ok(danach === 0, `die Zeile ist wirklich weg (${danach} uebrig)`); + + /* WIEDERHOLBAR: Beim zweiten Mal gibt es nichts zu tun, und es wird + auch nichts gesichert. Ohne diese Zeile waere nicht bewiesen, dass + bei jedem Neustart nicht doch wieder etwas passiert. */ + const nochmal = loeschspurenAufraeumen(d, null); + ok(nochmal.entfernt === 0 && nochmal.gesichert === null, + `beim zweiten Mal passiert nichts (${nochmal.entfernt}, Sicherung ${nochmal.gesichert})`); +} + /* ---------- Ende ------------------------------------------------------ */ d.close(); try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Windows haelt manchmal */ } diff --git a/server/workspace.js b/server/workspace.js index c40ed239..7fa9bb3f 100644 --- a/server/workspace.js +++ b/server/workspace.js @@ -2191,6 +2191,85 @@ export function kanaeleFuer(person) { ? KANAELE : []; } +/* ===================================================================== + DIE ALTEN ZURUECKGENOMMENEN NACHRICHTEN GEHEN GANZ (22.09.2026) + ===================================================================== + + Filipe: "Geloeschte Nachrichten verschwinden vollstaendig -- keine + Spur, kein ‚wurde geloescht'-Hinweis, bei niemandem, auch nicht bei + DogFather." + + Seit dem 22.09.2026 loescht der Chat wirklich (DELETE). Was davor + zurueckgenommen wurde, steht aber noch als Zeile da: Text leer, + `weg_am` gesetzt. Die Anzeige zeigt sie nicht mehr -- in der + Datenbank waere sie trotzdem. "Keine Spur" gaelte dann fuer alles, + was ab heute passiert, und nicht fuer das, was ihn zu dem Satz + gebracht hat. + + ES GEHT NUR UM ZEILEN OHNE INHALT. `text = ''` ist die Bedingung, + nicht bloss `weg_am IS NOT NULL`: Der Text wurde beim Zuruecknehmen + geleert, also ist hier nichts zu verlieren. Stuende irgendwo doch + noch Text in einer zurueckgenommenen Zeile, bliebe sie liegen -- + lieber ein Rest, den jemand ansehen kann, als ein stiller Verlust. + + WIEDERHOLBAR: Beim zweiten Start gibt es nichts mehr zu tun, und es + passiert nichts. GESICHERT WIRD VORHER -- aber nur, wenn es wirklich + etwas zu tun gibt. Eine Sicherung bei jedem Start waere eine Datei + je Neustart und damit kein Schutz, sondern Muell. + + --------------------------------------------------------------------- + ALS EIGENE, EXPORTIERTE FUNKTION -- und das hat einen Grund, der an + genau dieser Stelle entstanden ist. + + In der ersten Fassung stand `d.prepare("DELETE ...").changes` statt + `.run().changes`: die Eigenschaft der vorbereiteten ANWEISUNG, nicht + die des Laufs. Die Anweisung wurde vorbereitet und nie ausgefuehrt. + Nichts stuerzte ab, nichts war rot. Im Protokoll stand "undefined + zurueckgenommene Nachrichten endgueltig entfernt" -- ein Zaehler, + der `undefined` sagt, hat nicht gezaehlt, und was nicht zaehlt, hat + meist auch nicht gearbeitet. Die fuenf Zeilen blieben liegen, und + weil die Sicherung VOR dem Loeschen geschrieben wird, waere bei + jedem Neustart eine weitere Sicherungsdatei entstanden. + + Eine Umstellung, die tief im Hochfahren steckt, kann keine Pruefung + aufrufen. Eine Funktion schon: pruef-loeschen.mjs gibt ihr eine + vorbereitete Datenbank und sieht nach, was sie WIRKLICH getan hat. + + @returns {{entfernt:number, gesichert:string|null}} + ===================================================================== */ +export function loeschspurenAufraeumen(d, dbPfad = DB_PFAD) { + try { + const spalten = d.prepare("PRAGMA table_info(chat_nachrichten)").all().map((x) => x.name); + if (!spalten.includes("weg_am")) return { entfernt: 0, gesichert: null }; + const offen = d.prepare( + "SELECT COUNT(*) AS n FROM chat_nachrichten WHERE weg_am IS NOT NULL AND text = ''").get().n; + if (offen === 0) return { entfernt: 0, gesichert: null }; + + let sicherung = null; + if (dbPfad) { + const stempel = new Date().toISOString().replace(/[-:T]/g, "").slice(0, 14); + sicherung = `${dbPfad}.vor-loeschspuren-${stempel}`; + d.exec(`VACUUM INTO '${sicherung.replace(/'/g, "''")}'`); + console.log("[workspace] Sicherung vor dem Aufraeumen:", sicherung); + } + /* Reaktionen und Erwaehnungen haengen mit ON DELETE CASCADE daran + und gehen von selbst mit -- `PRAGMA foreign_keys = ON` steht + beim Oeffnen. */ + const entfernt = d.prepare( + "DELETE FROM chat_nachrichten WHERE weg_am IS NOT NULL AND text = ''").run().changes; + /* Und die Raeume rechnen ihren letzten Zeitpunkt neu -- sonst + stuende einer oben in der Liste, zu dessen Zeitpunkt es nichts + mehr gibt. */ + d.exec(`UPDATE chat_raeume SET letzte_am = + (SELECT MAX(erstellt) FROM chat_nachrichten WHERE raum_id = chat_raeume.id)`); + console.log(`[workspace] ${entfernt} zurueckgenommene Nachrichten endgueltig entfernt.`); + return { entfernt, gesichert: sicherung }; + } catch (fehler) { + console.error("[workspace] Aufraeumen der Loeschspuren:", fehler?.message); + return { entfernt: 0, gesichert: null }; + } +} + export function kategorienFuer(person) { if (!person) return []; return TEAM_DOGI_ROLLEN.has(person.rolle) || person.rolle === "admin" @@ -3459,61 +3538,7 @@ function umstellungen(d) { } } - /* ===================================================================== - DIE ALTEN ZURUECKGENOMMENEN NACHRICHTEN GEHEN GANZ (22.09.2026) - ===================================================================== - - Filipe: „Geloeschte Nachrichten verschwinden vollstaendig -- keine - Spur, kein ‚wurde geloescht'-Hinweis, bei niemandem, auch nicht - bei DogFather." - - Seit heute loescht der Chat wirklich (DELETE). Was davor - zurueckgenommen wurde, steht aber noch als Zeile da: Text leer, - `weg_am` gesetzt. Die Anzeige zeigt sie nicht mehr -- in der - Datenbank waere sie trotzdem. „Keine Spur" gilt dann fuer alles, - was ab heute passiert, und nicht fuer das, was ihn zu dem Satz - gebracht hat. - - ES GEHT NUR UM ZEILEN OHNE INHALT. `text = ''` ist die Bedingung, - nicht bloss `weg_am IS NOT NULL`: Der Text wurde beim Zuruecknehmen - geleert, also ist hier nichts zu verlieren. Stuende irgendwo doch - noch Text in einer zurueckgenommenen Zeile, bliebe sie liegen -- - lieber ein Rest, den jemand ansehen kann, als ein stiller Verlust. - - WIEDERHOLBAR: Beim zweiten Start gibt es nichts mehr zu tun, und - es passiert nichts. Deshalb steht es hier bei den Umstellungen - und nicht in einem einmaligen Skript, das jemand vergessen - koennte. - - GESICHERT WIRD VORHER -- aber nur, wenn es wirklich etwas zu tun - gibt. Eine Sicherung bei jedem Start waere eine Datei je Neustart - und damit kein Schutz, sondern Muell. */ - try { - const spalten = d.prepare("PRAGMA table_info(chat_nachrichten)").all().map((x) => x.name); - if (spalten.includes("weg_am")) { - const offen = d.prepare( - "SELECT COUNT(*) AS n FROM chat_nachrichten WHERE weg_am IS NOT NULL AND text = ''").get().n; - if (offen > 0) { - const stempel = new Date().toISOString().replace(/[-:T]/g, "").slice(0, 14); - const sicherung = `${DB_PFAD}.vor-loeschspuren-${stempel}`; - d.exec(`VACUUM INTO '${sicherung.replace(/'/g, "''")}'`); - console.log("[workspace] Sicherung vor dem Aufraeumen:", sicherung); - /* Reaktionen und Erwaehnungen haengen mit ON DELETE CASCADE - daran und gehen von selbst mit -- `PRAGMA foreign_keys = ON` - steht beim Oeffnen. Nachgesehen, nicht angenommen. */ - const weg = d.prepare( - "DELETE FROM chat_nachrichten WHERE weg_am IS NOT NULL AND text = ''").changes; - /* Und die Raeume rechnen ihren letzten Zeitpunkt neu -- sonst - stuende einer oben in der Liste, zu dessen Zeitpunkt es - nichts mehr gibt. */ - d.exec(`UPDATE chat_raeume SET letzte_am = - (SELECT MAX(erstellt) FROM chat_nachrichten WHERE raum_id = chat_raeume.id)`); - console.log(`[workspace] ${weg} zurueckgenommene Nachrichten endgueltig entfernt.`); - } - } - } catch (fehler) { - console.error("[workspace] Aufraeumen der Loeschspuren:", fehler?.message); - } + loeschspurenAufraeumen(d); /* ===================================================================== DIE BLAUEN HERZEN WERDEN BABYBLAU (22.09.2026)