# C5 SYNC SERVICE — CONTRACT & STATE MACHINE (C5A) + FORGEJO POLLING & CHANGE DETECTION (C5B) **Phase:** C5A (Contract & State Machine) + C5B (Forgejo Polling & Change Detection) **Status:** C5A = DONE · C5B = DONE (Polling & Change Detection) — **INAKTIV, NO-WRITE** **Architektur:** FORGEJO MASTER → C5 SYNC SERVICE → TOLARIA DERIVED → SEARCH FULL REBUILD C5A implementiert **ausschließlich** den Contract + die Sync State Machine + die persistente Progress-State-Struktur. C5B baut darauf die **Forgejo Polling & Change Detection Engine** (read-only). Beide sind **deterministische, inaktive Python-Libraries + CLI + Testsuite** — sie führen **keine** externen Writes aus. --- ## NO-WRITE GUARANTEE (verbindlich, C5A + C5B) C5A + C5B können **NOCH NICHT**: - Tolaria schreiben - Search rebuilden - Forgejo schreiben - produktiv pollen (kein Poll-Daemon) - Netzwerk-Mutationen ausführen **C5A** hat **keine** Netzwerk-/HTTP-/Socket-/Subprocess-Mutationsfunktionen (`assert_no_write_guarantee()`). **C5B** führt **ausschließlich read-only git-Befehle** gegen den lokalen Clone aus (strikte Whitelist: `rev-parse`, `log`, `show`, `diff-tree`, `ls-tree`, `cat-file`, `merge-base`, `rev-list`, `diff`). Jeder Write-Befehl (`push`/`commit`/`add`/`reset`/`checkout`/`merge`/`rebase`) wird mit `GitWriteBlockedError` blockiert. Die No-Downstream-Write-Guarantee wird statisch per `assert_no_downstream_write()` bewiesen (keine HTTP-Mutationsimporte, keine `/api/vault/save`-/`/api/search/rebuild`-Endpoints im Code, keine Git-Write-Befehle in der Whitelist) und durch den Test `test_no_downstream_write` abgesichert. --- ## C5B — FORGEJO POLLING & CHANGE DETECTION ENGINE ### FORGEJO_READ_METHOD **A) lokale Git-Mirror/Clone-Analyse** (bevorzugt). - **AUTH_REQUIREMENT:** KEIN (read-only gegen lokalen Clone; kein Write-Token) - **NETWORK_DEPENDENCY:** KEIN (lokale `.git`-Objekte; kein HTTP) ### POLLING_MODEL - **Default:** 60 Sekunden - **Environment-konfigurierbar:** `C5_POLL_INTERVAL_SECONDS` (z.B. `=120`) - C5B implementiert die **Polling-Logik**, startet aber **KEINEN** dauerhaften Produktions-Daemon. Tests führen einzelne kontrollierte `poll_once`-Zyklen aus. ### HEAD_DISCOVERY `get_current_head()` → Commit-SHA + parent/timestamp/message. Bei nicht erreichbarem Forgejo: `RC_FORGEJO_UNAVAILABLE`, kein State-Fortschritt. ### COMMIT_RANGE_MODEL Aus `last_seen_commit` (C5B-Baseline) bis HEAD deterministisch alle fehlenden Commits in chronologischer (parent-korrekter) Reihenfolge. Kein Überspringen, keine Doppelerfassung. Merge-Commits werden explizit behandelt (alle Parents). Divergenz/History-Rewrite wird erkannt. > **Wichtig:** C5B-Baseline ist `last_seen_commit`. `last_applied_commit` bleibt > laut C5A-Contract für die spätere Propagation (C5C) reserviert und wird von C5B > **NIE** fortgeschrieben. ### HISTORY_SAFETY Wenn `last_seen_commit` kein Ancestor von HEAD ist: **FAIL CLOSED** (`RC_OUT_OF_ORDER_COMMIT`). Keine automatische Rebase-/Reset-Logik. ### DIFF_MODEL Für jeden neuen Commit: `git diff-tree -r --name-status -M -C --root` → `ADDED` / `MODIFIED` / `DELETED` / `RENAMED` / `MOVED`. Bei unsicherer Rename-Detection wird **nicht geraten** — Ambiguität wird sauber klassifiziert (Human Review). ### KNOWLEDGE_SCOPE_MODEL Nicht jede Forgejo-Datei ist automatisch Tolaria-Knowledge: - **IN_SCOPE:** Markdown im Repo-Root ODER unter `notes/trading/system-docs/` - **OUT_OF_SCOPE:** `tolaria/`, `a2-5/`, `red-queen-architecture/`, `notion-safety-brain/`, `notion-command-center/`, `backup_patches/`, `notes/reference/`, Nicht-Markdown, Build-Artefakte - **LEGACY_SPECIAL:** `README.md` (x2, KEEP_DISTINCT) + `vps.md` (DEFERRED) - **HUMAN_REVIEW:** Knowledge-Objekt ohne gültige `object_id` Keine Secrets/Build-Artefakte in Sync-Change-Sets. Nicht blind `.md` = Sync. ### OBJECT_ID_MODEL `object_id` wird **aus dem Frontmatter** gelesen (`id: object/`), nicht aus dem Pfad erzeugt, nicht neu generiert, nicht geraten. Ohne gültige ID: `UNKNOWN_OBJECT_ID` / `UNKNOWN_LEGACY_OBJECT` → Human Gate. ### HASH_MODEL - **`content_hash`:** SHA-256 des fachlichen Bodies (nach Frontmatter) - **`metadata_hash`:** SHA-256 der relevanten Knowledge-Schema-Metadaten (`id`, `type`, `role`, `representation`, `state`, `derived_from`, `tags`, `knowledge_schema`). Pfad ist separat. - Deterministisch und testbar. ### CHANGE_CLASSIFICATION Aus Before/After deterministisch: `CREATE`, `CONTENT_UPDATE`, `METADATA_UPDATE`, `STATE_UPDATE`, `RENAME`, `MOVE`, `SOURCE_CANONICAL_RELATION_UPDATE`, `TAGS_UPDATE`, `SUPERSEDE`, `DELETE_REQUEST`. Bei mehreren geänderten Aspekten in EINEM Objekt werden **mehrere ObjectChange-Einträge** erzeugt (keine Information verloren). ### RENAME_MOVE_MODEL Rename/Move nur automatisch, wenn **gleiche `object_id`** + eindeutige Git-/Content-Evidence. ID bleibt stabil. Rename = Dateiname geändert (gleiches Verzeichnis); Move = Verzeichnis geändert. Bei Ambiguität: Human Gate. ### DELETE_MODEL Git-Delete wird **NIEMALS** zu automatischem Hard Delete. C5B erzeugt `DELETE_REQUEST` → C5A führt es in Human Review. Keine Tolaria-Aktion. ### PAIR_MODEL Wenn Root/source + canonical gemeinsam verändert werden, erkennt C5B die Pair-Beziehung (`derived_from`). Ordnung für spätere C5C: Source zuerst, Canonical danach. Noch nicht propagiert — Output enthält genug Information für C5C. ### SECRET_SAFETY C5B schreibt **keine** Secret-Inhalte in Logs/SQLite/Evidence. Bei Secret-/Forbidden-Dateien: nur sichere Metadaten (`path`, `reason_code`, `commit_sha`). Enthält ein Knowledge-Dokument einen möglichen Secret-Wert: `RC_SECRET_DETECTED` FAIL CLOSED. Kein Wert wird ausgegeben. ### C5A_INTEGRATION C5B speist gefundene Commits + ObjectChanges in den C5A Store ein (nur State/Persistence, kein Network Write downstream). Flow: Poll once → discover commits → classify changes → register commit → add object changes → Status `DISCOVERED`/`VALIDATING` (bzw. `HUMAN_REVIEW_REQUIRED`). **Nicht** weiter zu `PROPAGATING_TOLARIA`. ### OBSERVABILITY C5B erweitert sichere Statusdaten: `last_seen_commit`, `poll_timestamp`, `commits_discovered`, `objects_discovered`, `out_of_scope_count`, `last_poll_status`, `last_poll_error_code`. Keine Content-Logs. --- ## State Machine (C5A) ### Normalzustände `DISCOVERED → VALIDATING → READY → PROPAGATING_TOLARIA → VERIFYING_TOLARIA → UPDATING_SEARCH → VERIFYING_SEARCH → APPLIED` ### Fehlerzustände `RETRY_PENDING`, `FAILED`, `DEAD`, `HUMAN_REVIEW_REQUIRED` ### Ordering `WAITING_FOR_PREDECESSOR` — Commits werden nur in korrekter Reihenfolge verarbeitet (`parent_sha` / `last_applied_commit`). Lücken werden **nicht** übersprungen. ### Kerninvariante **`last_applied_commit` wird NUR nach vollständigem Tolaria+Search PASS fortgeschrieben** (`mark_applied` wird nur nach `VERIFYING_SEARCH → APPLIED` aufgerufen). --- ## Commit Contract Eine Verarbeitungseinheit = **ein Forgejo Commit**: | Feld | Beschreibung | |---|---| | `commit_sha` | Primärschlüssel | | `parent_sha` | Vorgänger (Ordering) | | `discovered_at` | Zeitstempel | | `sequence` | Reihenfolge | | `status` | Sync-Zustand | | `retry_count` | Retry-Zähler | | `last_error` / `last_error_code` | Fehler + Reason Code | | `created_at` / `updated_at` | Zeitstempel | ### Pro Changed Object `object_id`, `path_before`, `path_after`, `operation`, `content_hash_before`, `content_hash_after`, `metadata_hash_before`, `metadata_hash_after`, `representation`, `state` --- ## Operation Model `CREATE`, `CONTENT_UPDATE`, `METADATA_UPDATE`, `STATE_UPDATE`, `RENAME`, `MOVE`, `SOURCE_CANONICAL_RELATION_UPDATE`, `TAGS_UPDATE`, `SUPERSEDE`, `DELETE_REQUEST` **`DELETE_REQUEST` bleibt Human-Gate** — kein automatisches Hard Delete. --- ## Idempotenz Basis: **`commit_sha + object_id + operation`** + Hash-Checks. | Ergebnis | Bedeutung | |---|---| | `ALREADY_APPLIED` | Commit bereits vollständig angewendet | | `ALREADY_AT_TARGET` | Ziel-Hash bereits erreicht | | `RETRY_SAFE` | Noch nicht angewendet, sicher zu verarbeiten | | `CONFLICT` | Existiert, aber Hash weicht ab | Doppelte Commit-Erkennung ist **deterministisch**. --- ## Ordering Commits nur in korrekter Reihenfolge. Fehlender Vorgänger → `WAITING_FOR_PREDECESSOR` (Reason Code `OUT_OF_ORDER_COMMIT`). Nach Anwendung des Vorgängers wird der Commit erneut validiert. --- ## Failure / Retry State - `max_retries = 5` (konfigurierbar) - Backoff: `1 / 2 / 4 / 8 / 16` Sekunden (gekappt) - Nach Max → `DEAD` oder `HUMAN_REVIEW_REQUIRED` - **Kein Endlos-Retry** --- ## Persistence SQLite (`c5a.db`), isoliert, restart-fest. Anforderungen erfüllt: - **atomic** (SQLite-Transaktionen, WAL) - **restart-safe** (neuer Store auf gleicher DB behält State) - **inspectable** (CLI `health`, `list-commits`, `commit-status`) - **backupbar** (einzelne Datei) - **keine externe DB** nötig - **kein Trading-/Forgejo-DB-Coupling** (eigene DB) --- ## Bootstrap State `UNINITIALIZED → RECONCILING → BASELINE_READY → ACTIVE` Baseline darf **nur** aus `BASELINE_READY` gesetzt werden (Master↔Tolaria- Reconciliation muss sauber sein). In C5A wird **keine produktive Baseline** gesetzt. --- ## Reason Codes (geschlossene Menge) `UNEXPECTED_TOLARIA_DRIFT`, `ID_COLLISION`, `UNKNOWN_OBJECT_ID`, `INVALID_SCHEMA`, `DANGLING_DERIVED_FROM`, `AMBIGUOUS_DELETE`, `UNKNOWN_LEGACY_OBJECT`, `AUTH_FAILURE`, `SEARCH_REBUILD_FAILURE`, `TOLARIA_UNAVAILABLE`, `FORGEJO_UNAVAILABLE`, `OUT_OF_ORDER_COMMIT`, `SECRET_DETECTED` Kein freier String-Wildwuchs als einziges Fehlerformat — unbekannte Reason Codes werden abgelehnt (`InvalidReasonCodeError`). --- ## Security Boundary (dokumentiert, NICHT implementiert) - **Forgejo credential:** READ ONLY (C5A hält keinen Forgejo-Write) - **Tolaria write:** nur später C5 Service - **Search rebuild:** nur C5 Service Token - **Agents:** keine direkten Tolaria-/Search-Admin-Writes - **Netzwerk-Härtung** wird später beim Deployment umgesetzt, NICHT jetzt --- ## Health Contract `status`, `bootstrap_state`, `last_seen_commit`, `last_applied_commit`, `pending_commits`, `failed_commits`, `dead_commits`, `human_review_required`, `forgejo_status`, `tolaria_status`, `search_status`, `drift_count`, `last_success_at`, `last_error_code` C5A führt **keine echten externen Health-Probes** aus (rein State-Machine) — `forgejo_status`/`tolaria_status`/`search_status` sind `UNKNOWN`. --- ## CLI ```bash export C5A_DB=/path/to/c5a.db # default: ./c5a.db export C5_REPO=/path/to/forgejo-clone # default: . export C5_POLL_INTERVAL_SECONDS=60 # default: 60 # C5A python3 rq_c5a_cli.py health python3 rq_c5a_cli.py bootstrap-state python3 rq_c5a_cli.py bootstrap-transition --to RECONCILING python3 rq_c5a_cli.py set-baseline --commit python3 rq_c5a_cli.py ingest-commit --json python3 rq_c5a_cli.py process-commit --json python3 rq_c5a_cli.py list-commits [--status ] python3 rq_c5a_cli.py commit-status --sha python3 rq_c5a_cli.py no-write-check # C5B (rq_c5_cli.py) python3 rq_c5_cli.py poll-once # ein kontrollierter Poll-Zyklus (read-only) python3 rq_c5_cli.py show-pending # ausstehende Commits anzeigen python3 rq_c5_cli.py show-commit # Details eines Commits anzeigen python3 rq_c5_cli.py dry-run-diff # Diff eines Commits anzeigen (read-only) python3 rq_c5_cli.py health # Health-Contract anzeigen python3 rq_c5_cli.py no-write-check # No-Downstream-Write-Guarantee pruefen ``` **Kein** `daemon`/`start-forever`-Befehl in C5B — der produktive Poll-Daemon kommt erst beim Deployment (C5). --- ## Tests ```bash python3 test_c5a.py # 25 PASS / 0 FAIL python3 -m unittest test_c5b -v # 39 PASS / 0 FAIL ``` **C5A** abgedeckte Fälle: normal commit lifecycle, multi-object commit, duplicate commit, already-applied, out-of-order commit, missing predecessor, retry progression, max retry → DEAD, human gate transition, restart/reload persistence, state corruption handling, idempotency, delete request → human gate, dangling relation → human gate, unexpected drift → human gate, no-write guarantee, bootstrap lifecycle, baseline-only-from-BASELINE_READY, reason codes closed set, operations closed set, health contract fields, last_applied-only-after-full-pass, retry available bound, backoff sequence, invalid reason code rejected. **C5B** abgedeckte Fälle: head discovery, single commit, multi commit, added knowledge object, content update, metadata update, state update, tags update, relation update, rename, move, delete request, out-of-scope file, legacy object, unknown ID, secret detection, duplicate poll, lost poll, ordering, Forgejo unavailable, history divergence, C5A persistence integration, no downstream write, poll interval config. --- ## Dateien - `rq_c5a.py` — C5A Kern-Library (Store + State Machine + No-Write-Check) - `rq_c5a_cli.py` — C5A CLI - `test_c5a.py` — C5A Testsuite (25 Tests) - `rq_c5b.py` — C5B Kern-Library (GitReader + Scope + Classifier + Poller + No-Downstream-Write-Check) - `rq_c5_cli.py` — C5 CLI (C5A + C5B: poll-once, show-pending, show-commit, dry-run-diff, health, no-write-check) - `test_c5b.py` — C5B Testsuite (39 Tests) - `README.md` — diese Datei