> For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt.

# Realtime browser messages

Centrifugo with Protobuf is the browser update transport. Contexts publish typed
messages to an explicit domain or security audience. The API calculates every
channel from the authenticated principal's current authority. Browsers neither
name nor select channels.

Run applications have a separate
[Project-private serving gateway](/reference/docs/decisions/0069-project-private-run-services.md).
It forwards application HTTP, SSE and WebSocket traffic with continuously checked
member authority. Platform projections and notifications use the rules in this document.

## Audiences and permissions

`DeliveryTarget` permits one audience:

| Target            | Purpose                                                          | Channel derived by the gateway         |
| ----------------- | ---------------------------------------------------------------- | -------------------------------------- |
| Project ID        | State shared with connections currently authorised for a Project | `pharos:project:<canonical-id>`        |
| Party ID          | State shared with a Party                                        | `pharos:party:<canonical-id>`          |
| Principal subject | Private asynchronous outcome for one authenticated subject       | `pharos:principal:<base64url-subject>` |

Project and Party are domain audiences. Principal is the authenticated security
subject used for private messages, including recorded command rejections and a
per-principal projection. A Run is Project-owned, so Run state uses the Project
target. Its payload remains a typed `execution_core/run` message with the Run ID
and revision. SSH Access writes one principal-targeted publication for every actor
that acts as the member.

The connect proxy authenticates the session and asks an application outbound port
for the principal's current authorised realtime audiences. The API adaptor composes
published owner queries from Party Governance and Project: Party Governance maps
the security principal to the Party authority it may exercise, and Project decides
which Projects that Party may currently access. The API maps the returned canonical
IDs to server-side Centrifugo subscriptions. It does not reproduce either
context's access rules.

The principal target is included for ordinary authorised connections. A limited
API-owned session awaiting AUP receives only its own Party readiness channel.
Invalid or unavailable session/Party authority fails closed. The connection carries a server-only audience fingerprint. Refresh asks
the owners again and expires the connection when that fingerprint changes; the
client reconnects to receive the new subscription set. Limited sessions use the
canonical Party already revalidated by authentication. The registered Party
payload is currently readiness only; adding other Party projections requires
reviewing limited-session delivery authority. Once AUP is current, `party.Audiences`
asks `GetCurrentParty` and traverses `ListPartyProjectAudiences`, whose Project
owner checks `runs.view` for the exact API Party actor and authentication
generation. A page may contain no allowed Projects and still supply a continuation
cursor. Refresh retains its one-minute maximum and is capped by the earliest
absolute `valid_until` returned by Beto. Expired, malformed or incomplete authority
closes the connection. Development operator/CLI sessions without canonical Party
authority retain only their principal channel.

The connect grant starts before owner reads to preserve the logout fence. If those
reads exhaust its lifetime, the API refuses the attempt with retryable disconnect
code `4001`; a fresh attempt checks authority again. Invalid sessions and unresolved
audiences retain terminal code `4501`. Expired grants supply no subscriptions.

The <a href="/source/docs/qualification/dashboard-navigation-990.md.txt">dashboard navigation qualification</a>
records the regression and live authentication results.

## Publishing

The aggregate transaction records its current state, receipt, outgoing message
bytes and dispatch intent atomically. Each message supplies a `RealtimeTarget` and
a context-owned typed snapshot. The relay sends only the encoded publication and
its idempotency key:

```json
{"b64data":"<protobuf bytes>","idempotency_key":"<event id>"}
```

The restricted gateway authenticates the publisher, verifies the envelope owner
and permitted target kinds, derives the channel and forwards the original bytes to
Centrifugo. Publisher code has no channel parameter. Execution Core may publish
Project-targeted Run messages. Support publishes principal-targeted Ticket and
TicketMessage messages, each to its owner. Party Governance publishes
`PartyProfileProjection` to the owning principal, including private profiles.
Profile data never uses the Party readiness channel available to limited sessions.
The API records technical session-readiness notifications atomically in its owned
Valkey state; its session credential can publish only the typed Party-targeted
notification described in the [authentication guide](/reference/services/api/adaptors/authentication/README.md).
Beto and Themelio expose the same typed
`RealtimeMessage`/`RealtimeTarget` application API; Support and Party Governance
explicitly enable their context-owned browser delivery in composition.

Metallo constructs Run changes with `application.realtime.run_change`, which
selects `RealtimeTarget.project(run.project_id)`. Beto publishers use
`application.ProjectTarget`, `PartyTarget` or `PrincipalTarget`. Themelio uses the
corresponding `RealtimeTarget` class methods. Rejection messages require a
principal target in both technical foundations.

SSH Access records its typed projection with every committed root revision,
including Site results and propagation completion. Its actor mapping creates one
principal-targeted envelope per actor; a member with no actor receives no
publication. The `ssh_access` publisher credential is accepted only for principal
targets.

## Frontend API

The new web application owns the connection in `RealtimeProvider`. Feature code
observes connection state and typed messages through hooks:

```tsx
const connection = useRealtimeConnection();

useRealtimeMessage(realtimeMessageTypes.runChanged, message => {
  applyIfNewer(message.run.runId, message.revision, message.run);
});
```

`useRealtimeMessage` registers a local typed handler. It does not subscribe to a
Centrifugo channel. `messages.ts` decodes the common envelope and its registered
payload, checks the target against payload identity and rejects unknown contracts.
The checked-in protobufjs schema is generated from the Protobuf sources by
`pnpm generate:contracts`.

The retained web keeps its Run connection in the authenticated `RunNoticeProvider`.
It observes every server-authorised Project while the member navigates between
pages. Resource managers select their Project's refresh counter; consent notices
remain one per Run for its initiating member, including on the dashboard. Replacing
the canonical member closes the old connection and clears its notices.

An authorised current-state read remains the initial source of truth. A feature
applies only newer revisions and tolerates duplicate delivery. Recoverable
Centrifugo history resumes by epoch and offset. An unrecoverable gap requires one
fresh owner query before later messages are applied.

The SSH Access view registers a recovery callback with `RealtimeProvider`. The
principal channel's first subscription reads `GET /api/v1/ssh-access/sites/{siteId}/projection`;
recovered history needs no second read, and loss of history triggers one
reconciliation. A view mounted after the principal subscription is ready receives
its initial read immediately. Views mounted during an outage read once on the
next subscription, including when existing views resume from recovered history.

Browser HTTP reads use the generated OpenAPI client. The initial Project directory
is loaded once after session establishment; it is not a polling source or a
substitute for an owner-authorised realtime audience.

## Security rules

- Client publish, client-selected subscriptions, polling, SSE and raw WebSocket
  business transports are prohibited. The one exception is the Run log panel, which
  polls its logs route every 5 seconds for now (<a href="/source/services/metallo/docs/epics/instantiate-and-run/run-logs.md.txt">decision</a>).
- Publisher credentials are context-scoped and never reach the browser.
- The gateway forwards exact persisted Protobuf bytes; it does not reconstruct a
  later snapshot.
- Realtime delivery carries committed state or an outcome. It grants no business
  authority.
- Session revocation disconnects every connection for that principal. Connect
  authority is short-lived and refreshed through the authenticated API proxy.

The browser edge serves `/auth`, `/api/v1` and `/connection/websocket` at
`https://platform.localtest.me`. Internal proxy, diagnostics and Centrifugo
administration endpoints remain private.

<details class="page-source"><summary>Page source</summary>

<a href="/source/CLIENT-SUBSCRIPTIONS.md.txt">Source snapshot</a> · Revision `f928834c8`.

</details>
