**Letzte Änderung:** Paralleler Lesepfad eingezogen — Agenten holen Vorab-Informationen aus registrierten **Datenquellen** über das gleiche Trust-Modell wie bei Senken (Query Attestations, Session Scope Tokens). Erster produktiver Rollout: eigene DB-App als Quelle UND Senke.
Diese Lösung wird als **Identity-Aware Agent Messaging Platform** konzipiert: eine Plattform, die Benutzerfreigaben, delegierte Berechtigungen und sämtliche Verarbeitungsschritte kryptografisch nachvollziehbar macht — inkl. der *Lesezugriffe*, die ein Agent vor der eigentlichen Freigabe durchführt, um dem Benutzer sinnvolle Vorschläge zu machen.
> **Der Benutzer autorisiert den ursprünglichen Geschäftsinhalt. Jeder Agent attestiert anschließend seine eigene Verarbeitung — inkl. jedes Datenzugriffs, der die Vorschlagsgrundlage geformt hat. Die empfangende Senke überprüft die vollständige Kette, bevor sie eine Aktion ausführt.**
- **Scope Evidence Store:** kein rechtssicherer, kein WORM-/GoBD-Anspruch. Ziel ist der kryptografische Nachweis einer unverfälschten Kommunikation während eines begrenzten Zeitraums (Default: 1 Jahr).
- **PII-Trennung:** verbindlich. Attestierungen enthalten nur Hashes und Referenzen; personenbezogene Inhalte liegen in einem separaten, redaktionierbaren PII-Store.
- **Senken-Generalisierung:** SAP ist eine von vielen möglichen Senken. Trust-Core senken-agnostisch. Senken-Adapter sind Plug-ins mit festem Vertrag.
- **🆕 Datenquellen und paralleler Lesepfad:** Agenten holen während der Vorbereitung Informationen aus registrierten **Datenquellen** (Section 3b, 4.5a) über das gleiche Vertrauens- und Autorisierungsmodell wie bei Senken. Jede Abfrage erzeugt eine **Query Attestation**; die Benutzerfreigabe bindet an diese Query-Materialien.
- **🆕 Session Scope Tokens:** Session-gebundene, kurzlebige Delegations-Tokens, mit denen Agenten „im Auftrag von Benutzer X in Session S" lesen dürfen. Siehe Abschnitt 3c.
- **🆕 Erster produktiver Rollout:** **eigene kleine Go-DB-App** (`sink:expense-db-app` + `ds:expense-db-app`) als kombinierte Quelle und Senke. SAP folgt später.
- **🆕 Begriffsklärung PII-Store:** Der Begriff bezeichnet eine **Rolle im Datenmodell**, kein Produkt. Konkrete Technologie-Wahl und Löschsemantik stehen in Abschnitt 8.4.
- **🆕 Zeilen-Allokationen auf mehrere Projekte:** Reisekostenbelege dürfen pro Zeile prozentual oder absolut auf mehrere Projekte aufgeteilt werden (Schema `expense.canonical` v1.1.0). Policy erzwingt Summenerhalt pro Item. DB-App legt eine zusätzliche Tabelle `expense_item_allocations` an. Details in Abschnitt 7.2a; Schemas unter `schemas/expense/canonical/v1.1.0.json` und `schemas/sink/db/expense/v1.1.0.json`.
- **🆕 Belege (Receipts) als eigene Datenquelle:** Hochladen durch Agent-Travel, Ablage im PII-Store, Hash in Approval, Lesen durch nachfolgende Agenten über die registrierte Datenquelle `ds:receipts` (Session Scope Tokens, OPA-kontrolliert). Details in Abschnitt 4.7.
- **🆕 Deterministische Regelwerke (Beispiel Spesensätze):** Eine neue Datenquelle `ds:per-diem-rates` liefert versionierte Pauschalen; ein eigener Compute-Agent (`agent-per-diem-calculator`) berechnet aus Reisedaten die Spesen und ergänzt `expense_items[]`. Keine LLM-Freiheit auf dem Rechenpfad. Details in Abschnitt 6.2.
- **Lesepfad (Datenquelle):** Agent → Session Scope Token → Datenquellen-Adapter → signierte Query Attestation. Query-Materialien gehen als Eingaben in die nachfolgenden Attestierungen und in die Benutzerfreigabe ein.
Der Transport (HTTPS/mTLS + NATS, optional Matrix) überträgt nur Referenzen und Nachrichten — nie die Vertrauensentscheidung.
Senken sind nach außen offene Zielsysteme — ERP, eigene Datenbankanwendungen, HTTP-Webhooks, Message-Queues, Dateiablage u.a. Datenquellen sind nach außen offene Lesesysteme — eigene DBs, HR-Systeme, Projektmanagement-APIs, SAP-OData-Reads u.a. Beide Typen folgen demselben Adapter-Vertrag.
3.**Verkettete Attestierungen** — jeder Verarbeitungsschritt referenziert exakte Eingabe- und Ausgabe-Hashes; Lesezugriffe werden ebenfalls attestiert und als Materialien verkettet.
4.**Unabhängige Autorisierung** — eine Signatur allein erteilt keine Berechtigung; eine Policy Engine kontrolliert erlaubte Transformationen, Lesezugriffe und Senken-Aktionen.
5.**Transportneutralität** — Nachweise funktionieren über HTTP, NATS, Queue oder Matrix.
6.**Quellen- und Senken-Neutralität** — Trust-Core und Verifikationspfad kennen keine konkrete Quellen- oder Senken-Semantik; beide sind Plug-ins mit festem Vertrag.
| Approval Service | Go, REST API | Inhaltsgebundene Benutzerfreigabe |
| **Session Scope Token Svc** | Go (Teil des trust-api) | **Short-lived Delegations-Tokens „Agent X im Auftrag von User Y in Session S für Scope Z"** |
Approval, Session Scope Token Service, Attestation, Registry (Agent + Sink + Data Source) und Verification laufen in **einem modularen Go-Dienst** (`trust-api`). Signing Service läuft als eigenes Deployment. Senken- und Datenquellen-Adapter laufen pro Typ (oft als derselbe Prozess, wenn eine Komponente beides anbietet — siehe 7.2).
Pocket ID unterstützt neben dem Authorization-Code-Flow auch den OIDC `client_credentials`-Grant (`POST /api/oidc/token`) und bietet Standard-OIDC-Discovery inkl. JWKS unter `https://auth.basicstack.de/.well-known/openid-configuration`.
Pro Agent-Datenquellen-Kombination gibt es OPA-Regeln (siehe Abschnitt 6).
### 3c. Session Scope Tokens — Delegation „Agent im Auftrag von Benutzer"
Problem: Der Lese-Agent muss der Datenquelle beweisen, dass er für Benutzer Y in Session S liest — nicht nur, dass er irgendein Agent ist.
Lösung: Der **Session Scope Token Service** (Teil von `trust-api`) stellt kurzlebige, signierte Delegations-Tokens aus. Technisch ein JWT mit `act`-Claim nach RFC 8693 (OAuth 2.0 Token Exchange — die Semantik, nicht der volle Flow).
Ablauf:
1. Benutzer meldet sich per Pocket ID an und startet eine Session im Approval Service (`POST /api/v1/sessions`). Approval Service hält `session_id`, `user_sub`, `active_until`.
2. Agent A will für diese Session eine Datenquelle lesen. Er ruft:
```
POST /api/v1/session-scope-tokens
Authorization: Bearer <agent-pocket-id-token>
X-Workload-Token: <k8s-sa-token>
Content-Type: application/json
```
```json
{
"session_id": "SES-2026-10-25-abc",
"requested_scope": {
"data_source": "ds:expense-db-app",
"resource": "project_assignments",
"filters": { "employee_id": "<EMPfromsession>" },
"operation": "read"
},
"ttl_seconds": 300
}
```
3. Der Service prüft:
- beide Agent-Credentials (Pocket-ID + K8s SA) → `agent_id`;
- OPA: darf `agent_id` die angeforderte Operation auf dieser Datenquelle im Namen *dieses konkreten Benutzers* ausführen? (z.B. „nur eigene Projekte, nicht fremde");
- Session aktiv und valide?
4. Rückgabe: Scope-Token (JWT), signiert vom trust-api-Schlüssel:
```json
{
"iss": "https://trust.basicstack.de",
"sub": "agent-travel",
"act": {
"iss": "https://auth.basicstack.de",
"sub": "user-12345",
"session_id": "SES-2026-10-25-abc"
},
"scope": {
"data_source": "ds:expense-db-app",
"resource": "project_assignments",
"filters": { "employee_id": "EMP-12345" },
"operation": "read"
},
"jti": "<unique>",
"iat": 1735120000,
"exp": 1735120300
}
```
5. Agent A ruft die Datenquelle: `GET https://expense-db-adapter/api/v1/project_assignments?employee_id=EMP-12345` mit Header `Authorization: Bearer <scope-token>`.
6. Datenquellen-Adapter prüft Scope-Token (Signatur gegen trust-api-JWKS, `exp`, `scope` deckt den Request), führt die Abfrage aus und schreibt eine **Query Attestation** (siehe 4.5a).
Vorteile:
- Datenquellen müssen weder Session-State noch Benutzer-Authentifizierung selbst halten — nur Token verifizieren.
- OPA entscheidet zentral, welche Agent-Benutzer-Kombinationen welche Lesescopes bekommen.
- Kompromittierte Agent-Credentials erlauben nur die Scopes, die vom Session Scope Token Service herausgegeben wurden — nicht die Weltlese-Erlaubnis.
Jeder Vorgang erhält eine unveränderliche `workflow_id`. Jede Fassung der Daten wird als eigenes Artefakt mit SHA-256-Hash gespeichert. PII bleibt im separaten PII-Store; Attestierungen enthalten nur Hashes und Referenzen.
0.**Session-Start.** Benutzer meldet sich per Passkey an, Agent-Travel beginnt den Chat.
1.**Vorab-Lesezugriffe (parallel-Pfad).** Agent-Travel holt sich pro Lesezugriff ein Session Scope Token und ruft die Datenquelle (z.B. `ds:expense-db-app`, Resource `project_assignments`). Jeder Zugriff erzeugt eine **Query Attestation** (siehe 4.5a). Der User sieht die zurückgelieferten Vorschläge und wählt (z.B. ein Projekt).
2.**Benutzerfreigabe.** Approval Service bindet die Signatur an den Hash des Inhalts, den der Benutzer per Content Binding bestätigt hat, UND an die Materialien-Hashes der oben erzeugten Query Attestations (siehe 4.1).
3.**Agent A: Vorbereitung.** Agent-Travel signiert eine Transform-Attestierung über Eingabe-Hash, Ausgabe-Hash, Transformationsart, Freigabe-Referenz und die genutzten Query-Materialien.
4.**Agent B: Mapping.** Agent-Sink-Mapping führt eigene Lesezugriffe durch (z.B. Kontierungsvorschläge → neue Query Attestations), transformiert, signiert.
5.**Agent C: Übergabe.** Agent-Sink-Posting verifiziert die komplette Kette, übergibt an den Senken-Adapter, erzeugt den signierten Übergabenachweis.
### 4.0 Content Binding bei der Benutzerfreigabe
Risiko: der Browser könnte dem Benutzer etwas anderes zeigen als dem Approval Service. Verbindlicher Ablauf:
1. Approval Service rendert den Freigabe-Inhalt serverseitig, berechnet SHA-256 `H_content` und speichert `(session_id, H_content, salt, challenge_nonce)`.
2. Browser zeigt den Inhalt *und* eine aus `H_content + challenge_nonce` abgeleitete menschenlesbare Prüfziffer.
3. Benutzer bestätigt per Passkey (WebAuthn-Assertion über `challenge_nonce || H_content`).
4. Approval Service verifiziert die WebAuthn-Assertion und erzeugt die DSSE-signierte Approval-Attestierung.
Verifikation (Abschnitt 5) prüft, dass genau die hier referenzierten Query Attestations existieren, gültig signiert sind und ihre Ergebnisse den im Freigabeinhalt verwendeten Werten entsprechen (z.B. „das ausgewählte Projekt ist tatsächlich in der Ergebnisliste").
Validierung alter Attestierungen nutzt den Schlüssel, der zur `issued_at` galt (TUF-Pattern). Rotation Default 90 Tage × Retention 1 Jahr → max. ~5 nicht-archivierte Schlüssel/Agent.
Jeder Lesezugriff eines Agenten auf eine Datenquelle erzeugt eine signierte Query Attestation. Die Attestierung enthält Query-Parameter und einen deterministischen Hash der Rückgabe — nicht die Rückgabe selbst (PII bleibt im PII-Store).
Wesentlich: Die Query Attestation wird **vom Datenquellen-Adapter** erzeugt und signiert (nicht vom Agenten), damit der Agent nicht die Rückgabe fälschen kann. Der Adapter benutzt seinen eigenen Schlüssel (`ds-adapter-<name>-key-<rev>`), der in der Agent Registry mit `agent_id = "ds-adapter:<name>"` registriert ist.
Der PII-Anteil der Rückgabe landet im PII-Store unter `pii_store_ref`; dort gelten die normalen Retention- und Löschregeln. Verifikation nach Löschung: die Signaturkette bleibt gültig, aber `result_hash` kann dann nicht mehr gegen den Inhalt geprüft werden — das ist der bewusste Trade-off aus der DSGVO-Löschbarkeit.
Der Session Scope Token Service konsultiert diese Tabelle UND OPA (für feinere Regeln). `requires_user_context = TRUE` erzwingt, dass der Scope-Token nur mit aktiver Benutzer-Session ausgestellt wird (verhindert hintergründige Lesezugriffe ohne Benutzer).
Benutzer laden Belege (PDF, PNG, JPG) im Chat mit Agent-Travel hoch. Der Beleg selbst ist PII und darf keinesfalls in Attestierungen landen — nur sein kryptografischer Hash plus eine stabile Store-Referenz.
**Komponenten:**
- **Datenquelle `ds:receipts`** (neu in der Data Source Registry) — zwei Operationen: `write` (nur agent-travel darf hochladen) und `read` (agent-sink-mapping darf lesen für fachliche Prüfung; agent-sink-posting darf lesen für Weitergabe an die Senke, sofern die Senke Beleg-Attachments unterstützt).
- **Receipt-Adapter** — läuft als eigenes Deployment im Namespace `sinks-and-sources/`. Spricht intern mit dem **PII-Mediator** (siehe 8.4), der die eigentliche verschlüsselte Ablage im PII-Store orchestriert. Der Adapter signiert eine **Receipt Attestation** (eigener predicateType `https://basicstack.de/attestations/receipt_ingest/v1`) mit `pii_store_ref`, `sha256(content)`, `mime_type`, `size_bytes`, `uploaded_by_agent`, `on_behalf_of_user`.
**Upload-Flow (schematisch):**
1. User lädt Beleg im Chat hoch. Agent-Travel ruft `POST /api/v1/receipts/upload` am Receipt-Adapter mit einem Session-Scope-Token für `ds:receipts`/`write`/user_sub (siehe 3c). Payload: multipart mit der Datei.
2. Receipt-Adapter prüft Token, nimmt Datei entgegen, berechnet `sha256`, legt sie über den PII-Mediator in `pii://receipts/<workflow_id>/<uuid>.<ext>` ab.
3. Adapter signiert die Receipt Attestation (eigener Adapter-Schlüssel in Agent Registry als `agent_id = "receipt-adapter"`), gibt an Agent-Travel zurück: `{ "pii_store_ref": "pii://...", "content_hash": "sha256:...", "attestation_ref": "sha256:..." }`.
4. Agent-Travel trägt in `expense_items[i].receipt_ref` die `pii://...`-URI ein. Zusätzlich wird der `content_hash` und die `attestation_ref` als zusätzliches Feld `receipt_content_hash` bzw. `receipt_attestation_ref` ins Item geschrieben (additives Feld, kommt in einer `expense.canonical` v1.2.0 Minor-Erweiterung).
5. Approval Service bindet im `materials[]`-Array der Benutzerfreigabe die Receipt Attestations der betroffenen Items mit — damit prüft die Verification, dass die vom User freigegebenen Beträge zu den Belegen passen, die zum Freigabezeitpunkt vorlagen.
6. Agent-Sink-Mapping ODER Agent-Sink-Posting (je nach Senke) kann über einen eigenen Session-Scope-Token (`ds:receipts`/`read`/user_sub) die Belege per `GET /api/v1/receipts/{content_hash}` abrufen — der Receipt-Adapter prüft, dass genau dieser `content_hash` zum angefragten Workflow und zur Benutzer-Session gehört, bevor er den Blob zurückliefert.
7. Senken, die Belege weitergeben müssen (DMS, SAP-Anhang, Workflow-Prüfer-UI), bekommen den Blob über den Senken-Adapter.
**Was im PII-Store liegt, was in der Attestation:**
**DSGVO-Löschung** (siehe ADR-0003): wird der Beleg nach Retention gelöscht, bleibt die Receipt Attestation gültig; aber `GET /api/v1/receipts/{content_hash}` liefert `410 Gone` plus eine signierte Deletion-Attestierung, und Verification markiert nachfolgende Prüfungen gegen den Belegsinhalt als `content_check: "pii_purged"`.
**Scope-Policy (Rego, Entwurf — kommt beim Phase-2-Spike):**
-`agent-travel` darf `ds:receipts`/`write` nur mit aktiver User-Session.
-`agent-sink-mapping` + `agent-sink-posting` dürfen `ds:receipts`/`read` nur gefiltert nach `content_hash`, der in einem Material der aktuellen `workflow_id` auftaucht (OPA kann das gegen die bekannte Attestations-Kette prüfen).
- Niemand darf `ds:receipts`/`list` — zufälliges Durchbrowsen ist konzeptionell nicht vorgesehen.
**Senken, die KEINE Beleg-Weitergabe brauchen** (z.B. unsere DB-App im ersten Rollout): Senken-Adapter liest Belege gar nicht, sondern persistiert nur `receipt_ref` als Fremdschlüssel. Belege bleiben im PII-Store, Zugriff für Prüfer erfolgt über die ProofedMesh-UI (eigener Endpunkt mit eigener OPA-Policy).
- Verification Service: `UNIQUE (payload_hash, target)` in `decisions`.
- Jeder Senken-Adapter führt eigene Replay-Tabelle `sink_decision_log`.
- Scope-Tokens (für Reads) haben eigene `jti` und `exp`; Replay-Schutz pro Datenquelle: UNIQUE `(data_source_id, jti)` in einer kleinen `scope_token_log`-Tabelle beim Adapter (optional — die kurze TTL deckt den Normalfall).
- Agent-Travel darf `ds:expense-db-app` → `project_assignments` lesen, aber nur gefiltert nach `employee_id` des aktuellen Benutzers (keine fremden Zuweisungen).
- Agent-Sink-Mapping darf `ds:expense-db-app` → `account_assignments` lesen, aber nur für Projekte, die aus einer vorherigen Query Attestation desselben Workflows stammen.
- Agent-Travel darf `ds:hr-system` → `employee_profile` NUR mit `requires_user_context=TRUE` und nur die Felder `name`, `cost_center`, nicht `salary`.
- Agenten dürfen keine Datenquellen abfragen, deren `retired_at IS NOT NULL`.
Rego-Policies werden zentral versioniert; die zum Entscheidungszeitpunkt verwendete Policy-Version wird in der Query Attestation bzw. Decision mit abgelegt.
Kanonische und Senken-/Quellen-spezifische Zielschemas liegen versioniert in `schemas/` im Repo. Approval signiert `schema.name` und `schema.version` mit. Transform- und Query-Attestierungen tragen ebenfalls `schema`-Felder. Schema-Änderungen sind Breaking Changes und erfordern eine neue Hauptversion.
### 6.2 Deterministische Regelwerke am Beispiel Spesensätze (Verpflegungsmehraufwand)
Reisekosten enthalten regel-basierte Komponenten, die nicht aus Belegen stammen, sondern aus **versionierten Pauschalen** errechnet werden — in Deutschland typisch die BMF-Pauschalen für Verpflegungsmehraufwand (§ 9 Abs. 4a EStG) und Übernachtungskosten im Ausland, abhängig von Land, ggf. Stadt, Abwesenheitsdauer und Reisejahr. Diese Sätze ändern sich mindestens jährlich.
**Grundregel:** Pauschalen gehören weder in Agent-Prompts noch in LLM-Berechnungen. Sie sind strukturierte Referenzdaten mit zeitlicher Gültigkeit und müssen deterministisch angewendet werden — sonst bricht die Nachweiskette genau dort, wo ein Finanzprüfer am genauesten hinschaut.
**Komponenten:**
- **Datenquelle `ds:per-diem-rates`** (neu in der Data Source Registry). Resource `rates` mit filter `{country, city?, valid_on}`. Rückgabe-Schema `ds.per_diem.rate` v1.0.0: `country`, `city?`, `currency`, `full_day`, `partial_day_min8h`, `arrival_day`, `departure_day`, `valid_from`, `valid_to`, `source_regulation` (z.B. "BMF-Schreiben 2026-11-15 Az. IV C 5"). Betrage als Dezimal-Strings analog zu `expense.canonical`.
- **Rate-Pflege:** die Rates liegen im `trust-postgres` (Tabelle `per_diem_rates`) und werden ausschließlich durch autorisierte Admins über eine eigene UI (`/admin/per-diem-rates`) gepflegt. Jede Rate hat `valid_from`/`valid_to`; neue BMF-Sätze werden mit neuem `valid_from` angelegt, alte Rates bleiben für retrospektive Berechnungen vorhanden. Jede Pflegeoperation wird im Transparency Log als `predicateType=https://basicstack.de/attestations/rate_change/v1` abgelegt, signiert vom pflegenden Admin.
- **Compute-Agent `agent-per-diem-calculator`** — ein winziger Go-Service, kein LLM. Rechenlogik: aus (Reise-Beginn, Reise-Ende, Zielland(e)) → tageweise Zuordnung → Abruf gültiger Rates für jedes Reisedatum → Betrag pro Tag → Summierung. Die Logik ist in [BMF-konformen Regeltests](https://www.bundesfinanzministerium.de/) abgedeckt, Testdaten liegen im Repo (`policies/compute/per_diem_test.rego` + Fixtures).
- **Berechnungs-Attestation:** Agent-per-diem-calculator signiert eine Transform-Attestation (`operation: expense.compute_per_diem`), die als `materials[]` die konkrete Query Attestation `ds:per-diem-rates` referenziert sowie die eingegebenen Reisedaten, und als `subject` die generierten `expense_items[]`-Zeilen (`type: MEALS`/`ARRIVAL_DAY`/`DEPARTURE_DAY`) mit ihrem Hash. Agent-Travel schickt dem User die berechneten Beträge zur Content-Binding-Freigabe.
**OPA-Regeln (Entwurf — kommt beim Phase-2-Spike):**
-`agent-per-diem-calculator` darf `ds:per-diem-rates`/`rates` mit beliebigen `(country, city, valid_on)`-Filtern lesen, aber nur im Rahmen einer aktiven User-Session.
-`agent-per-diem-calculator` darf nur die Operation `expense.compute_per_diem` ausführen; Input-Schema `travel.plan` v1.0.0 (Reisedaten), Output-Schema fügt `expense_items[]` vom Typ `MEALS` o.ä. zur bestehenden `expense.canonical` hinzu.
- Weitere Agenten (`agent-sink-mapping`, `agent-sink-posting`) dürfen `ds:per-diem-rates` NICHT lesen — ihre Logik hängt nicht von Pauschalen ab, nur vom bereits berechneten Ergebnis.
- Admin legt neue Rate-Reihe mit `valid_from = YYYY-01-01` (oder dem im BMF-Schreiben benannten Datum) an. Vorhandene Rates werden mit `valid_to = <neues valid_from - 1 Tag>` versehen. Alles im Transparency Log auditierbar.
- Bestehende Workflows mit altem `incurred_on` berechnen weiterhin gegen die alten Rates (dafür ist der `valid_on`-Filter da).
- Retrospektive Rate-Korrekturen (z.B. BMF-Erratum) sind möglich, erzeugen aber ein `rate_change`-Event im Log und markieren alle betroffenen Workflows für Nachbearbeitung — automatisches Nachbuchen passiert NICHT ohne neue Benutzerfreigabe.
**Warum keine LLM-Berechnung:**
- Reproduzierbarkeit: dieselbe Eingabe muss morgen denselben Betrag ergeben wie heute. Deterministischer Code garantiert das, ein LLM nicht.
- Nachvollziehbarkeit: die Query Attestation + die Transform Attestation bilden gemeinsam einen vollständigen Nachweis "aus Reisedatum X und Rate Y ergibt sich Betrag Z". Ein LLM lässt sich diesen Nachweis nicht sauber abringen.
- Angriffsfläche: ein manipulierter Prompt könnte sonst Pauschalen künstlich erhöhen und über eine LLM-Halluzination ins System tragen.
Diese Trennung "LLM für Interpretation + Freigabe-UX, deterministischer Code für Zahlen-Logik" ist unser allgemeines Muster und soll auch für künftige Pauschal-ähnliche Themen (z.B. Kilometerpauschale, Übernachtungs-Höchstbeträge) gelten.
Die eigene DB-App ist **gleichzeitig Datenquelle (`ds:expense-db-app`) und Senke (`sink:expense-db-app`)**. Ein gemeinsamer Go-Service deployed als `expense-db-adapter` mit Backing-Store `expense-db-postgres`.
Der Adapter prüft bei jeder Lese-Anfrage: Scope-Token signiert durch trust-api, Resource und Filter aus Scope-Token decken Request-Resource und -Filter, `exp` nicht abgelaufen. Danach führt er die Abfrage aus und erzeugt die Query Attestation.
### 7.2a Zeilen-Allokationen auf mehrere Projekte (v1.1.0)
Für Reisekosten, die anteilig auf mehrere Projekte gebucht werden sollen, nutzen wir ab `expense.canonical` v1.1.0 bzw. `sink.db.expense` v1.1.0 ein Allocation-Modell:
- Jedes Item in `expense_items[]` trägt ein optionales `allocations[]`. Ohne Allocation gilt das top-level `project_assignment` als Default für dieses Item.
- Allocation-Entry hat `project_code`, `cost_center`, `source_attestation_ref` und einen Modus:
-`mode: "percentage"` + `percentage` (0.00..100.00) — alle Entries desselben Items mit mode=percentage, Summe exakt 100.00.
-`mode: "absolute"` + `amount` — alle Entries desselben Items mit mode=absolute, Summe exakt gleich `item.amount`.
- Mischen von Modi innerhalb eines Items ist unzulässig (OPA-Regel).
-`source_attestation_ref` ist Pflicht — bindet die gewählten Projekte/Kostenstellen an eine Query Attestation von `ds:expense-db-app`.
**Agent-sink-mapping** löst `percentage`-Allokationen in `absolute` Beträge auf (letzte Allocation absorbiert den Rundungsresiduum, damit die Summe exakt bleibt) und schreibt in die Senke immer absolute Beträge. Die OPA-Policy `proofedmesh.transformations.expense` prüft pro Item `sum(allocations.amount) == item.amount` in Cents (siehe `policies/transformations/expense.rego`).
**DB-Schema-Erweiterung in der eigenen DB-App:**
```sql
-- Neu bzw. ergänzend zu 7.2:
CREATE TABLE expense_items (
id BIGSERIAL PRIMARY KEY,
workflow_id TEXT NOT NULL REFERENCES expenses(workflow_id) ON DELETE CASCADE,
item_index INT NOT NULL,
item_type TEXT NOT NULL,
amount NUMERIC(12,2) NOT NULL,
incurred_on DATE NOT NULL,
description TEXT,
receipt_ref TEXT, -- "pii://..." Referenz in den PII-Store
UNIQUE (workflow_id, item_index)
);
CREATE TABLE expense_item_allocations (
id BIGSERIAL PRIMARY KEY,
expense_item_id BIGINT NOT NULL REFERENCES expense_items(id) ON DELETE CASCADE,
project_code TEXT NOT NULL,
cost_center TEXT NOT NULL,
gl_account TEXT NOT NULL,
amount NUMERIC(12,2) NOT NULL, -- immer absolut
percentage NUMERIC(5,2), -- optional, nur informativ aus canonical mode=percentage
Die ursprüngliche Tabelle `expenses` behält `project_code` und `cost_center` als Kompatibilitätsfelder für v1.0.0-Vorgänge (dort gibt es nur ein Projekt pro Vorgang). Für v1.1.0-Vorgänge sind beide Felder in `expenses` NULL und die Zuordnung ergibt sich aus `expense_item_allocations`.
Beispiel-Payloads: `schemas/expense/canonical/examples/v1.1.0/*.json` und `schemas/sink/db/expense/examples/v1.1.0/split-resolved.json`.
1.**Scope-Token verifizieren** — Signatur gegen trust-api-JWKS, `exp` nicht abgelaufen, Scope deckt den Request (`data_source`, `resource`, `filters`, `operation`), `jti` noch nicht verbraucht (sofern Replay-Schutz aktiv).
2.**Query ausführen** mit genau den im Scope-Token enthaltenen Filtern — keine Erweiterung erlaubt.
3.**Query Attestation signieren** mit eigenem Adapter-Schlüssel.
4.**PII-Anteil der Rückgabe in den PII-Store schreiben**, Ergebnis-Hash in die Attestation stellen.
5.**An Agent zurückliefern** — Rückgabe-Payload (für die unmittelbare Nutzung) PLUS die Query Attestation (für die spätere Verwendung in Approvals/Transforms).
Externer HR-Dienst (OData/REST). Datenquellen-Adapter läuft in unserem Cluster, kapselt das Backend-Secret, mappt zwischen externem Schema und `data_source_resources`-Schema. OPA-Policy kann einzelne Felder (z.B. `salary`) ausschließen.
Default-Retention: 1 Jahr. Attestierungen, Decisions, Query Attestations und zugehörige Schlüsselhistorie werden danach automatisiert gelöscht. Konfigurierbar pro Prozess-Typ, Senke und Datenquelle.
**Begriffsklärung.** „PII" steht für *Personally Identifiable Information* (deutsch: personenbezogene Daten im Sinne der DSGVO Art. 4 Nr. 1). „PII-Store" ist **kein Produktname und keine Technologie**, sondern eine **architektonische Rolle** in unserem Datenmodell. Das Muster — personenbezogene Daten in einem *dedizierten, löschbaren* Speicher getrennt vom unveränderlichen Nachweismaterial zu halten — ist in Privacy-Engineering-Literatur unter Namen wie „PII vault", „tokenization vault" oder „data minimization layer" bekannt (siehe NIST SP 800-188 „De-Identifying Government Datasets" oder ENISA „Pseudonymisation techniques and best practices").
**Warum wir die Rolle brauchen.** Wir müssen zwei scheinbar widersprüchliche Anforderungen gleichzeitig erfüllen:
- **Nachweiskette darf nicht geändert werden** — eine Signaturkette, deren Inhalte nachträglich veränderbar sind, ist nutzlos.
- **DSGVO Art. 17 „Recht auf Löschung"** — personenbezogene Daten müssen auf Antrag entfernt werden können.
Lösung: wir trennen *Nachweis* und *Nutzinhalt* physisch:
- Der **Evidence Store** hält DSSE-Envelopes, Attestierungen, Transparency-Log-Einträge und Hashes. Dieser Speicher ist für die Dauer der Retention unveränderlich.
- Der **PII-Store** hält die eigentlichen Nutzinhalte (Reisekostenbelege, Dateien, Query-Ergebnisse mit Personen- oder Projektdaten). Dieser Speicher ist pro Vorgang und pro Betroffenem löschbar.
Löscht man im PII-Store, bleibt die Signaturkette intakt — nur die Nachkontrolle gegen den Inhalt ist dann nicht mehr möglich. Das ist der bewusste Trade-off: Löschbarkeit gewinnt gegenüber Nachkontrolle, der *Beweis der Kommunikation an sich* bleibt bestehen.
**Konkrete Technologie-Wahl für ProofedMesh (Default).** Der PII-Store wird aus zwei Bausteinen gebaut, die wir bereits im Cluster haben:
1.**Verschlüsselter Hetzner Object Storage (S3-kompatibel), eigener Bucket.** Jeder Workflow bekommt ein eigenes Prefix (`pii/<workflow_id>/…`). Der Bucket hat keine öffentlichen ACLs; Zugriff nur per Service-Account aus unserem Cluster.
2.**PII-Mediator-Service** (kleine Go-Komponente). Alle anderen Dienste (Approval, Datenquellen-Adapter, Senken-Adapter, Verifikations-UI) sprechen **nur** mit dem Mediator, nie direkt mit S3. Der Mediator:
- vergibt pro Objekt einen Datenschlüssel (envelope-encrypted über OpenBao Transit — der eigentliche Objektschlüssel liegt nicht im Mediator),
- prüft bei jedem Zugriff: Token, Scope, OPA-Policy,
- führt ein Zugriffs-Audit (`workflow_id`, `agent_id`/`user_sub`, `operation`, `created_at`),
Dadurch ergibt sich automatisch ein **doppeltes Löschen**: Objekt entfernt UND Entschlüsselungsschlüssel vergessen. Beides muss gelingen, aber jedes einzelne davon reicht bereits, um die Daten unlesbar zu machen.
**Was im PII-Store liegt (vs. was NICHT dort liegt):**
- Default-Retention: **1 Jahr nach letzter Workflow-Aktivität** (identisch zu Evidence Store), danach automatisches Löschen durch Cron-Job.
- Vorzeitige Löschung auf Antrag eines Betroffenen (DSGVO Art. 17): PII-Mediator ruft `DELETE /pii/<workflow_id>` → S3-Objekte entfernen + Transit-Keys vergessen + Audit-Zeile schreiben. Die Signaturkette im Evidence Store wird NICHT angerührt.
- Löschprotokoll: der Mediator schreibt eine signierte **Deletion-Attestierung** in den Transparency Log (`predicateType=https://basicstack.de/attestations/pii_deletion/v1`), damit später nachweisbar ist „zu diesem Workflow waren PII-Daten vorhanden und wurden am Datum X auf Antrag Y gelöscht".
**Alternativen, die wir bewusst nicht wählen (Begründung).**
| **Keine Trennung**, PII in den DSSE-Payloads | Macht DSGVO Art. 17 unlösbar — die Signaturkette müsste bei jeder Löschung invalidiert werden. |
| Kommerzielle PII-Vaults (Skyflow, Piiano, VGS) | Externer Dienst, Vendor-Lock-in, Kosten; keine Zusatzgarantien, die wir für den geplanten Scope brauchen. |
| Dedizierte Postgres-Instanz mit TDE | Löschbarkeit braucht Zusatzlogik; kein nennenswerter Vorteil gegenüber verschlüsseltem Object Storage für Blob-PII. |
| OpenBao Transform Engine (Format-Preserving Encryption) | Lohnt sich bei strukturierten Feldern (z.B. Kreditkarten) in einer bestehenden App — Overhead für unsere Use Cases. |
| Confidential Computing (TEE/SGX) | Overkill und derzeit auf Hetzner nicht in der Variante verfügbar, die wir brauchen würden. |
**Begrifflichkeit im weiteren Doc.** Wo bisher „PII-Store" steht, ist jeweils diese Rolle gemeint — konkret implementiert als „verschlüsselter S3-Bucket + PII-Mediator-Service". Wenn wir später z.B. Confluence-Export oder Teams-Daten anbinden, bleibt der Begriff gleich; nur der Mediator bekommt zusätzliche Backend-Adapter.
Signing Service und Session Scope Token Service verifizieren beide: Pocket-ID-Access-Token UND projected K8s ServiceAccount-Token. Nur bei übereinstimmendem `agent_id` wird signiert bzw. ein Scope-Token ausgestellt.
Pocket ID als IdP, Approval Service mit Content Binding, ein Testagent, Signing Service mit doppelter Identitätsprüfung, Verification Service für zweistufige Kette, triviale Mock-Senke (`sink:dummy-sink`). Noch ohne Lesepfad.
**Phase 4 — Eigene DB-App als kombinierte Quelle und Senke (erster produktiver Rollout).**
`expense-db-adapter` + `expense-db-postgres`, mit `ds:expense-db-app` (Reads auf `project_assignments`, `account_assignments`, `cost_centers`) und `sink:expense-db-app` (Writes auf `expenses`). Vollständiger produktiver Reisekostenprozess: Chat-Agent liest Projektzuweisungen, Benutzer wählt und bestätigt per Passkey, Mapping-Agent liest Kontierungsvorschläge, Posting-Agent schreibt — alles kryptografisch verkettet, prüfbar, mit 1-Jahres-Retention.
**Wichtigste Empfehlung:** Phase 1–3 bauen den Trust-Core samt Lesepfad mit Mock-Komponenten. **Phase 4 ist der erste produktive Durchlauf** mit eigener DB-App als kombinierter Quelle und Senke. SAP und externe Datenquellen folgen in Phase 6.