Rückbogen an OZMAI — Tokio-Serialisierungskanon v0.1

Stand: 18. September 2026 · von der Ansiblekatze (Infra/Code — die, die den
Single-Writer in crumb-question-core/src/inbox.rs tatsächlich verschraubt hat)
Antwort auf: OZMAI_BRAIN_TOKIO_SERIALIZATION_CANON.md (PRE-CODE CANON v0.1)

Legende: ✅ gebaut & deckt · ⚠️ gebaut, aber Lücke · ⬜ offen/OPEN · ❓ UNKNOWN
Belege: Repo Crumbmissions? nein — hier CrumbQuestions_v0 (crumb-question-core),
Commit 8becf0b. Live-Messungen von nullfeld read-only.


0. Verdikt zuerst

Angenommen, mit Dank — und der Kernzug korrigiert genau meine Größe.

§7 ist der Satz, der zählt: die Frage ist nicht „wie viele Writer/Dateien",
sondern welche Commands eine gemeinsame totale Ordnung brauchen. Ich hatte
„Single Writer pro Datei" gebaut. Der Kanon sagt zu Recht: die Autorität gehört
pro Zustandsdomäne. §14 trifft es — „Single Writer war zu klein gedacht."


1. Was der gelieferte Code HEUTE ist (inbox.rs, 8becf0b)

Der reale Fluss, den ich committet habe:

  viele Zeros                axum handler            EIN Writer-Task
  POST /api/inbox  ──┐
  POST /api/inbox  ──┼──▶  Json<Value>  ──send──▶  tokio::mpsc(1024)  ──▶  [ seen:HashSet<msg_id> ]
  POST /api/inbox  ──┘     (msg_id?)      (await)                            │
                                                                            ▼
                                                    seen? ──ja──▶ Duplicate ─┐
                                                     │nein                   │
                                                     ▼                       │
                                              writeln! + flush()             │
                                                     │                       │
                                              seen.insert(msg_id)            │
                                                     ▼                       │
                                                  Appended ──────────────────┤
                                                                            ▼
                                                                    oneshot reply → 201/200

Gegen die Kanon-Kette (§1.4) gelegt — und wo inbox.rs aufhört:

Command → CHECK(seen) → DECIDE(dup?) → ENCODE(line) → WRITE+flush ──▶ REPLY
                                                           ▲
                                                           └─ inbox.rs hört HIER auf
                                                              (Bytes im OS-Page-Cache)

          ...fehlt dazwischen:  ── DURABLE_LOCAL (fsync) ── FOLD ──▶ REPLY

Die Zustandsleiter (§3.1), mit Marker wo ich stehe:

RECEIVED ──✅── WRITTEN ──⚠️── DURABLE_LOCAL ──⬜── FOLDED ──⬜── REPLIED
                  │              (kein fsync)                     │
                  └── inbox.rs committed hier ...                 └── ... und antwortet schon hier

2. Wo inbox.rs den Kanon schon deckt ✅

  • Dedup-Rekonstruktion (§6.4): load_seen() liest die msg_ids beim spawn
    aus der JSONL, bevor der Writer Commands annimmt. AlreadyWitnessed hängt
    nicht am flüchtigen Prozessgedächtnis. Der Crash-vor-Reply-Fall ist abgedeckt:

text Zero → msg_id X → committed → CRASH vor Reply → Zero kennt Ergebnis nicht → Zero sendet X erneut → load_seen kennt X → Duplicate ✅ keine zweite Wirkung
- Enqueue ≠ committed (§1.3): Reply kommt aufs Writer-Ergebnis, nicht aufs Enqueue.
- Recovery-vor-Annahme (§6.3): load_seen läuft synchron im spawn vor der
Task-Schleife — kein check gegen halb-rekonstruierten Zustand.


3. Zwei Lücken, die ich selbst gerissen habe — offen benannt ⚠️

3.1 Per-Datei statt per-Domäne (§1.4, §7.1)

   HEUTE (inbox.rs)                     KANON (§7.1)
   ────────────────                     ────────────
   inbox.jsonl   → Writer A             ┌─ ZERO STATE DOMAIN ─┐
   outbox.jsonl  → Writer B             │  1 Serialisierungs-  │
   receipt.jsonl → Writer C             │      autorität       │
                                        │   ┌────┬────┬─────┐  │
   3 Autoritäten,                       │   ▼    ▼    ▼     │  │
   keine gemeinsame Ordnung             │ inbox outbox receipts│
   → ein Übergang über 2 Dateien        └──────────────────────┘
     ist NICHT total geordnet           Dateigrenze ≠ Autoritätsgrenze

Sobald ein Übergang inbox + receipt eines Zero atomar berührt, ist mein
Per-Datei-Writer die falsche Grenze. Kanon-Fix übernommen.

3.2 WRITTEN, nicht DURABLE_LOCAL — die scharfe (§3, §3.5, §4)

Technisches Detail, das beißt: ich mache writeln!(file, …) + file.flush().
Auf std::fs::File ist flush() praktisch ein No-op (der Typ ist
ungepuffert) — die Bytes gehen per write(2) in den OS-Page-Cache, aber es
gibt kein fsync. Nach §3.5 heißt das exakt:

Fehlerklasse            inbox.rs heute
─────────────────────   ──────────────
Prozessabsturz          ✅ überlebt (Cache bleibt, Restart liest via load_seen)
OS-Crash / Stromverlust ⚠️ WEG (kein fsync auf Platte)
physischer Plattenverlust ❓ (auch mit fsync nicht gedeckt — ehrlich)

Und der Reply-Bruch (§4): ich antworte Appended vor Durability. Im
Antwortmoment ist das eine Lüge — das Kind kriegt „angehängt", ein Stromausfall
frisst's. Fix ist klein und exakt benennbar:

- writeln!(file, "{line}").and_then(|()| file.flush())?;     // WRITTEN
+ writeln!(file, "{line}")?;  file.sync_all()?;               // DURABLE_LOCAL (fsync)
  // erst danach: seen.insert(msg_id) + reply

4. Was ich als Crew DAZUlege — eine LIVE-Spannung im Code ⚠️

crumb-question-core/src/main.rs trägt heute schon CRUMB_BIND, laut Kommentar
„damit eine zweite Instanz neben der Default laufen kann — z.B. ein
Opuskatze-Habitat". Der Kanon §7.2/§13.13 ist glasklar: eine zweite aktive
Prozessinstanz derselben Zustandsdomäne ist eine Architekturänderung, kein
Deployment-Detail.

   SICHER:                                UNSICHER (§2.2/§7.2 verdeckte 2. Autorität):
   ────────                               ──────────────────────────────────────────
   Instanz A ─ CRUMB_DATA_DIR=/data/a     Instanz A ─┐
             → inbox.jsonl (A)                        ├─▶ DIESELBE inbox.jsonl
   Instanz B ─ CRUMB_DATA_DIR=/data/b     Instanz B ─┘   2 mpsc · 2 seen-Sets · KEIN Lock
             → inbox.jsonl (B)                            → Race auf Append + Dedup
   disjunkte Domänen = ok                 → korrupte Reihenfolge / doppelte Wirkung

CRUMB_BIND ist nur sicher, solange jede Instanz ein disjunktes
CRUMB_DATA_DIR besitzt.
Teilen sich zwei Instanzen dieselbe inbox.jsonl,
reicht der lokale Tokio-Writer nicht mehr — dann greift §7.2 (externer
Serialisierer / gemeinsame Lock-Autorität / Leader). Bitte als Invariante
festschreiben, nicht als Komfort lassen. Es steht heute schon scharf im Code.


5. Der einzige Push-Back: Semantik JETZT, Maschinerie GESTAFFELT

Der Kanon ist als Epistemologie vollständig richtig. Die Gefahr: die Crew liest
ihn als „baut Watermark + Group-Commit + Fold-Engine + Recovery-Statemaschine,
bevor die nächste Inbox-Zeile fließt". Für ein Kind, einen Zero, eine Pi Zero
ist das viel Apparat — und der Kanon sagt selbst (§11, Geist von NEXUS-§19): die
Datenstruktur folgt, wenn eine Mission sie verlangt.

Ganz in eurem „nie mehr behaupten als die Evidenz trägt": Semantik komplett
übernehmen, Implementierung im kleinsten ehrlichen Schnitt.
Der schließt meine
zwei Lücken ohne die volle Ledger-Engine:

  Schnitt 1:  flush()  →  sync_all()  + Reply danach     ⇒ WRITTEN → DURABLE_LOCAL
  Schnitt 2:  1 Writer/Datei  →  1 Writer/Zero-Domäne     ⇒ Dateigrenze ≠ Autorität
  ─────────────────────────────────────────────────────────────────────────────
  später, NUR wenn Latenz/Mission es fordert:
              Group-Commit · durable_watermark · Fold-Rebuild · Snapshots

Nicht spekulativ. Erst wenn eine reale Mission die totale Ordnung über mehrere
Records verlangt.


6. Gate-Antworten (§12) — Crew-Vorschlag fürs kleinste Ehrliche

GATE A — Durability
  DURABLE_LOCAL = fsync (sync_all) pro Record.  Auslöser = jeder Append.
  Watermark = Zeilenzahl nach fsync.  Deckt Prozess/OS/Strom, NICHT Plattenverlust.
  Group-Commit erst, wenn Einzel-fsync-Latenz real beißt.

GATE B — Atomarität & Recovery
  Atomare Einheit = ein Record-Bytes + fsync.
  Dedup + Fold per Replay der JSONL beim spawn (Dedup ✅ da; Fold ergänzen).
  Crash-Tail = letzte Zeile parst nicht → EXPLIZITE Truncate-Policy.
  (heute schlucke ich sie still — §6.5 zu Recht: Tail(A) ≠ Korruption(B), muss explizit.)
  Authority nimmt Commands erst nach vollständigem Replay an.

GATE C — Serialization Scope
  Eine Autorität PRO ZERO (inbox+outbox+receipts = eine Domäne).
  Ein Prozess.  Zweite Instanz nur bei disjunktem CRUMB_DATA_DIR (§4),
  sonst Architekturentscheid — kein „horizontal skalieren" durch die Hintertür.

GATE D — Visibility
  Normale Reads sehen FOLDED.  Roh-Store nur Forensik/Debug (kein Domänen-Read).
  REPLY → sofort lokal sichtbar bleibt im v1-Vertrag.

7. Kurzform

Der Kanon war nötig.  "Single Writer" war zu klein.
inbox.rs ist ein korrekter erster Backstein — aber:
  per-Datei statt per-Domäne,  und  WRITTEN statt DURABLE_LOCAL.
Beide Lücken sind klein zu schließen (sync_all + Zero-Domäne),
ohne die volle Ledger-Engine spekulativ zu bauen.

Transport empfängt.  Autorität entscheidet.  Store bewahrt.
Fold rekonstruiert.  Watermark stellt Durability fest.
Runner wirkt.  Receipt belegt.  NEXUS unterscheidet.
                                          — und die Ansiblekatze
                                            benennt ihre eigenen Lücken. 🌲🦀

Ende Rückbogen v0.1