diff --git a/docs/architecture/v1.1.md b/docs/architecture/v1.1.md index e197f66..cd8af93 100644 --- a/docs/architecture/v1.1.md +++ b/docs/architecture/v1.1.md @@ -33,6 +33,8 @@ Diese Revision setzt die Richtungsentscheidungen der bisherigen Review-Runden um - **🆕 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. ## 1. Zielarchitektur @@ -421,6 +423,43 @@ CREATE TABLE agent_data_source_scopes ( 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). +### 4.7 Belege (Receipts) — Upload, Hashbindung, Lesezugriff entlang der Kette + +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//.` 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:** + +| Teil | Ort | +| --------------------------------------------------- | ---------------- | +| Beleg-Blob (PDF/PNG/JPG, verschlüsselt) | PII-Store | +| `sha256(content)`, `mime_type`, `size_bytes` | Receipt Attestation (Transparency Log) | +| `pii_store_ref` (nur Pfad, kein Inhalt) | Receipt Attestation + `expense_items[].receipt_ref` | + +**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). + ## 5. Verifikation ``` @@ -487,6 +526,39 @@ Rego-Policies werden zentral versioniert; die zum Entscheidungszeitpunkt verwend 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. + +**Rechtsänderungen** (neue BMF-Sätze, geänderte Länderliste): + +- 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 = ` 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. + ## 7. Senken-Integration (unverändert gegenüber V1.1 Erstfassung — Vertrag in 7.0, Beispiele 7.1–7.5)