#+title: 0.1.0 v0 Bridge Contract #+created: [2026-04-14 Tue 14:28] * summary v0 is a tiny Python relay bridging one XMPP MUC room to one Zulip stream/topic. The bridge is append-only. It relays new human-written messages in both directions. It does not attempt to sync edits, reactions, threads beyond a fixed Zulip topic, presence, history, or rich metadata. The design goal is to make transport behavior obvious. If something breaks, it should be clear whether the problem is Zulip ingress, XMPP ingress, relay logic, or configuration. * first-run assumptions - single bridge process - single XMPP MUC room - single Zulip stream + fixed topic - trusted private deployment - Python implementation - one bridge identity on each side - human-readable sender attribution is sufficient for v0 * system actors ** Zulip bot/client - connects to one Zulip server - subscribes to events for one stream/topic target - receives new Zulip messages - sends relay messages into the configured stream/topic ** XMPP client - connects as one XMPP account - joins one MUC room with one nick - receives new groupchat messages - sends relay messages into that room ** relay core - owns config, logging, message normalization, and loop prevention - accepts inbound events from either side - converts them into one minimal internal message shape - republishes them to the opposite side * message flow ** Zulip -> XMPP 1. a new Zulip message arrives in the configured stream/topic 2. Zulip bot/client emits an inbound event to the relay core 3. relay core validates scope - correct stream - correct fixed topic - not authored by the bridge itself 4. relay core normalizes sender name + body 5. relay core formats a plain relay message 6. XMPP client posts that message into the configured MUC room 7. relay core logs success/failure ** XMPP -> Zulip 1. a new XMPP groupchat message arrives in the configured MUC room 2. XMPP client emits an inbound event to the relay core 3. relay core validates scope - correct room - not authored by the bridge nick/account - ignore non-message noise 4. relay core normalizes sender name + body 5. relay core formats a plain relay message 6. Zulip bot/client posts that message into the configured stream/topic 7. relay core logs success/failure * v0 relay format Plain text only. Recommended shape: - Zulip -> XMPP: "[zulip] : " - XMPP -> Zulip: "[xmpp] : " This is intentionally crude. The point is attribution and debuggability, not elegance. * non-goals - no message edit sync - no delete sync - no emoji/reaction sync - no attachment mirroring in v0 - no presence mirroring - no roster/contact sync - no multi-room routing - no per-thread/topic mapping beyond one fixed Zulip topic - no history backfill - no Hermes behavior in the transport process - no Retcon writeback in the transport process * operational boundaries - the bridge should ignore service noise such as joins, parts, presence, and subscription events - the bridge should only handle newly observed messages after startup - the bridge should fail loudly in logs and stay simple in behavior - the bridge should not try to be clever about replay, dedupe, or recovery yet beyond basic self-loop avoidance * why this scope This gives the smallest useful system: - phone-friendly Zulip UI - XMPP room remains the low-friction bus - one boring relay to test whether the transport idea is worth keeping If this small version is unreliable, adding Hermes or Retcon would only hide the real problems. * exit condition for 0.1.0 Task 0.1.0 is complete when: - this note exists - the actor model is frozen - both message directions are described - v0 non-goals are explicit - first-run assumptions are frozen