Crumbforest Outbox & Inbox — Format v0 (SOT)

Jeder Krümel hat eine Inbox (hoch, gehasht, bewusst antwortlos) und eine
Outbox (runter, read-only). Der Zero greift ins Nullfeld hinein — das
Nullfeld greift nie zum Zero. Das Ergebnis kommt am LED-Streifen an,
nicht am Bildschirm. Kein Kind muss Tastatur oder Screen anfassen, um sich
zu freuen.

Status: DESIGN / v0. Legt nur das Format fest. Kein Code, keine Wirkung.
Konvergiert mit dem Receipt-Idiom (crumb-missions-core/src/receipt.rs):
append-only JSONL, echtes sha256, Einmal-Hash, bezeugen nicht speichern,
Identität declared/derived — nie über Netz geprobt. „jeder krümel zählt."


0. Topologie (festgelegt)

Ort Rolle
OZM — Container am Museumsparkplatz erster Crumbforest, läuft #lokal
Strato — Crumbforest Root Server Range, in der die Kids sitzen (lokaler Hotspot oder VPN)
OZMAI baut es anders herum — für den Raum und die lokale Verteilung

Beide entwickeln dasselbe, nur anders. Diese Datei beschreibt die Crumbforest-Seite:
die per-Krümel Inbox/Outbox. OZMAIs raumseitige Verteilung ist ein eigener Pfad.


1. Zwei Ebenen, die sich nie berühren

MQTT-Ebene (Raum) Inbox/Outbox-Ebene (Kind)
Hinter dem Signal steht kein Kind ein Kind
Transport MQTT publish/subscribe, per Raum Pull/Push über Rustcore, Zero = Client
Muster Broadcast, lokal verarbeitet Einbahn, adressiert an eine NetBox-ID

Diese Datei beschreibt nur die Kind-Ebene. Umgebungssensorik bleibt MQTT.


2. Die drei Richtungen des Krümels

   Krümel-Zero                         Nullfeld / Strato-Range
   ┌───────────────────┐               ┌──────────────────────────┐
 ① │ Mission fertig    │──push (hoch)─▶│ inbox/<netbox_id>.jsonl   │
   │ JSON, gehasht     │  bewusst      │ (gehashte Antworten,      │
   │ "keine Antwort"   │  antwortlos   │  Weiterverarbeitung)      │
   ├───────────────────┤               ├──────────────────────────┤
 ② │ cron: poll online │◀──GET (runter)│ outbox/<netbox_id>.jsonl  │
   │ msg_id neu? → LED  │  read-only    │ (Ergebnis, zertifiziert)  │
   ├───────────────────┤               └──────────────────────────┘
 ③ │ crumbtype Echtzeit │  rein lokal, kein Round-Trip
   │ Tastendruck → LED  │  (Abschnitt 6)
   └───────────────────┘
  • ① Inbox (hoch): Krümel macht z. B. bashpanda-Gürtelprüfung, sendet die
    Antwort als JSON. Bewusst kommt jetzt keine Antwort — fire-and-forget.
    Alle Missions-Antworten landen gehasht in der Inbox zur Weiterverarbeitung.
    Der Zero schreibt hoch; das Nullfeld liest den Zero nicht — es nimmt nur an.
  • ② Outbox (runter): Später/morgen liegt das Ergebnis bereit. cron-Poll (nur
    online), neue msg_id → LED. „Wenn morgen dein Streifen grün leuchtet, hast
    du bestanden, kleiner Freund." Kind freut sich ohne Screen, ohne Tastatur.
  • ③ crumbtype (lokal, Echtzeit): kein Nullfeld, kein Round-Trip. Siehe §6.

Was fließt hoch: nur die Missions-Antwort, gehasht — Inhalt aus der
Mission, adressiert an die eigene NetBox-ID. Kein Klarname, kein Kind-Attribut.
Was fließt runter: nur ein an eine NetBox-ID adressiertes, zertifiziertes
Ergebnis-JSON.


3. Adressierung — „nur für den einen Krümel"

Identität kommt aus der NetBox, nicht aus Chat/Klartextname. Ein Krümel-Raspi
trägt eine pseudonyme NetBox-Device-Identität; ihr Handle ist das to/from.

  • Eine Datei pro Krümel je Richtung: inbox/<id>.jsonl, outbox/<id>.jsonl.
  • Keine Klarnamen, keine Kind-Attribute im JSON. Der Handle ist die
    ganze Identität (crumbmatrix: Zugehörigkeit ohne Identifizierung).

4. Die Nachrichten — je eine JSONL-Zeile

4a. Inbox (hoch) — Missions-Antwort, gehasht

{
  "v": 0,
  "ts": 1758132000,
  "from": "krumel-7f3a",
  "mission": "bashpanda/guertelpruefung",
  "msg_id": "a1b2c3…64hex",
  "answer_hash": "e3b0c4…64hex",
  "answers": [{"q": 1, "hash": "…"}, {"q": 2, "hash": "…"}],
  "witness": {"netbox_check": "mac", "nic_mac_hash": "…64hex", "class": "device-registered"},
  "sig": "sha256:9f8e…64hex"
}

answer_hash bezeugt die Antwort per Hash (bezeugen statt speichern). answers
optional je Frage, für die granulare LED-Auswertung (§5). Bewusst keine
synchrone Antwort
— der Push ist vollständig, sobald die Zeile liegt.

witness — die Evidenzklasse steht IM Event (die Grenze in der Architektur):
Der Zero legt offen, wie stark seine Identität belegt ist, statt sie zu
behaupten. Die MAC geht nur gehasht raus (roher MAC bleibt off-wire), ist
Teil des signierten sig-Kerns (Klasse nicht fälschbar ohne Bruch).

class Bedeutung Grenze (ehrlich)
device-registered MAC lesbar → gegen NetBox der Range kreuzprüfbar MAC ist software-setzbar, keine Hardware-Attestierung
declared keine MAC lesbar, nur from-Handle reine Behauptung, keine Gerätebindung
hw-attested (Zukunft) Secure Element / TPM / DICE erst wenn real gebaut — bis dahin nie setzen

Kreuzprüfung grader-seitig: sha256 der in NetBox hinterlegten MAC für den
Handle vergleichen. Gotcha: MAC liegt in NetBox 4.x nur am
dcim/mac-addresses-Endpoint (nicht am Interface-Objekt). „device-registered"
sagt „diese NIC kennt die Range als diesen einen" — nicht „ein Kind hat
gedrückt" (Geräte-Identität ≠ Handlungs-Witness, NEXUS §IV).

4b. Outbox (runter) — Ergebnis

{
  "v": 0,
  "ts": 1758218400,
  "from": "capacitoby",
  "to": "krumel-7f3a",
  "msg_id": "d4e5f6…64hex",
  "kind": "result",
  "ref": "a1b2c3…64hex",
  "score": {"total": 10, "passed": 9, "per_q": [1,1,1,1,1,0,1,1,1,1]},
  "body": "Fast alles grün! Frage 6 nochmal, dann sitzt der Gürtel.",
  "body_hash": "…",
  "sig": "sha256:…"
}
Feld Bedeutung
kind result | unlock | badge | letter — steuert LED/Ton-Geste
ref msg_id der Inbox-Zeile, auf die geantwortet wird (Genealogie)
score.per_q Pro Frage 1=grün/0=rot → mappt direkt auf die LED-Kette (§5)
body lesbarer Inhalt für das Kind, nie über das Kind

5. Die LED als Ergebnis-Oberfläche (kein Bildschirm)

Das Ergebnis wird gesehen, nicht gelesen. Zwei Auflösungen:

  • Binär: morgen grün = bestanden. Ein Puls, eine Farbe.
  • Granular: 10 LEDs = 10 Fragen. Alle grün = 100 %, eine rot = 90 %.
    score.per_q bildet 1:1 auf den Streifen ab.

Farb-/Ton-Vokabular kommt aus crew_signals.json (CapaciToby: 🐿️ A3/220 Hz,
Bernstein). kind und score wählen die Geste. Read-Once wie in §7: die LED
feuert genau einmal pro msg_id.


6. crumbtype — Echtzeit, rein lokal (Ebene ③)

Getrennte Ebene, kein Inbox/Outbox-Round-Trip. Kinder schauen beim Tippen
auf die Hände, nicht auf den Bildschirm. Liegt der LED-Streifen vor der
Tastatur:

  • Tastendruck falsch → LED wechselt sofort die Farbe (rot). Richtig → ruhig/grün.
  • Tippfehler wird in Echtzeit am Ort des Blicks sichtbar, ohne Screen.

Das läuft komplett auf dem Zero (bzw. der lokalen crumbtype-App), ohne Netz,
ohne Feststellung im Nullfeld. Dieselbe LED-Hardware, andere Quelle.


7. Einmal-Hash & Read-Once

msg_id ist zur Nachricht, was exec_id zum Receipt ist: verhindert zweite
Wirkung. Der Zero führt lokal seen.jsonl (nur msg_id, append-only). Neue
msg_id → prüfen → LED → in seen.jsonl schreiben. Bekannte msg_id → still.
(Client-Spiegel von receipt::already_witnessed.)


8. Pull-/Push-Kante

Invariant, egal welcher Transport (Filesystem → MariaDB/DB):

  1. Outbox read-only & Client-initiiert. Nullfeld antwortet nur, ruft nie.
  2. Inbox append-only. Nullfeld nimmt an, liest den Zero nicht aktiv.
  3. Empfänger-gescopet. Ein Zero berührt nur */<eigene-id>.jsonl.

Oberfläche: Rustcore-Endpoint (empfohlen, wg0-gescopet, NetBox-Token-Gate) —
Zero ist Client. MQTT scheidet für diese Ebene aus (kein Broadcast fürs Kind).
Später MariaDB inbox/outbox(id, msg_id, ts, kind, hash, sig) über denselben
Endpoint — das Format ändert sich nicht.


9. Warum das ein NEXUS-Knoten ist (Seam zu OZMAI)

Crumbforest NEXUS-Satz
msg_id Einmal-Hash §7 stabile Identität, rückwirkend unverändert
Inbox/Outbox/seen append-only §2/§8 ergänzen statt umschreiben
answer_hash/body_hash/sig §12 Evidenz, §13 austauschbarer Nachweis (v0 sha256 → v1 echte Signatur)
to/from = NetBox-Handle §11 Berechtigungswurzel endet sichtbar in der NetBox
ref (Outbox→Inbox) §9 Genealogie: „beruht auf", nicht physische Kausalität

Die zwei Sicherheits-Achsen (§12-Trennung):
- „kein Kind baut eine Armbrust" = Berechtigung/Wirkung — der Raum begrenzt,
was buildbar ist (Vor-Gate, No-Double-Effect vor dem Routing).
- „der Agent lügt & verändert nix" (#hash #vertrag) = Evidenz/Verwahrung —
append-only, bezeugen, nie überschreiben (Nach-Gate, Receipt nach Wirkung).

Föderation (§17): jede IP-Range im RZ ist ein lokaler NEXUS. nullfeld und
backbone „dürfen mitspielen" = tauschen Belege, nicht Urteile. NetBox ist
der lokale Genesis-Anker pro Range, kein globaler Root (§19).


10. Was v0 bewusst NICHT festlegt

  • Echte Signatur statt sha256-Siegel (sig v1).
  • MariaDB-Schema im Detail (erst wenn die Datei zu klein wird).
  • Weiterverarbeitungs-Logik der gehashten Inbox (Grading-Pipeline).
  • LED-/Ton-Gesten je kind/score im Detail (mappt auf crew_signals.json).
  • crumbtype-Tastatur-Mapping (Ebene ③, eigener Schnitt).