trading-system-docs/tolaria/tolaria-write-auth/AUTH3E_DESIGN.md
Red Queen 10761f52b2 AUTH.3E: Executor Command Channel + Runtime Boundary Contract
- job_schema: geschlossene Job-Type-Allowlist (C5_SAVE_OBJECT/C5_DELETE_OBJECT), Pfad-/Längen-Validierung
- job_state_machine: deterministische States (CREATED/READY/CLAIMED/EXECUTING/SUCCEEDED/FAILED)
- job_claim: atomare Claim-/Lease-/Recovery-Logik (kein TOCTOU)
- job_store: getrennte SQLite-Inbox-DBs (c5a_save.db/c5a_delete.db), delegiert an job_claim
- save_executor_core: SAVE-only, Content-Rekonstruktion, Provenance-Validierung
- delete_executor_core: DELETE-only, AUTH.3D-Composition, TOCTOU-Defense (Re-Read nach Claim)
- test_job_channel: T1-T40 + adversarial (72 Tests)
- test_job_channel_adversarial: adversarial + Substitution + DB-Manipulation
- sensitivity_proof_auth3e: Mutationen A-L (12/12 Invarianten PRESENT)
- AUTH3B/3C/3D/3E_DESIGN: autoritative Security-Dokumentation (e25 Reconciliation)

COMMAND != AUTHORIZATION. Kein generischer Dispatcher. RQ credential-free.
Keine produktive Mutation. Keine echten Credentials.
2026-08-27 10:03:32 +00:00

714 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# AUTH.3E — EXECUTOR COMMAND CHANNEL + RUNTIME BOUNDARY CONTRACT
**Status:** DESIGN + CODE + ISOLATED TESTS + REVIEW ONLY (KEIN produktives Deployment)
**Datum:** 2026-08-27
**Mission Type:** SECURITY DESIGN + CODE + ISOLATED TESTS + REVIEW ONLY
**Baseline:** AUTH.1 (40bbc40) + AUTH.2 (edcb4902) + AUTH.3A (a11c1bb) + AUTH.3B + AUTH.3C + AUTH.3D (9c8d239) = COMPLETE
> ⚠️ Dies ist ein **Design-/Code-/Test-Dokument**. Es wird KEIN produktiver Umbau
> ausgeführt, kein Container erstellt, keine Compose/Network/Volume/DB geändert,
> kein echtes Credential erzeugt, keine ENV geändert, kein Deployment, keine
> produktive Tolaria-Mutationsprobe, keine Hermes-Autonomie. Bei Zweifel:
> FAIL CLOSED + HARD STOP.
---
## 1. CURRENT RUNTIME DISCOVERY (Phase 2) — rekonstruiert (READ-ONLY)
> Keine Annahmen. UNKNOWN bleibt UNKNOWN. Alle Fakten aus Code-Reading verifiziert.
### 1a. SAVE-Pfad: RQ/Hermes → C5 CLI → C5CPropagator → TolariaClient.write()
```
RQ/Hermes (Container 06a4a73573d0, User hermes uid 10010)
├─ [Entry Point] rq_c5_cli.py (argparse CLI, manuell aufgerufen)
│ KEIN CLI-Command führt C5CPropagator mit save_token aus (verifiziert)
├─ [Library] rq_c5c.py C5CPropagator (Z.497)
│ __init__(store, reader, client=None, ...) → client = client or TolariaClient()
│ Default: credential-frei (kein save_token)
│ write() → self.client.write(vault_path, after_content) (Z.638)
└─ [Library] rq_c5c.py TolariaClient.write() (Z.322)
token = self._require_token(self.save_token, "SAVE") # fail-closed
→ POST /api/vault/save (Bearer save_token)
```
**Boundary-Fakten (SAVE):**
- **Process Boundary:** C5CPropagator läuft im RQ/Hermes-Prozess (kein separater Prozess).
- **Container Boundary:** RQ-Container `06a4a73573d0` (User `hermes`, uid 10010, gid 10000).
- **Filesystem Boundary:** `/opt/data` (RQ-Container-Dateisystem); C5-DB `/opt/data/c5c_live/c5a.db`.
- **ENV Boundary:** `TOLARIA_SAVE_TOKEN` ABSENT im RQ-Container (verifiziert). `C5A_DB` steuert DB-Pfad.
- **Network Boundary:** Tolaria `187.124.31.123:5173` (auch 10.0.4.1/10.0.9.1); Forgejo 10.0.4.1:3000/10.0.9.1:3000.
- **DB-Zugriff:** C5AStore → SQLite `c5a.db` (Commit-State, ObjectChanges, Approvals, Ledger).
- **Forgejo-Zugriff:** GitReader liest Repo `nexo312/trading-system-docs` (Forgejo-Git-Push-Key).
- **Tolaria-Zugriff:** `write()` (SAVE) via `TolariaClient`.
- **Approval-DB-Zugriff:** C5AStore (delete_approvals-Tabelle) — SAVE-Pfad liest/schreibt sie nicht aktiv.
- **Benötigte Inputs:** Commit-SHA, ObjectChanges, Source-/Provenance-Daten aus Forgejo.
- **Erzeugte Outputs:** Tolaria-SAVE, C5-State-Update (PROPAGATING→VERIFYING→UPDATING_SEARCH).
### 1b. DELETE-Pfad: RQ/Hermes → C5 CLI → DeleteExecutor → Approval Validation → TolariaClient.delete()
```
RQ/Hermes (Container 06a4a73573d0)
├─ [Entry Point] rq_c5_cli.py cmd_c5_delete_approve (Z.355) → persistiert Human-Approval
├─ [Entry Point] rq_c5_cli.py cmd_c5_delete_execute (Z.424) → führt DELETE aus
├─ [Entry Point] rq_c5_cli.py cmd_c5_delete_replay (Z.458) → idempotenter Replay
├─ [Helper] rq_c5_cli.py _delete_executor (Z.344)
│ TolariaClient(base_url, delete_token=os.environ.get(ENV_TOLARIA_DELETE_TOKEN))
│ → liest NUR DELETE-Token (kein SAVE-Token)
├─ [Library] rq_c5_delete.py DeleteExecutor (Z.83)
│ __init__(store, client=None) → client = client or TolariaClient() (Default credential-frei)
│ execute(commit_sha) (Z.192):
│ Gate 1: ObjectChange existiert, operation == DELETE
│ Gate 2: Commit-Status == ST_DELETE_APPROVED
│ Gate 3: Approval existiert, APPROVED, passt exakt (Commit/Objekt/Pfad)
│ Gate 4: Pre-Delete-Drift-Check (Ziel existiert noch)
│ → _do_delete() → self.client.delete() (DELETE)
└─ [Library] rq_c5c.py TolariaClient.delete() (Z.~352)
token = self._require_token(self.delete_token, "DELETE") # fail-closed
→ POST /api/vault/delete (Bearer delete_token)
```
**Boundary-Fakten (DELETE):**
- **Process Boundary:** DeleteExecutor läuft im RQ/Hermes-Prozess (kein separater Prozess).
- **Container Boundary:** RQ-Container `06a4a73573d0`**SELBER Container wie SAVE**.
- **Filesystem Boundary:** `/opt/data`; C5-DB `/opt/data/c5c_live/c5a.db`.
- **ENV Boundary:** `TOLARIA_DELETE_TOKEN` ABSENT im RQ-Container (verifiziert).
- **Network Boundary:** Tolaria `187.124.31.123:5173`.
- **DB-Zugriff:** C5AStore → SQLite `c5a.db` (Approvals, Ledger, Commit-State).
- **Forgejo-Zugriff:** GitReader (Provenance-/Object-Daten).
- **Tolaria-Zugriff:** `delete()` (DELETE) via `TolariaClient`.
- **Approval-DB-Zugriff:** C5AStore delete_approvals-Tabelle (lesen + USED markieren).
- **Benötigte Inputs:** Commit-SHA, persistierte Approval, ObjectChange, Nonce.
- **Erzeugte Outputs:** Tolaria-DELETE, Approval→USED, Commit→ST_UPDATING_SEARCH.
### 1c. Kernbefund (bestätigt AUTH.3B/3C)
**SAVE- und DELETE-Caller laufen im SELBEN Prozess/Container** (`rq_c5_cli.py`, RQ-Container
`06a4a73573d0`). **CREDENTIAL_SEPARATION_STATUS = LOGICAL_ONLY.** Sobald Credentials
produktiv in denselben Container injiziert würden, wäre RQ_SAVE_ACCESS/DELETE_ACCESS
technisch NICHT mehr NO. → AUTH.4 BLOCKED.
### 1d. DB-Schema (C5AStore, `c5a.db`) — rekonstruiert
- **Tabellen:** `commits` (commit_sha, parent_sha, discovered_at, sequence, status, retry_count, last_error, last_error_code, updated_at), `objects` (commit_sha, object_id, path_before, path_after, operation, content_hash_before/after, metadata_hash_before/after, representation, state, reason_code; UNIQUE(commit_sha, object_id, operation)), `delete_approvals` (approval_id, workflow_commit, object_change_id, object_id, path, operation='DELETE', approval_status APPROVED/USED/REVOKED, approved_by, approval_nonce, approved_at, used_at; UNIQUE(workflow_commit, object_change_id, object_id, path)), `kv` (key, value — für search_source_provenance).
- **State Machine (commits.status):** DISCOVERED → VALIDATING → READY → PROPAGATING_TOLARIA → VERIFYING_TOLARIA → UPDATING_SEARCH → VERIFYING_SEARCH → APPLIED; plus RETRY_PENDING, FAILED, DEAD, HUMAN_REVIEW_REQUIRED, WAITING_FOR_PREDECESSOR, DELETE_APPROVED, DELETING.
- **Locks/Transactions:** SQLite; `_init_schema` erstellt Tabellen; `upsert_commit` nutzt `INSERT ... ON CONFLICT DO UPDATE` (idempotent); `add_object_change` nutzt `INSERT OR IGNORE` (idempotent per UNIQUE). Kein expliziter WAL-Modus im Code gesehen (Default SQLite).
- **Idempotenz:** `SyncStateMachine.idempotency_check` (commit_sha+object_id+operation; ALREADY_APPLIED/ALREADY_AT_TARGET/RETRY_SAFE/CONFLICT); `add_object_change` INSERT OR IGNORE; `upsert_commit` ON CONFLICT.
- **Retry:** `retry_commit` (increment_retry, max_retries → DEAD, sonst RETRY_PENDING + backoff); `_is_retryable` in C5C (nur TolariaUnavailableError retryable).
- **Replay:** `c5-delete-replay` (idempotent, nutzt denselben `_delete_executor`); Approval single-use (→ USED).
- **Recovery:** `c5e-recover`, `c5e-reconcile` (C5E); `c5e-replay --allow-writes` nur Test-/Canary-Scope.
- **Provenance:** `persist_search_source_provenance` (kv-Tabelle, key `search_source_provenance:<workflow_commit_sha>`); `get_source_provenance`.
- **Mission Binding:** **FEHLT** in C5A-Commits/ObjectChanges (kein mission_id-Feld). Nur in AUTH.3D-Approval-Payload (mission_id) vorhanden.
- **Object Binding:** `objects.object_id` (UNIQUE mit commit_sha+operation); Approval object_id-bound.
- **Commit Binding:** `commits.commit_sha`; Approval workflow_commit-bound.
### 1e. AUTH.3D-Approval (rekonstruiert, `approval_payload.py`/`approval_state.py`/`approval_verifier.py`)
- **Payload (12 kanonische Felder):** approval_version, approval_id, mission_id, delete_request_id, operation, object_id, vault_path, expected_commit, expected_provenance_hash, nonce, issued_at, expires_at.
- **Lifecycle:** CREATED → VERIFIED → RESERVED → EXECUTED → CONSUMED (atomare Reservation, TOCTOU-sicher).
- **Verifier:** verify-only (Ed25519 PUBLIC KEY ONLY), importiert nur key_id, verify.
- **Signature:** `approval_signature.py` enthält sign()/private_key_to_pem() NUR zur Simulation; KEIN Private Key im Stack.
- **Bindings:** Object, Commit, Path, Operation, Single-Use, Nonce, Expiry, Mission (in Payload).
---
## 2. COMMAND CHANNEL OPTIONS (Phase 3) — Bewertung
> Keine neue Infrastruktur bevorzugen, wenn eine einfachere bestehende Primitive
> die Sicherheitsanforderungen erfüllt.
| Kriterium | A) Shared SQLite Job DB | B) Getrennte SQLite Inbox pro Executor | C) Filesystem Inbox / atomic rename | D) Interner HTTP Executor Endpoint | E) RabbitMQ / Broker | F) Unix Socket / IPC | G) C5 State Machine (bestehend) |
|---|---|---|---|---|---|---|---|
| SECURITY | Mittel | **Hoch** | Mittel | Mittel | Hoch | Mittel | **Hoch** |
| COMPLEXITY | Niedrig | **Niedrig** | Niedrig | Mittel | **Hoch** | Mittel | **Niedrig** |
| FAIL_CLOSED | Ja | **Ja** | Ja | Ja | Ja | Ja | **Ja** |
| AUDITABILITY | Hoch | **Hoch** | Mittel | Hoch | Hoch | Mittel | **Hoch** |
| IDEMPOTENCY | Ja | **Ja** | Teilweise | Ja | Ja | Ja | **Ja** |
| REPLAY_RESISTANCE | Teilweise | **Ja** | Teilweise | Ja | Ja | Ja | **Ja** |
| CRASH_RECOVERY | Ja | **Ja** | Ja | Ja | Ja | Ja | **Ja** |
| CONCURRENCY | Mittel (Lock) | **Hoch (getrennte DBs)** | Mittel | Hoch | Hoch | Mittel | **Hoch** |
| RQ_SCOPE_ESCALATION | Nein | **Nein** | Nein | Nein | Nein | Nein | **Nein** |
| SECRET_EXPOSURE | Kein Secret | **Kein Secret** | Kein Secret | Kein Secret | Kein Secret | Kein Secret | **Kein Secret** |
| FILESYSTEM_BOUNDARY | Geteilt | **Getrennt** | Getrennt | Getrennt | Getrennt | Getrennt | **Getrennt** |
| NETWORK_BOUNDARY | Kein Netz | **Kein Netz** | Kein Netz | Netz nötig | Netz nötig | Kein Netz | **Kein Netz** |
| AUTH3D_COMPATIBILITY | Ja | **Ja** | Ja | Ja | Ja | Ja | **Ja** |
| CURRENT_C5_COMPATIBILITY | Mittel | **Hoch** | Mittel | Niedrig | **Niedrig** | Niedrig | **Hoch** |
| AUTH4_DEPLOYMENT_COMPLEXITY | Niedrig | **Niedrig** | Niedrig | Mittel | **Hoch** | Mittel | **Niedrig** |
**Bewertung:**
- **OPTION B (getrennte SQLite Inbox pro Executor)** ist die **bevorzugte** Wahl: Sie erfüllt
die geforderte **GETRENNTE INBOX/STATE BOUNDARY** (SAVE-Executor claimt nur SAVE-Jobs,
DELETE-Executor nur DELETE-Jobs), nutzt vorhandene SQLite-Primitive (keine neue
Infrastruktur), ist fail-closed, auditierbar, idempotent, replay-resistent und
crash-recoverable. Getrennte DB-Dateien (`c5a_save.db`/`c5a_delete.db`) vermeiden
Lock-Konflikte zwischen SAVE- und DELETE-Executor.
- **OPTION A (Shared SQLite Job DB)** ist einfacher, aber teilt die DB → schwächere
DB-Boundary (SAVE-Executor könnte DELETE-Jobs sehen, wenn Scope nicht strikt getrennt).
- **OPTION C (Filesystem Inbox)** ist einfach, aber weniger robust (kein atomarer
Claim/Lease, schwächere Idempotenz/Replay-Resistenz).
- **OPTION D (HTTP Endpoint)** erfordert neue Netzwerk-Endpoints → Scope-Creep.
- **OPTION E (RabbitMQ/Broker)** erfordert neue Infrastruktur → **abgelehnt** (nicht vorhanden).
- **OPTION F (Unix Socket)** erfordert neue IPC-Infrastruktur → Scope-Creep.
- **OPTION G (C5 State Machine)** ist die bestehende Primitive für DELETE (Approval
persistiert, Nonce, Single-Use, Replay-Schutz) und wird als **Komplement** zu B genutzt:
Der DELETE-Job verweist auf die persistierte AUTH.3D-Approval; der DELETE-Executor
validiert Job UND Approval unabhängig.
**Empfehlung:** **OPTION B (getrennte SQLite Inbox pro Executor) als Command Channel**,
kombiniert mit **OPTION G (C5 State Machine)** für die DELETE-Approval-Autorität.
Keine neue Infrastruktur.
---
## 3. PREFERRED ARCHITECTURAL PROPERTY (Phase 4)
**GETRENNTE INBOX / STATE BOUNDARIES.**
```
RQ/Hermes (Container 06a4a73573d0) — credential-frei
│ credential-freier Job-Request (kein Secret)
+---------------------------+ +-----------------------------+
| c5-save-executor (Cont.) | | c5-delete-executor (Cont.) |
| SAVE credential | | DELETE credential |
| NO DELETE | | NO SAVE |
| claimt NUR SAVE-Jobs | | claimt NUR DELETE-Jobs |
| c5a_save.db (Inbox) | | c5a_delete.db (Inbox) |
+---------------------------+ +-----------------------------+
│ SAVE (Bearer save_token) │ DELETE (Bearer delete_token)
▼ ▼
Tolaria Server (187.124.31.123:5173) — erwartet SAVE+DELETE-Verifier
```
**Kein generischer Dispatcher.** Kein `command="..."`, `endpoint="..."`, `url="..."`,
`method="..."`, `shell="..."`, `python="..."`, `handler="..."`. Keine RCE-artige Semantik.
**Job Types als geschlossene Allowlist:**
- `C5_SAVE_OBJECT`
- `C5_DELETE_OBJECT`
Gegen die reale C5-Architektur geprüft: Die C5-Operationen sind `OP_CREATE`,
`OP_CONTENT_UPDATE`, `OP_METADATA_UPDATE`, `OP_STATE_UPDATE`, `OP_TAGS_UPDATE`,
`OP_SOURCE_CANONICAL_RELATION_UPDATE`, `OP_RENAME`, `OP_MOVE`, `OP_DELETE_REQUEST`,
`OP_SUPERSEDE`. Für den Command Channel werden diese auf zwei geschlossene Job-Types
abgebildet: **SAVE** (alle Nicht-DELETE-Operationen, die Tolaria-Write erfordern) und
**DELETE** (nur `OP_DELETE_REQUEST`). Kein generischer Operation-Dispatcher.
---
## 4. SAVE JOB CONTRACT (Phase 5)
### Kanonischer SAVE Job
```json
{
"job_version": 1,
"job_id": "uuid4",
"mission_id": "uuid4",
"job_type": "C5_SAVE_OBJECT",
"object_id": "string",
"vault_path": "/app/vault/...",
"source_commit": "sha256",
"provenance_hash": "sha256",
"expected_state": "string",
"created_at": "ISO8601 UTC",
"idempotency_key": "uuid4"
}
```
### Content-Entscheidung: A) Content im Job ODER B) Executor lädt selbst
**Gewählt: B) Executor rekonstruiert sicherheitsrelevante Daten selbst aus autoritativer
Source (Forgejo).**
Begründung (Manipulationsrisiko):
- Wenn Content im Job transportiert würde, könnte RQ (oder ein kompromittierter
RQ-Prozess) beliebigen Content in den Job schreiben → der SAVE-Executor würde
manipulierten Content nach Tolaria schreiben.
- **Bevorzugte Eigenschaft (aus Mission):** Executor rekonstruiert sicherheitsrelevante
Daten soweit möglich selbst aus autoritativen Quellen, statt RQ blind zu vertrauen.
- Der SAVE-Executor lädt den Content selbst aus Forgejo (source_commit + object_id →
GitReader.file_content) und validiert gegen provenance_hash/expected_state.
**RQ darf NICHT bestimmen:**
- Tolaria Base URL (Executor-Konfiguration, nicht Job)
- HTTP Method (fix: POST /api/vault/save)
- Authorization Header (Executor-Credential, nicht Job)
- Credential (Executor-Credential, nicht Job)
- beliebigen Zielendpoint (fix: /api/vault/save)
---
## 5. DELETE JOB CONTRACT (Phase 6)
### Kanonischer DELETE Job (strenger)
```json
{
"job_version": 1,
"job_id": "uuid4",
"mission_id": "uuid4",
"delete_request_id": "uuid4",
"job_type": "C5_DELETE_OBJECT",
"object_id": "string",
"vault_path": "/app/vault/...",
"expected_commit": "sha256",
"expected_provenance_hash": "sha256",
"approval_id": "uuid4",
"created_at": "ISO8601 UTC",
"idempotency_key": "uuid4"
}
```
**WICHTIG: Der DELETE Job selbst ist KEINE Approval.**
DeleteExecutor muss unabhängig laden/verifizieren (AUTH.3D Approval Artifact):
- Ed25519 Signature (verify-only, PUBLIC KEY ONLY)
- mission_id
- delete_request_id
- object_id
- vault_path
- expected_commit
- expected_provenance_hash
- nonce
- expiry
- single-use state
**Job und Approval müssen exakt zusammenpassen. Mismatch → DELETE_DENIED.**
---
## 6. TRUST MODEL (Phase 7)
### RQ_JOB_FIELDS_TRUST_LEVEL
| Feld | SAVE | DELETE | Klassifikation |
|---|---|---|---|
| job_version | TRUSTED (Schema) | TRUSTED (Schema) | Schema-Validierung |
| job_id | TRUSTED (Identität) | TRUSTED (Identität) | Idempotenz-Schlüssel |
| mission_id | VERIFY_AGAINST_SOT | VERIFY_AGAINST_SOT | Gegen Approval/Provenance |
| job_type | TRUSTED (Allowlist) | TRUSTED (Allowlist) | Geschlossene Allowlist |
| object_id | VERIFY_AGAINST_SOT | VERIFY_AGAINST_SOT | Gegen Forgejo/Approval |
| vault_path | VERIFY_AGAINST_SOT | VERIFY_AGAINST_SOT | Gegen ObjectChange/Approval + Path Safety |
| source_commit | VERIFY_AGAINST_SOT | — | Gegen Forgejo |
| provenance_hash | RECOMPUTE | VERIFY_AGAINST_SOT | Executor rechnet selbst |
| expected_state | VERIFY_AGAINST_SOT | — | Gegen Forgejo |
| expected_commit | — | VERIFY_AGAINST_SOT | Gegen Approval |
| expected_provenance_hash | — | VERIFY_AGAINST_SOT | Gegen Approval |
| approval_id | — | VERIFY_AGAINST_SOT | Gegen AUTH.3D Approval |
| delete_request_id | — | VERIFY_AGAINST_SOT | Gegen Approval |
| created_at | TRUSTED (Metadaten) | TRUSTED (Metadaten) | Audit |
| idempotency_key | TRUSTED (Idempotenz) | TRUSTED (Idempotenz) | Replay-Schutz |
**Grundsatz: DATA FROM RQ != AUTHORITY.** RQ ist Orchestrator, nicht Security Authority.
Für sicherheitskritische Felder: VERIFY_AGAINST_SOT oder RECOMPUTE.
---
## 7. JOB STATE MACHINE (Phase 8)
### Deterministische State Machine
```
CREATED → READY → CLAIMED → EXECUTING → SUCCEEDED
├→ FAILED
├→ REJECTED
└→ OUTCOME_UNKNOWN
```
**Erlaubte Transitionen (geschlossene Allowlist):**
- CREATED → READY (Schema validiert, Job akzeptiert)
- CREATED → REJECTED (Schema/Allowlist/Injection-Fehler)
- READY → CLAIMED (atomarer Claim)
- CLAIMED → EXECUTING (Claim bestätigt)
- EXECUTING → SUCCEEDED (bestätigter Erfolg)
- EXECUTING → FAILED (bestätigter Fehler, kein Mutationseffekt)
- EXECUTING → OUTCOME_UNKNOWN (unklares HTTP-Ergebnis)
- EXECUTING → REJECTED (Gate-Fehler während Execution)
**RETRYABLE:** Optional, nur sicher begründet. Für SAVE: RETRYABLE nur bei
TolariaUnavailableError (transient, kein Mutationseffekt). Für DELETE: **KEIN
RETRYABLE** — ein bereits gestarteter DELETE darf nicht wegen Lease-Expiry von einem
zweiten Worker wiederholt werden. OUTCOME_UNKNOWN → kein blinder Retry.
**Ungültige Transition → FAIL CLOSED** (InvalidTransitionError).
---
## 8. CLAIM / LEASE / CONCURRENCY (Phase 9)
### Claim-Modell
- **worker_id:** UUID des Executors (SAVE-Executor vs DELETE-Executor).
- **claim_id:** UUID pro Claim.
- **claimed_at:** Timestamp.
- **lease_until:** Timestamp (Claim-Expiry).
- **attempt_count:** Zähler.
### Atomarer Claim
Der Claim muss **atomar** sein (SQLite `UPDATE ... WHERE status='READY' AND
(lease_until IS NULL OR lease_until < now) RETURNING ...` in einer Transaktion).
Kein TOCTOU.
### Concurrency-Prüfung
- **Zwei SAVE Worker:** Nur einer kann denselben Job atomar claimen (Status-Transition
READY→CLAIMED ist atomar).
- **Zwei DELETE Worker:** Nur einer kann denselben DELETE-Job claimen. **Ein bereits
gestarteter DELETE darf NICHT einfach wegen Lease-Expiry von einem zweiten Worker
wiederholt werden.** → DELETE-Job, der in EXECUTING ist, wird bei Lease-Expiry in
OUTCOME_UNKNOWN überführt (nicht zurück zu READY), und ein zweiter Worker darf ihn
NICHT erneut claimen. Nur ein expliziter Human-/Admin-Eingriff (AUTH.3D-Approval
erneut verifizieren) kann einen DELETE-Job aus OUTCOME_UNKNOWN reaktivieren.
- **Worker Crash nach Claim:** Lease-Expiry → Job wird für SAVE wieder claimbar
(RETRYABLE), für DELETE → OUTCOME_UNKNOWN (nicht blind wiederholen).
- **Stale Claim:** Lease-Expiry erkennt stale Claims.
- **Restart / Doppelstart / Race:** Atomarer Claim + Lease verhindern Doppelverarbeitung.
---
## 9. IDEMPOTENCY (Phase 10)
### SAVE
Idempotenz über: `job_id` + `idempotency_key` + `object_id` + `source_commit`/`provenance`.
- Gleicher Job (job_id) → bereits verarbeitet → kein zweiter Write.
- Gleicher idempotency_key → idempotent (kein zweiter Write).
- Gleicher object_id + source_commit → ALREADY_AT_TARGET (Ziel-Hash erreicht).
### DELETE (strenger)
- Nach bestätigtem DELETE: gleicher Job niemals erneut mutieren.
- Bei unklarem HTTP-Ergebnis: **OUTCOME_UNKNOWN. KEIN BLINDER RETRY.**
- AUTH.3D OUTCOME_UNKNOWN-Modell bleibt erhalten.
- Approval single-use (→ USED/CONSUMED) → Replay denied.
---
## 10. JOB AUTHENTICITY / INTEGRITY (Phase 11)
### Ist Job-DB-Manipulation durch RQ relevant?
- **Für SAVE:** Ein manipulierter Job darf niemals Scope erweitern. Da der SAVE-Executor
Content selbst aus Forgejo lädt und gegen provenance_hash/expected_state validiert,
kann ein manipulierter Job keinen beliebigen Content einschleusen. Scope bleibt
geschlossen (nur SAVE, nur erlaubter Pfad).
- **Für DELETE:** Selbst vollständige Job-DB-Schreibkontrolle darf NICHT ausreichen, um
DELETE zu autorisieren. **AUTH.3D Signature bleibt die Autorität.** Ein manipulierter
DELETE-Job ohne gültige, exakt passende AUTH.3D-Approval → DELETE_DENIED.
### Zusätzliche Schutzmechanismen (bewertet)
- **Job HMAC:** NICHT nötig für DELETE (AUTH.3D-Signatur schützt die kritische
DELETE-Autorität). Für SAVE optional, aber nicht erforderlich (Content wird
rekonstruiert).
- **DB ownership / filesystem permissions:** Ja — getrennte DB-Dateien mit
Executor-spezifischen Permissions (siehe Phase 12).
- **Immutable fields after claim:** Ja — nach CLAIMED sind job_type, object_id,
vault_path, approval_id, expected_* unveränderlich (UPDATE verboten).
- **State hash / row version / compare-and-swap:** Optional für SAVE; für DELETE
wird die AUTH.3D-Signatur als Autorität genutzt (keine unnötige Kryptografie).
**Keine unnötige Kryptografie einführen, wenn die AUTH.3D-Signatur bereits die
kritische DELETE-Autorität schützt.**
---
## 11. DB / FILESYSTEM BOUNDARY (Phase 12)
### Getrennte SQLite-Dateien (bestätigt AUTH.3C)
- `c5a_save.db` — SAVE-Inbox (SAVE-Jobs, SAVE-State)
- `c5a_delete.db` — DELETE-Inbox (DELETE-Jobs, DELETE-State, Approval-Referenzen)
### Zugriffsmatrix
| DB | RQ_READ | RQ_WRITE | SAVE_EXEC_READ | SAVE_EXEC_WRITE | DELETE_EXEC_READ | DELETE_EXEC_WRITE |
|---|---|---|---|---|---|---|
| c5a_save.db | Ja (Job erzeugen) | Ja (Job erzeugen) | Ja | Ja | **NEIN** | **NEIN** |
| c5a_delete.db | Ja (Job erzeugen) | Ja (Job erzeugen) | **NEIN** | **NEIN** | Ja | Ja |
**Ziel:**
- SAVE Executor braucht keinen Zugriff auf DELETE Credential/DB, außer explizit
notwendige read-only Referenzen (keine).
- DELETE Executor braucht keinen SAVE-State.
### Linux UID/GID-/Volume-Modell (für AUTH.4, NICHT produktiv ändern)
- SAVE-Executor-Container: eigener User, Volume nur für `c5a_save.db`.
- DELETE-Executor-Container: eigener User, Volume nur für `c5a_delete.db`.
- RQ-Container: Volume für beide Inbox-DBs (nur Job-Erzeugung, kein Executor-Zugriff).
- Kein geteiltes Secret-Volume.
---
## 12. NETWORK BOUNDARY (Phase 13)
### SAVE Executor
**ALLOW:**
- Tolaria notwendige interne Adresse (187.124.31.123:5173) — SAVE + READ
- notwendige autoritative Source (Forgejo 10.0.4.1:3000) — Source-/Provenance-Read
**DENY (soweit möglich):**
- Docker Socket
- SSH
- Host Root
- Telegram
- beliebiges Internet
- DELETE-spezifische Dienste
### DELETE Executor
**ALLOW:**
- Tolaria (187.124.31.123:5173) — DELETE
- notwendige Approval-/State-Source (Forgejo 10.0.4.1:3000) — Object/Commit-Read
**DENY:**
- Docker Socket
- SSH
- Host Root
- Telegram
- beliebiges Internet
- SAVE-spezifische Credentials
### Forgejo-Runtime-Frage
**Wird Forgejo wirklich zur Runtime benötigt?**
- **SAVE:** JA — der SAVE-Executor lädt Content selbst aus Forgejo (source_commit +
object_id → GitReader.file_content). Forgejo ist die autoritative Source.
- **DELETE:** JA (für Object/Commit-Read zur Validierung). Aber: Falls der DELETE-Job
alle nötigen Daten (object_id, vault_path, expected_commit, expected_provenance_hash)
bereits enthält und die AUTH.3D-Approval diese exakt bestätigt, könnte Forgejo-Read
optional sein. **Empfehlung:** Forgejo-Read für DELETE beibehalten (Defense in depth),
aber als read-only, nicht als Write.
**Falls nicht nötig: KEIN Forgejo Runtime Access.** Für SAVE ist er nötig (Content-
Rekonstruktion). Für DELETE ist er nötig (Object/Commit-Validierung).
---
## 13. SECRET BOUNDARY (Phase 14)
### Zielmatrix (beweisen)
| Akteur | SAVE_TOKEN | DELETE_TOKEN | PRIVATE_SIGNING_KEY | PUBLIC_VERIFY_KEY |
|---|---|---|---|---|
| RQ | ABSENT | ABSENT | ABSENT | ABSENT |
| SAVE Executor | **PRESENT** | ABSENT | ABSENT | ABSENT |
| DELETE Executor | ABSENT | **PRESENT** | ABSENT | **PRESENT** |
| Rain | ABSENT | ABSENT | ABSENT | ABSENT |
| Tolaria Server | PRESENT (verifier) | PRESENT (verifier) | ABSENT | ABSENT |
**Beweis (Design):**
- RQ: credential-frei (kein SAVE/DELETE-Token, kein Private Key). Nur Job-Erzeugung.
- SAVE Executor: erhält NUR SAVE-Token (injiziert), kein DELETE-Token, kein Private Key.
- DELETE Executor: erhält NUR DELETE-Token + PUBLIC_VERIFY_KEY, kein SAVE-Token, kein
Private Key.
- Rain: kein Zugriff auf irgendein Secret (separate Container).
- Tolaria Server: hält SAVE+DELETE-Verifier-Secrets (eigene ENV), KEIN Christians
Private Key.
---
## 14. COMMAND INJECTION DEFENSE (Phase 15)
Jobs dürfen niemals enthalten oder ausführen:
- shell commands
- arbitrary executable
- Python snippets
- URLs
- HTTP headers
- credentials
- SQL
- filesystem absolute targets (außerhalb erlaubten Schemas)
- Docker commands
- SSH commands
**Enum + typed schema:**
- `job_type` ist ein Enum (`C5_SAVE_OBJECT` | `C5_DELETE_OBJECT`), kein freier String.
- Alle Felder sind typisiert (uuid, sha256-hex, ISO8601, enum).
- **Unknown job_type → REJECTED.**
- **Extra privileged fields → REJECTED** (Schema-Validierung lehnt unbekannte Felder ab).
- **Malformed schema → REJECTED.**
---
## 15. PATH SAFETY COMPOSITION (Phase 16)
AUTH.2 Path Safety bleibt serverseitig zwingend. Executor muss zusätzlich eigene
Pfadprüfung durchführen.
**Defense in depth:**
```
RQ Job → Executor validation → Tolaria AUTH.2 validation
```
- **Executor validation:** vault_path wird gegen das erlaubte Schema geprüft
(kein `..`, kein absoluter Pfad außerhalb `/app/vault`, keine Symlink-Escapes,
keine Unicode-Ambiguität, keine encoded traversal).
- **Tolaria AUTH.2 validation:** serverseitige Path Safety (bestehend, zwingend).
- **Kein Caller darf AUTH.2 Path Safety ersetzen.**
---
## 16. AUTH.3D COMPOSITION (Phase 17)
Für DELETE muss gelten (alle Faktoren, keiner ersetzt einen anderen):
```
VALID_JOB
AND VALID_STATE
AND VALID_AUTH3D_SIGNATURE
AND VALID_NONCE
AND VALID_EXPIRY
AND NOT_CONSUMED
AND VALID_OBJECT
AND VALID_PATH
AND VALID_COMMIT
AND VALID_PROVENANCE
AND DELETE_CREDENTIAL
```
erst dann: **Tolaria DELETE.**
---
## 17. FAILURE MODEL (Phase 18)
| Szenario | Verhalten |
|---|---|
| DB unavailable | FAIL CLOSED — kein Write ohne State |
| DB locked | SQLite-Lock → Retry/Fehler; kein Write ohne State |
| malformed job | REJECTED (Schema) |
| unknown version | REJECTED |
| unknown type | REJECTED |
| state mismatch | FAIL CLOSED (InvalidTransitionError) |
| source unavailable | FAIL CLOSED (kein Content-Rekonstruktions-Write) |
| Tolaria unavailable | SAVE: RETRYABLE (transient); DELETE: OUTCOME_UNKNOWN |
| 401 | FAIL CLOSED (Credential/Scope-Fehler) |
| 403 | FAIL CLOSED |
| 404 | FAIL CLOSED (Ziel fehlt) |
| 409 | FAIL CLOSED (Konflikt) |
| 500 | FAIL CLOSED |
| timeout | SAVE: RETRYABLE; DELETE: OUTCOME_UNKNOWN |
| connection reset | SAVE: RETRYABLE; DELETE: OUTCOME_UNKNOWN |
| worker crash | Lease-Expiry; SAVE: wieder claimbar; DELETE: OUTCOME_UNKNOWN |
| process kill | dito |
| container restart | Zustand aus DB; keine Sicherheitsinvariante verloren |
| duplicate job | Idempotenz (job_id/idempotency_key) |
| duplicate worker | Atomarer Claim verhindert Doppelverarbeitung |
| approval unavailable | DELETE_DENIED |
| approval invalid | DELETE_DENIED |
| approval expired | DELETE_DENIED |
| credential missing | FAIL CLOSED (kein HTTP) |
**Security-Grundsatz: Unsicherheit darf nicht zu privilegierter Mutation führen.**
---
## 18. AUDIT MODEL (Phase 19)
Jeder Job benötigt Audit Trail. Mindestens:
- job_id, mission_id, job_type, object_id
- state transitions (mit Timestamps)
- worker_id, attempt
- timestamps
- result code
- approval_id (bei DELETE)
- provenance
**NICHT loggen:**
- Credentials
- Authorization Header
- Private Key
- sensitive Approval internals, falls unnötig
**Audit muss unterscheiden:**
- **REQUESTED** (RQ hat Job erzeugt)
- **AUTHORIZED** (Executor hat Job + Approval validiert)
- **EXECUTED** (Mutation ausgeführt)
**WICHTIG: RQ REQUEST != HUMAN AUTHORIZATION.** Ein REQUESTED-Eintrag ist keine
AUTHORIZATION. Nur AUTHORIZED (nach AUTH.3D-Validierung) ist eine Autorisierung.
---
## 19. ISOLATED IMPLEMENTATION (Phase 20) — Design
> Implementiert in Code + Tests. KEIN produktiver Container. Modulare Komponenten.
### Module
- `job_schema.py` — Job-Schema-Validierung (Enum, typed, Allowlist, REJECTED)
- `job_store.py` — SQLite Inbox (getrennte DBs, atomarer Claim, Lease)
- `job_state_machine.py` — deterministische State Machine (FAIL CLOSED)
- `job_claim.py` — atomarer Claim/Lease/Concurrency
- `save_executor_core.py` — SAVE-Executor-Logik (SAVE-only, Content-Rekonstruktion)
- `delete_executor_core.py` — DELETE-Executor-Logik (DELETE-only, AUTH.3D-Validierung)
### Wiederverwendung
- Bestehende C5-Module (rq_c5a, rq_c5c, rq_c5_delete, approval_*) werden wiederverwendet,
nicht neu geschrieben.
- Keine Big-Bang-Neuschreibung.
---
## 20. TEST CONTRACT (Phase 21) — Design
> T1T40 (siehe Mission §21). Implementiert in isolierten Tests (synthetische Secrets,
> Fake-Tolaria, Temp-DB). Keine produktiven Mutationsprobes.
---
## 21. ADVERSARIAL TESTS (Phase 22) — Design
> JSON duplicate keys, Unicode path ambiguity, encoded traversal, job_type casing,
> enum confusion, integer/string confusion, oversized payload, stale mission, stale
> provenance, race claim, replay after restart, DB row manipulation, cross-executor
> DB injection, approval substitution, object substitution after claim, path
> substitution after claim, state mutation after approval, crash before HTTP, crash
> after uncertain HTTP.
---
## 22. TEST SENSITIVITY (Phase 23) — Design
> Mutationen AL (siehe Mission §23). Jede Mutation muss relevante Tests ROT machen.
> Falls nicht: Tests verstärken.
---
## 23. REGRESSION (Phase 24) — Design
> Volle relevante C5-Test-Suite + AUTH.3A-Tests + AUTH.3D-Tests. Keine bestehenden
> Sicherheitsinvarianten abschwächen. Baseline-Fails separat klassifizieren. Keine
> unrelated Reparaturen.
---
## 24. SECURITY REVIEW (Phase 25) — Design
> Explizit beweisen: RQ_CREDENTIAL_FREE, HERMES_CREDENTIAL_FREE, RAIN_CREDENTIAL_FREE,
> SAVE_EXECUTOR_SAVE_ONLY, DELETE_EXECUTOR_DELETE_ONLY, DELETE_EXECUTOR_VERIFY_ONLY,
> NO_PRIVATE_SIGNING_KEY_IN_STACK, NO_GENERIC_COMMAND_DISPATCH, NO_ARBITRARY_URL,
> NO_ARBITRARY_HTTP_METHOD, NO_ARBITRARY_HEADERS, NO_SHELL_EXECUTION,
> JOB_DOES_NOT_EQUAL_AUTHORIZATION, AUTH3D_REQUIRED_FOR_DELETE, ATOMIC_CLAIM,
> IDEMPOTENCY, REPLAY_PROTECTION, OUTCOME_UNKNOWN_FAIL_CLOSED.
---
## 25. DESIGN DOC RECONCILIATION (Phase 26) — Design
> AUTH3B_DESIGN.md, AUTH3C_DESIGN.md, AUTH3D_DESIGN.md (untracked). Entscheidungsmatrix
> (FILE, SECURITY_RELEVANCE, UNIQUE_INFORMATION, ALREADY_REPRESENTED_IN_COMMITTED_CODE/DOCS,
> STALE_INFORMATION, RECOMMENDED_SOT_ACTION; Klassifikation COMMIT/MERGE_INTO_ARCH_DOC/
> ARCHIVE/DISCARD/KEEP_UNTRACKED_TEMPORARILY). Nur Empfehlung. Keine Mutation ohne
> separate Freigabe.
---
## 26. COMMIT / PUSH (Phase 27) — Design
> Nur AUTH.3E-Code + Tests + ausdrücklich notwendige AUTH.3E-Dokumentation. Vor Commit:
> git status, git diff, Secret Scan, Private-Key Scan, Credential Pattern Scan. Keine
> echten Credentials. Nur synthetische Testwerte. FF-only Push. HEAD == origin/main
> verifizieren. Falls Hooks scheitern: Ursache analysieren. --no-verify NICHT
> automatisch verwenden.
---
## 27. FRESH CHECKER (Phase 28) — Design
> Unabhängiger Checker (32 Punkte, siehe Mission §28).
---
## 28. FINAL REPORT (Phase 29) + HARD STOP (Phase 30)
> Nach FINAL REPORT sofort STOP. Keine produktiven Mutationen, keine Container,
> keine echten Credentials, keine Hermes-Autonomie.