> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Transcript-verified video-only gate

> How Orbit gates video publishing in audio-only rooms — the ceiling-widening rule, the transcript-verified predicate, and why a verified gate can restore camera and screen-share but never convert a subscriber into a publisher.

# Transcript-verified video-only gate

An audio-only room tells every participant the same story at join time: you
may talk, and everyone can hear you, but your camera and screen-share stay off
until the moderator has proof you are a genuine speaker. The gate that
enforces this is the **transcript-verified video-only gate** — a server-side
grant transform that sits between the audio-only room policy and the LiveKit
token mint. This page is the integrator's map of it: why the ceiling exists,
what the widen predicate actually checks, where in the request path it runs,
and the two hard negatives it is designed never to allow.

The channel-level overview is [Video: rooms, embeds, recording, and
broadcast](/channels/video); this page is the grant-semantics contract
behind the `audio_only` flag those surfaces configure.

## 1. Participant-grant semantics: a ceiling, never a ladder

Orbit's video rooms mint an **effective grant** per participant — the join
response's `permissions.can_publish` plus a per-source publication allow-list
(`canPublishSources`) baked into the LiveKit JWT. In a full-video room, a
publishing tier (`panelist`, `host`) gets camera, screen-share, and
microphone sources. A subscribe-only tier (`viewer`,
`hidden_supervisor`, a lobby hold) gets no publish capability at all.

The audio-only policy applies a **ceiling** on top of that: every publisher's
allow-list is clamped down to the audio sources (`microphone`) at the token
mint. The ceiling is authoritative server-side — even a client that ignores
the `audio_only` room-metadata hint cannot publish camera or screen-share
video, because the minted JWT itself carries the clamped source list.

Two consequences follow from the ceiling model:

* **Audio-only is not widenable in principle.** The policy bans camera and
  screen-share publishing from the whole room; your client cannot talk its
  way back into video by sending anything extra on join. The only way video
  returns is the gate's own widen path, below.
* **The ceiling can never create a publisher.** A `viewer` or
  `hidden_supervisor` grant has `can_publish: false`. Neither the
  audio-only clamp nor the gate ever flips that to `true` — they only edit
  `canPublishSources` for a participant who was already a publisher. A
  widening operation on a non-publishing tier is a no-op by construction,
  the same rule the route-layer per-source override runs: an allow-list
  narrows a non-publisher, it never widens one.

## 2. The helper contract: clamp, verify predicate, widen

The gate is a pure transform on the grant — no I/O, the same shape in mirror
as in mint, so the decision is deterministic and auditable. Read as three
separate clauses, they describe the whole lifecycle:

* **Clamp (before verification)** — the grant comes back with the
  publication allow-list intersected to the un-widened audio tier. Camera,
  screen-share, and screen-share-audio are all dropped; whoever held only
  `microphone` keeps it. If the participant was never a publisher, the
  grant comes back unchanged — the clamp is a ceiling, not a ladder.
* **Verify predicate** — a pure, fail-closed decision on the signed-speaker
  audio cluster's end-to-end latency against the caller's tolerance
  window. When no tolerance is declared the widener is always admitted;
  when the latency cannot be read or the value is malformed it is always
  denied. The gate widens only when this predicate comes back true.
* **Widen (after verification)** — exactly `camera` and `screen_share` are
  unioned onto whatever sources the publisher already holds. The audio-leg
  source is never added, non-video sources already present are preserved,
  and no subscriber is ever promoted into a publisher. The two video
  sources are the only thing the gate can ever widen; nothing else moves.

The widen is deliberately video-exclusive. Because the audio-only ceiling
already grants `microphone`, the gate's own source vocabulary excludes it —
there is nothing to add there — and excludes the screen-share audio leg for
the same reason the audio-only ceiling does: letting it in would re-open a
policy the room explicitly banned.

## 3. Where the clamp applies upstream: the route layer, not a toggle

The route layer applies the gate **before the access token is minted** — at
grant materialization, not at media runtime. The flow on
`POST /video/rooms-scheduled/:id/join` looks like this:

1. The base tier's grant is resolved from the requested `participant_tier`
   (after host downgrade or lobby hold).
2. The audio-only room policy clamps the publisher's source list down to
   the audio ceiling.
3. The transcript-verified gate re-clamps to the un-widened tier or unions
   the two video sources depending on the verify predicate.
4. The resulting grant is minted into the LiveKit JWT — the SDK client
   reads the same clamped or widened source list from the token.

This is **handler behavior, not an operator toggle**. There is no switch on
the room or the tenant that turns the gate off independently of
`audio_only`; the gate only ever runs inside the audio-only path, and the
moderator's widen decision is a per-participant call made on the room's
transcript-cluster signal, not a configuration knob. An integrator never
"enables the gate" — they enable `audio_only` on the room, and the gate is
the only mechanism by which video comes back afterward.

## 4. Why widen can never promote to publish

The same non-widening invariant that the route layer's per-source override
enforces holds here: an allow-list can narrow a grant, never widen a
subscribe-only one. Concretely:

* A `viewer` join with a `canPublishSources` override is returned unchanged
  — the override cannot convert them into a publisher.
* A `viewer` join verified by the transcript gate is likewise returned
  unchanged — the gate cannot convert them into a publisher either.
* The gate only ever moves the two video sources in or out of an
  **already-publishing** participant's `canPublishSources`. It is
  structurally incapable of flipping `can_publish` from `false` to `true`.

For the integrator designing a room, this means the gate's widen verb is
safe to call on the whole room's joined set: nobody not already allowed to
publish can gain publish from it. The full participant-tier matrix itself
lives in [The video room model](/concepts/video-room-model).

## 5. Two negative-test examples

The gate's two hard negatives are what it is designed to guarantee:

**Audio-only with no transcript — nothing widens.**

```json theme={null}
{
  "participant_tier": "panelist",
  "permissions": {
    "can_publish": true,
    "can_publish_sources": ["microphone"]
  }
}
```

A `panelist` joins an `audio_only: true` room before any signed-speech
cluster converges. The gate's clamp leaves them on the audio ceiling:
`can_publish` stays `true`, and the publication sources stay exactly
`["microphone"]`. Camera, screen-share, and screen-share-audio never appear.
They stay audio-only until verification.

**Audio-only with a verified transcript — video, not publish.**

```json theme={null}
{
  "participant_tier": "panelist",
  "permissions": {
    "can_publish": true,
    "can_publish_sources": ["camera", "microphone", "screen_share"]
  }
}
```

The same `panelist` once the signed-speech cluster verifies them. The gate
unions `camera` and `screen_share` onto the audio sources — the
pre-verification microphone source is preserved — but `can_publish` was
already `true`; the gate added video sources, not publish capability. A
subscribe-only tier (`viewer`, `hidden_supervisor`, a lobby hold) run
through the same verified gate gets back its original grant unchanged: it
upgrades **to video-only, never to publish.**

## Related

<CardGroup cols={2}>
  <Card title="Video: rooms, embeds, recording, and broadcast" href="/channels/video">
    The channel-level page — room kinds, join tokens, the embed, recording,
    moderation, and broadcast.
  </Card>

  <Card title="Video meetings and conferences" href="/guides/video-meetings">
    The ordered walkthrough — create a scheduled room, invite guests, run the
    meeting end to end.
  </Card>

  <Card title="The video room model" href="/concepts/video-room-model">
    Scheduled vs ad-hoc rooms, the created-to-closed lifecycle, and join-token
    semantics this gate composes over.
  </Card>

  <Card title="Video room access tokens" href="/guides/video-room-access-tokens">
    Full token scope — `participant_tier`, effective-grant read-back, and
    expiry semantics.
  </Card>
</CardGroup>
