docs(architecture): add section 7.2a for per-line project allocations (expense.canonical v1.1.0)
All checks were successful
validate / Rego (opa fmt + opa test) (push) Successful in 13s
validate / JSON Schema (ajv) (push) Successful in 16s

This commit is contained in:
CTO Agent 2026-10-25 16:20:38 +00:00
parent 5d309e16c1
commit 2181225022

View file

@ -32,6 +32,7 @@ Diese Revision setzt die Richtungsentscheidungen der bisherigen Review-Runden um
- **🆕 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. - **🆕 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. - **🆕 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. - **🆕 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`.
## 1. Zielarchitektur ## 1. Zielarchitektur
@ -570,6 +571,53 @@ CREATE TABLE sink_decision_log (
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. 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
source_attestation_ref TEXT NOT NULL,
UNIQUE (expense_item_id, project_code, cost_center)
);
```
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`.
### 7.3–7.5 Weitere Senken-Typen (HTTP-Webhook, Message-Queue, Dateiablage) — unverändert aus V1.1 Erstfassung. ### 7.3–7.5 Weitere Senken-Typen (HTTP-Webhook, Message-Queue, Dateiablage) — unverändert aus V1.1 Erstfassung.
### 7.6 Welchen Adapter wo deployen? — unverändert. ### 7.6 Welchen Adapter wo deployen? — unverändert.