bd750f26d055f296c66827695f9ab3f60f766523
8
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
42dbf0f6b1 |
Anhaenge zurueckziehen statt sie liegen zu lassen
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
Bisher gab es ueberhaupt keinen Weg, einen Anhang wieder loszuwerden. Wer die falsche Datei hochlud, musste damit leben. Das ist keine Strenge, sondern eine Luecke: die Regel des Hauses lautet nicht "nichts geht wieder weg", sondern "nichts geht unbemerkt weg". Also derselbe Weg wie beim Verlaufseintrag und beim Dokument. Zurueckziehen mit Pflichtbegruendung, der Anhang bleibt als Grabstein an seiner Stelle stehen - durchgestrichen, mit Zeitpunkt und Grund, fuer jeden sichtbar, der ihn sehen darf. Aufheben laesst es sich, beides steht im Protokoll. Aus dem Umlauf heisst wirklich aus dem Umlauf: die Auslieferung verweigert die Datei, sie verschwindet aus Lichtbildfeldern, und das Vorschaubild faellt weg. Ein Grabstein, unter dem der Download weiterlaeuft, waere eine Beschriftung und keine Ruecknahme. Fuer `audit.read` bleibt sie abrufbar - wie der Text eines zurueckgezogenen Eintrags, sonst waere es doch ein Loeschen. Im Ausdruck steht der Anhang weiter im Verzeichnis, mit dem Grund daneben. Ihn dort wegzulassen hiesse, den Ausdruck sauberer zu machen als die Akte. Die Datei auf der Platte bleibt liegen. Sie zu entfernen waere das Loeschen, das hier gerade nicht stattfindet - und ein Anhang, dessen Pruefsumme nicht mehr nachrechenbar ist, waere als Beweismittel wertlos. Dazu eine Regel in der Datenbank, die DELETE auf Attachment verbietet, und eine Bedingung, die einen Grabstein ohne Begruendung ausschliesst. Zuruecknehmen darf, wer die Datei hochgeladen hat - der haeufige Fall, und ohne das bliebe jeder Fehlgriff liegen, weil dafuer die Leitung zu rufen waere - sowie jeder, der am Besitzer schreiben darf. |
||
|
|
4652dd08ec |
Virenpruefung an clamd anbinden, wo clamd tatsaechlich lauscht
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
Die Adresse zeigte auf /var/run/clamav/clamd.sock in einem geteilten Volume. Das offizielle clamav-Image legt seinen Unix-Socket aber unter /tmp im eigenen Container an - nachgemessen, das Verzeichnis im Volume bleibt leer. Jeder Verbindungsversuch lief damit in ENOENT, und die Statusseite meldete die Virenpruefung dauerhaft als gestoert. Folgenreich war das nicht nur fuer die Anzeige: ohne erreichbaren Scanner bleiben Uploads auf "Pruefung unvollstaendig" liegen und brauchen die Freigabe von Hand. Die Kette hat also gehalten, aber niemand kam durch. Dasselbe Image oeffnet ab Werk TCPSocket 3310. Darueber laeuft die Anbindung jetzt; der Port ist im Stack nicht veroeffentlicht und nur im internen Netz erreichbar. CLAMAV_ADDRESS nimmt weiterhin einen Pfad an, falls irgendwo doch ein Unix-Socket erreichbar ist. Das Socket-Volume faellt weg, es enthielt nie etwas. Der Scanner sieht ausserdem die Dateiablage nicht mehr: uebertragen wird per INSTREAM durch die Verbindung, gelesen hat er dort nie etwas. Geprueft gegen ein laufendes clamd, einschliesslich EICAR. Diese Pruefung bleibt als abschaltbarer Teil der Suite stehen: CLAMAV_LIVE=clamav:3310 pnpm --filter @afis/web test pruefkette Die uebrigen neuen Pruefungen decken das Zerlegen der Adresse ab. Sie haetten den Fehler nicht gefunden - eine Adresse, die auf nichts zeigt, ist syntaktisch tadellos. Nur eine echte Verbindung beantwortet das. |
||
|
|
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. |
||
|
|
38be773636 |
README erklärt das Projekt, Zusicherungen unter Nummern
Die README beschrieb das Vorgehen statt der Anwendung. Sie erklärt jetzt, was das System kann, wie es aufgebaut ist und wie man es lokal und im Betrieb fährt. Dazu tragen die acht Zusicherungen dort ihre Nummern - Berechtigungsprüfung in der Abfrage, kein DELETE im Verlauf, Protokollpflicht ab Stufe 3, Magic Bytes statt Dateiendungen, Skelette statt Spinner, Existenz als Information, keine Geheimnisse im Repository, keine Einmal-Container. Achtzehn Dateien verwiesen für ebendiese Regeln auf eine Datei, die nicht mehr mitverfolgt wird; sie zeigen jetzt auf die README. Arbeitsunterlagen bleiben auf der Platte, werden aber nicht mehr verfolgt. Die Ausschlussregel steht in .git/info/exclude statt in .gitignore, damit sie nicht selbst im Repository steht. |
||
|
|
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. |
||
|
|
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. |
||
|
|
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. |