ad3bf48d2cdfa44b2820399cbd1147eb082c592c
13
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e0b03ea336 |
Gedankenstriche durch Bindestriche ersetzt
Prüfung / Lint, Typen, Tests (push) Canceled after 0s
Prüfung / Images bauen (pdf) (push) Canceled after 0s
Prüfung / Images bauen (web) (push) Canceled after 0s
Prüfung / Images bauen (worker) (push) Canceled after 0s
Images / bauen (pdf, docker/pdf.Dockerfile) (push) Canceled after 0s
Images / bauen (web, docker/web.Dockerfile) (push) Canceled after 0s
Images / bauen (worker, docker/worker.Dockerfile) (push) Canceled after 0s
375 Vorkommen in 210 Dateien. Betrifft Kommentare, Oberflaechentexte und die README; an keiner Stelle Bedeutung, nur Zeichensetzung. |
||
|
|
79d2cc4753 |
Entzug greift auch im offenen Ereignisstrom
Nachgesehen, was bei Herabstufung, Rollenentzug, Rauswurf und Austritt passiert. Für Requests war alles abgedeckt: der Worker löscht die Sitzungen, ein Trigger verwirft die zwischengespeicherten Rechte, getCurrentUser prüft bei jedem Request Sitzungszeile, isActive und Mitgliedschaft nach. Die Zeile in der Datenbank ist der Nachweis, nicht das Cookie - und eine Zeile lässt sich zurückziehen. Eine Stelle fiel dabei durch: der Ereignisstrom stellt keine neuen Requests. Er prüfte einmal beim Verbinden und lief danach mit einer eingefrorenen Rechtekopie weiter. Wer mit offenem Tab hinausgeworfen wurde, bekam weiter Chatnachrichten zugestellt, mit vollem Text - jeder Seitenaufruf wäre abgemeldet gewesen, aber es machte ja keiner mehr einen. /api/stream löst Sitzung und Rechte jetzt bei jedem Ereignis und bei jedem Herzschlag neu auf. Ändern sich die Rechte, werden auch die erlaubten Themen neu bestimmt: ein Ereignis zu einem Verlaufseintrag trägt nur eine Kennung, das Thema ist dort die einzige Schranke. Ist die Sitzung weg, geht ein revoked hinaus und die Verbindung wird geschlossen - Schliessen allein genügt nicht, EventSource baut sofort wieder auf. Fünf Tests für die Web-Hälfte der Kette; die Discord-Hälfte deckte worker/tests/mitgliedschaft.test.ts schon ab. |
||
|
|
aef3df0d44 |
Abteilungen als Ebene über der Personalakte
Punkt 3 der Wünsche. Die Abteilung stand als Freitext in der Personalakte. Damit liess sich nichts fragen: „SHD", „Shd" und „Sicherheitsdienst" waren drei Abteilungen. Sie ist jetzt ein eigenes Modell, im Adminbereich verwaltbar, und die drei vorhandenen Freitexte sind in der Migration zu Abteilungen geworden. **Eine Fallakte trägt keine Abteilung.** Sie gehört zu einer, wenn eine beteiligte Person ihr angehört - abgeleitet über CaseParticipant und die Personalakte. Eine eingetragene Zuordnung wäre nach zwei Wochen falsch: Leute wechseln, Beteiligte kommen dazu, und niemand pflegt das nach. Dasselbe gilt für die Dokumente: ein Dokument unmittelbar an der Abteilung wäre halb verdrahtet, weil die Vorlagen über TemplateUse nur Fall- und Personalakte kennen. Die Abteilungsseite zeigt Mitglieder, abgeleitete Fälle und Dokumente, Vermerke, Chat und ein Kreisdiagramm der Fallarten; die Übersicht eines über alle Abteilungen. Das Diagramm ist serverseitig gerendertes SVG - eine Bibliothek wäre das Teuerste auf der Seite gewesen. Die Segmente sind Abstufungen von --steel und nicht die Stufenfarben: ein Tortenstück in Oxidrot würde als „streng geheim" gelesen. Die Zusammenfassung ist die gefährliche Stelle. Eine Abteilungsseite fasst zusammen, was einzeln längst geschützt ist, und eine Zusammenfassung, die nicht filtert, verrät mehr als jede Einzelansicht. Deshalb läuft hier jede Liste und jede Zahl durch dieselben Fragmente wie überall; ein Test prüft, dass die Summe der Diagrammanteile mit der Länge der Liste übereinstimmt. Wo jemand Personalakten gar nicht sehen darf, steht null und keine Null. Vermerke erben Regel 2 unverändert: kein DELETE, Version bei jeder Änderung, Grabstein bei jeder Zurücknahme, als Regel und Trigger in der Datenbank. Ohne Korrekturkette, Anhänge und Karenzzeit - was Beweiswert hat, gehört in eine Fallakte. Nebenbei: das Zugriffsprotokoll hängt jetzt am dafür vorgesehenen Recht audit.read statt an admin. |
||
|
|
89edaa6ae1 |
Protokoll im Adminbereich, ein Knopf für das ganze Blatt
Die restlichen zwei Änderungswünsche. **Protokoll.** „Wer was wo macht" stand seit dem ersten Tag in AuditLog - harte Regel 3 sorgt dafür. Was fehlte, war die Ansicht darauf. /admin/protokoll ist genau das und keine zweite Aufzeichnung: filterbar nach Person, Aktion, Art und Zeitraum, rückwärts blätternd. Nur für die Leitung. Eine Zeile nennt Aktenzeichen und Namen, und schon die Existenz einer Zeile verrät, dass es einen Vorgang gibt; ein gefiltertes Protokoll für alle wäre ein Weg, die Sichtbarkeitsregeln von hinten aufzurollen. Aus demselben Grund werden Kennungen ohne Sichtbarkeitsfilter zu Aktenzeichen aufgelöst - ein Protokoll aus UUIDs liest niemand, und eines, das niemand liest, ist keine Kontrolle. **Speichern.** Jedes Feld trug seinen eigenen Knopf: ein Arbeitsvertrag mit zwölf Feldern hiess zwölfmal speichern. Der Knopf steht jetzt am Blatt. Geschrieben wird trotzdem je Feld und nicht als Transaktion, und das folgt aus dem Datenmodell: Abschnitte sind einzeln versioniert, weil mehrere Leute gleichzeitig an einer Akte arbeiten. Als Transaktion blockierte ein einziges zwischenzeitlich geändertes Feld das ganze Blatt - elf gute Änderungen verloren, weil jemand das zwölfte angefasst hat. So geht durch, was durchgeht, und die Rückmeldung sagt, was offen blieb und wo. Lichtbilder bleiben ausgenommen: ein Upload mit Prüfkette ist keine Feldänderung. lib/audit.ts liegt jetzt als lib/audit/write.ts neben lib/audit/query.ts. |
||
|
|
6af1e437e7 |
Runde Bedienelemente, Admin zusammengelegt, Kanäle entfallen
Vier der acht Änderungswünsche. **Knöpfe.** Rund vierzig der siebzig Knöpfe waren blosser Text in Akzentfarbe, von einem Link nicht zu unterscheiden. Jeder trägt jetzt Rahmen und Grundfläche, über die Klasse .btn an einer Stelle definiert statt siebzigmal als Klassenkette. Beim Drücken zieht der Knopf sich zusammen und bekommt einen inneren Ring - er wirkt kurz dicker, ohne dass die Rahmenbreite sich ändert und das Umfeld springt; die Rückmeldung setzt ohne Verzögerung ein und klingt langsam ab, sonst bliebe ein kurzer Klick unsichtbar. Dazu die neue Regel: Flächen bleiben eckig, alles Bedienbare ist rund. Damit ist die Rundung eine Auskunft und keine Verzierung. **Verwaltung.** Rollen, Vorlagen und Änderungsrichtlinie waren drei Einträge in der Kopfzeile für etwas, das man selten und dann zusammenhängend braucht. Jetzt ein Eintrag /admin mit Reiterleiste, dazu eine gemeinsame Tabs-Komponente, die auch der Fallakte zeigt, welcher Reiter offen ist. **Kanäle.** Ersatzlos entfernt, mitsamt Tabelle. Der Chat gehört dorthin, wo der Vorgang steht; ein eigener Reiter mit freien Räumen war der Rückweg nach Discord. Die Personalakte hat jetzt denselben Chat wie die Fallakte - ohne „als Vermerk übernehmen", denn dafür braucht es einen Verlauf, und Meldungen gehen nur an Erwähnte, nie in den allgemeinen Kanal: dass es zu jemandem eine Personalakte gibt, ist selbst eine Auskunft. Ein CHECK erzwingt jetzt, dass eine Nachricht zu genau einem Ort gehört. **Kopfzeile.** Suchbalken statt Suchlink, als gewöhnliches Formular ohne ein Byte im Bündel. Thema, Benachrichtigungen und Abmelden sitzen hinter dem Profilbild, das von Discord kommt; wer keins hat, bekommt seine Initialen. Nebenbei: Aktenzeichen in Tests waren nur auf vier Stellen eindeutig und kollidierten zwischen Läufen, weil Akten nicht löschbar sind. |
||
|
|
da4a067850 |
Zuletzt geöffnet als Sprungliste, ohne Verlauf
Die letzte offene Zeile aus TASKS.md. Sie blieb offen, weil eine Spur über
jeden Aktenaufruf schnell etwas anderes wird als eine Bequemlichkeit.
Vier Festlegungen halten sie auf der richtigen Seite:
- Kein Verlauf. Der Primärschlüssel (userId, targetType, targetId) lässt je
Nutzer und Datensatz genau eine Zeile zu, die überschrieben wird. Die
Tabelle kann nicht sagen, wie oft oder in welcher Reihenfolge gearbeitet
wurde. Zwanzig Einträge, älteres fällt hinten heraus.
- Nur die eigene. Keine Ansicht, kein Adminbereich, keine Funktion mit
fremder Kennung. Jede Abfrage nimmt p.userId.
- Jederzeit löschbar, ohne Rückfrage und ohne Grabstein.
- Vorabrufe zählen nicht. Ohne die Prüfung auf next-router-prefetch stünde
dort, worüber jemand mit der Maus gefahren ist.
Gelesen wird über dieselben Sichtbarkeitsfragmente wie überall: eine
vorgemerkte Kennung ist keine Berechtigung. Steigt eine Akte über die
Freigabestufe, verschwindet sie ohne Lücke und ohne Platzhalter; Personalakten
brauchen zusätzlich personnel.read. Das Zugriffsprotokoll bleibt unberührt und
bleibt die einzige Auskunft darüber, wer wann was gelesen hat.
Sie steht auf der Übersicht und im leeren Feld der Kommandopalette. Der zweite
Ort ist der eigentliche.
Nebenbei: NEXT_DIST_DIR, damit ein Build neben einem laufenden
Entwicklungsserver nicht dasselbe .next-Verzeichnis abräumt.
|
||
|
|
addef20e16 |
Phase 9 abgeschlossen: Übersicht, Beobachtungsliste, helles Thema, Messung
Die Übersicht beantwortet vier Fragen, die man morgens hat: woran arbeite ich,
was ist fällig, wo hat jemand geschrieben, was hat sich getan. Jede Liste
läuft durch visibleCaseWhere - das ist der Ort, an dem eine unachtsame
Abfrage am ehesten eine Akte verrät. Ein Test prüft jede einzeln gegen eine zu
hoch eingestufte Akte. Der bisherige Inhalt ("Dein Zugang") steht jetzt unter
/zugang.
Beobachten ist eine Merkliste, kein Recht: nur was sichtbar ist, lässt sich
merken, und steigt eine Akte später über die eigene Stufe, verschwindet sie
aus der Liste, ohne dass jemand aufräumt.
Das helle Thema tauscht nur die semantische Ebene; die Grundpalette bleibt.
Die Klassifizierungsfarben sind für hellen Grund eine Spur dunkler, im Farbton
aber dieselben - eine Stufe muss in beiden Themen wiedererkennbar sein. Drei
Zustände, "wie das System" ist der Auslieferungszustand, und ein Skript im
Kopf setzt die Wahl, bevor das erste Mal gemalt wird.
Die Messung misst nicht den Nutzer. web-vitals schickt Zahlen und das
Routenmuster, keine Kennung und keinen konkreten Pfad: wer wann welche Akte
geöffnet hat, steht im Zugriffsprotokoll, und eine zweite Spur daneben wäre
eine Überwachungsfunktion ohne Auftrag. Ausgewertet wird Median und 75.
Perzentil, nicht der Mittelwert.
Mobil: die breiten Tabellen scrollen in sich, statt die Seite breiter zu
machen. Ein e2e-Test hält fest, dass keine Seite waagerecht überläuft.
scripts/restore-test.sh spielt eine Sicherung in eine Wegwerf-Datenbank und
prüft ausdrücklich, dass Regeln und Trigger mitkommen: eine Wiederherstellung
ohne entry_no_delete wäre eine Datenbank ohne die Zusicherung, um derentwillen
das System gebaut wird. Einmal durchgespielt, alles angekommen.
186 Tests im Web-Paket, 17 in der Datenbank, 7 im Worker, 54 e2e.
Grösste Route 116,2 von 120 kB.
|
||
|
|
f1106a57d6 |
Phase 8 abgeschlossen: Export als PDF, Wasserzeichen, Vier-Augen-Freigabe
Die Druckansicht ist die einzige Vorlage für den Export: kein zweites Layout von Hand in einer PDF-Bibliothek. Der Playwright-Dienst ruft genau diese Seite auf und bekommt dafür ein signiertes, kurzlebiges Zeichen statt eines Sitzungscookies. Es gilt nur, solange der Auftrag läuft, ist an den Anfragenden gebunden, und die Stufe wird beim Einlösen frisch aufgelöst und auf die im Zeichen vermerkte gedeckelt. Der Export darf nie sauberer aussehen als die Akte: gesperrte Felder erscheinen als Balken mit Vermerk statt zu fehlen, geänderte Einträge tragen ihre Version, zurückgezogene stehen als Grabstein. Wasserzeichen mit Name, Zeitstempel und Stufe auf jeder Seite. Die Stufe steckt im Zwischenspeicherschlüssel. Ohne sie bekäme ein Leser der Stufe 1 das PDF, das für Stufe 4 gerendert wurde. Ab Stufe 3 wartet der Auftrag auf Freigabe durch ein zweites Augenpaar - und der Anfragende darf sich nicht selbst freigeben, auch nicht mit dem Recht dazu. Offene Freigaben stehen auf der Übersicht. Dazu Einzeldokumente als PDF (Aktenblatt bleibt Aktenblatt), ZIP-Sammelexport mit eigenem achtzigzeiligem Schreiber statt einer Abhängigkeit, und der JSON-Vollabzug ohne Stufenfilterung - die einzige Stelle, an der die Klassifizierung nicht greift, deshalb hinter einem eigenen Admin-Endpunkt. Zwei Funde unterwegs, beide in docs/13 festgehalten: Die Druckansicht warf notFound(), und das Skelett aus dem Wurzel-loading.tsx blieb im Browser stehen - der PDF-Dienst hätte leere Blätter gedruckt. Gefunden nur, weil der e2e-Test auf den Inhalt prüft und nicht auf den Statuscode. Sie rendert die Auskunft jetzt selbst. Und die Entdopplung der Anhänge verwies auf Dateien, ohne nachzusehen, ob sie noch daliegen. Nach einem geleerten /tmp zeigte jeder weitere Upload desselben Inhalts stumm ins Leere. 178 Tests im Web-Paket, 17 in der Datenbank, 7 im Worker, 44 e2e. Fallakte bei 119,8 von 120 kB - das Budget ist aufgebraucht. |
||
|
|
cb0eabb266 |
Phase 7 abgeschlossen: Suche mit gestuftem Index, Facetten, Palette
Der bisherige Suchvektor enthielt nur, was jeder sehen darf. Das war leck-sicher und unbrauchbar zugleich: ab Stufe 1 fand die Suche nichts von dem, was einen Fall ausmacht. SearchIndex hält jetzt sechs Zeilen je Ziel, eine pro Freigabestufe. Die Zeile zur Stufe N enthält alles, was ein Leser dieser Stufe sehen darf. Eine Suche nimmt genau diese eine Zeile und kann deshalb nichts finden, was sie nicht lesen darf - auch keinen höher eingestuften Eintrag in einer sichtbaren Akte. Schwärzungen werden dabei stufenweise aufgelöst: strip_redactions_above entfernt nur, was über der Stufe liegt. Sonst fände ein Leser der Stufe 4 einen Namen nicht, den er auf der Seite im Klartext sieht, und ein Leser der Stufe 1 einen, den er dort nur als Balken sieht. Die Suchbedingung ist nicht die Zugriffsprüfung: der Volltext liefert Kandidaten, die Sichtbarkeit kommt aus visibleCaseWhere - derselben Funktion wie in der Fallliste, und Treffer, Trefferzahl und Facettenzähler entstehen gemeinsam in einer Abfrage. Eine in SQL nachgebaute Sichtbarkeitsregel wäre die Sorte Duplikat, die auseinanderläuft. Filter stehen in der Adresse. Damit ist jeder Zustand teilbar, der Zurück-Knopf tut das Richtige, und eine gespeicherte Ansicht ist ein Lesezeichen. Die mitgelieferten Ansichten stehen im Programm, weil sie von der Gegenwart abhängen. Strg+K öffnet die Kommandopalette. Im Grundgerüst steckt nur das Tastaturereignis, die Palette selbst wird beim Öffnen geholt - das kostet die anderen Seiten rund hundert Byte. Offen geblieben: "Zuletzt geöffnet" als Ansicht. Dafür müsste jeder Aufruf jeder Akte mitgeschrieben werden; das wäre eine Überwachungsfunktion, die niemand bestellt hat. In TASKS vermerkt. 165 Tests im Web-Paket, 17 in der Datenbank, 7 im Worker, 34 e2e. Grösste Route bei 119,7 kB gegen 120 erlaubte. |
||
|
|
88c257e793 |
Phase 6 abgeschlossen: Aktenchat, Kanäle, Echtzeit, Benachrichtigungen
Ein Ereignisstrom mit Themen (/api/stream), in dem der Dateistrom aus Phase 5 aufgegangen ist. Zwei Prüfungen: beim Verbinden, welche Themen der Nutzer abonnieren darf, und je Ereignis wird der Inhalt mit seinen Rechten geladen. Das zweite ist nicht nur Vorsicht - eine Umklassifizierung wirkt dadurch in einer offenen Verbindung sofort. In der pg_notify-Nutzlast steht nie ein Inhalt, nur Thema, Art und Kennung. Der Aktenchat sendet optimistisch mit clientseitiger UUIDv7, die zugleich Idempotenzschlüssel ist. Antworten, vier feste Reaktionen, Bearbeiten mit sichtbarem editedAt, Zurückziehen mit Platzhalter - Löschen gibt es nicht, und eine Regel in der Datenbank hält das fest. Eine zurückgezogene Nachricht kommt ohne Text an, nicht ausgeblendet. Aktenzeichen im Text werden zu Vorschaukarten, serverseitig aufgelöst. Eine Akte, die der Leser nicht sehen darf, sieht darin genauso aus wie eine, die es gar nicht gibt. "Als Vermerk übernehmen" steht sichtbar an jeder Nachricht. Der Vermerk gehört dem Verfasser, nicht dem Übernehmenden; ein Systemeintrag nennt den Ursprung, damit der Wortlaut später nicht für die Akte geschrieben wirkt. Bei den Discord-Benachrichtigungen entscheidet die Web-App, wer was erfährt, und der Worker liefert nur aus. Die Alternative hätte geheissen, die Sichtbarkeitsprüfung im Worker ein zweites Mal zu bauen. In meta steht ein Titel nur, wenn er hinaus darf - ab Stufe 3 keiner. Postausgang als Tabelle, angestossen über LISTEN, mit Takt als Sicherheitsnetz. Nebenbei riss das Budget der Fallakte erneut (122 kB). Drei Schritte zurück auf 119,6: Chattexte in eine eigene i18n-Datei, ein gemeinsames AddPanel statt viermal derselbe Aufklapp-Code, und eine eigene UUIDv7 statt des Pakets - die Sortierbarkeit, auf der die Nachlieferung beruht, steht damit im eigenen Code und hat Tests. Unterwegs habe ich NotifyMode und User.notifyMode ein zweites Mal angelegt, obwohl es beides seit dem Grundgerüst gibt. Die Migration schlug fehl und die Datenbank musste von Hand aufgeräumt werden; die Lehre steht in docs/13. 155 Tests im Web-Paket, 15 in der Datenbank, 7 im Worker, 32 e2e. |
||
|
|
55bcb0d95c |
Phase 5 abgeschlossen: Dateien mit vollständiger Prüfkette
Upload → quarantine/ → Typprüfung → ClamAV → Neukodierung → store/. Es gibt keinen Pfad, auf dem eine Datei ungeprüft in store/ landet, und keinen Schalter, der die Kette abschaltet. Die Typprüfung liest Magic Bytes aus einer eigenen Signaturtabelle statt aus file-type: bei einer Positivliste aus fünf Formaten hilft es nicht, hundert erkennen zu können. Die Signatur muss an Position 0 stehen - ein Vorspann vor %PDF- ist der klassische Weg, eine Datei für zwei Programme zugleich lesbar zu machen. Wer trotzdem durchkommt, kommt als text/plain durch und wird unter der Endung des erkannten Typs ausgeliefert, nicht unter der hochgeladenen. Drei Ausgänge statt zwei: BLOCKED heisst "ist gefährlich", FAILED heisst "wissen wir nicht". Nur FAILED lässt sich freigeben, und die Freigabe wiederholt die Kette ohne den Scanner - Typprüfung und Neukodierung laufen dabei mit. Ein fehlender clamd macht damit keine Datei sauber; ein Test hält das fest. Auslieferung nur über /api/files/[id], mit Sichtbarkeitsprüfung der zugehörigen Akte, Protokoll ab Stufe 3 und den harten Kopfzeilen aus docs/07. Vorschaubilder dürfen inline, weil sie serverseitig als WebP neu kodiert wurden. Das Lichtbild im Aktenblatt ist damit gefüllt: der Feldwert ist die Kennung eines geprüften Anhangs, serverseitig zu einer Adresse aufgelöst. /api/events liefert attachment.scanned über pg_notify - die Grundlage, auf der Phase 6 den Aktenchat aufsetzt. Die Sichtbarkeit wird je Ereignis geprüft, sonst verriete schon die Kennung die Existenz. Nebenbei riss das Bündelbudget (121 kB gegen 120 erlaubte): lib/i18n/de.ts war ein Objekt, und eine Client-Komponente zieht die ganze Datei mit. Die Texte stehen jetzt in sechs Dateien nach Bereichen, de.ts setzt sie nur zusammen. Grösste Route wieder bei 118 kB, und sie wächst nicht mehr mit jedem neuen Bereich mit. 133 Tests im Web-Paket, 15 in der Datenbank, 28 e2e. |
||
|
|
c6aefb310c |
Phase 1: Anmeldung, Rechte und Rollenverwaltung
Auth.js übernimmt nur den OAuth-Ablauf mit state und PKCE, Scope identify. Das Zugangstoken des Nutzers wird danach verworfen: die Rollen holt der Bot mit seinem eigenen Token. Die Sitzung steht in der Datenbank, im Cookie liegt nur userId und sessionId. Ohne die Zeile in Session ist das Cookie wertlos, und nur so lässt sich eine Sitzung zurückziehen. Wer sich anmelden darf, steckt in lib/auth/signInPolicy.ts statt im Callback: das ist die Regel, an der alles hängt, und sie gehört einzeln prüfbar. Kein Guild-Mitglied, kein Zugang. Keine Rolle mit grantsMembership, kein Zugang. Notfall-Admins umgehen die Rolle, aber nicht die Guild-Mitgliedschaft. RoleMapping trägt jetzt grantsMembership, category und isPresent. Der Worker spiegelt jede Rolle der Guild hierher, damit im Adminbereich nichts von Hand eingetragen werden muss. Gelöschte Rollen werden nicht entfernt, sondern auf isPresent = false gesetzt: beim Umzug auf den echten Server bleiben sie als Vorlage stehen. Der Worker gleicht über Gateway-Ereignisse und zusätzlich alle fünf Minuten voll ab. Verliert jemand seine AfiS-Rolle, beendet er dessen Sitzungen sofort und setzt isActive auf false - der Datensatz bleibt, damit Autorenangaben in alten Akten gültig bleiben. Das passiert im Worker und nicht in der Web-App, weil es dort erst beim nächsten Request des Betroffenen griffe. Zwei Widersprüche der Dokumente aufgelöst, festgehalten in docs/13-entscheidungen.md: der Ausschluss schlägt auch den Admin (das Codebeispiel in 03-auth-und-rechte.md übergeht ihn, die Reihenfolge eine Zeile darüber nicht), und versiegelte Akten fallen im Where-Fragment heraus statt in einer Prüfung danach. 45 Tests, davon die Negativfälle aus docs/03 gegen eine echte Datenbank: Stufe 2 sieht die Stufe-4-Akte nicht und findet sie auch nicht in der Trefferzahl, ein Ausgeschlossener mit Stufe 5 kommt nicht heran, ein Rollenentzug beendet die Sitzungen, und ein Ausfall der Discord-API sieht nicht aus wie eine verweigerte Mitgliedschaft. |
||
|
|
ade977538d |
Web-Anwendung: Design-Tokens, Texte, Gesundheitsendpunkt
Design-Tokens aus docs/10-design-system.md als CSS-Variablen: Grundpalette, die sechs Klassifizierungsfarben, Eckenradius durchgehend 2 px, keine Schatten ausser dem Fokusring. Der Schwärzungsbalken mit dem einmaligen Durchwischen respektiert prefers-reduced-motion. Vier Tests halten die Tokens gegen das Dokument. IBM Plex selbst gehostet als woff2, latin und latin-ext getrennt über unicode-range, font-display: swap, immutable-Cache. Alle Oberflächentexte in lib/i18n/de.ts. Ein Test durchsucht app/ und components/ nach Umlauten im JSX und schlägt fehl, wenn dort deutscher Text steht. Er hat beim ersten Lauf gleich einen Verstoss gefunden. /api/health prüft Datenbank, Discord-Gateway und ClamAV. Die Gesamtbewertung fällt nur auf "down", wenn die Datenbank weg ist: die anderen Dienste haben eigene Healthchecks im Stack, und ein ausgefallener Virenscanner soll nicht den Web-Container als tot markieren. Strikte CSP mit Nonce, kein unsafe-inline für Skripte. Die erste Route liegt bei 100 kB JavaScript nach Kompression, Budget sind 120 kB. |