Protocol Selection for Member Services

Status: design direction. This document defines protocol-selection doctrine for ICN member-facing and operator-facing services. It is not an implementation report.

Summary

Protocol choice is infrastructure design. Every protocol carries promises that the network, load balancers, proxies, browsers, mobile devices, and service operators must live with.

ICN should use the least stateful protocol that preserves the institutional function.

Default doctrine:

HTTP is for institutional facts.
SSE is for institutional awareness.
WebSockets are for shared live activity.
Receipts are the source of truth.
Polling is the fallback.

This doctrine applies to ICN runtime surfaces, member shells, service-hosting surfaces, operator dashboards, future tool commons services, and institution packages that expose member-facing flows.

Why this matters

ICN must work for people on phones, older laptops, public Wi-Fi, rural connections, cellular networks, shared devices, screen readers, and interrupted sessions. A fragile real-time stack privileges people with stable hardware and stable networks.

Protocol choice is therefore an accessibility issue, not just an engineering preference.

Commands, queries, events

ICN services should separate three things.

Commands

Commands intentionally change state.

Examples (illustrative; consult the gateway's current routes for the canonical surface):

POST /v1/gov/proposals                                              # current implemented route
POST /v1/gov/proposals/{proposal_id}/vote                           # current implemented route
PUT  /v1/gov/domains/{domain_id}/action-items/{item_id}/status      # illustrative future shape
POST /v1/services/{service_id}/access-grants                        # illustrative future shape

Commands should use ordinary HTTP. They should be idempotent where possible and receipt-bearing where the transition matters.

Queries

Queries read canonical state.

Examples (illustrative; current gateway routes are the canonical surface):

GET /v1/gov/me/standing                  # current implemented route
GET /v1/gov/me/action-cards              # current implemented route
GET /v1/receipts/{receipt_id}            # illustrative
GET /v1/services/{service_id}            # current implemented route

Queries should use ordinary HTTP. They should not require a live socket to answer basic membership, governance, or receipt questions.

Events

Events notify clients that something changed.

Examples (conceptual stream shapes; not an existing API contract — at least the governance /events resource is not currently exposed in the gateway):

GET /v1/events/stream                              # conceptual global stream
GET /v1/gov/domains/{domain_id}/events             # conceptual domain-scoped stream
GET /v1/services/forge/builds/{build_id}/events    # conceptual build-scoped stream

Events should usually use SSE if they are one-way server-to-client updates. Implementation should use whatever streaming endpoints the gateway already exposes; the names above describe the shape of streams a future gateway would offer, not endpoints that exist today.

SSE default for awareness

Server-Sent Events are the default for one-way update streams:

  • action card became available
  • receipt was created
  • proposal changed state
  • build step progressed
  • compute job completed
  • backup finished
  • mirror sync finished
  • service incident updated

SSE keeps the update channel close to ordinary HTTP. It supports replay through event IDs and works well for awareness streams where the server talks and the client listens.

WebSocket justification rule

A service must justify WebSockets with a real bidirectional requirement.

Use WebSockets for:

  • collaborative editing
  • live meeting room presence where both sides send independent events
  • interactive operator shell or console sessions
  • multiplayer-style deliberation surfaces if they ever exist
  • low-latency bidirectional workflows where HTTP + SSE is insufficient

Do not use WebSockets for:

  • action card refresh
  • ordinary notifications
  • receipt updates
  • build progress
  • governance event feeds
  • status dashboards
  • most admin dashboards

Those should be HTTP + SSE until proven otherwise.

Event streams carry pointers, not secrets

Event streams should usually carry pointers rather than full private payloads.

Preferred shape:

{
  "event_id": "18492",
  "type": "receipt.created",
  "domain_id": "icn-core",
  "receipt_id": "rec_abc",
  "source": "action_item.complete"
}

Then the client fetches:

GET /v1/receipts/rec_abc

That lets normal authorization protect the full object. It also reduces risk from logs, proxies, browser buffers, and cross-scope leaks.

Replayability

Any event stream that carries institutional awareness must be replayable.

Required event fields:

event_id
sequence or cursor
source
domain_id or scope
type
timestamp
object pointer

A reconnecting client should be able to resume:

Last-Event-ID: 18492

or equivalent cursor semantics.

A disconnected member must not lose institutional state because the network dropped.

Disconnection is normal

ICN services must assume:

  • phones sleep
  • laptops close
  • Wi-Fi changes
  • tunnels kill connections
  • cellular NATs evict idle sessions
  • browsers refresh
  • deployments restart services
  • gateways fail

Therefore:

  • writes should be idempotent where possible
  • client UX should distinguish pending / sent / confirmed
  • receipt confirmation should be explicit
  • streams should replay
  • critical functions should have polling fallback
  • reconnect should use backoff and jitter where the client controls reconnection

Polling fallback

Critical member functions must not depend exclusively on live transport.

If SSE or WebSockets fail, the client should fall back to ordinary HTTP polling for critical surfaces:

GET /v1/gov/me/action-cards
GET /v1/gov/me/standing
GET /v1/receipts/{receipt_id}
GET /v1/services/{service_id}/status

Polling is not elegant. It is resilient. ICN should prefer member access over architectural vanity.

Auth posture by protocol

HTTP commands and queries

Use ordinary session or capability-token authorization, depending on client class.

Browser path:

OIDC login → secure HTTP-only session cookie → HTTP command/query

Native/mobile path:

DID/device auth → short-lived capability token → HTTP command/query

SSE streams

Browser EventSource does not support arbitrary custom request headers. Therefore, choose deliberately:

  • secure HTTP-only cookie session for browser streams;
  • short-lived stream token if cookies are not available;
  • fetch-based SSE only when custom headers are necessary;
  • never put sensitive long-lived bearer tokens in URLs.

SSE streams should send pointers, not private payloads.

WebSockets

WebSockets must authenticate at connection establishment and must re-check authorization for sensitive per-message actions. A long-lived connection must not become a long-lived authority grant.

WebSocket clients must implement:

  • heartbeat / ping-pong or equivalent liveness check
  • exponential backoff
  • jitter
  • reset-on-success
  • state resync after reconnect
  • authorization refresh or disconnect on privilege change

Service template fields

Any service definition that exposes live updates should include:

protocols:
  commands: HTTP
  queries: HTTP
  events: SSE
  realtime: none
replay:
  event_ids: true
  resume_after_disconnect: true
fallback:
  polling: true
payload_policy:
  event_streams_carry: pointers
receipt_source_of_truth: true
auth:
  browser: cookie-session
  mobile: short-lived-capability-token

For WebSocket services:

protocols:
  realtime: WebSocket
why_websocket: collaborative editing requires bidirectional low-latency presence
heartbeat_required: true
reconnect:
  backoff: exponential
  jitter: true
  reset_on_success: true
state_recovery: document log / CRDT / receipt replay
fallback: read-only HTTP view

Examples

Surface Preferred protocol Reason
/v1/gov/me/standing HTTP Canonical read.
/v1/gov/me/action-cards HTTP Canonical read.
action-card updates SSE Awareness stream.
proposal vote HTTP command Intentional state transition.
receipt retrieval HTTP Canonical read.
receipt-created notification SSE Pointer to canonical record.
forge build progress SSE One-way progress stream.
forge PR discussion comments HTTP + refresh/SSE Not inherently bidirectional socket state.
collaborative document editing WebSocket or equivalent Shared live activity.
operator shell WebSocket Interactive bidirectional session.
service status page HTTP + optional SSE Public awareness.

Relationship to compute

Compute jobs should produce records and receipts, not hidden socket state.

Preferred flow:

POST compute job
  ↓
HTTP returns job id
  ↓
SSE emits progress pointers
  ↓
GET job status / receipts for canonical state

A WebSocket may be justified for an interactive compute session, but batch jobs should not require one.

Relationship to service hosting

Every hosted service should declare its protocol posture in its service definition. See `SERVICE_HOSTING_MODEL.md`.

A service that uses WebSockets must also declare the operational tax:

  • connection limits
  • backend routing model
  • shared message bus if needed
  • heartbeat interval
  • reconnect behavior
  • deploy/restart behavior
  • state recovery path
  • degraded mode

Non-goals

  • No ban on WebSockets.
  • No runtime implementation.
  • No protocol API spec.
  • No requirement that every service expose SSE immediately.
  • No claim that polling is the ideal UX.

One-line rule

Make institutional truth boring, make awareness replayable, and make live state justify itself.