From f857a244dd5ca1f3fcc86880f0396f81b0790b77 Mon Sep 17 00:00:00 2001
From: 0qln <0qln@proton.me>
Date: Tue, 25 Aug 2026 20:33:15 +0200
Subject: [PATCH] =?UTF-8?q?chap=205:=20Umsetzung=20=E2=80=94=2011=20Abschn?=
=?UTF-8?q?itte=20+=20module-components/zip-stream/race-condition=20diagra?=
=?UTF-8?q?ms?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
chapters/execution/architecture.tex | 47 ++++++++++++---
chapters/execution/document-explorer.tex | 31 +++++++++-
chapters/execution/downloads.tex | 46 +++++++++++----
chapters/execution/filter-pagination.tex | 32 +++++++++-
chapters/execution/folder-management.tex | 42 ++++++++++++--
chapters/execution/pdf-preview.tex | 34 +++++++++--
chapters/execution/race-conditions.tex | 71 +++++++++++++++++++----
chapters/execution/s3-client.tex | 31 +++++++++-
chapters/execution/search.tex | 30 ++++++++--
chapters/execution/type-icons.tex | 19 +++++-
chapters/execution/url-files.tex | 40 +++++++++++--
figures/diagrams/module-components.mmd | 68 +++++++++++++---------
figures/diagrams/module-components.pdf | Bin 0 -> 34006 bytes
figures/diagrams/race-condition-a.mmd | 30 ++++++----
figures/diagrams/race-condition-a.pdf | Bin 0 -> 28607 bytes
figures/diagrams/zip-stream.mmd | 26 +++++----
figures/diagrams/zip-stream.pdf | Bin 0 -> 40708 bytes
17 files changed, 437 insertions(+), 110 deletions(-)
create mode 100644 figures/diagrams/module-components.pdf
create mode 100644 figures/diagrams/race-condition-a.pdf
create mode 100644 figures/diagrams/zip-stream.pdf
diff --git a/chapters/execution/architecture.tex b/chapters/execution/architecture.tex
index a673dad..2f49369 100644
--- a/chapters/execution/architecture.tex
+++ b/chapters/execution/architecture.tex
@@ -1,12 +1,41 @@
-\section{Architektur des Documents-Moduls}
+\section{Architektur des Dokumentenmoduls}
\label{sec:architecture}
-% TODO: Razor Page DocumentsPage.cshtml → DocumentsService → S3DocumentsClient
-% Klassendiagramm module-components.pdf
+\subsection{Schichtung}
-% \begin{figure}[H]
-% \centering
-% \includegraphics[width=0.9\textwidth]{figures/diagrams/module-components.pdf}
-% \caption{Komponentenstruktur des Documents-Moduls}
-% \label{fig:module-components}
-% \end{figure}
+Das Dokumentenmodul folgt der in Houston bereits etablierten Schichtung und führt keine neuen Strukturmuster ein. Abbildung~\ref{fig:module-components} zeigt die Komponenten und ihre Beziehungen.
+
+\begin{figure}[H]
+ \centering
+ \includegraphics[width=0.95\textwidth]{figures/diagrams/module-components.pdf}
+ \caption{Komponenten des Dokumentenmoduls}
+ \label{fig:module-components}
+\end{figure}
+
+Die oberste Schicht bilden drei Razor Pages. \texttt{Documents} stellt die eigentliche Liste samt Suche, Filtern und Blätterelementen dar. \texttt{Document/Download} nimmt Downloadanforderungen entgegen, sowohl für einzelne Dokumente als auch für ZIP-Archive. \texttt{Document/Share} liefert die Vorschauseite für Freigabelinks. Die Aufteilung auf getrennte Seiten statt auf mehrere Handler einer einzigen Seite folgt daraus, dass Download und Freigabe eigene Routen mit eigenen Antwortformaten benötigen.
+
+Darunter liegt der \texttt{DocumentsService} als fachliche Schicht. Er kennt die Regeln des Ablagekonzepts: wie ein Kundenordner aufgelöst wird, welche Objekte als Dokumente gelten, wie der Typ aus dem Pfad abgeleitet wird und welche Einträge auszublenden sind. Der \texttt{S3DocumentsClient} kapselt darunter den technischen Zugriff auf den Speicher und ist die einzige Stelle, an der \texttt{IAmazonS3} unmittelbar verwendet wird.
+
+\subsection{Eigene Typen statt Zeichenketten}
+
+Eine Entwurfsentscheidung, die sich erst im Verlauf der Umsetzung herausbildete, betrifft den Umgang mit Pfaden. In der ersten Fassung wurden Dokumentschlüssel als gewöhnliche Zeichenketten durch die Schichten gereicht. Im Review des Downloads führte das zu wiederholten Rückfragen zur Pfadvalidierung — und zwar deshalb, weil einer Zeichenkette nicht anzusehen ist, ob sie bereits geprüft wurde.
+
+Daraufhin wurden eigene Typen eingeführt, die diese Unterscheidung im Typsystem sichtbar machen. Ein \emph{Schlüssel} bezeichnet den vollständigen, bereits gegen das Kundenpräfix validierten Pfad im Speicher; ein \emph{relativer Pfad} bezeichnet den Anteil unterhalb des Kundenordners, wie er in Freigabelinks und in der ZIP-Struktur auftritt. Beide Typen können nur über Konstruktionswege entstehen, die die jeweilige Prüfung durchführen.
+
+Der Nutzen liegt darin, dass eine vergessene Prüfung nicht mehr zu einer Sicherheitslücke, sondern zu einem Übersetzungsfehler führt. Der zugehörige Pull Request durchlief 23 Iterationen und bestand zu einem erheblichen Teil aus genau diesem Refactoring — ein Aufwand, der sich in den folgenden Anforderungen mehrfach auszahlte, weil ZIP-Download, PDF-Vorschau und Freigabelinks dieselben Typen wiederverwenden konnten.
+
+Nach demselben Muster entstanden \texttt{DocumentType} für die Typisierung, \texttt{DocumentName} für die Behandlung von Anzeigenamen und Dateiendungen sowie \texttt{OrgSlug} für die Normalisierung des Firmennamens.
+
+\subsection{Zentrale Autorisierung}
+
+Die erste Fassung des Document Explorers prüfte die Berechtigung im PageModel selbst. Im Review wies \emph{Hanna Ebner} darauf hin, dass die Autorisierung in Houston zentral in der Anwendungskonfiguration eingerichtet wird und eine zusätzliche Prüfung an der Seite deshalb überflüssig ist.
+
+Der Einwand betrifft mehr als nur doppelten Code. Eine an der Seite hinterlegte Prüfung ist leicht zu übersehen, wenn später eine weitere Seite hinzukommt — genau das wäre bei \texttt{Download} und \texttt{Share} passiert. Die zentrale Registrierung stellt dagegen sicher, dass alle Routen des Moduls derselben Richtlinie unterliegen, ohne dass dies an jeder einzelnen Stelle wiederholt werden muss.
+
+\subsection{Gestapelte Pull Requests}
+
+Ein Vorgehen, das sich durch die gesamte Umsetzung zieht und das Verständnis der Reviewhistorie erleichtert, ist die Verkettung der Pull Requests. Der erste Pull Request des Document Explorers ging gegen den Hauptbranch. Der darauf aufbauende Pull Request für die Icons wurde jedoch nicht ebenfalls gegen den Hauptbranch geführt, sondern gegen den Branch des Explorers. Der Suchen-Pull-Request wiederum ging gegen den Icon-Branch, der Download-Pull-Request gegen den Suchen-Branch und der Freigabelink-Pull-Request gegen den Download-Branch.
+
+Der Grund ist die Reviewbarkeit. Da die Arbeiten inhaltlich aufeinander aufbauen, hätte ein direkter Vergleich gegen den Hauptbranch bei jedem Pull Request auch sämtliche Änderungen der Vorgänger enthalten. Ein Reviewer hätte die für das jeweilige Backlog Item relevanten Änderungen aus einem stetig wachsenden Gesamtunterschied heraussuchen müssen. Durch die Verkettung enthält jeder Pull Request genau die Änderungen seines eigenen Backlog Items. Auf diesen Umstand wurde jeweils im ersten Kommentar des Pull Requests hingewiesen.
+
+Der Preis dieses Vorgehens zeigte sich beim Zusammenführen. Sobald ein Vorgänger in den Hauptbranch übernommen war, musste der Nachfolger auf den Hauptbranch umgestellt werden. Diese Umstellung setzt in Azure DevOps die bereits abgegebenen Freigaben zurück, sodass mehrere Reviewer ein zweites Mal zustimmen mussten. Bei zwei Pull Requests ist dieser Effekt im Ereignisprotokoll dokumentiert. Für künftige Arbeiten wäre abzuwägen, ob der Gewinn an Reviewbarkeit diesen zusätzlichen Abstimmungsaufwand rechtfertigt; bei der vorliegenden Zahl aufeinander aufbauender Backlog Items überwog er deutlich.
diff --git a/chapters/execution/document-explorer.tex b/chapters/execution/document-explorer.tex
index b1ecd74..abb2acf 100644
--- a/chapters/execution/document-explorer.tex
+++ b/chapters/execution/document-explorer.tex
@@ -1,5 +1,32 @@
\section{Document Explorer}
\label{sec:document-explorer}
-% TODO: PBI 9295 — /documents Seite, ListObjectsV2, Empty State, Fehlerbehandlung
-% Abbildung~\ref{fig:documents-page-model}, \ref{fig:document-view-model}, \ref{fig:documents-table-body}
+\subsection{Umfang}
+
+Der Document Explorer war das erste umgesetzte Backlog Item und mit acht Aufwandspunkten zugleich das umfangreichste. Er umfasst die Grundstruktur des gesamten Moduls: die Seite selbst, den Dienst, den Speicherclient, die Modelle sowie die Einbindung in Navigation und Autorisierung. Alle nachfolgenden Backlog Items bauen darauf auf.
+
+\subsection{Vom Kachel- zum Listenentwurf}
+
+Die ursprüngliche Fassung der Anforderung sah vor, Dokumente als Kacheln darzustellen. Im Verlauf wurde dies auf eine Zeilendarstellung geändert. Ausschlaggebend war weniger eine gestalterische Vorliebe als die absehbare Entwicklung der Anforderungen: Bereits im Backlog standen Auswahlboxen für den ZIP-Download, Download- und Teilen-Schaltflächen sowie eine Vorschaufunktion. Eine Kachel hätte für diese Elemente keinen natürlichen Platz geboten, eine Tabellenzeile dagegen schon. Die Feature-Beschreibung hält ausdrücklich fest, dass die Tabelle flexibel um weitere Spalten erweiterbar sein soll.
+
+\subsection{Ableitung des Dokumententyps}
+
+Der Typ eines Dokuments ergibt sich aus dem Namen des Unterordners, in dem es liegt. In der Umsetzung wird der Ordnername gegen eine Aufzählung der bekannten Typen abgeglichen. Im Review schlug \emph{Timo Walter} vor, hierfür die Zeichenketten-Umwandlung der Aufzählung mit Nichtbeachtung der Groß- und Kleinschreibung zu verwenden, statt eine eigene Zuordnung zu pflegen.
+
+Der Vorschlag wurde übernommen. Er hat den Vorteil, dass beim Hinzufügen eines Typs nur die Aufzählung ergänzt werden muss und die Zuordnung nicht an einer zweiten Stelle nachgeführt werden kann. Schlägt die Umwandlung fehl, liefert die Methode kein Ergebnis, und das Dokument gilt als untypisiert — genau das gewünschte Verhalten für unbekannte Ordner.
+
+Eine bewusste Ausnahme betrifft die Übersetzung der Typbezeichnungen. Fehlt für einen bekannten Typ der Eintrag in der Ressourcendatei, wird kein Ersatzwert verwendet, sondern eine Ausnahme ausgelöst. Im Review wurde hinterfragt, ob das nicht ein zulässiger Fall sei. Die Begründung dagegen: Jeder Wert der Aufzählung ist per Definition gültig; fehlt seine Übersetzung, ist das kein Laufzeitzustand, sondern eine unvollständige Implementierung. Ein stiller Rückfall auf den technischen Ordnernamen würde diesen Fehler verdecken, statt ihn sichtbar zu machen.
+
+\subsection{Leerer Zustand und Fehlerbehandlung}
+
+Zwei Anforderungen betreffen Situationen, in denen keine Dokumente angezeigt werden können, und sie sind ausdrücklich voneinander zu unterscheiden.
+
+Der \textbf{leere Zustand} tritt ein, wenn der Kunde berechtigt ist, aber noch keine Dokumente für ihn abgelegt wurden. Er ist ein normaler Betriebszustand und wird mit einem entsprechenden Hinweis dargestellt. Dieser Fall tritt insbesondere unmittelbar nach der automatischen Anlage der Ordnerstruktur auf.
+
+Ein \textbf{Fehler} liegt dagegen vor, wenn die Dokumente nicht geladen werden konnten — etwa weil der Speicher nicht erreichbar ist oder die Zugangsdaten nicht stimmen. Im Review wurde ausdrücklich angemerkt, dass dieser Fall nicht in einer stillschweigend leeren Seite münden darf. Der Unterschied ist für den Kunden erheblich: Im einen Fall gibt es nichts zu sehen, im anderen funktioniert die Anwendung nicht. Würden beide Fälle gleich dargestellt, bliebe eine Störung unbemerkt, bis jemand sie zufällig meldet.
+
+\subsection{Die bekannte Schwäche}
+
+Bereits im Review dieses Pull Requests fragte \emph{Hanna Ebner}, warum für jeden Kunden eine eigene Abfrage abgesetzt werde und ob sich das nicht über einen Filter lösen lasse. Die Antwort war, dass die Schnittstelle dies nicht zulässt, verbunden mit dem Verweis auf das eigens angelegte Recherche-Item.
+
+Diese Stelle ist aus zwei Gründen bemerkenswert. Erstens zeigt sie, dass die Schwäche nicht übersehen, sondern erkannt und bewusst zurückgestellt wurde: Der Explorer sollte nicht daran scheitern, dass eine Optimierung noch nicht gefunden war. Zweitens dokumentiert der Verweis auf das Folgeitem die Entscheidung nachvollziehbar — der Reviewer musste sie nicht glauben, sondern konnte sie im Backlog nachvollziehen. Die tatsächliche Lösung dieses Problems beschreibt Abschnitt~\ref{sec:folder-management}.
diff --git a/chapters/execution/downloads.tex b/chapters/execution/downloads.tex
index 509c561..ddccaf9 100644
--- a/chapters/execution/downloads.tex
+++ b/chapters/execution/downloads.tex
@@ -1,13 +1,39 @@
-\section{Dokumenten-Downloads}
+\section{Downloads}
\label{sec:downloads}
-% TODO: PBI 9299 (Einzeldownload), PBI 9296 (ZIP-Download)
-% Streaming: S3 GetObject → ZipArchive → HTTP Response Stream
-% Abbildung~\ref{fig:zip-download-action}
+\subsection{Einzeldownload über zeitlich begrenzte Zugriffs-URLs}
-% \begin{figure}[H]
-% \centering
-% \includegraphics[width=0.9\textwidth]{figures/diagrams/zip-stream.pdf}
-% \caption{ZIP-Download-Ablauf}
-% \label{fig:zip-stream}
-% \end{figure}
+Für den Download einzelner Dokumente wäre es möglich gewesen, den Inhalt aus dem Speicher zu lesen und durch die Anwendung hindurch an den Browser weiterzureichen. Stattdessen wird eine zeitlich begrenzte Zugriffs-URL erzeugt, mit der der Browser das Dokument unmittelbar vom Speicher abruft \autocite{aws-presigned-urls}.
+
+Der Vorteil liegt darin, dass die eigentliche Datenübertragung nicht über die Anwendung läuft. Ein großes Dokument belegt weder Arbeitsspeicher noch eine Verbindung des Webservers; die Anwendung ist nach dem Erzeugen der URL nicht mehr beteiligt. Die URL ist nur wenige Minuten gültig und danach wertlos.
+
+Wichtig ist dabei, dass die Berechtigungsprüfung \emph{vor} dem Erzeugen der URL stattfindet. Die URL selbst trägt keine Prüfung mehr in sich — wer sie besitzt, kann innerhalb ihrer Gültigkeitsdauer auf das Dokument zugreifen. Sie darf deshalb nur für einen Schlüssel erzeugt werden, der nachweislich innerhalb des Kundenordners liegt.
+
+Genau an dieser Stelle setzte der überwiegende Teil des Reviews an. Die Prüfung, dass ein angeforderter Pfad den Kundenordner nicht verlässt, war die inhaltlich kritischste Einzelanforderung des Moduls, und die wiederholten Rückfragen dazu führten zu der in Abschnitt~\ref{sec:architecture} beschriebenen Einführung eigener Pfadtypen. Der Pull Request durchlief 23 Iterationen; ein erheblicher Anteil davon entfiel auf dieses Refactoring und nicht auf die Downloadfunktion selbst.
+
+Zusätzlich musste sichergestellt werden, dass das Dokument unter seinem ursprünglichen Namen ankommt. Da der Schlüssel im Speicher den vollständigen Pfad enthält, würde ein unbehandelter Download unter einem technischen Namen gespeichert. Der gewünschte Dateiname wird deshalb ausdrücklich mitgegeben.
+
+\subsection{ZIP-Download}
+
+Für den Download mehrerer Dokumente erhält jede Zeile eine Auswahlbox. Die Schaltfläche für den ZIP-Download ist deaktiviert, solange nichts ausgewählt ist, und wird erst mit der ersten Auswahl freigegeben. Damit ist die Anforderung, dass ohne Auswahl kein Download ausgelöst werden kann, unmittelbar in der Bedienoberfläche abgebildet und muss nicht durch eine Fehlermeldung nachgereicht werden.
+
+Abbildung~\ref{fig:zip-stream} zeigt den Ablauf.
+
+\begin{figure}[H]
+ \centering
+ \includegraphics[width=0.95\textwidth]{figures/diagrams/zip-stream.pdf}
+ \caption{Ablauf des ZIP-Downloads}
+ \label{fig:zip-stream}
+\end{figure}
+
+Das Archiv wird erst beim Klick erzeugt und nicht im Voraus vorgehalten — auch dies eine ausdrückliche Anforderung. Entscheidend für die Umsetzung ist, dass es dabei zu keinem Zeitpunkt vollständig im Arbeitsspeicher oder auf der Festplatte entsteht: Die Anwendung öffnet die Antwort als Archivstrom, liest die ausgewählten Dokumente nacheinander aus dem Speicher und schreibt sie unmittelbar als Einträge hinein \autocite{dotnet-zip}. Die komprimierten Daten fließen dabei direkt zum Browser.
+
+Diese Arbeitsweise ist notwendig, weil die Gesamtgröße einer Auswahl nicht begrenzt ist. Würde das Archiv zunächst vollständig aufgebaut, bestimmte die größte denkbare Auswahl den Speicherbedarf des Servers. Beim Strömen bleibt dieser dagegen unabhängig von der Auswahlgröße konstant. Die Struktur innerhalb des Archivs entspricht der Ablagestruktur, sodass die Typordner erhalten bleiben.
+
+\subsection{ZIP auch bei einer einzelnen Datei}
+
+Im Review stellte \emph{Sarah Hinzmann} die Frage, ob auch bei nur einer ausgewählten Datei ein Archiv erzeugt werden solle, statt die Datei direkt auszuliefern.
+
+Die Entscheidung fiel für das Archiv. Der Grund ist die Vorhersagbarkeit der Bedienung: Die Schaltfläche ist mit „Als ZIP herunterladen" beschriftet, und wer sie betätigt, soll ein Archiv erhalten — unabhängig davon, wie viele Dokumente ausgewählt sind. Wer eine einzelne Datei unverpackt benötigt, verwendet die Download-Schaltfläche in der jeweiligen Zeile. Eine automatische Umschaltung des Verhaltens abhängig von der Auswahlgröße wäre für den Benutzer schwerer zu antizipieren und hätte zudem bedeutet, dass ein Skript auf der Seite die beiden Fälle unterscheiden müsste.
+
+Der Fall zeigt beispielhaft, wie eine scheinbar kleine Bedienfrage eine begründete Entscheidung erfordert. Beide Varianten sind vertretbar; ausschlaggebend war, dass jede Schaltfläche genau eine Bedeutung behält.
diff --git a/chapters/execution/filter-pagination.tex b/chapters/execution/filter-pagination.tex
index e4f2550..44c5fb4 100644
--- a/chapters/execution/filter-pagination.tex
+++ b/chapters/execution/filter-pagination.tex
@@ -1,6 +1,32 @@
\section{Typfilter und Paginierung}
\label{sec:filter-pagination}
-% TODO: PBI 9749 (Typfilter), PBI 9731 (Pagination)
-% ContinuationToken statt Offset-Pagination
-% Abbildung~\ref{fig:pagination-continuation-token}
+\subsection{Filterung nach Dokumententyp}
+
+Unterhalb der Suchleiste steht je Dokumententyp ein Bedienelement zum Ein- und Ausblenden. Die Auswahl wird als Abfrageparameter übertragen und im PageModel ausgewertet; die Filterung selbst erfolgt anschließend im Dienst.
+
+Die Auswertung des Parameters war Gegenstand des Reviews. Da die Werte aus der Adresszeile stammen, muss die Anwendung damit rechnen, dass sie manipuliert oder veraltet sind — etwa wenn ein gespeicherter Link auf einen inzwischen entfernten Typ verweist. \emph{Robin Noack} schlug vor, die Umwandlung fehlertolerant vorzunehmen und nicht erkennbare Werte stillschweigend zu verwerfen, statt die Anfrage scheitern zu lassen.
+
+Die Begründung überzeugt: Ein unbekannter Filterwert ist kein Angriff und kein Fehler des Benutzers, sondern in aller Regel ein veralteter Link. Die Anwendung ignoriert ihn und zeigt die verbleibenden Typen an. Ein Fehlerbild wäre hier unangemessen, zumal keine Sicherheitsentscheidung von diesem Wert abhängt — die Mandantentrennung geschieht über das Präfix, nicht über den Filter.
+
+Ein zweiter Reviewhinweis betraf die Benennung. Der Typ für eine Menge von Dokumententypen unterschied sich vom Typ für einen einzelnen Dokumententyp lediglich durch ein angehängtes Zeichen. Da eine Verwechslung beim Lesen leicht möglich und beim Übersetzen nicht auffällig ist, wurde die Benennung geschärft.
+
+\subsection{Paginierung mit Fortsetzungsmerkmalen}
+
+Die Paginierung folgt der Arbeitsweise des Speichers. Dieser liefert Ergebnisse blockweise und gibt, sofern weitere Treffer vorliegen, ein Fortsetzungsmerkmal zurück, mit dem der jeweils nächste Block angefordert werden kann \autocite{aws-listobjectsv2}.
+
+Dieses Verfahren unterscheidet sich grundlegend von der sonst üblichen Paginierung über eine Positionsangabe. Ein Fortsetzungsmerkmal beschreibt keine Position im Ergebnis, sondern setzt die Auflistung an der Stelle fort, an der sie zuletzt endete. Daraus folgt eine Einschränkung, die sich nicht umgehen lässt: Ein direkter Sprung auf eine beliebige Seite ist nicht möglich, ohne alle vorhergehenden Blöcke abzurufen. Die Akzeptanzkriterien verlangten dies auch nicht — gefordert waren eine Vor- und eine Zurück-Navigation.
+
+\subsection{Übernommene Annahmen}
+
+Ein aufschlussreicher Reviewfund betraf einen Zahlenwert für die maximale Seitengröße. Er war aus den bestehenden Houston-Seiten übernommen worden, um die geforderte Konsistenz herzustellen (NFA-4). \emph{Robin Noack} wies darauf hin, dass dieser Wert dort aus einer Beschränkung des ITSM-Systems stammt und für den Speicher keine Bedeutung hat.
+
+Der Fund ist typisch für das Übernehmen bestehender Muster: Was wie eine Konvention aussah, war tatsächlich eine technische Grenze eines ganz anderen Systems. Konsistenz in der Bedienung bedeutet nicht, dass auch die zugrunde liegenden technischen Werte zu übernehmen sind. Der Wert wurde an die Gegebenheiten des Speichers angepasst.
+
+Diskutiert wurde in diesem Zusammenhang auch, ob die vom Benutzer wählbare Seitengröße nach oben begrenzt werden sollte. Da der Wert aus der Adresszeile stammt, könnte ein sehr großer Wert eine entsprechend große Antwort erzwingen.
+
+\subsection{Zusammenspiel mit Freigabelinks}
+
+Die Paginierung wirkt sich unmittelbar auf die Freigabelinks aus. Ein Link, der auf ein bestimmtes Dokument verweist, ist wertlos, wenn er stets auf der ersten Seite landet und das Dokument auf der vierten liegt.
+
+Die Weiterleitung eines Freigabelinks setzt deshalb nicht nur die Sprungmarke auf das Dokument, sondern auch die Abfrageparameter, die zur richtigen Seite führen. Diese Anforderung war bereits bei der Formulierung des Paginierungs-Items berücksichtigt worden — ein Hinweis darauf, dass die wechselseitigen Abhängigkeiten der nachgeschobenen Items zu diesem Zeitpunkt bereits im Blick waren.
diff --git a/chapters/execution/folder-management.tex b/chapters/execution/folder-management.tex
index 213c6fb..a60159a 100644
--- a/chapters/execution/folder-management.tex
+++ b/chapters/execution/folder-management.tex
@@ -1,8 +1,38 @@
-\section{Ordnerstruktur und Kundenordner-Lookup}
+\section{Ordnerverwaltung und Kundenordner-Lookup}
\label{sec:folder-management}
-% TODO: PBI 9560 (Ordneranlage beim ersten Seitenaufruf), PBI 10018 (O(1)-Lookup)
-% OrgSlug: deterministische, S3-konforme Normalisierung des Efecte-Namens (Umlaute bleiben erhalten)
-% IBM-Naming-Constraints: \cite{ibm-s3-naming}
-% ResolveCustomerPrefixAsync, Self-Healing-Rename
-% Abbildung~\ref{fig:org-slug}, \ref{fig:resolve-customer-prefix}, \ref{fig:rename-customer-folder}
+\subsection{Gemeinsame Umsetzung zweier Backlog Items}
+
+Die automatische Anlage der Ordnerstruktur und der neue Kundenordner-Lookup wurden ursprünglich als getrennte Backlog Items geführt und in einem gemeinsamen Pull Request umgesetzt. Der Grund ist in der Beschreibung des Pull Requests festgehalten: Der Lookup benötigt in seiner Rückfallebene ohnehin den Fall, dass kein Ordner existiert und die Struktur angelegt werden muss. Die Ordneranlage getrennt zu implementieren hätte bedeutet, an dieser Stelle zunächst einen Platzhalter einzufügen, um ihn im unmittelbar folgenden Pull Request wieder zu entfernen.
+
+Die Zusammenlegung wurde nicht stillschweigend vorgenommen, sondern in der Beschreibung begründet und beide Items wurden verknüpft. Damit bleibt der Zusammenhang zwischen Planung und Umsetzung nachvollziehbar, auch wenn die Aufteilung nicht eingehalten wurde.
+
+\subsection{Normalisierung des Firmennamens}
+
+Der erwartete Ordnername entsteht aus dem Firmennamen des angemeldeten Benutzers. Da dieser Name aus einem Fremdsystem stammt und beliebige Zeichen enthalten kann, wird er zuvor normalisiert. Die Normalisierung entfernt umschließende Leerzeichen, ersetzt Zeichen, die im Schlüssel eine strukturelle Bedeutung haben oder nicht darstellbar sind, verhindert führende und abschließende Punkte und begrenzt die Länge.
+
+Zwei Eigenschaften sind dabei entscheidend. Die Normalisierung ist \textbf{deterministisch} — derselbe Eingabename ergibt stets denselben Ordnernamen, was die Voraussetzung dafür ist, dass der Direktzugriff überhaupt funktionieren kann. Und sie erhält \textbf{Umlaute}, weil der Ordner in Filestash von Menschen gelesen wird; die Zumutung, eine Firma „Müller GmbH" unter \texttt{Mueller GmbH} zu suchen, wäre der Bedienbarkeit abträglich.
+
+Im Review fragte \emph{Robin Noack}, ob die gewählten Einschränkungen ausreichen. Die Frage ist berechtigt, denn eine zu schwache Normalisierung führt zu Schlüsseln, die der Speicher zurückweist oder anders interpretiert als erwartet — und beides wäre nicht bei der Entwicklung, sondern erst bei einem Kunden mit einem ungewöhnlichen Firmennamen aufgefallen. Die Antwort verwies auf die Herstellerdokumentation zu den Namensbeschränkungen \autocite{ibm-s3-naming, aws-s3-naming}. Der Punkt ist erwähnenswert, weil er zeigt, dass eine solche Zusicherung sich belegen lassen muss und nicht auf einer Vermutung beruhen darf.
+
+\subsection{Umsetzung des Lookups}
+
+Die in Abschnitt~\ref{sec:lookup-decision} beschriebene dreistufige Auflösung wurde in einer Methode des Dienstes zusammengefasst. Sie ist die einzige Stelle, an der ein Kundenpräfix entsteht; alle übrigen Zugriffe erhalten das Ergebnis als bereits aufgelösten Wert.
+
+Der erste Schritt fragt die Metadaten des erwarteten Markers ab. Ist er vorhanden und trägt die Organisations-ID des Benutzers, endet die Auflösung. Der zweite Schritt wiederholt dies mit dem um die Organisations-ID ergänzten Namen und deckt damit den Kollisionsfall ab. Erst der dritte Schritt greift auf das ursprüngliche, lineare Verfahren zurück.
+
+Beim Prüfen, ob ein Zielname beansprucht werden darf, wird nicht nur der Marker betrachtet, sondern zusätzlich geprüft, ob unterhalb des Zielpräfixes bereits Objekte liegen. Dazu genügt eine Auflistung, die nach dem ersten Treffer abbricht. Ein Zielname gilt nur dann als frei, wenn dort weder ein fremder Marker liegt noch bereits Inhalte vorhanden sind.
+
+\subsection{Umbenennen als Kopiervorgang}
+
+Der Speicher kennt keine Umbenennung. Ein Objekt lässt sich nur unter einem neuen Schlüssel kopieren und anschließend unter dem alten löschen. Für einen Ordner mit vielen Dokumenten bedeutet das entsprechend viele Einzelvorgänge.
+
+Daraus folgt, dass eine Umbenennung nicht unteilbar ist. Sie kann in der Mitte abbrechen — etwa durch einen Netzwerkfehler —, und dann liegen die Objekte teils am alten, teils am neuen Ort. Die Akzeptanzkriterien verlangten für diesen Fall ausdrücklich, dass keine Dokumente verloren gehen.
+
+Umgesetzt wurde dies über zwei Maßnahmen. Erstens wird der \textbf{Marker zuletzt kopiert}. Solange er fehlt, gilt das Ziel als unvollständig und wird von einer parallelen Anfrage nicht als bestehender Kundenordner erkannt. Der Zielordner wird also erst in dem Moment „gültig", in dem er vollständig ist. Zweitens versucht die Fehlerbehandlung, die bereits erzeugten Teilkopien wieder zu entfernen, damit das Ziel leer bleibt und der Vorgang wiederholbar ist.
+
+Die zweite Maßnahme beruht auf einer Annahme, die sich später als nicht allgemeingültig erwies — sie gilt nur, solange kein zweiter Vorgang dieselbe Umbenennung ausführt. Dieser Fall ist Gegenstand des folgenden Abschnitts.
+
+\subsection{Stand bei Abgabe}
+
+Der zugehörige Pull Request war zum Zeitpunkt der Erstellung dieser Dokumentation noch offen. Die inhaltlichen Anmerkungen aus dem Review waren geschlossen, die abschließende Freigabe stand jedoch aus. Der Abschnitt beschreibt damit — anders als die vorangegangenen — Arbeit, die fachlich fertiggestellt, aber noch nicht in den Hauptbranch übernommen war.
diff --git a/chapters/execution/pdf-preview.tex b/chapters/execution/pdf-preview.tex
index 4b38385..1368212 100644
--- a/chapters/execution/pdf-preview.tex
+++ b/chapters/execution/pdf-preview.tex
@@ -1,6 +1,32 @@
-\section{PDF-Modal-Vorschau und Share-Link-Previews}
+\section{PDF-Vorschau und Freigabelinks}
\label{sec:pdf-preview}
-% TODO: PBI 9300 (PDF-Modal), PBI 9294 (Share-Link-Previews)
-% Pre-Signed URLs, zeitlich begrenzte Gültigkeit
-% Abbildung~\ref{fig:presigned-url}
+\subsection{PDF-Vorschau im Modal}
+
+Die Vorschau öffnet PDF-Dokumente in einem überlagerten Fenster, ohne dass sie zuvor heruntergeladen werden müssen. Sie greift dabei auf dieselbe zeitlich begrenzte Zugriffs-URL zurück, die auch dem Download zugrunde liegt — der Unterschied liegt allein darin, dass der Browser das Dokument anzeigt, statt es zu speichern. Für Dateien, die keine PDF-Dokumente sind, wird keine Vorschau angeboten.
+
+Bewusst nicht umgesetzt wurde eine Prüfung der Dateigröße. Diese Abgrenzung wurde im Approval-Termin ausdrücklich in die Anforderung aufgenommen: Das Dokument wird angezeigt, unabhängig davon, wie groß es ist. Die Begründung ist pragmatisch — jede Größengrenze wäre willkürlich, und die Betrachtung eines großen Dokuments ist kein Fehlerfall, sondern lediglich langsam. Der Browser stellt PDF-Dokumente ohnehin fortlaufend dar.
+
+\subsection{Aufbau der Freigabelinks}
+
+Ein Freigabelink verweist auf eine eigene Seite, deren Adresse den relativen Pfad des Dokuments in kodierter Form enthält. Kodiert wird dabei nur der Anteil unterhalb des Kundenordners, nicht der Kundenordner selbst. Das ist keine Sicherheitsmaßnahme — die Kodierung ist trivial umkehrbar —, sondern dient dazu, Pfadtrennzeichen und Sonderzeichen unbeschadet durch die Adresse zu transportieren.
+
+Dass der Kundenordner nicht Bestandteil des Links ist, hat dagegen sehr wohl eine Wirkung: Der Link enthält keinen Hinweis auf die Organisation und ist ohne den Kontext des angemeldeten Benutzers nicht auflösbar. Die Zuordnung geschieht ausschließlich über die Anmeldung des Aufrufers.
+
+Die Seite selbst zeigt keinen Dokumentinhalt an. Sie stellt Metaangaben für die Vorschau bereit und leitet anschließend auf die Dokumentenliste weiter, wobei die Sprungmarke auf das jeweilige Dokument gesetzt und — nach Umsetzung der Paginierung — die passende Seite angesteuert wird.
+
+\subsection{Vorschau in Messengern}
+
+Der eigentliche Zweck der Freigabeseite ist, dass ein in einem Messenger geteilter Link dort mit Titel und Symbol dargestellt wird statt als nackte Adresse. Dazu werden entsprechende Metaangaben ausgeliefert: der Dokumentname als Titel, das Typsymbol als Bild und die Adresse der Dokumentenliste als Ziel.
+
+Hier trat ein Problem auf, das sich nicht aus der Spezifikation ableiten ließ. Die Typsymbole liegen als Vektorgrafiken vor, und diese werden von den Vorschaudiensten gängiger Messenger nicht zuverlässig dargestellt. Die Symbole mussten daher zusätzlich als Rastergrafiken bereitgestellt werden, allein für diesen Zweck.
+
+Der Umstand hat eine unangenehme Nebenwirkung: Jedes Symbol existiert nun in zwei Formaten, die bei einer Gestaltungsänderung gemeinsam nachzuziehen sind. Im Pull Request wurde ausdrücklich vermerkt, dass die Rastergrafiken zu aktualisieren sind, sobald das Symboldesign endgültig feststeht.
+
+\subsection{Sichtbarkeit der Vorschaubilder}
+
+Eine Entscheidung, die erst bei der Unterstützung von URL-Dateien vollständig zum Tragen kam, betrifft die Auslieferung der Vorschaubilder. Damit ein Messenger eine Vorschau erzeugen kann, muss er das Bild abrufen können — und zwar ohne Anmeldung, da der Vorschaudienst des Messengers keine Sitzung des Benutzers besitzt. Das Bild liegt somit auf einem öffentlich erreichbaren Pfad.
+
+Solange es sich um die sieben festen Typsymbole handelt, ist das unbedenklich: Sie sind Bestandteil der Anwendung und enthalten keine kundenbezogene Information. Anders verhielte es sich bei Symbolen, die aus dem Inhalt einer Datei stammen. Aus diesem Grund wurde festgelegt, dass benutzerdefinierte Symbole aus URL-Dateien \emph{nicht} für die Vorschau ausgeliefert werden — andernfalls müsste Dateiinhalt über einen nicht authentifizierten Pfad zugänglich gemacht werden.
+
+Die Entscheidung wurde im Pull Request ausdrücklich festgehalten. Sie ist ein Beispiel dafür, dass eine an sich harmlose Funktion — ein Vorschaubild — in Verbindung mit einer anderen Funktion eine Sicherheitsfrage aufwirft, die keine der beiden für sich genommen aufgeworfen hätte.
diff --git a/chapters/execution/race-conditions.tex b/chapters/execution/race-conditions.tex
index 071c8fd..ed67829 100644
--- a/chapters/execution/race-conditions.tex
+++ b/chapters/execution/race-conditions.tex
@@ -1,15 +1,62 @@
-\section{Bekannte Grenze: Race Conditions im Lookup}
+\section{Erkannte Grenze: Nebenläufigkeit im Lookup}
\label{sec:race-conditions}
-% TODO: PBI 10070 — Houston läuft in mehreren Instanzen, In-Memory-Lock reicht nicht
-% Szenario A: Datenverlust beim parallelen Rename (Rollback löscht Dokumente des Gewinners)
-% Szenario B: Mandantenvermischung beim gleichzeitigen Anlegen gleicher Efecte-Namen
-% Lösungsansätze: Rollback-Absicherung, Conditional Writes, verteiltes Lock, prozessuale Migration
-% Timebox: 4–6 Stunden (Stephan Janßen)
+\subsection{Ausgangslage}
-% \begin{figure}[H]
-% \centering
-% \includegraphics[width=0.9\textwidth]{figures/diagrams/race-condition-a.pdf}
-% \caption{Race Condition Szenario A: Datenverlust beim parallelen Rename}
-% \label{fig:race-condition-a}
-% \end{figure}
+Der Lesepfad des Kundenordner-Lookups ist unkritisch. Er fragt Metadaten ab und listet Objekte auf; gleichzeitige Aufrufe stören einander nicht. Der in Abschnitt~\ref{sec:folder-management} beschriebene dritte Schritt ist jedoch \emph{verändernd}: Er benennt Ordner um und legt sie an. Und er tut dies im Rahmen einer gewöhnlichen Seitenanfrage.
+
+Erschwerend kommt hinzu, dass Houston in mehr als einer Instanz betrieben wird. Eine Sperre innerhalb einer Instanz — der naheliegende erste Gedanke — reicht deshalb nicht aus, weil die zweite Instanz von ihr nichts weiß.
+
+Bei der Durchsicht des eigenen Codes im Anschluss an die Umsetzung wurden zwei konkrete Fehlerszenarien gefunden. Beide verletzen Akzeptanzkriterien, die im selben Backlog Item ausdrücklich formuliert worden waren.
+
+\subsection{Szenario A: Datenverlust beim gleichzeitigen Umbenennen}
+
+Das erste Szenario betrifft die Fehlerbehandlung des Umbenennens. Sie entfernt die eigenen Teilkopien, damit das Ziel leer bleibt und der Vorgang wiederholbar ist. Diese Annahme trifft nicht mehr zu, sobald eine zweite Anfrage dieselbe Umbenennung ausführt. Abbildung~\ref{fig:race-condition-a} zeigt den Ablauf.
+
+\begin{figure}[H]
+ \centering
+ \includegraphics[width=\textwidth]{figures/diagrams/race-condition-a.pdf}
+ \caption{Datenverlust bei gleichzeitigem Umbenennen}
+ \label{fig:race-condition-a}
+\end{figure}
+
+Beide Anfragen halten den Zielnamen für frei, weil beide prüfen, bevor eine von ihnen schreibt. Anfrage~A kopiert alle Objekte und löscht anschließend die Quelle. Anfrage~B, die langsamer ist, hat zu diesem Zeitpunkt erst einen Teil kopiert und findet beim nächsten Objekt die Quelle nicht mehr vor. Ihre Fehlerbehandlung greift und entfernt die von ihr erzeugten Kopien — das sind aber genau dieselben Objekte, die A soeben erfolgreich geschrieben hat. Da die Quelle bereits gelöscht ist, existiert von ihnen keine weitere Kopie.
+
+Das Ergebnis ist endgültiger Datenverlust. Zusätzlich liefert B anschließend ein Präfix zurück, unter dem nichts mehr liegt. Die Fehlerbehandlung, die Datenverlust verhindern sollte, verursacht ihn.
+
+\subsection{Szenario B: Vermischung zweier Mandanten}
+
+Das zweite Szenario betrifft das Anlegen. Der Marker wird ohne Bedingung geschrieben. Haben zwei Organisationen denselben Firmennamen und greifen beide erstmals gleichzeitig zu, sehen beide den Zielnamen als frei an, und beide legen den Marker an. Der zuletzt geschriebene gewinnt.
+
+Beide Anfragen arbeiten anschließend in demselben Ordner weiter, obwohl das Metadatum nur einer von ihnen gehört. Bis zum nächsten Aufruf sieht die unterlegene Organisation Dokumente in einem Ordner, der laut Metadatum der anderen zugeordnet ist. Der Folgeaufruf korrigiert sich zwar selbst — die unterlegene Organisation erkennt dann den fremden Marker und erhält den um die Organisations-ID ergänzten Namen —, doch was zwischenzeitlich abgelegt wurde, verbleibt im fremden Ordner und ist dort für den falschen Kunden sichtbar.
+
+Von den beiden Szenarien ist dieses das schwerwiegendere: Datenverlust ist ein Betriebsproblem, die Offenlegung von Kundendokumenten gegenüber einem anderen Kunden ein Vertraulichkeitsproblem.
+
+\subsection{Abgegrenzte Fälle}
+
+Zur Eingrenzung wurde geprüft, welche nebenläufigen Abläufe \emph{nicht} betroffen sind. Zwei Anfragen derselben Organisation, die denselben Ordner anlegen, erzeugen identische Schlüssel mit identischen Metadaten und sind harmlos. Zwei Anfragen, die dieselbe Umbenennung vollständig ausführen, erzeugen inhaltsgleiche Kopien; ein Löschen bereits gelöschter Objekte ist unproblematisch. Auch der Fall, dass eine Anfrage liest, während eine andere verschiebt, ist abgedeckt: Da der Marker zuletzt kopiert wird und zusätzlich geprüft wird, ob am Ziel bereits Objekte liegen, wird ein halb gefülltes Ziel nicht als gültiger Kundenordner erkannt.
+
+Diese Abgrenzung ist für die Bewertung wesentlich. Sie zeigt, dass die getroffenen Vorkehrungen wirken und das Problem auf den Fall zweier \emph{unterschiedlich weit fortgeschrittener} verändernder Vorgänge beschränkt ist.
+
+\subsection{Erwogene Gegenmaßnahmen}
+
+Vier Ansätze wurden formuliert:
+
+\begin{enumerate}
+ \item \textbf{Absicherung der Fehlerbehandlung.} Vor dem Entfernen der Teilkopien wird geprüft, ob der Marker der Quelle noch existiert. Fehlt er, hat ein anderer Vorgang die Umbenennung bereits abgeschlossen, und es darf nichts gelöscht werden. Das beseitigt Szenario~A weitgehend und verschiebt den verbleibenden Fehler in die ungefährliche Richtung: Es bleiben überzählige Objekte zurück statt Daten verloren zu gehen.
+ \item \textbf{Bedingtes Schreiben.} Wird der Marker nur unter der Bedingung geschrieben, dass er noch nicht existiert, ist das Beanspruchen eines Namens unteilbar und Szenario~B ausgeschlossen \autocite{aws-conditional-writes}. Voraussetzung ist, dass der eingesetzte Speicher diese vergleichsweise junge Erweiterung unterstützt — was angesichts der Erfahrungen mit anderen Funktionen ausdrücklich nachzuweisen wäre.
+ \item \textbf{Instanzübergreifende Sperre.} Eine über die Datenbank realisierte Sperre je Organisation würde den verändernden Teil sauber serialisieren, kostet aber einen zusätzlichen Zugriff je Anfrage und erfordert ein Konzept für den Fall, dass eine Sperre nicht freigegeben wird.
+ \item \textbf{Verlagerung aus dem Anfragepfad.} Der verändernde Teil wird gar nicht mehr im Rahmen einer Benutzeranfrage ausgeführt. Bestandsordner werden einmalig kontrolliert migriert, und die Anwendung protokolliert lediglich, wenn ein Ordner vom Sollzustand abweicht. Damit verschwindet die Ursache vollständig statt abgesichert zu werden.
+\end{enumerate}
+
+Der erste Ansatz sollte in jedem Fall umgesetzt werden, unabhängig davon, wie die eigentliche Serialisierung gelöst wird. Der vierte ist insofern bemerkenswert, als er keine technische, sondern eine organisatorische Lösung darstellt — er beseitigt das Problem, indem er den verändernden Vorgang aus dem nebenläufigen Kontext herausnimmt.
+
+\subsection{Umgang mit dem Befund}
+
+Der Befund wurde als eigenes Backlog Item erfasst, mit erhöhter Priorität versehen und ausdrücklich vom laufenden Pull Request abgegrenzt. Diese Abgrenzung erfolgte nach Absprache im Team und wurde am Item dokumentiert. Das Team versah es mit einem Zeitrahmen von vier bis sechs Stunden.
+
+Die Alternative wäre gewesen, den Pull Request so lange offenzuhalten, bis auch die Nebenläufigkeit gelöst ist. Dagegen sprach, dass die Wahl der Gegenmaßnahme selbst eine offene Frage ist: Ob bedingtes Schreiben zur Verfügung steht, ließ sich angesichts der Erfahrungen mit anderen Speicherfunktionen nicht ohne Rückfrage beim Betreiber beantworten, und deren Bearbeitungsdauer war zuvor mit mehreren Wochen bemessen worden. Der Lookup selbst — die eigentliche Verbesserung — wäre dadurch blockiert worden, obwohl er den Zustand gegenüber vorher bereits deutlich verbessert.
+
+Hinzu kommt, dass der verändernde Pfad ausschließlich beim ersten Zugriff einer Organisation durchlaufen wird. Nach der einmaligen Selbstheilung ist er für diesen Kunden dauerhaft irrelevant. Das Risikofenster ist damit eng begrenzt — es besteht pro Kunde genau einmal.
+
+Die Akzeptanzkriterien des Folgeitems halten fest, dass eine reine Sperre innerhalb einer Instanz ausdrücklich nicht als Lösung akzeptiert wird und dass beide Szenarien durch Unit-Tests nachzubilden sind, die ohne die Korrektur fehlschlagen.
diff --git a/chapters/execution/s3-client.tex b/chapters/execution/s3-client.tex
index ff7f07e..74ad5b6 100644
--- a/chapters/execution/s3-client.tex
+++ b/chapters/execution/s3-client.tex
@@ -1,5 +1,32 @@
\section{Anbindung des S3-Speichers}
\label{sec:s3-client}
-% TODO: AWS SDK für .NET, IAmazonS3, keyed services in Program.cs, S3Settings (DEV/TEST/PROD Buckets)
-% Abbildung~\ref{fig:s3-settings-registration}, \ref{fig:s3-documents-client}
+\subsection{Zugriff über das AWS SDK}
+
+Der Zugriff auf den Speicher erfolgt über das AWS SDK für .NET. Obwohl der Speicher nicht bei Amazon betrieben wird, ist dies der naheliegende Weg: StorageGRID implementiert die S3-Schnittstelle, und das SDK lässt sich über die Angabe einer abweichenden Dienstadresse auf einen beliebigen kompatiblen Endpunkt richten. Eine eigene Implementierung der Protokolldetails — insbesondere der Signaturberechnung — wäre aufwendig und fehleranfällig gewesen.
+
+Der \texttt{S3DocumentsClient} kapselt die verwendeten Operationen. Er ist bewusst schmal gehalten und bietet nur die tatsächlich benötigten Zugriffe an: das Auflisten von Objekten unterhalb eines Präfixes, das Abrufen der Metadaten eines einzelnen Objekts, das Erzeugen zeitlich begrenzter Zugriffs-URLs, das Lesen eines Objektinhalts sowie — für die Ordnerverwaltung — das Anlegen, Kopieren und Löschen von Objekten.
+
+Diese Kapselung erfüllt zwei Zwecke. Zum einen hält sie die SDK-spezifischen Anfrage- und Antworttypen aus der fachlichen Schicht heraus. Zum anderen ist sie die Voraussetzung dafür, den Speicherzugriff in Tests durch ein Mock zu ersetzen (siehe Abschnitt~\ref{sec:unit-tests}).
+
+\subsection{Konfiguration mehrerer Speicher}
+
+Eine Besonderheit ergab sich daraus, dass Houston bereits vor diesem Projekt einen S3-Speicher verwendete — für die Anbindung des Dokumentationssystems. Mit dem Dokumentenbereich kam ein zweiter, davon unabhängiger Speicher hinzu, mit eigenem Bucket und eigenen Zugangsdaten.
+
+In der ersten Fassung wurden die Einstellungen des neuen Speichers als eigenständiger Satz von Konfigurationswerten geführt. Im Review wurde angeregt, stattdessen eine gemeinsame Struktur für S3-Einstellungen zu verwenden und die beiden Verwendungen über benannte Registrierungen im Dienstcontainer auseinanderzuhalten \autocite{dotnet-keyed-di}.
+
+Der Vorteil dieser Lösung liegt in der Erweiterbarkeit: Ein dritter Speicher erfordert lediglich einen weiteren Konfigurationsabschnitt und eine weitere Registrierung, nicht aber eine erneute Verdopplung der Einstellungsklassen. Zudem ist an der Registrierung unmittelbar ablesbar, welcher Programmteil auf welchen Speicher zugreift — bei zwei gleichartig benannten Konfigurationssätzen wäre diese Zuordnung nur aus dem Kontext erkennbar gewesen.
+
+\subsection{Umgebungen und Zugangsdaten}
+
+Für die drei Umgebungen existiert je ein eigener Bucket. Die Trennung erfolgt damit nicht über Präfixe innerhalb eines gemeinsamen Buckets, sondern über getrennte Buckets mit getrennten Zugangsdaten. Ein fehlerhaft konfigurierter Entwicklungsstand kann dadurch nicht auf Produktivdaten zugreifen.
+
+Die Zugangsdaten selbst liegen nicht im Quelltext, sondern werden über die Konfigurationsmechanismen der Anwendung bereitgestellt; hinterlegt sind sie im unternehmensweiten Passwortmanager. Für die lokale Entwicklung wurden die Entwicklungseinstellungen um die entsprechenden Werte ergänzt.
+
+\subsection{Auflisten von Objekten}
+
+Die zentrale Leseoperation ist das Auflisten von Objekten unterhalb eines Präfixes. Sie wird mit dem Kundenpräfix aufgerufen und liefert sämtliche Objekte des jeweiligen Kunden — über alle Typordner hinweg, da die Anzeige eine flache Liste ist.
+
+Zwei Eigenschaften dieser Operation prägten die Umsetzung. Erstens liefert sie Ergebnisse blockweise: Überschreitet die Trefferzahl eine bestimmte Größe, wird ein Fortsetzungsmerkmal zurückgegeben, mit dem der nächste Block abgerufen werden kann \autocite{aws-listobjectsv2}. Diese Eigenschaft wurde für die Paginierung genutzt (siehe Abschnitt~\ref{sec:filter-pagination}). Zweitens liefert sie zwar Schlüssel, Größe und Änderungszeitpunkt, aber keine benutzerdefinierten Metadaten. Genau diese Einschränkung ist die Ursache des in Abschnitt~\ref{sec:lookup-research} beschriebenen Lookup-Problems.
+
+Beim Auflisten werden zwei Arten von Einträgen herausgefiltert. Zum einen die Platzhalterobjekte, die die Typordner repräsentieren — sie sind technisch Objekte, fachlich aber keine Dokumente. Zum anderen der Marker des Kundenordners selbst. Beide erkennt der Dienst daran, dass ihr Schlüssel auf das Trennzeichen endet.
diff --git a/chapters/execution/search.tex b/chapters/execution/search.tex
index 31c6927..d0ab4fd 100644
--- a/chapters/execution/search.tex
+++ b/chapters/execution/search.tex
@@ -1,6 +1,28 @@
-\section{Serverseitige Suche}
+\section{Suche}
\label{sec:search}
-% TODO: PBI 9298 — Suchleiste, titelbasiert, Teiltreffer, serverseitiges Filtern
-% S3-API bietet keine direkte Filterung → Houston-seitiger Filter
-% Abbildung~\ref{fig:document-search}
+\subsection{Umsetzung}
+
+Die Suche filtert die Dokumentenliste anhand des Dokumentnamens und berücksichtigt dabei Teiltreffer. Ein geleertes Suchfeld stellt die vollständige Liste wieder her. Fachlich beschränkt sie sich bewusst auf den Namen: Im Approval-Termin wurde gefragt, ob auch eine Beschreibung durchsucht werden solle; da Dokumente im Speicher keine Beschreibung tragen, wurde die Suche auf den Namen begrenzt.
+
+\subsection{Der Begriff „serverseitig"}
+
+Die Anforderung verlangt eine serverseitige Suche. Dieser Begriff bedarf in diesem Zusammenhang einer Präzisierung, weil er auf zwei verschiedene Ebenen zutreffen kann.
+
+Gemeint und umgesetzt ist, dass die Filterung in der Anwendung stattfindet und nicht im Browser des Benutzers. Der Browser erhält also bereits die gefilterte Menge. Das ist die für die Anforderung entscheidende Eigenschaft, denn eine Filterung im Browser würde voraussetzen, dass sämtliche Dokumente zuvor übertragen wurden — was mit der Paginierung unvereinbar wäre und bei großen Beständen unnötig Daten überträgt.
+
+Nicht umgesetzt — und mit den verfügbaren Mitteln nicht umsetzbar — ist eine Filterung durch den \emph{Speicher}. Houston listet die Objekte des Kundenordners auf und filtert die zurückgelieferten Namen anschließend selbst. Die Frage, ob sich das nicht direkt über die Schnittstelle lösen lasse, wurde im Review von \emph{Hanna Ebner} gestellt und musste verneint werden.
+
+\subsection{Warum keine Filterung im Speicher möglich war}
+
+Im weiteren Verlauf des Reviews brachte \emph{Timo Walter} die zuvor gemeinsam betrachtete Abfragefunktion des Speichers ins Spiel. Die Prüfung ergab, dass diese Funktion Objekt\emph{inhalte} filtert und nicht Objektnamen — sie ist dafür gedacht, aus einer strukturierten Datei einzelne Datensätze auszuwählen \autocite{aws-s3-select}. Für eine Namenssuche über mehrere Objekte hinweg ist sie nicht geeignet.
+
+Als zweite Möglichkeit wurde der Suchdienst des Speicherherstellers erwogen, der Objektmetadaten in einen durchsuchbaren Index spiegelt \autocite{storagegrid-search-integration}. Zum Zeitpunkt des Reviews stand dessen Verfügbarkeit noch nicht fest; die Anfrage über den Betreiber lief bereits (siehe Abschnitt~\ref{sec:infrastructure}). Die spätere Antwort fiel negativ aus.
+
+Auf Anregung des Reviewers wurde dieser Umstand nicht nur im Gesprächsverlauf festgehalten, sondern ausdrücklich in das Recherche-Item aufgenommen. Damit war die Suche nicht länger eine Funktion mit einer unausgesprochenen Schwäche, sondern eine Funktion mit einer dokumentierten und einem Folgeitem zugeordneten Einschränkung.
+
+\subsection{Bewertung}
+
+Die praktische Auswirkung ist derzeit gering. Die Zahl der Dokumente je Kunde bewegt sich in einer Größenordnung, in der das Auflisten und anschließende Filtern nicht spürbar ins Gewicht fällt — anders als beim Kundenordner-Lookup, der über \emph{alle} Kunden iterierte und daher mit der Kundenzahl skalierte. Die Suche skaliert dagegen nur mit der Dokumentenzahl eines einzelnen Kunden.
+
+Damit war die Priorisierung klar: Der Lookup wurde noch im Projektzeitraum gelöst, die Suchoptimierung blieb ein Thema für den Ausblick.
diff --git a/chapters/execution/type-icons.tex b/chapters/execution/type-icons.tex
index a0a17e2..7830e67 100644
--- a/chapters/execution/type-icons.tex
+++ b/chapters/execution/type-icons.tex
@@ -1,5 +1,18 @@
-\section{Dokumenttypen und Icons}
+\section{Typ-Icons}
\label{sec:type-icons}
-% TODO: PBI 9549 — Typ-Mapping aus Unterordner, .resx-Lokalisierung
-% Abbildung~\ref{fig:document-type-mapping}, \ref{fig:document-types-resx}
+\subsection{Eigene Symbole statt Symbolschrift}
+
+Houston verwendet für Symbole eine gängige Symbolbibliothek. Für die sieben Dokumententypen enthielt diese jedoch keine passenden Motive — Begriffe wie „SLA-Report Ticketbearbeitung" oder „Security Assessment" lassen sich mit allgemeinen Symbolen nicht sinnvoll unterscheiden. Es wurden daher eigene Vektorgrafiken erstellt.
+
+Damit stellte sich die Frage, wie diese in die bestehende Oberfläche eingebunden werden. Eine Einbindung als gewöhnliche Grafik hätte bedeutet, dass die Symbole eine feste Farbe tragen. Houston unterstützt jedoch ein helles und ein dunkles Erscheinungsbild, und die umgebenden Symbole der Bibliothek passen sich der Textfarbe an. Fest eingefärbte Symbole wären im dunklen Erscheinungsbild kaum sichtbar gewesen.
+
+\subsection{Einfärbung über Masken}
+
+Gelöst wurde dies, indem die Vektorgrafiken nicht als Bild eingebunden, sondern als Maske über einer Hintergrundfläche verwendet werden. Die Fläche übernimmt dabei die aktuelle Textfarbe, sodass sich das Symbol automatisch an das Erscheinungsbild anpasst — genau wie die Symbole der Bibliothek.
+
+Der Ansatz hat den zusätzlichen Vorteil, dass sich die eigenen Symbole über dieselben Gestaltungsklassen ansprechen lassen wie die vorhandenen. Für die Verwendung in der Seite ist damit nicht erkennbar, ob ein Symbol aus der Bibliothek stammt oder eigens erstellt wurde. Als Nebenbedingung ergab sich, dass alle Grafiken dieselben Abmessungen haben müssen, damit sie im Textfluss gleich ausgerichtet erscheinen. Im Review fiel auf, dass eine der Grafiken von den übrigen abwich; die Maße wurden angeglichen.
+
+\subsection{Standardsymbol}
+
+Dokumente ohne erkennbaren Typ — solche, die unmittelbar im Kundenordner liegen oder in einem unbekannten Unterordner — erhalten ein neutrales Standardsymbol. Damit ist sichergestellt, dass jede Zeile ein Symbol trägt und die Liste optisch gleichmäßig bleibt. Zugleich ist an der Darstellung erkennbar, dass für dieses Dokument keine Typzuordnung vorliegt, ohne dass dies wie ein Fehler wirkt.
diff --git a/chapters/execution/url-files.tex b/chapters/execution/url-files.tex
index d4482ec..3341b6c 100644
--- a/chapters/execution/url-files.tex
+++ b/chapters/execution/url-files.tex
@@ -1,6 +1,38 @@
-\section{Support für URL-Dateien}
+\section{Unterstützung von URL-Dateien}
\label{sec:url-files}
-% TODO: PBI 9301 — .url-Dateien parsen, Verlinkung beliebiger URLs (Teams, SharePoint)
-% Zum Parsen einfach schnell einen eigenen INI Parser selber geschrieben als auf eine 3rd party dependency einzubinden.
-% Abbildung~\ref{fig:url-file-parsing}
+\subsection{Anwendungsfall}
+
+Nicht alle Unterlagen, die für einen Kunden relevant sind, lassen sich sinnvoll in den Speicher kopieren. Ein fortlaufend gepflegtes Dokument in einem Dokumentenverwaltungssystem oder ein Kanal in einem Kollaborationswerkzeug soll verlinkt und nicht dupliziert werden — eine Kopie wäre sofort veraltet.
+
+Für diesen Zweck werden \texttt{.url}-Dateien unterstützt. Dabei handelt es sich um ein etabliertes Format für Verknüpfungen: eine kleine Textdatei, die im INI-Format die Zieladresse und optional ein Symbol enthält. Legt ein Kundenbetreuer eine solche Datei im Speicher ab, erscheint sie in der Dokumentenliste wie ein gewöhnliches Dokument, führt beim Anklicken jedoch auf die hinterlegte Adresse.
+
+Der Anwendungsfall war zunächst missverständlich formuliert und wurde in der Feature-Analyse geklärt: Gemeint ist das Einbetten \emph{externer} Ressourcen in den Dokumentenbereich, nicht das Teilen von Houston-Dokumenten nach außen (siehe Abschnitt~\ref{sec:feature-analysis}).
+
+\subsection{Auswertung des Dateiformats}
+
+Für das Auswerten des INI-Formats wurde bewusst keine externe Bibliothek eingebunden, sondern eine kleine eigene Auswertung geschrieben. Der Grund ist das Verhältnis von Aufwand zu Nutzen: Benötigt wird ein einziger Wert aus einem einzigen Abschnitt. Eine allgemeine Bibliothek für ein Format, von dem ein sehr kleiner Ausschnitt gebraucht wird, brächte eine dauerhaft zu pflegende Abhängigkeit mit sich — samt Aktualisierungen, Lizenzprüfung und einer weiteren Position in der Abhängigkeitsliste — für Funktionalität, die in wenigen Zeilen selbst geschrieben ist.
+
+Hinzu kommt, dass eine allgemeine Bibliothek in diesem Fall gar nicht das gewünschte Verhalten böte. Sie würde bei fehlerhaften Dateien vermutlich einen Fehler melden, während hier ein stiller Rückfall erforderlich ist (siehe unten). Die eigene Auswertung ist auf genau dieses Verhalten hin geschrieben.
+
+\subsection{Rückfall auf normales Verhalten}
+
+Kann in einer \texttt{.url}-Datei keine gültige Adresse erkannt werden, wird sie wie eine gewöhnliche Datei behandelt und zum Herunterladen angeboten. Dieses Verhalten war ausdrücklich gefordert.
+
+Die Überlegung dahinter ist, dass eine fehlerhafte Verknüpfungsdatei kein Grund sein darf, das Dokument unzugänglich zu machen. Der Kunde erhält im schlechtesten Fall eine kleine Textdatei statt einer Weiterleitung — ein nachvollziehbares Ergebnis, aus dem sich zudem ablesen lässt, was schiefgegangen ist. Die Alternative, einen Fehler anzuzeigen oder den Eintrag auszublenden, wäre für den Kunden weniger hilfreich.
+
+\subsection{Anzeigename}
+
+Der Dateiname einer Verknüpfung endet auf \texttt{.url}. Diese Endung ist für den Kunden bedeutungslos und würde in der Liste lediglich stören. Sie wird deshalb für die Anzeige entfernt.
+
+Im Review wies \emph{Robin Noack} darauf hin, dass die dafür verwendete Zeichenzahl als unmittelbare Zahl im Code stand, und schlug vor, stattdessen die Länge der Endung selbst zu verwenden. Der Hinweis ist auf den ersten Blick eine Kleinigkeit, trifft aber einen realen Fehlerfall: Zahl und Endung stehen an verschiedenen Stellen, und wird die eine geändert, bleibt die andere unbemerkt zurück.
+
+\subsection{Symbole für Verknüpfungen}
+
+Das INI-Format sieht die Angabe eines Symbols vor. Da benutzerdefinierte Symbole aus den in Abschnitt~\ref{sec:pdf-preview} genannten Gründen nicht ausgeliefert werden, wurde ein anderer Weg gewählt: Es stehen benannte Voreinstellungen zur Verfügung, die auf Symbole der in Houston vorhandenen Bibliothek verweisen. Für die häufigsten Ziele — die Dateiablage, das Dokumentenverwaltungssystem und das Kollaborationswerkzeug des Unternehmens — wurden eigene Symbole ergänzt.
+
+Damit erhält ein Kundenbetreuer die gewünschte visuelle Unterscheidung, ohne dass Dateiinhalt an einer nicht authentifizierten Stelle verarbeitet werden muss. Da diese Voreinstellungen nur nützen, wenn sie bekannt sind, wurden die verfügbaren Namen im internen Wiki dokumentiert und im Pull Request darauf verwiesen.
+
+\subsection{Ausschluss vom ZIP-Download}
+
+Verknüpfungsdateien erhalten keine Auswahlbox und können damit nicht Teil eines Archivs werden. Der Grund ist, dass ein Archiv sonst eine Datei enthielte, die beim Öffnen nicht das erwartete Dokument liefert, sondern eine Verknüpfung, die möglicherweise nur innerhalb des Unternehmensnetzes auflösbar ist. Da eine Verknüpfung fachlich kein Dokument ist, sondern ein Verweis, wäre ihre Aufnahme in ein Dokumentenarchiv irreführend.
diff --git a/figures/diagrams/module-components.mmd b/figures/diagrams/module-components.mmd
index 22f65b0..4447732 100644
--- a/figures/diagrams/module-components.mmd
+++ b/figures/diagrams/module-components.mmd
@@ -1,29 +1,39 @@
-classDiagram
- class DocumentsPage {
- +OnGetAsync()
- +OnGetDownloadAsync()
- +OnGetZipAsync()
- }
- class DocumentsService {
- +ListDocumentsAsync(orgId, search, type, token)
- +GetDownloadUrlAsync(orgId, key)
- +GetZipStreamAsync(orgId, keys)
- +ResolveCustomerPrefixAsync(orgId, orgName)
- }
- class S3DocumentsClient {
- -IAmazonS3 _s3
- +ListObjectsAsync(prefix, delimiter, token)
- +GetObjectMetadataAsync(key)
- +GetPresignedUrlAsync(key)
- }
- class OrgSlug {
- +Slugify(name) string
- }
- class S3Settings {
- +BucketName string
- +ServiceUrl string
- }
- DocumentsPage --> DocumentsService
- DocumentsService --> S3DocumentsClient
- DocumentsService --> OrgSlug
- S3DocumentsClient --> S3Settings
+flowchart TB
+ subgraph Pages["Pages"]
+ P1["Documents.cshtml
+ .cshtml.cs
Liste, Suche, Filter, Seiten"]
+ P2["Document/Download.cshtml.cs
Einzel- und ZIP-Download"]
+ P3["Document/Share.cshtml.cs
OpenGraph-Vorschau"]
+ end
+
+ subgraph Services["Services"]
+ S["DocumentsService
Auflisten, Filtern, Aufloesen
des Kundenordners"]
+ C["S3DocumentsClient
Kapselung von IAmazonS3"]
+ end
+
+ subgraph Model["Model/Documents"]
+ M1["DocumentType
Enum + Ableitung
aus Ordnername"]
+ M2["DocumentName
Anzeigename,
.url-Erkennung"]
+ M3["DocumentKey /
RelativePath
validierte Pfade"]
+ M4["OrgSlug
Normalisierung des
Firmennamens"]
+ M5["DocumentViewModel"]
+ end
+
+ EXT["IAmazonS3
(AWS SDK)"]
+ CFG["S3Settings
keyed service"]
+
+ P1 --> S
+ P2 --> S
+ P3 --> S
+ S --> C
+ S --> M1
+ S --> M2
+ S --> M4
+ S --> M5
+ P2 --> M3
+ C --> EXT
+ C --> CFG
+
+ style S fill:#dae8fc,stroke:#6c8ebf
+ style C fill:#dae8fc,stroke:#6c8ebf
+ style EXT fill:#f5f5f5,stroke:#999
+ style CFG fill:#f5f5f5,stroke:#999
diff --git a/figures/diagrams/module-components.pdf b/figures/diagrams/module-components.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..ba1f3432e637266389517fdeac468eef7d446ac3
GIT binary patch
literal 34006
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
literal 0
HcmV?d00001
diff --git a/figures/diagrams/race-condition-a.mmd b/figures/diagrams/race-condition-a.mmd
index 301570c..f8e8e56 100644
--- a/figures/diagrams/race-condition-a.mmd
+++ b/figures/diagrams/race-condition-a.mmd
@@ -1,15 +1,21 @@
sequenceDiagram
- participant A as Request A
+ autonumber
+ participant A as Anfrage A
participant S3 as S3-Speicher
- participant B as Request B
+ participant B as Anfrage B
- Note over A,B: Ordner AltName/ mit 200 Objekten, Zielname: Kunde GmbH/
- A->>S3: CanClaim(Kunde GmbH) → Missing → true
- B->>S3: CanClaim(Kunde GmbH) → Missing → true
- A->>S3: Kopiere 200 Objekte nach Kunde GmbH/
- B->>S3: Kopiere Objekte #1–60 nach Kunde GmbH/
- A->>S3: DeleteObjects(AltName/*) ✓
- B->>S3: Kopie #61 → NoSuchKey ✗
- B->>S3: catch: DeleteObjects(copiedKeys #1–60)
- Note over S3: 60 Objekte aus Kunde GmbH/ gelöscht!
- Note over S3: AltName/ existiert nicht mehr → Datenverlust
+ Note over A,B: Ausgangslage: Ordner "AltName/" mit 200 Objekten,
Zielname "Kunde GmbH/". Beide Anfragen gehoeren derselben Org.
+
+ A->>S3: CanClaim("Kunde GmbH") → frei
+ B->>S3: CanClaim("Kunde GmbH") → frei
+ Note over A,B: Beide halten den Zielnamen fuer beanspruchbar
+
+ A->>S3: kopiert alle 200 Objekte
+ B->>S3: kopiert Objekte 1 bis 60
+ A->>S3: DeleteObjects("AltName/*")
+ Note over S3: Quelle ist jetzt geloescht
+
+ B->>S3: kopiert Objekt 61 → NoSuchKey
+ B->>S3: catch: DeleteObjects(eigene 60 Kopien)
+
+ Note over S3: Die 60 geloeschten Objekte sind genau die,
die A erfolgreich geschrieben hatte.
Die Quelle existiert nicht mehr — Datenverlust.
diff --git a/figures/diagrams/race-condition-a.pdf b/figures/diagrams/race-condition-a.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..4b745615c6a7c1a4fd458796d3fed906e007aadf
GIT binary patch
literal 28607
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
Czd4U43@j-hes`z97;d*-F(g9h#(
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