Skip to content

0012 — Bridge Decomposition and Surface Budget

Status: Proposed

Recovery note: accepted ADR 0016 replaces this proposal's process-local ordering premise with a fenced Postgres ledger. The single ready intake replica and surface budget remain unchanged.

Context

The bridge (ADR 0005) is deliberately small, and its replicas: 1 + non-overlapping rollout posture is an enforced intake invariant (D3, docs/bridge.md §5). ADR 0016 subsequently moved per-room ordering and work recovery from one process into Postgres without making intake highly available. The 2026-07-12 roadmap wave concentrates 29 open issues on this one component, proposing accretions of very different kinds: Matrix-plane rendering (threads, polls, media, profile fields, task state events), outbound drivers (extension negotiation, sandbox lifecycle, room context digests), and — critically — new inbound network surfaces (an MCP server for rooms, an A2A push-notification webhook receiver, a read-only admin page). Each issue hedges "or a sibling app — justify" individually; nobody owns the aggregate. Without a rule, the bridge drifts into a monolith whose every new inbound surface inherits deploy-time downtime from the single-replica invariant, and whose blast radius grows with each feature.

Decision

  1. The bridge binary's scope is the Matrix appservice delegation plane: mention → policy → A2A → reply, plus Matrix-plane rendering of delegation state (edits, threads, polls, pinning, media round-trip, profile fields, task state events, room context digests) and outbound drivers it alone can key (per-room activity → sandbox pause/resume, extension negotiation, delayed-event heartbeats). Issues #114–#120, #173, the state-publisher half of #121, and #150's driver stay in-bridge.
  2. Surface budget rule: any new inbound network surface beyond the appservice transaction endpoint, the metrics port, and cluster-internal webhook callbacks — and any component with an independent availability or scaling profile — ships as a sibling self-contained app under apps/ (own Go module, chart, Flux unit, NetworkPolicy), never inside the bridge binary. Concretely: the Matrix MCP server (#131), any remote-facing push receiver (#124's remote half), and a standalone operator console (#140, if built beyond read-only-in-bridge) are sibling apps.
  3. A2A push notifications (#124) are cluster-internal only in v1, served by the bridge's existing HTTP server as a wake-up signal; exposing a remote-facing receiver requires a sibling app and an amendment to the "sole public A2A exception" principle in .agents/AGENTS.md and docs/federation.md.
  4. The single-ready-intake invariant is not weakened by any accretion; Postgres owns durable per-room ordering and recovery under ADR 0016. Sibling apps must tolerate the bridge's non-overlapping rollout window without data loss (state lives in Postgres or Matrix room state, never in cross-app memory).
  5. Not done: no message-bus or microservice split of the core delegation path, and no bridge HA work — both remain out of scope until measured need (Principle 10), revisited only by a future ADR.

Consequences

  1. The bridge stays reviewable and its threat surface bounded: inbound surfaces are enumerable per app, each behind its own NetworkPolicy and Flux unit.
  2. More Flux units and charts to maintain — the accepted cost; the repo's self-contained-app convention (bridge, synapse-federation-policy) already amortizes it.
  3. Issues #124, #131, and #140 are re-scoped to comply (cluster-internal v1, sibling app, and inventory-first respectively); future issues cite this ADR instead of re-arguing placement.
  4. Escape hatch: if sibling apps proliferate past roughly four, or the non-overlapping rollout window becomes a measured availability problem, a new ADR revisits decomposition and the single-replica invariant together.