docs(architecture): add 4.7 receipts flow (ds:receipts, PII-store, hash binding) and 6.2 deterministic rules example (ds:per-diem-rates, compute-agent, no LLM on numeric path)
This commit is contained in:
parent
2181225022
commit
95e50aff7d
1 changed files with 72 additions and 0 deletions
|
|
@ -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/<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:**
|
||||
|
||||
| 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 = <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.
|
||||
|
||||
## 7. Senken-Integration
|
||||
|
||||
(unverändert gegenüber V1.1 Erstfassung — Vertrag in 7.0, Beispiele 7.1–7.5)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue