412 lines
31 KiB
Markdown
412 lines
31 KiB
Markdown
# Changelog
|
||
|
||
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 Seed macht Petra Klein zur Vorgesetzten von Maria Huber — die Zuordnung aus dem Auftrag, die
|
||
sich vorher nirgends speichern ließ (#020)
|
||
- 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
|
||
|
||
- **Wiederverwendbare Bausteine.** Ein Abschnitt, den mehrere Vorlagen teilen und der an einer
|
||
Stelle gepflegt wird — „Datensicherung prüfen“ steht in vier der sieben mitgelieferten Vorlagen,
|
||
wortgleich. Versioniert nach denselben Regeln wie eine Vorlage: höchstens ein Entwurf, eine
|
||
veröffentlichte Fassung wird nie verändert. Eingesetzt wird ein Baustein im Editor; beim
|
||
**Veröffentlichen** einer Vorlage setzt der Dienst seine Schritte als tiefe Kopie ein. Damit
|
||
ändert eine neue Baustein-Fassung **keine** veröffentlichte Vorlagenversion und **keinen**
|
||
laufenden Lauf — sie wirkt erst in der nächsten veröffentlichten Version. Auf der Seite eines
|
||
Bausteins steht, in welchen Vorlagen er vorkommt und welche Fassung dort eingesetzt ist (#030)
|
||
- **Das Protokoll ist ansehbar**, unter Verwaltung → Protokoll. Geschrieben wurde es seit dem
|
||
Datenmodell, ansehen konnte es niemand: wer wissen wollte, wer wann eine Rolle geändert hat,
|
||
brauchte `psql`. Jetzt mit Volltext über Aktion und Lauftitel, Filter nach Urheber und Zeitraum,
|
||
fünfzig Einträge je Seite. Ein Eintrag ohne Urheber wird als **Systemlauf** gezeigt und nicht als
|
||
Lücke — der Zeitplan des Verzeichnisabgleichs hat keinen Menschen hinter sich. Am Lauf selbst
|
||
stehen seine Einträge eingeklappt in der Abarbeitungsansicht; wer den Lauf sehen darf, sieht auch
|
||
dessen Geschichte (#023)
|
||
- **`Protokoll` trägt eine `kundeId`**, wie CLAUDE.md es für jede kundenbezogene Zeile verlangt. Ohne
|
||
sie ließ sich die Mandantensicht nicht anwenden, ohne je Eintrag die Entität nachzuschlagen. Leer
|
||
heißt global; bestehende Einträge nimmt die Migration mit. Ein Eintrag, der sich **keinem** Kunden
|
||
zuordnen lässt — etwa zu einem gelöschten Gerät —, wird einem auf Kunden beschränkten Benutzer
|
||
nicht gezeigt (#023)
|
||
- **Schnellstart je Gerätetyp pflegen**, unter Verwaltung → Schnellstart. Welche Vorlagen auf der
|
||
Geräteseite als Kachel erscheinen, stand bisher nur im Seed: wer eine eigene Vorlage dort haben
|
||
wollte, kam nicht heran, und wer eine mitgelieferte loswerden wollte, auch nicht. Zur Auswahl
|
||
stehen nur veröffentlichte, nicht archivierte Vorlagen; die Reihenfolge der Liste ist die
|
||
Reihenfolge der Kacheln. Sobald jemand hier gespeichert hat, legt der Seed keine eigenen Vorgaben
|
||
mehr nach — eine entfernte Kachel soll nicht beim nächsten Einrichtungslauf zurückkommen (#022)
|
||
- **Vorgesetzter auch für von Hand angelegte Personen.** Die Zuordnung lief allein über die Kennung
|
||
des Verzeichnisses, und die ist nur innerhalb einer Verzeichnisquelle eindeutig: wer eine Person
|
||
selbst eingetragen hatte, konnte ihr keinen Vorgesetzten geben, und `{{vorgesetzter_sam}}` blieb im
|
||
Onboarding-Befehl leer — genau dort, wo er gebraucht wird. Jetzt trägt jede Person eine gepflegte
|
||
Zuordnung, die sich auf ihrer Seite setzen und entfernen lässt; der Abgleich schreibt sie aus dem
|
||
Verzeichnis mit, und liefert das Verzeichnis keinen Vorgesetzten, bleibt eine von Hand gesetzte
|
||
Zuordnung stehen. Bestehende Zuordnungen nimmt die Migration mit (#020)
|
||
- Mitgelieferte Vorlagen tragen einen **stabilen Schlüssel** (`seedSchluessel`). Der Seed erkennt
|
||
seine sieben Vorlagen daran statt am Namen — wer eine umbenannte, bekam vorher beim nächsten Lauf
|
||
eine zweite, samt doppeltem Schnellstart auf der Geräteseite. Eine bestehende Installation bekommt
|
||
den Schlüssel beim ersten Lauf danach nachgetragen, ohne Dubletten. Eine über den Import
|
||
eingespielte Kopie bekommt **keinen** Schlüssel: sie soll nicht den Platz des Originals einnehmen
|
||
(#021)
|
||
- `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
|
||
|
||
- Projektgerüst: Next 16 mit App Router und TypeScript strict, Tailwind 4, Vitest 5,
|
||
Playwright 1.63, ESLint flat config, Prettier, husky mit lint-staged und commitlint (#005)
|
||
- Gestaltung: Design-Token als CSS-Variablen, selbst gehostete Schriften, Wortmarke und
|
||
Bildmarke `[ ✓ ]`, Versionsanzeige unten links (#005)
|
||
- Betriebsstand mit Docker Compose: die Datenbank ist von außen nicht erreichbar — kein
|
||
veröffentlichter Port und ein Netz ohne Gateway; Migrationen laufen beim Start in einem
|
||
eigenen Einmal-Dienst, der Seed bleibt ein bewusster Handgriff (#005)
|
||
- Healthcheck `GET /api/gesundheit` und Sicherheitskopfzeilen samt CSP-Nonce je Anfrage (#005)
|
||
- shadcn/ui eingerichtet: sein Variablen-Vertrag wird aus den Laufzettel-Token gespeist, damit
|
||
kopierte Bauteile ohne Nacharbeit passen (#005)
|
||
- `pnpm pruefe:doku` hält Dokumentation und Code zusammen: Version im Changelog, Issue-Dateien
|
||
gegen ihre Übersicht, keine Verweise auf fehlende Dateien, keine fehlenden Bilder (#005)
|
||
- `pnpm docs:screenshots` erzeugt Screenshots reproduzierbar und prüft dabei gleich, dass am
|
||
Handy nicht horizontal gescrollt wird (#005)
|
||
- Issues, Labels und Meilensteine für alle vier Phasen im Gitea-Tracker (#001–#019)
|
||
- Vollständiges Datenmodell in deutscher Benennung: 20 Tabellen, 9 Aufzählungen, erste
|
||
Migration, idempotenter Seed für Betriebsmodus, eigene Organisation und Administrator (#006)
|
||
- Erzeugung der `tagCode`s für die QR-Aufkleber, mit Vergabebuch, aus dem nie gelöscht wird —
|
||
ein Code kommt auch nach dem Löschen eines Geräts nicht zurück (#006)
|
||
- Verschlüsselung von Geheimnissen mit AES-256-GCM aus `APP_SECRET`, für das LDAP-Bind-Passwort
|
||
und später die Zugangsdaten der Connectoren (#006)
|
||
- Anmeldung über einen generischen OIDC-Anbieter mit Discovery, PKCE, `state` und `nonce`;
|
||
unbekannte Benutzer entstehen beim ersten Login, die Rolle kommt bei jeder Anmeldung neu aus
|
||
den Gruppen (#007)
|
||
- Lokale Anmeldung mit E-Mail und Passwort als Notfallzugang, nur bei `AUTH_LOCAL_ENABLED=true`
|
||
(#007)
|
||
- Rechte in einer Tabelle mit vier Rollen; geprüft ausschließlich serverseitig, Rolle und
|
||
Aktiv-Status pro Anfrage aus der Datenbank (#007)
|
||
- Benutzerverwaltung für Administratoren: Rolle setzen, Konto abschalten, lokales Konto
|
||
anlegen (#007)
|
||
- Navigation zeigt nur, was die eigene Rolle darf (#007)
|
||
- Betriebsmodus **Intern** und **Dienstleistung**, umschaltbar ohne Neustart. Im Modus Intern
|
||
erscheint die eigene Organisation nirgends; der Wechsel nimmt alle Geräte, Personen,
|
||
Abteilungen und Läufe mit, weil jede Zeile ohnehin schon eine Kunden-Zuordnung trägt (#008)
|
||
- Bestätigungsdialog für den Moduswechsel, der die Folgen aus derselben Prüfung zeigt, die der
|
||
Wechsel danach durchsetzt (#008)
|
||
- Kundenverwaltung im Modus Dienstleistung: anlegen, bearbeiten, archivieren statt löschen (#008)
|
||
- Admin → Einstellungen: Betriebsmodus, Name der eigenen Organisation, globale Namensregeln,
|
||
Sperr-OU und die Anzeige der Adresse, die in den QR-Codes steckt (#008)
|
||
- Navigation und Beschriftungen folgen dem Betriebsmodus — ein Codepfad, nur ein- und
|
||
ausgeblendete Einträge (#008)
|
||
- Versionierte Vorlagen: „Bearbeiten“ erzeugt eine tiefe Kopie der zuletzt veröffentlichten
|
||
Version als Entwurf, je Vorlage gibt es höchstens einen. Veröffentlichen hält Zeitpunkt,
|
||
Urheber und Änderungsnotiz fest (#009)
|
||
- Vorlagen duplizieren und archivieren statt löschen (#009)
|
||
- JSON-Export und -Import für Vorlagen, Format dokumentiert in `docs/vorlagen-format.md` (#009)
|
||
- Zwei Redakteure am selben Entwurf treten sich nicht mehr auf die Füße: wer auf einem überholten
|
||
Stand speichert, wird abgewiesen statt die Arbeit des anderen spurlos zu überschreiben (#009)
|
||
- Vorlagenliste mit Suche, Filter nach Kategorie und Schlagwort und dem Kennzeichen
|
||
„Entwurf vorhanden“; eine Vorlage lässt sich anlegen, duplizieren, archivieren und als JSON
|
||
ein- und ausspielen (#010)
|
||
- Vorlagen-Editor: Abschnitte, Schritte und Variablen anlegen, bearbeiten, löschen und sortieren
|
||
— mit dem Finger, der Maus, der Tastatur oder zwei Knöpfen an jeder Zeile (#010)
|
||
- Typabhängige Felder je Schritt: Auswahloptionen, Zahlenbereich mit Einheit, mehrzeiliger Text,
|
||
Shell und Befehlstext; Hilfetexte als Markdown, dazu ein Doku-Link (#010)
|
||
- Warnung im Editor, wenn ein Platzhalter zu keiner Variablen und zu keinem eingebauten Wert
|
||
gehört — sie blockiert nichts, sie zeigt den Tippfehler, bevor der Techniker ihn im Lauf findet
|
||
(#010)
|
||
- Vorschau der Vorlage mit Beispielwerten, so wie ein Lauf sie zeigen wird (#010)
|
||
- Versionsgeschichte je Vorlage: wer wann mit welcher Notiz veröffentlicht hat und wie viele Läufe
|
||
an einer Version hängen (#010)
|
||
- Mitarbeiterliste mit Suche über Anzeigename, Mail, UPN und sAMAccountName, Filter nach Abteilung;
|
||
standardmäßig nur aktive Personen, mit Umschalter auch deaktivierte (#011)
|
||
- Mitarbeiterseite mit Stammdaten, zugeordneten Geräten und Platz für die Laufhistorie; Personen
|
||
werden deaktiviert statt gelöscht (#011)
|
||
- Abteilungen unter Admin: Ziel-OU, Standardgruppen und Mail-Domain — daraus entstehen später die
|
||
Befehle, die ein Konto anlegen. Abteilungsnamen, die bei Personen vorkommen, aber nicht gepflegt
|
||
sind, werden eigens ausgewiesen (#011)
|
||
- Geräteliste mit Suche über Hostname, Bezeichnung, Seriennummer und Tag-Code, Filter nach Typ;
|
||
Geräte anlegen, bearbeiten, archivieren (#011)
|
||
- CSV-Import für Geräte: je Zeile ein Ergebnis, eine fehlerhafte Zeile bricht den Import nicht ab,
|
||
sondern wird mit Zeilennummer und Grund gemeldet. Der Zerleger kommt mit Semikolon, BOM, CRLF,
|
||
Anführungszeichen und Zeilenumbrüchen im Wert zurecht (#011)
|
||
- Die Seitenleiste führt Personen und Geräte unter einem Eintrag; ein Umschalter wechselt zwischen
|
||
beiden Listen (#011)
|
||
- Platzhalter werden aufgelöst: eingebaute Werte aus Gerät, Lauf, Mitarbeiter und Abteilung, dazu
|
||
die Variablen der Vorlage. Ein unbekannter Platzhalter bleibt sichtbar stehen, statt still zu
|
||
verschwinden (#012)
|
||
- `{{ou}}` einer Person aus dem Verzeichnis ist der Eltern-Container ihres DN — genau der Pfad, den
|
||
ein `New-ADUser -Path` braucht (#012)
|
||
- Namensregeln für `upn`, `sam`, `mail` und `hostname` mit Transliteration, `{v}`, `{nnn}` und
|
||
`{kundenkuerzel}`; im Modus Dienstleistung überlagern Kundenregeln die globalen Feld für Feld
|
||
(#012)
|
||
- „Alle Befehle als Skript kopieren“ je Abschnitt: die Befehle in Reihenfolge, je mit einer
|
||
Kommentarzeile darüber und dem Zeilenumbruch der jeweiligen Shell (#012)
|
||
- Läufe: anlegen — auch als `offen` ohne Start, das ist die Zuweisung —, abarbeiten, abschließen
|
||
und archivieren. Ein Lauf wird erst `in Arbeit`, wenn ihn jemand wirklich öffnet (#013)
|
||
- Abarbeitungsansicht: nummerierte Abschnitte, Fortschrittsbalken, je Schritt Zustand, Wert,
|
||
Notiz, Kommentare und „wer wann“; jeder Schritt speichert einzeln, damit ein verlorener Klick
|
||
nicht zwei Stunden Arbeit kostet (#013)
|
||
- Die Ansicht frischt sich alle zehn Sekunden auf und pausiert, solange der Tab im Hintergrund
|
||
liegt — zwei Techniker an einem Onboarding sehen die Arbeit des anderen (#013)
|
||
- Umschalter „Kompakt / Ausführlich“: beim ersten Mal sind die Hilfetexte der eigentliche Wert,
|
||
beim dritten Mal stehen sie im Weg (#013)
|
||
- Befehlsschritte zeigen den fertigen Befehl mit eingesetzten Werten und einem Kopier-Knopf; ein
|
||
unaufgelöster Platzhalter wird angezeigt, statt stillschweigend mitkopiert zu werden (#013)
|
||
- „Abschnitt trifft nicht zu“ setzt alle Schritte eines Abschnitts mit einer Begründung auf
|
||
`nicht zutreffend` — für die Konto-Abschnitte eines Onboardings ohne eigenes AD (#013)
|
||
- Kommentare am Lauf und am einzelnen Schritt (#013)
|
||
- Ein Lauf mit Freitextnamen lässt sich nachträglich mit einem Konto aus dem Verzeichnis
|
||
verknüpfen: beim Onboarding gibt es die Person dort noch nicht (#013)
|
||
- Start-Dialog mit Vorbelegung aus der Adresse, Laufliste mit Suche nach Titel, Ticket, Hostname
|
||
und Person sowie Filter nach Status und eigener Zuweisung (#013)
|
||
- Der Start-Dialog schlägt `upn`, `sam` und `mail` nach den Namensregeln vor, sobald die Vorlage
|
||
solche Variablen hat; ausgefüllte Felder bleiben stehen (#013)
|
||
- Abschnitte klappen zu, sobald in ihnen nichts mehr zu tun ist — wer einen wieder aufklappt,
|
||
behält ihn offen, auch über das Speichern hinweg (#013)
|
||
|
||
- QR-Aufkleber: Stapel vorgedruckter Tags, Etikettendruck als Einzeletikett (62 × 29 mm) und als
|
||
A4-Bogen (3 × 8 à 70 × 37 mm), je mit QR, lesbarem Code, Hostname und Besitzer (#014)
|
||
- `/t/<code>` als Einstieg vom Aufkleber: registriertes Gerät → Geräteseite, vorgedruckter Tag →
|
||
Registrierformular, unbekannter Code → Hinweis mit Weg zur Suche (#014)
|
||
- Geräteseite mit Kopf, „Zugewiesen“, Schnellstart aus den Gerätetyp-Vorgaben, „Vorlage zuweisen“
|
||
und Historie — am Handy bedienbar, weil sie dort benutzt wird (#014)
|
||
- „Scannen“ mit der BarcodeDetector-API des Browsers, wo es sie gibt, sonst mit WebAssembly; wo
|
||
keine Kamera läuft, steht ein Feld für den abgetippten Code (#014)
|
||
- Der Tag-Code in der Geräteliste und in der Tag-Liste führt dorthin, wo auch der Scan landet (#014)
|
||
|
||
- Übersicht: überfällige Läufe, die eigenen Zuweisungen, zuletzt Bearbeitetes und ein Schnellstart
|
||
aus den Vorlagen, aus denen im Haus am häufigsten Läufe entstehen (#015)
|
||
- Archiv mit Volltextsuche über Titel, Notizen, erfasste Werte und Kommentare in einem gemeinsamen
|
||
Lesemodell; die Trefferliste zeigt, **wo** der Begriff steht, und hebt ihn hervor (#015)
|
||
- Filter im Archiv nach Vorlage, Person, Abteilung, Gerät, Zeitraum und Bearbeiter — im Modus
|
||
Dienstleistung zusätzlich nach Kunde (#015)
|
||
- Druckansicht eines Laufs: hell, ohne Navigation, auf A4, mit allen Schritten, Werten, Notizen,
|
||
Namen und Zeitstempeln. Sie ersetzt im MVP den PDF-Export (#015)
|
||
|
||
- Verzeichnis-Abgleich über LDAP: Quelle anlegen, Verbindung testen, nach Zeitplan oder auf Knopfdruck
|
||
abgleichen. Wer im Verzeichnis verschwindet oder deaktiviert wird, wird hier inaktiv — gelöscht wird
|
||
nie, sonst verlöre die Historie ihre Namen (#016)
|
||
- „Verbindung testen“ zeigt die ersten Einträge **mit den gemappten Feldern** und schreibt nichts: erst
|
||
daran sieht ein Administrator, ob die Zuordnung stimmt (#016)
|
||
- Der Zeitplan läuft im selben Prozess wie die Anwendung, sieht jede Minute nach, welche Quelle fällig
|
||
ist, und verlässt sich auf eine Sperre in der Datenbank — zwei Instanzen kommen sich nicht in die
|
||
Quere (#016)
|
||
- Personen aus einer CSV-Datei einlesen: je Zeile ein Ergebnis, eine fehlerhafte Zeile bricht den
|
||
Import nicht ab. Eine leere Zelle löscht nichts, und wer schon aus dem Abgleich stammt, wird nicht
|
||
überschrieben (#016)
|
||
- Abteilungsvorschläge aus den Verzeichnis-Werten: ein Klick macht daraus eine gepflegte Abteilung
|
||
(#016)
|
||
- Die Attributzuordnung ist in der Oberfläche bearbeitbar: zehn Felder mit den AD-Vorgaben, damit ein
|
||
OpenLDAP nicht in der Datenbank nachgepflegt werden muss (#016)
|
||
- Abgleich-Log je Quelle: die letzten zehn Läufe mit gelesen, angelegt, übernommen, aktualisiert,
|
||
deaktiviert und übersprungen — und wer sie angestoßen hat, Mensch oder Zeitplan (#016)
|
||
- Wer schon von Hand angelegt wurde und dann im Verzeichnis auftaucht, wird **übernommen** statt
|
||
verdoppelt: dieselbe Zeile behält ihre Läufe und Geräte (#016)
|
||
- Ein hinterlegtes CA-Zertifikat ist als vorhanden erkennbar und lässt sich entfernen; der
|
||
Zertifikatstext selbst kommt nie zurück in die Oberfläche (#016)
|
||
- Die sieben Abläufe des Hauses liegen nach dem Seed als veröffentlichte Version 1 bereit: PC
|
||
Neuinstallation, Onboarding, Offboarding, Server-Inbetriebnahme, Firewall-Rollout, Patchday und
|
||
Restore-Test — 36 Abschnitte, 155 Schritte, 28 Variablen, jeder Schritt mit Hilfetext (#017)
|
||
- Die Vorlagen stehen als JSON im Austauschformat unter `prisma/seed-daten/vorlagen/` und werden
|
||
beim Seed gegen dasselbe Schema geprüft wie ein Import: ein Fehler fällt beim Einrichten auf und
|
||
nicht erst im ersten Lauf (#017)
|
||
- Beispielbestand zum Vorführen: drei Abteilungen mit Ziel-OU und Standardgruppen, fünf Personen,
|
||
`PC-001` und `NB-001` mit ihren Tag-Codes, fünf vorgedruckte Tags und die Schnellstart-Vorgaben
|
||
je Gerätetyp (#017)
|
||
- `SEED_MODE=DIENSTLEISTUNG` legt „Musterfirma GmbH" und „Bäckerei Reiter" an; der Beispielbestand
|
||
hängt dann an der Musterfirma, `LZ-7K3F9Q` führt also auf `MUS-PC-001` (#017)
|
||
- Namensregeln und die Sperr-OU kommen mit dem Seed: „Namen aus den Regeln vorschlagen" schlägt für
|
||
Maria Huber genau `m.huber@firma.example` und `mhuber` vor — dieselben Werte, die im
|
||
Beispielbestand stehen (#017)
|
||
- Anleitung für Technik und Verwaltung unter `docs/anleitung/`: neun Kapitel von den ersten Schritten
|
||
bis zur Fehlerbehebung, mit Bildern, die `pnpm docs:screenshots` reproduzierbar neu erzeugt (#018)
|
||
- `pnpm docs:pdf` macht daraus ein A4-PDF mit Deckblatt, Inhaltsverzeichnis und Seitenzahlen — hell
|
||
gedruckt, auch wenn die App dunkel ist (#018)
|
||
- Screenshots lassen sich markieren: Rahmen in der Akzentfarbe mit Nummer, für die Stellen, die ein
|
||
Kapitel gerade beschreibt (#018)
|
||
- `{{ou_gesperrt}}` wird aus Admin → Einstellungen vorbelegt. Das Feld war gespeichert und wurde von
|
||
keinem Platzhalter gelesen; `Move-ADObject -TargetPath "{{ou_gesperrt}}"` blieb leer, obwohl die
|
||
Einstellungsseite es versprach (#017)
|
||
- Die Ende-zu-Ende-Abläufe prüfen ihre Voraussetzungen, bevor der erste startet: läuft ein gebauter
|
||
Stand, steht der Seed-Bestand, und liest der Server **dieselbe** Datenbank wie der Testprozess.
|
||
Vorher fiel eine fehlende Voraussetzung erst mitten im Lauf auf — als Zeitablauf oder als „0
|
||
Treffer" (#018)
|
||
- Jeder Ablauf legt an, was er braucht, und räumt es weg. Keiner setzt mehr voraus, dass die
|
||
Datenbank schon Läufe, Entwürfe oder bestimmte Personen enthält, und keiner sucht seine Vorlage
|
||
über die Position in der Liste (#018)
|
||
|
||
### Behoben
|
||
|
||
- Die Umsortierung im Schnellstart wurde nicht gespeichert, wenn mehrere Zeilen desselben
|
||
Gerätetyps dieselbe `position` trugen — bei gleicher Position ist die Reihenfolge in Postgres nicht
|
||
festgelegt, und Lesemodell und Dienst gaben die Zeilen unterschiedlich zurück. Gefunden beim
|
||
Durchklicken des gebauten Stands, nicht von der Testsuite (#022)
|
||
- Der Compose-Stand las `DATABASE_URL` aus der `.env` — dort steht die Adresse für die Entwicklung
|
||
(`localhost` und ein offener Port), und im Container zeigt sie ins Leere. Wer den dokumentierten
|
||
Weg ging, bekam einen Stand, der nicht startet. Die Adresse baut sich jetzt immer aus den
|
||
`POSTGRES_*`-Werten zusammen (#019)
|
||
- Der dokumentierte Seed-Befehl `docker compose run --rm migrate pnpm db:seed` scheiterte auf einer
|
||
frischen Installation: der Einmal-Dienst bekam kein `APP_SECRET`, und ohne das lässt sich das
|
||
Bind-Passwort der Beispiel-Verzeichnisquelle nicht verschlüsseln. Abgebrochen wurde erst, nachdem
|
||
die Vorlagen schon angelegt waren (#019)
|
||
- Der Healthcheck des Webdienstes konnte nie bestehen: busybox löst `localhost` im Container zuerst
|
||
nach `::1` auf, Next lauscht aber nur auf IPv4. `docker compose ps` zeigte `web` dauerhaft als
|
||
`unhealthy`, obwohl die App einwandfrei antwortete — und jedes `--wait` lief in den Zeitablauf.
|
||
Geprüft wird jetzt über `127.0.0.1`, und ein Test hält die Adresse fest (#019)
|
||
- Die CI-Pipeline konnte in keinem der beiden Jobs grün werden: dem Prüf-Job fehlte das
|
||
LDAP-Testverzeichnis, das `pnpm test` dort verlangt, dem E2E-Job fehlte ein gestarteter Server, und
|
||
`shellcheck` wurde aufgerufen, ohne vorhanden zu sein. Die Zusage „Qualitätsschranken“ galt damit
|
||
nur lokal (#019)
|
||
- Der README nannte für die Entwicklungsdatenbank Port 5433; `docker-compose.dev.yml` nimmt 5434.
|
||
Wer dem README folgte, landete auf der Datenbank eines anderen Projekts — mit „0 Treffer“ als
|
||
einzigem Hinweis (#019)
|
||
|
||
### Sicherheit
|
||
|
||
- Der Compose-Stand bricht ab, wenn `APP_SECRET`, `POSTGRES_PASSWORD` oder `APP_URL` fehlen,
|
||
statt mit Standardwerten zu starten (#005)
|
||
- Die Mandantengrenze steht als zusammengesetzter Fremdschlüssel in der Datenbank: ein Lauf kann
|
||
strukturell kein Gerät, keine Person und keine Abteilung eines fremden Kunden wählen (#006)
|
||
- Ein Bind-Passwort ohne Versionspräfix wird von der Datenbank abgewiesen, ein versehentlich im
|
||
Klartext geschriebenes fällt damit sofort auf (#006)
|
||
- Ein Lauf nimmt seine Schrittergebnisse und Kommentare beim Löschen nicht mehr mit, und ein
|
||
Protokolleintrag kann seinen Urheber nicht mehr verlieren (#006)
|
||
- Das Rücksprungziel nach der Anmeldung wird aufgelöst und gegen die eigene Herkunft geprüft,
|
||
statt nur Präfixe zu vergleichen — `/<TAB>/fremd.example` wäre sonst eine offene Weiterleitung
|
||
gewesen (#007)
|
||
- Alle Umleitungen gehen über `APP_URL` statt über die Herkunft der Anfrage; hinter einem Reverse
|
||
Proxy zeigten sie sonst auf den internen Host (#007)
|
||
- Eine Anmeldung über den Anbieter verknüpft nur mit einem bestehenden Konto, wenn die Adresse
|
||
bestätigt ist (#007)
|
||
- Ohne eingerichtete `OIDC_GROUP_*`-Zuordnung bleibt die Rolle unangetastet, statt jeden auf
|
||
`LESER` herabzustufen (#007)
|
||
- Der letzte aktive Administrator kann sich weder herabstufen noch abschalten (#007)
|
||
- Beim Abschalten eines Kontos werden laufende Sitzungen sofort ungültig (#007)
|
||
- Im Modus Intern wird eine vom Client mitgeschickte Kunden-Zuordnung verworfen, nicht bloß
|
||
abgelehnt — es gibt in diesem Modus keine Kundenwahl (#008)
|
||
- Der Rückweg nach Intern wird abgelehnt, solange mehr als ein nicht archivierter Kunde
|
||
existiert (#008)
|
||
- Die eigene Organisation lässt sich nicht archivieren (#008)
|
||
- Eine veröffentlichte Vorlagen-Version ist unveränderlich: jeder Schreibversuch wird abgewiesen,
|
||
und ein bestehender Lauf zeigt auch nach neuen Veröffentlichungen genau die Schritte, mit denen
|
||
er gestartet wurde (#009)
|
||
- Die Unveränderlichkeit setzt zusätzlich die Datenbank durch, damit auch Migrationen, `psql` und
|
||
spätere Connectoren nicht daran vorbeischreiben können; der Rückweg nach Entwurf ist versperrt
|
||
(#009)
|
||
- Vorlagen eines fremden Kunden bleiben auch bei Suche und Filtern verborgen, und Historie wie
|
||
Aufbau einer Version geben nichts heraus, was der Benutzer nicht sehen darf (#009)
|
||
- Der Doku-Link eines Schritts nimmt nur `http` und `https` an; über eine importierte Datei wäre
|
||
ein `javascript:`-Link sonst fremder Code im Browser des Technikers (#009)
|
||
- Hilfetexte werden als Markdown ohne rohes HTML gerendert — ein Skript im Hilfetext einer
|
||
eingespielten Vorlage bleibt sichtbarer Text (#010)
|
||
- Der Doku-Link eines Schritts wird auch in der Vorschau nur dann verlinkt, wenn er `http` oder
|
||
`https` ist — dort hat ihn das Schema noch nicht gesehen (#010)
|
||
- Die Content-Security-Policy lockert `unsafe-eval` und WebSocket-Verbindungen **nur** im
|
||
Entwicklungsmodus; ein Test prüft die Entscheidung im Proxy selbst und nicht bloß die Funktion
|
||
darunter (#010)
|
||
- Eine abgelaufene Anmeldung beendet den Vorgang mit einer Meldung, statt die Seite auszuhängen —
|
||
im Editor wäre sonst jede ungespeicherte Arbeit verloren gewesen (#010)
|
||
- Eine archivierte Kundenzuordnung bleibt beim Speichern der Kopfdaten erhalten, statt sich
|
||
stillschweigend in „für alle Kunden“ zu verwandeln (#010)
|
||
- Die Stammdaten einer Person aus dem Verzeichnis lassen sich nicht von Hand überschreiben — der
|
||
nächste Abgleich würde die Änderung ohnehin zurückdrehen, und ein Wert, der zwischen zwei Läufen
|
||
hin- und herspringt, ist schlimmer als ein falscher. Deaktivieren bleibt möglich (#011)
|
||
- Ein Gerät kann nur einer Person desselben Kunden gehören; der Dienst sagt es verständlich, bevor
|
||
der zusammengesetzte Fremdschlüssel zuschlägt (#011)
|
||
- Ein Gerät, an dem Läufe hängen, lässt sich nicht löschen, nur archivieren (#011)
|
||
- Der Tag-Code entsteht beim Anlegen, steht sofort im Vergabebuch und lässt sich nie ändern — beides
|
||
in einer Transaktion, damit kein Code ohne Eintrag entsteht (#011)
|
||
- Werte werden für die jeweilige Shell maskiert, bevor sie in einen Befehl kommen: PowerShell
|
||
verdoppelt `"` und Backtick und entschärft `$`, Bash setzt einfache Anführungszeichen. Für Cmd
|
||
wird ein Wert mit `"` oder Zeilenumbruch **abgelehnt** statt unsicher maskiert — dort gibt es
|
||
keine Regel, die in jedem Fall stimmt (#012)
|
||
- `{{generiertes_passwort}}` entsteht bei jeder Anzeige neu, besteht aus 20 Zeichen ohne
|
||
verwechselbare Buchstaben und wird nirgends gespeichert; in der Druckansicht steht
|
||
`[Passwort – nicht gespeichert]` (#012)
|
||
- Ein eingesetzter Wert wird nicht doppelt maskiert: React maskiert Textknoten selbst, und ein Test
|
||
am gerenderten HTML hält fest, dass ein `<script>` im Namen wirklich als Text ankommt (#012)
|
||
- Der Archiv-Schreibschutz sitzt an genau einer Stelle und wird vor jeder Änderung an Kopfdaten,
|
||
Schrittergebnis und Kommentar gerufen; ein abgeschlossener Lauf bleibt änderbar, ein archivierter
|
||
nicht, und zurückholen darf nur ein Administrator (#013)
|
||
- Ein Abschluss nimmt sich selbst zurück, sobald eine spätere Änderung einen Pflichtschritt wieder
|
||
offen lässt — „abgeschlossen“ soll nicht lügen (#013)
|
||
- `nicht zutreffend` und `fehlgeschlagen` verlangen eine Begründung; das Feld erscheint, bevor
|
||
gespeichert wird, statt den Benutzer in eine Absage laufen zu lassen (#013)
|
||
- Der Zustands-Endpunkt für die Aktualisierung prüft das Leserecht selbst, liefert einen fremden
|
||
Lauf als „nicht gefunden“ und **keine Befehle** — sonst entstünde bei jedem Abruf ein neues
|
||
generiertes Passwort (#013)
|
||
- In einem Lauf steht **ein** generiertes Passwort, nicht je Vorkommen ein anderes: der Befehl, der
|
||
das Konto anlegt, und die Zeile, die das Passwort mitteilt, meinen dasselbe. Vorher hätte das
|
||
Konto A bekommen und der Benutzer B erfahren — und A wäre unwiederbringlich gewesen (#013)
|
||
- Die Aktualisierung lädt die Seite nicht von selbst neu, solange ein generiertes Passwort darauf
|
||
steht; sie fragt (#013)
|
||
- Eine Anfrage unter `/api/` ohne Anmeldung bekommt `401` statt einer Weiterleitung auf die
|
||
Anmeldeseite — ein `fetch` folgte ihr und hielt die HTML-Antwort für einen Erfolg (#013)
|
||
- Zuweisen lässt sich ein Lauf nur an jemanden, der ihn auch öffnen könnte; die Auswahl zeigt keine
|
||
Bearbeiter fremder Kunden mehr (#013)
|
||
- „Nicht zutreffend“ und „fehlgeschlagen“ lassen sich mit einem Klick zurücknehmen. Vorher sah es
|
||
danach aus, als wäre nichts gewählt, während in der Datenbank weiter der alte Zustand stand (#013)
|
||
- Im QR steht ausschließlich `{APP_URL}/t/{tagCode}` — kein Name, keine Seriennummer, kein
|
||
Besitzer. Ein Aufkleber hängt jahrelang sichtbar am Gerät und wird von jedem Telefon gelesen;
|
||
was darauf steht, ist veröffentlicht (#014)
|
||
- Der QR wird lokal erzeugt, nie über eine externe API: die bekäme mit jedem Aufruf den Bestand an
|
||
Tag-Codes geliefert (#014)
|
||
- Das WebAssembly des QR-Lesers kommt aus dem eigenen Haus statt aus einem CDN — die
|
||
Content-Security-Policy erlaubt nur `connect-src 'self'`, und die Anwendung soll ohne Internet
|
||
laufen (#014)
|
||
- Ein Tag, ein Etikett und eine Geräteseite eines fremden Kunden sind nicht erreichbar; „gibt es
|
||
nicht“ und „darfst du nicht“ sind dieselbe Antwort (#014)
|
||
- In der Druckansicht erscheint vom Mitarbeiter nur Name, UPN und Abteilung — das Lesemodell holt
|
||
gar nichts anderes erst. Ein Ausdruck liegt herum; was darauf steht, ist aus der Hand gegeben
|
||
(#015)
|
||
- Für den Ausdruck entsteht **kein** generiertes Passwort: wo in einem Vorlagentext
|
||
`{{generiertes_passwort}}` steht, erscheint `[Passwort – nicht gespeichert]`. Zwei Ausdrucke
|
||
desselben Laufs sind deshalb Zeichen für Zeichen gleich — der Test prüft genau das (#015)
|
||
- Die Archivsuche setzt jeden Wert als Parameter und maskiert `%`, `_` und `\` für `ILIKE`: die
|
||
Suche nach „50 %“ trifft nicht „5099“ (#015)
|
||
- Das Bind-Passwort einer Verzeichnisquelle wird mit AES-256-GCM verschlüsselt, erreicht keine
|
||
Antwort und kein Log: Fehlermeldungen der LDAP-Bibliothek werden gefiltert, bevor sie gespeichert
|
||
werden, und die Ausnahme trägt keine `cause` — sonst stünde die ganze Kette im Server-Log (#016)
|
||
- Gespeichert werden nur die zugeordneten Attribute: keine Passwörter, keine
|
||
Gruppenmitgliedschaften (#016)
|
||
- Die Hostnamen-Prüfung von TLS lässt sich nicht abschalten; ein Test hält fest, dass weder
|
||
`rejectUnauthorized` noch `checkServerIdentity` gesetzt werden (#016)
|
||
- Das Verzeichnis gibt es nur im Modus Intern — Reiter, Seite und Dienst prüfen das je selbst (#016)
|
||
- Der Abgleich deaktiviert niemanden, wenn sich **kein einziger** gelesener Eintrag zuordnen ließ:
|
||
ein falsch gesetztes Kennungsattribut hätte sonst die gesamte Belegschaft abgeschaltet und dabei
|
||
Erfolg gemeldet (#016)
|
||
- Übersprungene Einträge werden nicht deaktiviert: sie standen im Ergebnis, nur ohne brauchbare
|
||
Kennung — wer sie mit abschaltete, nähme einem anwesenden Menschen den Zugang (#016)
|
||
- Ergebniscodes des Verzeichnisses werden in Sätze übersetzt: falsches Passwort, fehlendes Leserecht
|
||
und ein unbekannter Basis-DN sahen vorher gleich aus (#016)
|
||
- Die Beispiel-Verzeichnisquelle des Seeds ist abgeschaltet, und ihr Bind-Passwort ist ein bei jedem
|
||
Lauf neu gewürfelter Zufallswert: ein fest verdrahtetes Beispielpasswort im Quelltext stünde am
|
||
Ende in einer echten Installation (#017)
|
||
- Titel und Hilfetexte eines Laufs bekommen **dasselbe** generierte Passwort wie der Befehl daneben.
|
||
Vorher erzeugte jede Auflösung ein eigenes: im Befehl stand A, im Hilfetext B — wer B in den
|
||
Passwortmanager legte, sperrte den neuen Mitarbeiter aus (#017)
|
||
- Der Seed vergibt keinen verbrannten Tag-Code neu: er fragt das Vergabebuch und nicht nur die
|
||
Geräteliste, wie es der Gerätedienst tut (#017)
|
||
- Der Verzeichnis-Ablauf legt seine eigene Quelle an, statt die erste vorhandene zu nehmen: er hätte
|
||
in einer echten Installation die Attributzuordnung der produktiven Quelle überschrieben (#018)
|
||
- Ein Nachlauf setzte die Namensregeln auf leer, statt den vorherigen Wert wiederherzustellen — er
|
||
löschte damit, was der Seed eingerichtet hatte (#018)
|