From 2e8681abf58bec9c666205477c2f797213f92872 Mon Sep 17 00:00:00 2001 From: Florian Wilde Date: Sun, 4 Oct 2026 16:11:28 +0200 Subject: [PATCH 1/2] fix(gesundheit): Fehlergrund nicht mehr unangemeldet herausgeben /api/gesundheit steht in der Allowlist des Proxys, damit ein Healthcheck kein Cookie braucht. Eine Meldung von Prisma oder pg nennt aber Host, Port, Benutzernamen und Datenbanknamen -- fuer einen Pfad, der ohne Anmeldung antwortet, ist das zu viel. Nach aussen bleibt {"status":"fehler"} mit 503, damit Docker und der Reverse Proxy weiter richtig entscheiden. Die Begruendung geht ins Protokoll des Prozesses, nach demselben Muster wie in src/server/actions/verzeichnis.ts. Fuenf Tests, darunter einer fuer die andere Haelfte der Zusage: dass die Begruendung wirklich geschrieben und nicht verschluckt wird. Gegenprobe gefahren -- mit der alten Fassung werden drei davon rot. Closes #39 --- docs/betrieb/inbetriebnahme.md | 2 +- docs/betrieb/reverse-proxy.md | 5 + docs/issues/025-gesundheit-fehlermeldung.md | 14 +-- src/app/api/gesundheit/route.ts | 20 +++- tests/gesundheit.test.ts | 102 ++++++++++++++++++++ 5 files changed, 132 insertions(+), 11 deletions(-) create mode 100644 tests/gesundheit.test.ts diff --git a/docs/betrieb/inbetriebnahme.md b/docs/betrieb/inbetriebnahme.md index 65b1461..48e77df 100644 --- a/docs/betrieb/inbetriebnahme.md +++ b/docs/betrieb/inbetriebnahme.md @@ -249,7 +249,7 @@ Datei nicht neu. Änderungen an `APP_VERSION` oder `GIT_SHA` sind Build-Argument |---|---| | `APP_SECRET fehlt in der .env` beim `up` | Genau das. Der Stand bricht absichtlich ab, statt mit einem Standardwert zu starten. Gilt ebenso für `APP_URL` und `POSTGRES_PASSWORD`. | | `web` startet nicht, `migrate` steht auf `exited (1)` | `docker compose logs migrate`. Meist erreicht die Migration die Datenbank nicht — oder du hast `POSTGRES_PASSWORD` nach dem ersten Start geändert. Das Passwort steckt dann noch im Volume. | -| `/api/gesundheit` antwortet mit 503 | Die Anwendung läuft, die Datenbank antwortet nicht. `docker compose ps` — ist `postgres` gesund? | +| `/api/gesundheit` antwortet mit 503 | Die Anwendung läuft, die Datenbank antwortet nicht. `docker compose ps` — ist `postgres` gesund? Die Antwort nennt absichtlich keinen Grund, weil der Pfad ohne Anmeldung erreichbar ist; der Grund steht in `docker compose logs web`. | | Der Seed bricht mittendrin ab | Bis zur Abnahme von #019 fehlte dem Einmal-Dienst `APP_SECRET`, und das Bind-Passwort der Beispiel-Verzeichnisquelle ließ sich nicht verschlüsseln — abgebrochen wurde erst, nachdem die Vorlagen schon standen. Steht dein `docker-compose.yml` auf dem aktuellen Stand, ist das behoben; ein erneuter Lauf holt den Rest nach. | | Nach dem Login wieder auf der Anmeldeseite | Das Cookie ist `Secure`, die Verbindung aber `http`. `APP_URL` auf `https://…` setzen und HTTPS über den Proxy anbieten. | | Die QR-Codes zeigen auf `localhost:3000` | `APP_URL` ist nicht oder falsch gesetzt. Die Oberfläche warnt unter **Admin** und in der Etikettenansicht davor. Aufkleber, die so gedruckt wurden, sind wertlos — bevor du weiterdruckst, `APP_URL` richtigstellen und `docker compose up -d`. | diff --git a/docs/betrieb/reverse-proxy.md b/docs/betrieb/reverse-proxy.md index 4c9c7eb..12d2b7b 100644 --- a/docs/betrieb/reverse-proxy.md +++ b/docs/betrieb/reverse-proxy.md @@ -203,6 +203,11 @@ schlimmsten Fall, macht den Unterpfad aber nicht betriebsfähig. antwortet `{"status":"ok"}` oder mit Status 503. Das ist der richtige Pfad für die Überwachung des Proxys; `/` leitet ohne Cookie auf die Anmeldeseite um und sagt nichts über den Zustand. +Im Fehlerfall sagt er nur `{"status":"fehler"}` und **nicht, warum** — der Pfad ist ohne Anmeldung +erreichbar, und eine Datenbankmeldung nennt Host, Port, Benutzernamen und Datenbanknamen. Die +Begründung steht im Protokoll des Prozesses: `docker compose logs web` beziehungsweise +`journalctl -u laufzettel -n 50`. + **Die Kamera braucht HTTPS.** Der QR-Scanner in der Anwendung benutzt die Kamera des Geräts, und Browser geben sie nur über eine gesicherte Verbindung frei (`localhost` ausgenommen). Ohne TLS scannt am Handy niemand — das ist der praktische Grund, warum der Proxy nicht optional ist. diff --git a/docs/issues/025-gesundheit-fehlermeldung.md b/docs/issues/025-gesundheit-fehlermeldung.md index a472f58..5d312a5 100644 --- a/docs/issues/025-gesundheit-fehlermeldung.md +++ b/docs/issues/025-gesundheit-fehlermeldung.md @@ -1,10 +1,10 @@ # #025 Healthcheck gibt die rohe Datenbankfehlermeldung heraus -**Status:** offen +**Status:** erledigt **Im Tracker:** [#39](https://git.wilde-server.de/florian.wilde/Laufzettel/issues/39) **Meilenstein:** v0.2.0 **Labels:** phase-2, backend -**Geschätzt:** 1 h **Tatsächlich:** – +**Geschätzt:** 1 h **Tatsächlich:** 25 min ## Ziel @@ -23,13 +23,13 @@ Aufgefallen beim Abnahme-Review zu [#019](019-betrieb-release.md). ## Aufgaben -- [ ] Nach außen nur `{ "status": "fehler" }` mit Status 503 -- [ ] Die Begründung serverseitig protokollieren, damit sie beim Suchen nicht fehlt -- [ ] Prüfen, ob `docs/betrieb/` irgendwo die Meldung als Diagnosehilfe nennt — dann auf das Log +- [x] Nach außen nur `{ "status": "fehler" }` mit Status 503 +- [x] Die Begründung serverseitig protokollieren, damit sie beim Suchen nicht fehlt +- [x] Prüfen, ob `docs/betrieb/` irgendwo die Meldung als Diagnosehilfe nennt — dann auf das Log verweisen ## Akzeptanzkriterien -- [ ] Test: bei nicht erreichbarer Datenbank enthält die Antwort weder Host noch Port noch +- [x] Test: bei nicht erreichbarer Datenbank enthält die Antwort weder Host noch Port noch Benutzernamen -- [ ] Test: der Statuscode bleibt 503, damit Docker und der Reverse Proxy weiter richtig entscheiden +- [x] Test: der Statuscode bleibt 503, damit Docker und der Reverse Proxy weiter richtig entscheiden diff --git a/src/app/api/gesundheit/route.ts b/src/app/api/gesundheit/route.ts index 65ce8c8..4a5b597 100644 --- a/src/app/api/gesundheit/route.ts +++ b/src/app/api/gesundheit/route.ts @@ -1,12 +1,26 @@ import { prisma } from "@/lib/db"; -/** Healthcheck fuer Docker und den Reverse Proxy. Prueft, ob die Datenbank antwortet. */ +/** + * Healthcheck fuer Docker und den Reverse Proxy. Prueft, ob die Datenbank antwortet. + * + * Dieser Pfad ist absichtlich ohne Anmeldung erreichbar — er steht in der + * Allowlist von `src/proxy.ts`, damit ein Healthcheck kein Cookie braucht. + * Deshalb geht die **Begruendung** eines Fehlers nicht hinaus: eine Meldung von + * Prisma oder `pg` nennt typischerweise Host, Port, Benutzernamen und + * Datenbanknamen. Nach aussen genuegt, **ob** es geht; warum nicht, steht im + * Protokoll des Prozesses (`docker compose logs web`, + * `journalctl -u laufzettel`). Dasselbe Muster wie in + * `src/server/actions/verzeichnis.ts` (#039). + */ export async function GET(): Promise { try { await prisma.$queryRaw`SELECT 1`; return Response.json({ status: "ok" }); } catch (fehler) { - const meldung = fehler instanceof Error ? fehler.message : "unbekannter Fehler"; - return Response.json({ status: "fehler", meldung }, { status: 503 }); + console.error( + "Healthcheck fehlgeschlagen:", + fehler instanceof Error ? `${fehler.name}: ${fehler.message}` : "unbekannter Fehler", + ); + return Response.json({ status: "fehler" }, { status: 503 }); } } diff --git a/tests/gesundheit.test.ts b/tests/gesundheit.test.ts new file mode 100644 index 0000000..5272087 --- /dev/null +++ b/tests/gesundheit.test.ts @@ -0,0 +1,102 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +/** + * Der Healthcheck (#039). + * + * Dieser Pfad ist der einzige, der ohne Anmeldung **und** mit Datenbankzugriff + * antwortet. Geprueft wird deshalb nicht, dass er funktioniert — das tun der + * Compose-Stand, `install.sh --test` und jeder e2e-Vorlauf —, sondern dass er im + * Fehlerfall **nichts verraet**. Die Datenbank wird dafuer ersetzt: eine echte + * Verbindung laesst sich nicht so kaputtmachen, dass die Meldung reproduzierbar + * Host, Port und Benutzernamen enthaelt. + */ + +const abfrage = vi.fn(); + +vi.mock("@/lib/db", () => ({ + prisma: { + get $queryRaw() { + return abfrage; + }, + }, +})); + +/** + * So sieht die Meldung wirklich aus, wenn `pg` nicht durchkommt — sie nennt + * alles, was ein Angreifer ueber die Datenbank wissen will. + */ +const ECHTE_PG_MELDUNG = + "Can't reach database server at `db-prod.firma.local:5432`. " + + "Authentication failed against database server, the provided database credentials for " + + "`laufzettel_prod` are not valid. Database `laufzettel_betrieb` does not exist."; + +const GEHEIMNISSE = [ + "db-prod.firma.local", + "5432", + "laufzettel_prod", + "laufzettel_betrieb", + "Authentication failed", +]; + +async function antwort() { + // Nach jedem Zuruecksetzen der Module neu geholt, damit der Mock greift. + const { GET } = await import("@/app/api/gesundheit/route"); + return GET(); +} + +describe("Healthcheck", () => { + let protokoll: ReturnType; + + beforeEach(() => { + vi.resetModules(); + abfrage.mockReset(); + protokoll = vi.spyOn(console, "error").mockImplementation(() => {}); + }); + + afterEach(() => { + protokoll.mockRestore(); + }); + + it("antwortet mit ok, wenn die Datenbank antwortet", async () => { + abfrage.mockResolvedValue([{ "1": 1 }]); + const a = await antwort(); + expect(a.status).toBe(200); + await expect(a.json()).resolves.toEqual({ status: "ok" }); + }); + + it("gibt im Fehlerfall weder Host noch Port noch Benutzernamen heraus", async () => { + abfrage.mockRejectedValue(new Error(ECHTE_PG_MELDUNG)); + const a = await antwort(); + + const text = await a.text(); + for (const geheimnis of GEHEIMNISSE) { + expect(text).not.toContain(geheimnis); + } + expect(JSON.parse(text)).toEqual({ status: "fehler" }); + }); + + it("behaelt 503, damit Docker und der Reverse Proxy richtig entscheiden", async () => { + abfrage.mockRejectedValue(new Error(ECHTE_PG_MELDUNG)); + expect((await antwort()).status).toBe(503); + }); + + it("schreibt die Begruendung ins Protokoll, statt sie zu verschlucken", async () => { + abfrage.mockRejectedValue(new Error(ECHTE_PG_MELDUNG)); + await antwort(); + + expect(protokoll).toHaveBeenCalledTimes(1); + const geschrieben = protokoll.mock.calls[0]?.join(" ") ?? ""; + expect(geschrieben).toContain("Healthcheck fehlgeschlagen"); + // Ohne das waere die Meldung nur weg, nicht aufgehoben. + expect(geschrieben).toContain("db-prod.firma.local:5432"); + }); + + it("nennt auch eine Ausnahme ohne Error-Klasse, ohne sie auszugeben", async () => { + abfrage.mockRejectedValue("nur ein Text"); + const a = await antwort(); + + expect(a.status).toBe(503); + await expect(a.json()).resolves.toEqual({ status: "fehler" }); + expect(protokoll.mock.calls[0]?.join(" ")).toContain("unbekannter Fehler"); + }); +}); -- 2.54.0 From 77389a2c5fcca7af6c00ac19f800cb0a676bfb49 Mon Sep 17 00:00:00 2001 From: Florian Wilde Date: Sun, 4 Oct 2026 16:11:45 +0200 Subject: [PATCH 2/2] docs(konventionen): Hexwert-Ausnahmen und LDAP-Container festhalten Zwei Saetze in CLAUDE.md beschrieben den Code nicht genau. "Nie auf rohe Hexwerte" hat sechs begruendete Ausnahmen, nicht null: Papier, ein QR-Decoder und das Web-App-Manifest entscheiden nicht nach unserem Farbschema. Sie stehen jetzt als Tabelle mit Begruendung je Zeile. Die Regel wurde ergaenzt, nicht der Code geaendert -- eine Konstante ueber drei unverwandte Dinge haette die Begruendung versteckt. Das Tor vor jedem Commit nennt den LDAP-Container. Ohne ihn ueberspringt die Suite 22 Tests -- nachgezaehlt, nicht geschaetzt: 81 mit Container, 59 ohne. Sie sagt es auf stderr, der Lauf sieht trotzdem gruen aus. Im README fehlte zusaetzlich format:pruefen, das CLAUDE.md seit #010 verlangt. Damit das haelt, auch wenn niemand hinsieht: pruefe:doku vergleicht die beiden Schranken-Bloecke und verlangt von beiden den Container. Gegenprobe gefahren. Der Review hatte vier Hexwerte genannt und src/lib/qr.ts uebersehen, und das Issue behauptete die Regel stehe auch in docs/architektur.md -- tut sie nicht. Beides im Issue-Text richtiggestellt, statt einen Haken ueber falschen Text zu setzen. Closes #40 --- CHANGELOG.md | 24 +++++++++ CLAUDE.md | 18 +++++++ README.md | 9 +++- docs/devlog.md | 51 +++++++++++++++++++- docs/issues/026-konventionen-praezisieren.md | 36 ++++++++------ docs/issues/README.md | 6 +-- scripts/pruefe-doku.mjs | 51 ++++++++++++++++++++ 7 files changed, 175 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 310f399..9b2c1e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,30 @@ Alle nennenswerten Änderungen an Laufzettel werden in dieser Datei dokumentiert Das Format basiert auf [Keep a Changelog](https://keepachangelog.com/de/1.1.0/), und dieses Projekt folgt [Semantic Versioning](https://semver.org/lang/de/). +## [Unveröffentlicht] + +### Geändert + +- Der Healthcheck `/api/gesundheit` nennt im Fehlerfall keinen Grund mehr. Der Pfad ist ohne + Anmeldung erreichbar, und eine Meldung von Prisma oder `pg` nennt Host, Port, Benutzernamen und + Datenbanknamen. Nach außen steht jetzt nur noch `{"status":"fehler"}` mit Status 503; die + Begründung geht ins Protokoll des Prozesses (#039) +- Die Konvention „nie auf rohe Hexwerte“ nennt ihre sechs begründeten Ausnahmen beim Namen — + Druckansichten, QR-Code und `themeColor` entscheiden nicht nach unserem Farbschema. Die Regel war + strenger formuliert als gemeint (#040) +- `CLAUDE.md` und `README.md` nennen den LDAP-Testcontainer als Teil des Tors vor dem Commit. Ohne + ihn überspringt die Suite **22 Tests** — den ganzen Verzeichnis-Abgleich —, sagt das auf stderr, + und der Lauf sieht trotzdem grün aus (#040) +- Im README fehlte `format:pruefen` in den Qualitätsschranken, obwohl `CLAUDE.md` es seit #010 + verlangt (#040) + +### Hinzugefügt + +- `pnpm pruefe:doku` vergleicht die Schranken-Befehle in `CLAUDE.md` und `README.md` und verlangt von + beiden den LDAP-Container. Genau diese Abweichung war unbemerkt entstanden (#040) +- Fünf Tests für den Healthcheck, darunter einer, der belegt, dass die Begründung ins Protokoll + geschrieben und nicht bloß verschluckt wird (#039) + ## [0.1.0] – 2026-09-26 ### Hinzugefügt diff --git a/CLAUDE.md b/CLAUDE.md index 9cf6200..c2d9908 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -176,9 +176,16 @@ speichern, neu laden, und nachsehen, ob es wirklich in der Datenbank steht. ## Vor jedem Commit ```bash +docker compose -f docker-compose.test.yml up -d --wait ldap pnpm lint && pnpm typecheck && pnpm format:pruefen && pnpm test && pnpm pruefe:doku ``` +**Der LDAP-Container gehört in die erste Zeile.** Ohne ihn überspringt die Suite **22 Tests** — den +ganzen Verzeichnis-Abgleich (nachgezählt: 81 statt 59 in `tests/abgleich.test.ts` und +`tests/verzeichnis.test.ts`). Sie sagt das auf stderr, aber der Lauf sieht grün aus, und wer nur +diesem Abschnitt folgte, hat den Abgleich nie geprüft. In CI bricht sie stattdessen ab, absichtlich. +Aufgefallen bei der Abnahme zu #019. + `format:pruefen` gehört dazu, seit der Review in #010 darauf gestoßen ist: `lint-staged` formatiert beim Commit automatisch nach, und das Ergebnis wäre ein Commit mit Änderungen, die niemand gesehen hat. Lieber vorher sehen, was sich ändert. @@ -198,6 +205,17 @@ Abschnitte mit zweistelliger Nummer (`01 · Vorbereitung`), die aus der Position kommt und nie gespeichert wird. Druckansichten und Etiketten immer hell. **Am Handy nie horizontales Scrollen** — bei 390 px geprüft. +**„Nie auf rohe Hexwerte" meint die Oberfläche.** Es gibt genau sechs begründete Ausnahmen, und sie +haben alle denselben Grund: dort entscheidet nicht unser Farbschema, sondern etwas außerhalb davon. + +| Stelle | Wert | Warum kein Token | +|---|---|---| +| `src/app/drucken/lauf/[id]/page.tsx`, `src/app/drucken/etiketten/page.tsx` | `#ffffff` | Druck ist immer hell, unabhängig vom Erscheinungsbild der Anwendung | +| `src/lib/qr.ts` | `#000000ff` / `#ffffffff` | Ein QR-Code muss reines Schwarz auf reinem Weiß sein, sonst liest ihn kein Decoder | +| `src/app/layout.tsx` (`themeColor`), `src/app/manifest.ts` (zweimal) | `#0B0D10` | `themeColor` und das Web-App-Manifest vertragen keine CSS-Variablen | + +Wer eine siebte hinzufügt, trägt sie hier ein — oder lässt es. + > Der folgende Block ist **nicht von uns**: `next dev` schreibt ihn bei jedem Start selbst in diese > Datei und stellt ihn wieder her, wenn man ihn löscht. Er steht deshalb hier, auf Englisch, und > wird mitcommittet, damit der Arbeitsbaum sauber bleibt. diff --git a/README.md b/README.md index 3d5bd4a..ecd8bc5 100644 --- a/README.md +++ b/README.md @@ -45,11 +45,16 @@ openssl rand -base64 48 ### Qualitätsschranken ```bash -pnpm lint && pnpm typecheck && pnpm test && pnpm pruefe:doku +docker compose -f docker-compose.test.yml up -d --wait ldap # sonst fehlen 22 Tests +pnpm lint && pnpm typecheck && pnpm format:pruefen && pnpm test && pnpm pruefe:doku ``` +Der LDAP-Container ist keine Zierde: ohne ihn überspringt die Suite den ganzen Verzeichnis-Abgleich +— sie sagt das auf stderr, aber der Lauf sieht grün aus. In CI bricht sie stattdessen ab. + Ein Pre-Commit-Hook erzwingt Lint und Format, ein Commit-Msg-Hook Conventional Commits. -Ende-zu-Ende-Tests: `pnpm test:e2e`. +Ende-zu-Ende-Tests: `pnpm test:e2e` (verlangt einen gebauten, laufenden Stand — siehe +`docs/anleitung/`). ## Betrieb mit Docker Compose diff --git a/docs/devlog.md b/docs/devlog.md index c31567a..eef83cd 100644 --- a/docs/devlog.md +++ b/docs/devlog.md @@ -22,11 +22,14 @@ als Unterpunkte mit Aufgabe und Dauer. | #017 | M12 – Seed der sieben Vorlagen | 1 h 30 min | | #018 | M13 – E2E-Tests, Anleitung, PDF | 2 h 15 min | | #019 | M14 – Betriebswege und Release v0.1.0 | 5 h 25 min | -| | **Summe** | **35 h 20 min** | +| #025 | Healthcheck gibt die rohe Datenbankfehlermeldung heraus | 25 min | +| #026 | Zwei Konventionen präzisieren | 20 min | +| | **Summe** | **36 h 05 min** | | Meilenstein | Tatsächlich | Status | |---|---|---| | v0.1.0 | 35 h 20 min | veröffentlicht | +| v0.2.0 | 45 min | in Arbeit | --- @@ -977,6 +980,52 @@ als Unterpunkte mit Aufgabe und Dauer. --- +## 2026-09-26 · Sitzung 16 · 16:00–16:45 + +**Issues:** #025, #026 (beide aus der Abnahme zu #019) +**Version:** 0.2.0 (unveröffentlicht) + +### Erledigt + +- **`/api/gesundheit` verrät nichts mehr.** Der Pfad ist ohne Anmeldung erreichbar; eine Meldung von + Prisma oder `pg` nennt Host, Port, Benutzernamen und Datenbanknamen. Nach außen bleibt + `{"status":"fehler"}` mit 503, die Begründung geht ins Protokoll — dasselbe Muster, das + `src/server/actions/verzeichnis.ts` schon für unbekannte Ausnahmen benutzt +- **Fünf Tests** dafür, einer davon für die andere Hälfte der Zusage: dass die Begründung wirklich + **geschrieben** und nicht bloß verschluckt wird +- **Die Hexwert-Regel nennt ihre Ausnahmen.** Sechs Stellen, als Tabelle mit Begründung je Zeile +- **Der LDAP-Container steht jetzt im Tor**, in `CLAUDE.md` und im README — mit der gemessenen Zahl +- **`pnpm pruefe:doku` vergleicht die beiden Schranken-Blöcke** und verlangt von beiden den Container + +### Entscheidungen + +- **Die Regel wurde ergänzt, nicht der Code geändert.** Alle sechs Hexwerte haben denselben Grund: + dort entscheidet nicht unser Farbschema, sondern etwas außerhalb davon — Papier, ein QR-Decoder, + das Web-App-Manifest. Sie in eine Konstante zu ziehen hätte eine Abstraktion über drei + unverwandte Dinge gelegt und die Begründung versteckt. Die Tabelle in `CLAUDE.md` zeigt sie. +- **Der Hinweis auf den Container reicht nicht, er muss geprüft werden.** Zwei Dateien beschreiben + dasselbe Tor und waren auseinandergelaufen. Ein Satz in einer Konvention hält nur, solange ihn + jemand liest; die sechste Prüfung in `pruefe:doku` hält ihn, auch wenn niemand hinsieht. + +### Probleme + +- **Ich hätte „rund zwanzig Tests" in eine Konvention geschrieben.** Die Zahl stammte aus dem + Abnahmebericht (dort: 18), nicht aus einer Messung. Nachgezählt sind es **22** — mit Container 81, + ohne 59. Eine ungefähre Zahl in einer verbindlichen Datei ist derselbe Fehlertyp, den dieses + Projekt vierzehn Meilensteine lang gesammelt hat, nur kleiner. +- **Der Review hat eine Ausnahme übersehen.** Er nannte vier rohe Hexwerte, es sind sechs: + `src/lib/qr.ts` fehlte. Das Issue #026 hat die falsche Zahl von ihm übernommen; beim Abhaken wäre + ein richtiger Haken über falschem Text entstanden. Issue-Text korrigiert, dann abgehakt. +- **`docs/architektur.md` trägt die Gestaltungsregel nicht**, obwohl `CLAUDE.md` verlangt, dass + Änderungen dort mitgehen und das Issue es behauptete. Geprüft statt geglaubt. + +### Nächster Schritt + +- Phase 2 (#002) oder die vier verbliebenen v0.2.0-Issues: #020 Vorgesetzter, #021 stabiler + Schlüssel, #022 Schnellstart pflegen, #023 Protokoll-Ansicht. #024 wartet auf einen PVE-Host. + +--- + ## Phase 1 abgeschlossen: was aus dem Plan wurde **35 h 20 min** über vierzehn Meilensteine, geschätzt waren **148 h**. Der Faktor ist kein Verdienst, diff --git a/docs/issues/026-konventionen-praezisieren.md b/docs/issues/026-konventionen-praezisieren.md index b68a250..828b34c 100644 --- a/docs/issues/026-konventionen-praezisieren.md +++ b/docs/issues/026-konventionen-praezisieren.md @@ -1,22 +1,27 @@ # #026 Zwei Konventionen präzisieren, die strenger klingen als sie gemeint sind -**Status:** offen +**Status:** erledigt **Im Tracker:** [#40](https://git.wilde-server.de/florian.wilde/Laufzettel/issues/40) **Meilenstein:** v0.2.0 **Labels:** phase-2, docs -**Geschätzt:** 1 h **Tatsächlich:** – +**Geschätzt:** 1 h **Tatsächlich:** 20 min ## Ziel -Zwei Sätze in `CLAUDE.md` (und gleichlautend in `docs/architektur.md`) beschreiben den Code nicht -genau. Beide fielen beim Abnahme-Review zu [#019](019-betrieb-release.md) auf. +Zwei Sätze in `CLAUDE.md` beschreiben den Code nicht genau. Beide fielen beim Abnahme-Review zu +[#019](019-betrieb-release.md) auf. + +**Nachtrag aus der Umsetzung:** `docs/architektur.md` trägt die Gestaltungsregel gar nicht — sie +steht nur in `CLAUDE.md`. Und es sind **sechs** Ausnahmen, nicht vier: der Review hat `src/lib/qr.ts` +übersehen, wo der QR-Code reines Schwarz auf reinem Weiß braucht, damit ein Decoder ihn liest. **1. Rohe Hexwerte.** Die Regel sagt „Komponenten greifen nur darauf zu, nie auf rohe Hexwerte“. -Tatsächlich stehen vier begründete Ausnahmen im Code: `#ffffff` in `drucken/lauf/[id]/page.tsx` und -`drucken/etiketten/page.tsx` (Druck ist immer hell, unabhängig vom Farbschema der Anwendung) sowie -`#0B0D10` in `layout.tsx` und `manifest.ts` (`themeColor` und das Manifest vertragen keine -CSS-Variablen). Die Regel ist strenger formuliert als gemeint — sie soll um diese Ausnahmen ergänzt -werden, nicht der Code geändert. +Tatsächlich stehen sechs begründete Ausnahmen im Code: `#ffffff` in `drucken/lauf/[id]/page.tsx` und +`drucken/etiketten/page.tsx` (Druck ist immer hell, unabhängig vom Farbschema der Anwendung), +`#000000ff`/`#ffffffff` in `src/lib/qr.ts` (ein QR-Code muss reines Schwarz auf reinem Weiß sein) +sowie `#0B0D10` in `layout.tsx` und zweimal in `manifest.ts` (`themeColor` und das Manifest +vertragen keine CSS-Variablen). Die Regel ist strenger formuliert als gemeint — sie soll um diese +Ausnahmen ergänzt werden, nicht der Code geändert. **2. `pnpm test` ohne LDAP-Container.** „Vor jedem Commit“ in `CLAUDE.md` und „Qualitätsschranken“ im README nennen `pnpm test`, aber nicht @@ -24,14 +29,17 @@ README nennen `pnpm test`, aber nicht Tests gegen das echte Verzeichnis — sie sagt das auf stderr, aber der Lauf sieht grün aus. Wer nur dem dokumentierten Weg folgt, prüft den Verzeichnis-Abgleich nie. Ein Satz in beiden Dateien. +Nachgezählt sind es **22 Tests**: `tests/abgleich.test.ts` und `tests/verzeichnis.test.ts` ergeben +mit Container 81, ohne 59. Dabei fiel auf, dass im README zusätzlich `format:pruefen` fehlte. + ## Aufgaben -- [ ] Ausnahme für rohe Hexwerte in `CLAUDE.md` und `docs/architektur.md` benennen, mit Begründung -- [ ] Den LDAP-Container in `CLAUDE.md` („Vor jedem Commit“) und `README.md` aufnehmen -- [ ] Prüfen, ob sich der Hinweis maschinell absichern lässt — `pruefe:doku` könnte verlangen, dass +- [x] Ausnahme für rohe Hexwerte in `CLAUDE.md` benennen, mit Begründung (in `docs/architektur.md` steht die Regel nicht) +- [x] Den LDAP-Container in `CLAUDE.md` („Vor jedem Commit“) und `README.md` aufnehmen +- [x] Prüfen, ob sich der Hinweis maschinell absichern lässt — `pruefe:doku` könnte verlangen, dass jede in `CLAUDE.md` genannte Schranke auch den nötigen Vorlauf nennt ## Akzeptanzkriterien -- [ ] Die vier Hexwerte im Code stehen in Einklang mit der Regel, ohne den Code zu ändern -- [ ] Wer `CLAUDE.md` von oben nach unten befolgt, fährt die Suite mit LDAP-Container +- [x] Die sechs Hexwerte im Code stehen in Einklang mit der Regel, ohne den Code zu ändern +- [x] Wer `CLAUDE.md` von oben nach unten befolgt, fährt die Suite mit LDAP-Container diff --git a/docs/issues/README.md b/docs/issues/README.md index 77cd287..1107a8b 100644 --- a/docs/issues/README.md +++ b/docs/issues/README.md @@ -29,6 +29,6 @@ Diese Dateien sind die Quelle; `pnpm issues:sync` spiegelt sie in den Gitea-Trac | #022 | [Schnellstart je Gerätetyp pflegen](022-schnellstart-pflegen.md) | offen | v0.2.0 | 3 h | – | | #023 | [Protokoll ansehen](023-protokoll-ansicht.md) | offen | v0.2.0 | 4 h | – | | #024 | [Proxmox-Einzeiler auf einem echten PVE-Host gegenprüfen](024-proxmox-echtlauf.md) | offen | v0.2.0 | 2 h | – | -| #025 | [Healthcheck gibt die rohe Datenbankfehlermeldung heraus](025-gesundheit-fehlermeldung.md) | offen | v0.2.0 | 1 h | – | -| #026 | [Zwei Konventionen präzisieren, die strenger klingen als sie gemeint sind](026-konventionen-praezisieren.md) | offen | v0.2.0 | 1 h | – | -| | **Summe** | | | **152 h** | **35 h 20 min** | +| #025 | [Healthcheck gibt die rohe Datenbankfehlermeldung heraus](025-gesundheit-fehlermeldung.md) | erledigt | v0.2.0 | 1 h | 25 min | +| #026 | [Zwei Konventionen präzisieren, die strenger klingen als sie gemeint sind](026-konventionen-praezisieren.md) | erledigt | v0.2.0 | 1 h | 20 min | +| | **Summe** | | | **152 h** | **36 h 05 min** | diff --git a/scripts/pruefe-doku.mjs b/scripts/pruefe-doku.mjs index 3462cc6..10d3e78 100755 --- a/scripts/pruefe-doku.mjs +++ b/scripts/pruefe-doku.mjs @@ -8,6 +8,7 @@ * 3. Geschaetzte und tatsaechliche Zeiten stimmen zwischen Datei und Uebersicht. * 4. Alle in der Anleitung verlinkten Bilder sind vorhanden. * 5. Kein Markdown verweist auf eine Datei, die es nicht gibt. + * 6. Die Schranken-Befehle in CLAUDE.md und README.md stimmen ueberein. */ import { readFileSync, existsSync, readdirSync } from "node:fs"; import { join, dirname, resolve } from "node:path"; @@ -134,6 +135,56 @@ function feld(text, name) { } } +// ---- 6. Die Schranken in CLAUDE.md und README.md ------------------------- +// +// Zwei Dateien beschreiben dasselbe Tor, und sie sind auseinandergelaufen: im +// README fehlte `format:pruefen`, und beide nannten den LDAP-Container nicht, ohne +// den die Suite 22 Tests still ueberspringt. Beides fiel erst bei der Abnahme zu +// #019 auf -- ein gruener Lauf sieht genauso aus. Geprueft wird deshalb der +// Befehlsblock selbst, nicht seine Umgebung (#040). +{ + /** Der erste eingezaeunte Block nach einer Ueberschrift. */ + const blockNach = (text, ueberschrift) => { + const ab = text.indexOf(ueberschrift); + if (ab < 0) return null; + const treffer = text.slice(ab).match(/```(?:bash|sh)?\n([\s\S]*?)```/); + return treffer?.[1] ?? null; + }; + + const skripte = (block) => [...block.matchAll(/pnpm ([a-z:]+)/g)].map((m) => m[1]).sort(); + + const quellen = [ + ["CLAUDE.md", "## Vor jedem Commit"], + ["README.md", "### Qualitätsschranken"], + ]; + + const bloecke = quellen.map(([datei, ueberschrift]) => { + const block = blockNach(lies(datei), ueberschrift); + if (block === null) melde(`${datei}: kein Befehlsblock unter "${ueberschrift}" gefunden.`); + return [datei, block]; + }); + + if (bloecke.every(([, block]) => block !== null)) { + const [[dateiA, blockA], [dateiB, blockB]] = bloecke; + const a = skripte(blockA); + const b = skripte(blockB); + if (a.join(" ") !== b.join(" ")) { + melde( + `Die Schranken laufen auseinander: ${dateiA} nennt [${a.join(", ")}], ` + + `${dateiB} nennt [${b.join(", ")}].`, + ); + } + for (const [datei, block] of bloecke) { + if (!/docker compose -f docker-compose\.test\.yml up .*ldap/.test(block)) { + melde( + `${datei}: der Schranken-Block nennt den LDAP-Container nicht. ` + + `Ohne ihn ueberspringt die Suite den Verzeichnis-Abgleich, und der Lauf sieht gruen aus.`, + ); + } + } + } +} + // ---- Ergebnis -------------------------------------------------------------- if (befunde.length > 0) { console.error(`\nDokumentation und Code laufen auseinander (${befunde.length}):\n`); -- 2.54.0