- 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.
32 KiB
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(Userhermes, uid 10010, gid 10000). - Filesystem Boundary:
/opt/data(RQ-Container-Dateisystem); C5-DB/opt/data/c5c_live/c5a.db. - ENV Boundary:
TOLARIA_SAVE_TOKENABSENT im RQ-Container (verifiziert).C5A_DBsteuert 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) viaTolariaClient. - 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_TOKENABSENT 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) viaTolariaClient. - 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_schemaerstellt Tabellen;upsert_commitnutztINSERT ... ON CONFLICT DO UPDATE(idempotent);add_object_changenutztINSERT 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_changeINSERT OR IGNORE;upsert_commitON CONFLICT. - Retry:
retry_commit(increment_retry, max_retries → DEAD, sonst RETRY_PENDING + backoff);_is_retryablein 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-writesnur Test-/Canary-Scope. - Provenance:
persist_search_source_provenance(kv-Tabelle, keysearch_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.pyenthä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_OBJECTC5_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
{
"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)
{
"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_typeist 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/Concurrencysave_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
T1–T40 (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 A–L (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.