Files
dogfather-universe/server/pruef-tagesgrenze.mjs
T
DogFatherGitandClaude Opus 5 365e789cce Tagesgrenze: jetzt auch die Vergleiche, nicht nur das Schreiben
Von den sechzehn Stellen vom 01.10. SCHREIBEN zehn einen Tag in die
Datenbank — die decken die zwei Abschnitte von vorhin ab. Sechs
VERGLEICHEN gegen „heute": ist diese Frist vorbei, faellt dieser
Lead in den Zeitraum, ist dieser Bericht aktuell.

Davon findet ein Schreibtest keinen einzigen. Die Spalte sieht
richtig aus; falsch ist die Zahl, die jemand auf dem Bildschirm
liest.

ZWEI AUFGABEN GENUEGEN. Die Regel im Haus ist `frist < heute`:

    Frist = gestern (Ortszeit)  ->  muss ueberfaellig sein
    Frist = heute   (Ortszeit)  ->  darf es nicht sein

Mit dem alten UTC-Tag geht das in BEIDEN Zweigen der verschobenen
Uhr schief:

    Etc/GMT-14 (Ortstag = UTC+1): heute_alt waere gestern -> 0 statt 1
    Etc/GMT+11 (Ortstag = UTC-1): heute_alt waere morgen  -> 2 statt 1

GEGENPROBE AM ECHTEN CODE: In workspace-aufgaben.js den UTC-Tag
wieder eingebaut -> „genau eine ist ueberfaellig (0)". Zurueckgedreht
und wieder gruen.

UND EIN BEFUND AN MEINER EIGENEN PRUEFUNG, bei genau dieser
Gegenprobe: Die zweite Zahl (`heute`) blieb 1 — aber sie zaehlte die
FALSCHE Aufgabe. Mit dem UTC-Tag galt die von gestern als heute
faellig. Mit zwei Zahlen allein ist das nicht zu trennen; in beiden
Zweigen bleibt sie 1.

Sie steht jetzt ausdruecklich als BEGLEITPROBE da: Sie zeigt, dass
ueberhaupt gezaehlt wird, und der Unterschied kommt aus der Zeile
darueber. Eine Zahl, die aus dem falschen Grund stimmt, soll nicht
aussehen wie ein Beweis.

Dazu im Lauf eine ausgerechnete Zeile, die sagt, WARUM die Zahlen
etwas beweisen: „mit dem UTC-Tag waeren es 0 statt 1".

IM FENSTER NACHGEMESSEN (00:09 bis 00:40 Ortszeit, UTC noch der
Vortag) — die restlichen Pruefungen, die ich gestern zur falschen
Tageszeit geprueft hatte: zuteilung 75, aufgabenbrett 49,
content 45, vorlagen 24, bewerbung 91, scout-zuteilung 37,
unterstuetzung 70, aufbewahrung 45, ics 38, entwicklung 79 — alle 0
Fehler. Zusammen mit den vierzehn von vorhin sind das 24 Dateien,
die diesmal im richtigen Zeitfenster gemessen wurden.

pruef-tagesgrenze: 8 -> 12 Pruefungen, 0 Fehler.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-02 00:36:44 +02:00

255 lines
12 KiB
JavaScript
Raw 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-tagesgrenze.mjs — der Tageswechsel, zu jeder Uhrzeit prüfbar
---------------------------------------------------------------------
DAS PROBLEM MIT DEN PRÜFUNGEN VON GESTERN
Am 01.10.2026 wurde ein Fehler gefunden, der sechzehn Stellen im
Haus betraf: Ein Kalendertag wurde aus UTC gebildet statt aus der
Ortszeit. Zwischen 00:00 und 02:00 (im Winter 00:00 bis 01:00)
liegt der UTC-Tag einen zurück — ein Termin, nachts angelegt, trug
gestern und landete auf dem Brett unter „Vorbei", wo er
zugeklappt ist.
Repariert, und mit `pruef-kreislauf` abgesichert:
ok und er liegt HEUTE, nicht gestern (…)
ABER: Diese Zeile kann den Fehler nur ZWEI STUNDEN AM TAG
überhaupt sehen. Den Rest des Tages sind Ortszeit und UTC-Tag
derselbe, und die Behauptung ist wahr, ohne irgendetwas zu
beweisen. Genau die Sorte grüner Haken, vor der dieses Haus an
mehreren Stellen warnt: Sie läuft, sie ist grün, und sie ist
wertlos.
Gemerkt habe ich es, weil ich die Reparatur am 01.10. zwischen
10 und 16 Uhr geprüft habe — also zu einer Zeit, in der der
Fehler gar nicht auftreten KONNTE. Erst um 00:09 des 02.10. war
sie wirklich bewiesen.
---------------------------------------------------------------------
WAS DIESE PRÜFUNG ANDERS MACHT
Sie wartet nicht auf Mitternacht, sie verschiebt die Uhr. Node
übernimmt eine zur Laufzeit gesetzte `TZ` (nachgemessen: Stunde 0
wird zu Stunde 12 unter `Etc/GMT-14`).
WELCHE ZEITZONE, HÄNGT VON DER UHRZEIT AB. Es gibt keine, in der
sich Orts- und UTC-Tag IMMER unterscheiden — dafür bräuchte es
24 Stunden Versatz. Also wird die passende gewählt:
UTC-Stunde ab 10 Uhr -> Etc/GMT-14 (UTC+14, Ortstag voraus)
UTC-Stunde vor 10 Uhr -> Etc/GMT+11 (UTC−11, Ortstag zurück)
Bei 10 Uhr UTC gehen beide; die Grenze ist deshalb kein scharfer
Rand, an dem etwas kippen kann.
UND DAS WIRD NACHGESEHEN, NICHT ANGENOMMEN: Unterscheiden sich
die zwei Tage in diesem Lauf wider Erwarten nicht, bricht die
Prüfung mit dem dritten Ausgang ab. Eine Prüfung, die ihre eigene
Voraussetzung nicht hat, darf nicht grün melden — daran ist am
03.09.2026 die Gitea-Pflichtprüfung wochenlang vorbeigelaufen.
Aufrufen mit: node server/pruef-tagesgrenze.mjs
===================================================================== */
/* ==== DIE ZEITZONE MUSS VOR ALLEM ANDEREN STEHEN ====================
Sie wirkt auf jedes `new Date()` danach — auch auf die im Server,
der gleich geladen wird. Stünde sie weiter unten, hätte der
Server schon seine Vorstellung vom heutigen Tag. */
const UTC_STUNDE = new Date().getUTCHours();
const ZONE = UTC_STUNDE >= 10 ? "Etc/GMT-14" : "Etc/GMT+11";
process.env.TZ = ZONE;
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { eigenerPort } from "./helfer-port.mjs";
import { heuteLokal, tagLokal } from "./helfer-tag.mjs";
let fehler = 0, geprueft = 0;
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
/* ---- Die Voraussetzung, bevor irgendetwas anderes passiert -------- */
console.log(`\n=== Die verschobene Uhr (${ZONE}) ===`);
const ORTSTAG = heuteLokal();
const UTC_TAG = new Date().toISOString().slice(0, 10);
console.log(` Ortstag ${ORTSTAG} · UTC-Tag ${UTC_TAG} · UTC-Stunde ${UTC_STUNDE}`);
if (ORTSTAG === UTC_TAG) {
/* DRITTER AUSGANG. Ohne Unterschied prüft diese Datei nichts, und
das muss sie sagen statt grün zu melden. */
console.log(" -- KONNTE NICHT NACHSEHEN: Orts- und UTC-Tag sind in dieser");
console.log(` Zeitzone gerade gleich (${ZONE}, UTC-Stunde ${UTC_STUNDE}).`);
console.log(" Die Wahl der Zone stimmt dann nicht mehr — sie steht im");
console.log(" Kopf dieser Datei und gehoert nachgerechnet.");
process.exit(3);
}
ok(true, `Orts- und UTC-Tag unterscheiden sich (${ORTSTAG} gegen ${UTC_TAG})`);
/* ---- Der Server, mit derselben verschobenen Uhr ------------------- */
const PORT = await eigenerPort(import.meta, "pruef-tagesgrenze");
const ordner = mkdtempSync(join(tmpdir(), "ws-tagesgrenze-"));
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
process.env.PORT = String(PORT);
process.env.SITE_ACCESS_SECRET = "lokaler-test";
process.env.SITE_ACCESS_CODE_DOGI = "DOGI-TEST-1";
process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
import { notbremse } from "./helfer-notbremse.mjs";
await import("./index.js");
notbremse(120_000, "pruef-tagesgrenze");
await new Promise((r) => setTimeout(r, 700));
const BASIS = `http://127.0.0.1:${PORT}`;
const { DatabaseSync } = await import("node:sqlite");
const { scryptSync, randomBytes, createHmac } = await import("node:crypto");
const d = new DatabaseSync(process.env.WORKSPACE_DB);
const jetztIso = new Date().toISOString();
const schluessel = randomBytes(32).toString("hex");
d.prepare("INSERT INTO einstellungen (schluessel, wert, geaendert, von)"
+ " VALUES (?,?,?,NULL) ON CONFLICT(schluessel) DO NOTHING")
.run("code_kennung_schluessel", schluessel, jetztIso);
function anlegen(name, rolle, code) {
const salt = randomBytes(16).toString("hex");
const hash = scryptSync(code, salt, 64,
{ N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex");
d.prepare("INSERT INTO personen"
+ " (name, rolle, code_hash, code_salt, code_n, code_kennung, aktiv, erstellt)"
+ " VALUES (?,?,?,?,?,?,1,?)")
.run(name, rolle, hash, salt, 32768,
createHmac("sha256", schluessel).update(code).digest("hex"), jetztIso);
}
anlegen("Filipe", "admin", "CODE-DOGI-0001");
anlegen("Lena", "modi", "CODE-LENA-0001");
d.close();
async function anmelden(kachel, code) {
const a = await fetch(BASIS + "/workspace/api/anmelden", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ rolle: kachel, code }),
});
return (a.headers.getSetCookie?.() || []).map((z) => z.split(";")[0]).join("; ");
}
const dogi = await anmelden("admin", "CODE-DOGI-0001");
const lena = await anmelden("modi", "CODE-LENA-0001");
const kopf = (keks) => ({ "Content-Type": "application/json", cookie: keks, origin: BASIS });
const liste = async (bereich, keks = dogi) =>
(await (await fetch(`${BASIS}/workspace/api/bereich/${bereich}`,
{ headers: { cookie: keks } })).json().catch(() => null))?.eintraege || [];
console.log("\n=== 1. Ein Eintrag OHNE Datum bekommt den ORTSTAG ===");
/* Die Stelle in workspace-bereiche.js, an der bis zum 01.10. stand:
if (!d) aus.datum = jetzt().slice(0, 10); // UTC */
{
const a = await fetch(`${BASIS}/workspace/api/bereich/wunsch`, {
method: "POST", headers: kopf(lena),
body: JSON.stringify({ art: "gemeinsam", titel: "Ohne Datum angelegt" }),
});
ok(a.status === 201, `der Wunsch wird angenommen (${a.status})`);
const e = (await liste("wunsch", lena)).find((x) => x.titel === "Ohne Datum angelegt");
ok(e?.datum === ORTSTAG,
`sein Datum ist der Ortstag ${ORTSTAG}, nicht der UTC-Tag ${UTC_TAG}`
+ ` (steht: ${e?.datum})`);
}
console.log("\n=== 2. Und die Kette, an der es aufgefallen ist ===");
/* `daraus` legte den neuen Eintrag mit `jetzt().slice(0, 10)` an.
Genau hier stand am 01.10. um 01:22 ein Tag zu frueh — und der
Termin landete auf „Was ansteht" im zugeklappten Abschnitt
„Vorbei". */
{
const w = await fetch(`${BASIS}/workspace/api/bereich/wunsch`, {
method: "POST", headers: kopf(lena),
body: JSON.stringify({ art: "gemeinsam", titel: "Daraus wird ein Termin", datum: ORTSTAG }),
});
const wunschId = (await w.json().catch(() => null))?.id || 0;
ok(wunschId > 0, `der Wunsch steht (#${wunschId})`);
const a = await fetch(`${BASIS}/workspace/api/bereich/wunsch/${wunschId}/daraus`, {
method: "POST", headers: kopf(dogi), body: JSON.stringify({ nach: "ansteht" }),
});
const terminId = (await a.json().catch(() => null))?.id || 0;
ok(a.status === 201 && terminId > 0, `daraus wird ein Termin (${a.status})`);
const t = (await liste("ansteht")).find((x) => x.id === terminId);
ok(t?.datum === ORTSTAG,
`und er liegt auf dem Ortstag ${ORTSTAG}, nicht auf ${UTC_TAG} (steht: ${t?.datum})`);
}
console.log("\n=== 3. Und die VERGLEICHE: was ist ueberfaellig? ===");
/* Von den sechzehn Stellen vom 01.10. SCHREIBEN zehn einen Tag; die
zwei Abschnitte oben decken sie ab. Sechs VERGLEICHEN gegen
„heute" — und davon findet ein Schreibtest keinen einzigen. Die
Spalte sieht richtig aus; falsch ist die Zahl, die jemand auf dem
Bildschirm liest.
Die Regel im Haus ist `frist < heute`. Zwei Aufgaben genuegen, und
sie unterscheiden in BEIDEN Zweigen der verschobenen Uhr:
Etc/GMT-14 (Ortstag = UTC+1): heute_alt waere gestern
-> „gestern" nicht mehr kleiner als heute_alt -> 0 statt 1
Etc/GMT+11 (Ortstag = UTC-1): heute_alt waere morgen
-> „heute" kleiner als heute_alt -> 2 statt 1 */
{
const GESTERN = tagLokal(-1);
const neueAufgabe = (titel, frist) => fetch(`${BASIS}/workspace/api/aufgaben`, {
method: "POST", headers: kopf(dogi),
body: JSON.stringify({ titel, frist, prioritaet: "mittel" }),
});
const a1 = await neueAufgabe("Frist war gestern", GESTERN);
const a2 = await neueAufgabe("Frist ist heute", ORTSTAG);
ok(a1.status === 201 && a2.status === 201,
`zwei Aufgaben angelegt, Fristen ${GESTERN} und ${ORTSTAG}`
+ ` (${a1.status}/${a2.status})`);
const u = await (await fetch(`${BASIS}/workspace/api/uebersicht`,
{ headers: { cookie: dogi } })).json().catch(() => ({}));
ok(u.ueberfaellig === 1,
`genau eine ist ueberfaellig — die von gestern (${u.ueberfaellig})`);
/* DIESE ZAHL UNTERSCHEIDET NICHT, und das gehoert dazugeschrieben.
Gemessen beim Rueckbau: Mit dem UTC-Tag blieb sie 1 — nur zaehlte
sie die FALSCHE Aufgabe (die von gestern galt dann als heute
faellig). Mit zwei Zahlen allein ist das nicht zu trennen; in
beiden Zweigen der verschobenen Uhr bleibt sie 1.
Sie steht hier als Begleitprobe: Sie zeigt, dass ueberhaupt
gezaehlt wird. Der Unterschied kommt aus der Zeile darueber. Eine
Zahl, die aus dem falschen Grund stimmt, soll nicht aussehen wie
ein Beweis. */
ok(u.heute === 1,
`und eine ist fuer heute faellig (${u.heute}) — Begleitprobe,`
+ " sie unterscheidet nicht");
/* WAS DER ALTE WEG GESAGT HAETTE. Nicht nachgebaut, sondern
ausgerechnet: Damit steht im Lauf, warum diese zwei Zahlen etwas
beweisen und nicht nur zufaellig stimmen. */
const alt = UTC_TAG;
const waere = [GESTERN, ORTSTAG].filter((f) => f < alt).length;
ok(waere !== 1,
`mit dem UTC-Tag (${alt}) waeren es ${waere} statt 1 — die Zeile`
+ " ueber dieser unterscheidet also wirklich");
}
console.log("\n=== 4. Gegenprobe: wuerde der alte Weg auffallen? ===");
/* Eine Pruefung, die immer bestaetigt, bestaetigt nichts. Hier wird
nachgebaut, was bis zum 01.10. im Code stand — und gezeigt, dass
die Behauptungen oben daran scheitern wuerden. */
{
const alterWeg = new Date().toISOString().slice(0, 10);
ok(alterWeg === UTC_TAG && alterWeg !== ORTSTAG,
`der alte Weg (toISOString) liefert ${alterWeg} — das waere der falsche Tag`);
ok(heuteLokal() === ORTSTAG,
`heuteLokal liefert ${heuteLokal()} — der richtige`);
}
console.log(`\n${geprueft} Pruefungen, ${fehler} Fehler`);
console.log(fehler ? "NICHT IN ORDNUNG\n" : "ALLES IN ORDNUNG\n");
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Aufraeumrest */ }
process.exit(fehler ? 1 : 0);