Die Agentur-Umstellung schreibt den Bauplan nicht mehr ab

Sie baute `eintraege` mit einer von Hand abgeschriebenen Spaltenliste
neu. 24 Spalten gingen hinein, 21 kamen heraus: einsatz, nur_leitung
und gesendet_am wurden vom Spalten-Nachtrag angelegt und unmittelbar
danach weggeworfen -- mit Inhalt, ohne Fehlermeldung, bei
unveraenderter Zeilenzahl.

DAS WAR DAS ZWEITE MAL. Am 07.09.2026 fehlten an derselben Stelle die
drei Event-Spalten; sie wurden nachgetragen, und daneben kam ein
Kommentar, der woertlich vor genau dieser Verlustart warnt. Seither
kamen drei neue Spalten dazu, und die Liste wurde still falsch. An
einer anderen Stelle im selben Modul steht sogar schon der Satz: "Eine
vierte Abschrift waere die vierte Gelegenheit dazu."

Jetzt uebernimmt checkListeErweitern -- dieselbe Funktion, die alle
spaeteren Bereiche umstellt. Sie holt den Bauplan aus sqlite_master
und die Spalten aus PRAGMA table_info: eine Liste, die nicht gepflegt
wird, kann nicht veralten. Sichern, Zeilen innerhalb der Transaktion
zaehlen, Indizes mitnehmen, Verweise pruefen -- alles, was der Block
auch tat. 90 Zeilen weniger.

NACHGEMESSEN, in dieser Reihenfolge:
- pruef-agentur: 31 Fehler + Absturz -> 62 Pruefungen, alle gruen.
- Die Umstellung meldet jetzt 24 Spalten statt 21.
- Der echte Fall ist durchgespielt: Auf einer Datenbank, die 'agentur'
  schon kennt, laeuft sie GAR NICHT mehr an. Neue Pruefung dazu, die
  die Sicherungsdateien zaehlt (genau eine, trotz mehrerer Neustarts)
  -- gemessen an der Spur, die ein Umbau hinterlaesst, nicht an der
  Absicht.
- Die Datenbank auf dem Server hat alle drei Spalten und kennt
  'agentur' bereits. Sie war nie in Gefahr; gefaehrlich war das
  Zurueckspielen einer Sicherung von vor dem 06.09.2026.

ZWEI ALTE PRUEFFEHLER LAGEN DAHINTER, beide bisher von einem Absturz
verdeckt:
- "der Wochenbericht kennt 6 Bereiche" -- eine feste Zahl, inzwischen
  sind es elf. Verglichen wird jetzt gegen die CHECK-Regel der
  Datenbank; damit stimmt sie auch beim zwoelften Bereich.
- Die Meldung wurde am Satzbau erkannt statt an der Aussage.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-11 14:43:14 +02:00
co-authored by Claude Opus 5
parent c98aa06554
commit b34586be91
2 changed files with 93 additions and 92 deletions
+61 -4
View File
@@ -26,7 +26,7 @@
schlimmere Ausgang. schlimmere Ausgang.
===================================================================== */ ===================================================================== */
import { mkdtempSync, rmSync, appendFileSync, writeFileSync } from "node:fs"; import { mkdtempSync, rmSync, appendFileSync, writeFileSync, readdirSync } from "node:fs";
import { tmpdir } from "node:os"; import { tmpdir } from "node:os";
import { join, dirname } from "node:path"; import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
@@ -259,7 +259,13 @@ let ausgabe = "";
ausgabe = aus(); ausgabe = aus();
} }
ok(/Bereich 'agentur' freigeschaltet/.test(ausgabe), /* NICHT AUF DEN WORTLAUT FESTNAGELN (11.09.2026). Hier stand
/Bereich 'agentur' freigeschaltet/ -- der Satz des alten, von Hand
abgeschriebenen Blocks. Seit die Umstellung ueber
checkListeErweitern laeuft, heisst es "'agentur' in
eintraege.bereich freigeschaltet". Gemeint ist beides Mal dasselbe;
geprueft wird deshalb die AUSSAGE, nicht der Satzbau. */
ok(/agentur.*freigeschaltet/.test(ausgabe),
"die Anwendung meldet die Umstellung" "die Anwendung meldet die Umstellung"
+ (/agentur/.test(ausgabe) ? "" : ` — Ausgabe: ${ausgabe.slice(-300)}`)); + (/agentur/.test(ausgabe) ? "" : ` — Ausgabe: ${ausgabe.slice(-300)}`));
@@ -585,8 +591,37 @@ melde("\n=== Ueberall, wo Bereiche vorkommen ===");
vorgekommen, ohne Fehler und ohne Luecke. */ vorgekommen, ohne Fehler und ohne Luecke. */
const { daten } = await ruf("GET", "/workspace/api/report", keksDogi); const { daten } = await ruf("GET", "/workspace/api/report", keksDogi);
const b = daten?.bereiche || {}; const b = daten?.bereiche || {};
ok(Object.keys(b).length === 6, /* ABGELEITET STATT GEZAEHLT (11.09.2026). Hier stand `=== 6`. Das
`der Wochenbericht kennt ${Object.keys(b).length} Bereiche: ${Object.keys(b).join(", ")}`); war richtig, als es sechs Bereiche gab; inzwischen sind es elf,
und die Zeile war seither still falsch -- versteckt hinter einem
Absturz weiter oben, der den Lauf vorher beendete. Dieselbe feste
Zahl, dieselbe Falle wie in der Umstellung, die diese Datei
aufgedeckt hat.
Verglichen wird jetzt gegen die Bereiche, die die DATENBANK
ueberhaupt zulaesst -- die CHECK-Regel ist die einzige Liste, an
der niemand vorbeikommt. Damit stimmt die Pruefung auch beim
zwoelften Bereich noch, ohne dass jemand sie anfasst. */
const erlaubt = (() => {
const dd = new DatabaseSync(DB, { readOnly: true });
const plan = dd.prepare(
"SELECT sql FROM sqlite_master WHERE type='table' AND name='eintraege'").get()?.sql || "";
dd.close();
/* Ohne regulaeren Ausdruck: Der Bauplan enthaelt die Regel
woertlich als "bereich IN (...)". Ein Muster mit Klammern und
Leerzeichenklassen waere hier nur eine weitere Stelle, an der
man sich vertun kann. */
const auf = plan.indexOf("bereich IN (");
if (auf < 0) return [];
const zu = plan.indexOf(")", auf);
if (zu < 0) return [];
return plan.slice(auf + "bereich IN (".length, zu)
.split(",").map((t) => t.trim().split("'").join(""));
})();
const fehlend = erlaubt.filter((n) => !(n in b));
ok(erlaubt.length > 0 && fehlend.length === 0,
`der Wochenbericht kennt alle ${erlaubt.length} Bereiche der Datenbank`
+ (fehlend.length ? ` -- es fehlen: ${fehlend.join(", ")}` : ""));
ok(b.agentur && b.agentur.neu >= ARTEN.length, ok(b.agentur && b.agentur.neu >= ARTEN.length,
`und zaehlt ${b.agentur?.neu} neue Agentur-Eintraege`); `und zaehlt ${b.agentur?.neu} neue Agentur-Eintraege`);
} }
@@ -741,6 +776,28 @@ melde("\n=== Die Kachel ===");
await browser.close(); await browser.close();
} }
/* =======================================================================
6. UND SIE LAEUFT GENAU EINMAL (11.09.2026)
=======================================================================
Der Fall, der auf dem echten Server gilt: Dort kennt die CHECK-Regel
'agentur' laengst. Ein Neustart darf die Tabelle dann NICHT noch
einmal umbauen -- jeder Umbau ist ein Kopieren von Hand auf echten
Daten, und was man nicht tun muss, tut man auf einer Live-Datenbank
nicht.
GEMESSEN AN DEN SICHERUNGEN, nicht an der Absicht: Vor jedem Umbau
legt die Anwendung eine Datei ".vor-agentur-<stempel>" an. Diese
Datei ist der Beweis, dass umgebaut wurde. Waeren es zwei, haette
ein Neustart erneut angefasst -- und genau das soll hier auffallen.
Der Server ist zwischendurch mehrfach neu gestartet worden, der Fall
ist also wirklich durchgespielt und nicht nur behauptet. */
{
const spuren = readdirSync(ordner).filter((n) => n.includes(".vor-agentur-"));
ok(spuren.length === 1,
`die Agentur-Umstellung lief genau EINMAL, trotz mehrerer Neustarts `
+ `(${spuren.length} Sicherung(en))`);
}
await serverStoppen(); await serverStoppen();
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ } try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ }
+32 -88
View File
@@ -1826,96 +1826,40 @@ function umstellungen(d) {
Derselbe Umbau wie bei "abgebrochen" darueber -- ein CHECK laesst Derselbe Umbau wie bei "abgebrochen" darueber -- ein CHECK laesst
sich in SQLite nicht aendern, die Tabelle muss neu gebaut werden: sich in SQLite nicht aendern, die Tabelle muss neu gebaut werden:
erst sichern, dann tauschen, Zeilen zaehlen, Verweise pruefen. */ erst sichern, dann tauschen, Zeilen zaehlen, Verweise pruefen. */
const eintraegePlan = d.prepare( /* ---- Der sechste Bereich: "agentur" (06.09.2026) ----
"SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'eintraege'").get()?.sql || ""; ABGELEITET STATT ABGESCHRIEBEN (11.09.2026).
if (eintraegePlan && !eintraegePlan.includes("'agentur'")) {
const sicherung = `${DB_PFAD}.vor-agentur-${jetztStempel}`;
try {
d.exec(`VACUUM INTO '${sicherung.replace(/'/g, "''")}'`);
console.log("[workspace] Sicherung vor der Umstellung:", sicherung);
} catch (fehler) {
console.error("[workspace] Sicherung fehlgeschlagen, Umstellung abgebrochen:", fehler?.message);
return;
}
d.exec("PRAGMA foreign_keys = OFF"); Hier stand bis heute der Bauplan der Tabelle von Hand abgeschrieben
try { -- alle Spalten, zweimal (einmal fuer CREATE, einmal fuer INSERT).
const vorher = d.prepare("SELECT COUNT(*) AS n FROM eintraege").get().n; Am 07.09.2026 fiel auf, dass dabei drei Event-Spalten fehlten; sie
d.exec("BEGIN"); wurden nachgetragen, und daneben kam ein Kommentar, der genau vor
d.exec(` dieser Verlustart warnt.
CREATE TABLE eintraege_neu (
id INTEGER PRIMARY KEY AUTOINCREMENT,
bereich TEXT NOT NULL
CHECK (bereich IN ('live','content','technik','community','schutz','agentur')),
art TEXT NOT NULL,
titel TEXT NOT NULL,
text TEXT,
datum TEXT NOT NULL,
bewertung INTEGER CHECK (bewertung IS NULL OR (bewertung BETWEEN 1 AND 5)),
dringlichkeit TEXT NOT NULL DEFAULT 'mittel'
CHECK (dringlichkeit IN ('hoch','mittel','niedrig')),
status TEXT NOT NULL DEFAULT 'offen'
CHECK (status IN ('offen','erledigt')),
creator_id INTEGER REFERENCES personen(id) ON DELETE SET NULL,
erstellt TEXT NOT NULL,
erstellt_von INTEGER REFERENCES personen(id) ON DELETE SET NULL,
geaendert TEXT,
hook TEXT,
format TEXT,
saeule_id INTEGER,
geplant TEXT,
creator_extern TEXT,
/* Die drei Event-Spalten MUESSEN hier mit stehen (07.09.2026).
Der Spalten-Nachtrag weiter oben laeuft frueher als dieser DIESELBE FALLE HAT DANACH EIN ZWEITES MAL ZUGESCHNAPPT. Seit dem
Neubau. Auf einer Datenbank, die noch den alten CHECK hat 07.09. kamen `einsatz`, `nur_leitung` und `gesendet_am` dazu. Der
-- eine Sicherung von vor dem 06.09., in einen heutigen Spalten-Nachtrag legt sie an, dieser Neubau warf sie unmittelbar
Stand eingespielt --, waeren die drei Spalten also erst danach weg -- mit Inhalt, ohne Fehlermeldung, bei unveraenderter
angelegt und hier sofort wieder weggeworfen worden: mit Zeilenzahl. Gemessen: 24 Spalten hinein, 21 heraus.
allem, was drinsteht, ohne Fehlermeldung, und die
Zeilenzahl haette weiterhin gestimmt. Genau die Gefunden hat es pruef-agentur, und zwar seit Tagen: 31-mal "FEHL"
Verlustart, vor der der Kommentar zu dieser Umstellung mit HTTP 503. Sagen konnte sie es erst, seit sie die
warnt. */ Serverausgabe zeigt -- dort stand es dann in drei Zeilen
event_ende TEXT, untereinander.
event_aufgaben TEXT,
event_regeln TEXT DESHALB WIRD NICHTS MEHR ABGESCHRIEBEN. `checkListeErweitern` holt
); den Bauplan aus sqlite_master und die Spaltenliste aus
INSERT INTO eintraege_neu PRAGMA table_info -- sie kann gar nicht veralten, weil sie nicht
(id, bereich, art, titel, text, datum, bewertung, dringlichkeit, status, gepflegt wird. Sie sichert vorher, zaehlt die Zeilen innerhalb der
creator_id, erstellt, erstellt_von, geaendert, Transaktion, nimmt die Indizes mit und prueft die Verweise, also
hook, format, saeule_id, geplant, creator_extern, alles, was der Block hier auch tat.
event_ende, event_aufgaben, event_regeln)
SELECT id, bereich, art, titel, text, datum, bewertung, dringlichkeit, status, Die echten Daten auf dem Server sind nicht betroffen: Ihr CHECK
creator_id, erstellt, erstellt_von, geaendert, kennt 'agentur' laengst, die Umstellung laeuft dort nie wieder.
hook, format, saeule_id, geplant, creator_extern, Gefaehrlich war es beim Zurueckspielen einer Sicherung von vor dem
event_ende, event_aufgaben, event_regeln 06.09.2026 -- also genau dann, wenn man sich am wenigsten einen
FROM eintraege; stillen Datenverlust leisten kann. */
DROP TABLE eintraege; checkListeErweitern(d, "eintraege", "bereich", "agentur",
ALTER TABLE eintraege_neu RENAME TO eintraege; ["live", "content", "technik", "community", "schutz", "agentur"], jetztStempel);
CREATE INDEX IF NOT EXISTS idx_eintraege_bereich ON eintraege (bereich, creator_id);
`);
const nachher = d.prepare("SELECT COUNT(*) AS n FROM eintraege").get().n;
if (nachher !== vorher) {
d.exec("ROLLBACK");
console.error(`[workspace] Umstellung abgebrochen: ${vorher} Eintraege vorher, `
+ `${nachher} nachher. Sicherung: ${sicherung}`);
} else {
d.exec("COMMIT");
const kaputt = d.prepare("PRAGMA foreign_key_check").all();
if (kaputt.length) {
console.error("[workspace] ACHTUNG: nach der Umstellung", kaputt.length,
"verwaiste Verweise. Sicherung liegt unter", sicherung);
} else {
console.log(`[workspace] Bereich 'agentur' freigeschaltet, ${vorher} Eintraege, Verweise geprueft.`);
}
}
} catch (fehler) {
try { d.exec("ROLLBACK"); } catch { /* schon zurueckgerollt */ }
console.error("[workspace] Umstellung 'agentur' fehlgeschlagen:", fehler?.message);
} finally {
d.exec("PRAGMA foreign_keys = ON");
}
}
/* ---- Rolle "manager" erlauben ---- */ /* ---- Rolle "manager" erlauben ---- */
const bauplan = d.prepare( const bauplan = d.prepare(