Skip to main content
Tuturuuu Meet calls are Google-Meet-style conferences carried by the Cloudflare Realtime SFU. This is a different product from Cloudflare Stream Live, which powers one-to-many broadcast. Meet opens Personal from the app launcher; use the workspace picker or direct workspace links for team meetings. Every participant holds exactly two peer connections to the SFU — one publishing, one subscribing — regardless of room size. Connection count stays fixed; bandwidth and decoding work still grow with subscribed media tracks.

Preview resource budgets

Hosting is available to all verified Plus, Pro, and Enterprise accounts, as well as verified @tuturuuu.com accounts. Eligibility uses the current confirmed authentication email and the host’s personal subscription; session claims cannot override it. Other accounts can join by invitation. Workspace membership and Calendar permissions still apply when creating or scheduling a meeting. A room lasts up to two hours for eligible Free hosts, ten hours for Plus, and twenty-four hours for Pro/Enterprise. The server resolves the host’s personal subscription in both Meet and the workspace token endpoint. The deadline starts with the first admitted participant; reconnects keep it. Signed room limits default to eight publishers and 96 viewers and may be lower. Server hard ceilings still apply to every role and tier. Each publisher can register at most six media tracks. The server accrues participant-milliseconds through the room deadline. Expiration ends the room, disconnects clients and persists outstanding SFU closures before provider cleanup. Failed cleanup retries with exponential backoff from ten seconds to one hour. Progress is persisted after each completed provider session so a later failure does not repeat already-confirmed work. Unconfirmed tracks remain recorded for retry/operator reconciliation. The UI shows the room deadline and explains resource-limit failures. Browser bandwidth counters are diagnostic, not billing evidence. Monthly workspace media aggregation and provider invoice reconciliation remain prerequisites for selling meeting overages. Internal or sponsored workspaces do not bypass these ceilings.

Empty rooms and owner restoration

Meet and Parley calls end automatically after five consecutive minutes with no admitted participant connected to the room. The durable timer starts at the last disconnect, including recoverable transport loss; the shorter media-presence recovery grace does not extend it. An admitted reconnect cancels the deadline. Quiet or muted participants with an open connection keep the meeting live; waiting-room connections do not. Rooms that have never admitted anyone do not start this timer. The persisted deadline survives room-object hibernation, and an alarm from an older connection generation cannot end a newer call. Only the meeting’s original owner may restore an ended call. Restoration requires a fresh room-state version, the same authenticated actor, a same-origin request, and a server-authorized owner command. It preserves the original room expiration, participant usage, capacity limits, and outstanding provider cleanup. It never creates a new resource allowance: expired rooms and rooms whose historical budget cannot be established cannot be restored. Restoring clears old live-assistant and share sessions. A restored empty room gets the same five-minute disconnect rule until an admitted participant connects. Ended-room browser storage is an actor-scoped hint, not authorization or permanent state. Meeting-list actions revalidate it against the server so a restored room can become joinable again. Delayed restoration work cannot clear another account’s hint or finish a newer attempt. The destination still checks access independently. Regression coverage lives in room-lifecycle.test.ts, room-do-empty-lifecycle.test.ts, the room-state route tests, and restore-room-button.test.tsx. These source tests do not establish actual deployed alarm behavior or browser reconnection; verify both on the deployed commit. Cloudflare validation runs the explicit durable-object test files from RUNNER_TEMP, using absolute checkout paths. Running bun test from the repository root invokes its preload and redirects to the monorepo test suite instead.

Pieces

Meet’s pre-join preview and in-call controls are rendered from packages/meet-core/src/features/call. Keep that package in the Tailwind source scan of apps/meet/src/app/[locale]/meet.css when moving shared call UI. The scan must be in the stylesheet that imports the shared UI theme; a separate CSS import containing only @source does not generate those utilities for Meet. room.ts is a pure reducer: it returns the next room snapshot plus the messages to fan out and any Cloudflare work to perform. Both transports run the same reducer, so the Bun server and the Durable Object cannot drift. The app secret never reaches a browser. The client receives only a short-lived HMAC join token; the room server performs every SFU call on its behalf.

Running a call locally

Create an SFU app under Cloudflare dashboard → Realtime → SFU and put the values in apps/meet-realtime/.dev.vars (gitignored). See the apps/meet-realtime README for variable names.
Start the room server and the app, then open a meeting and press Join call:
Point the app at it with NEXT_PUBLIC_MEET_REALTIME_URL, which defaults to ws://127.0.0.1:8786/realtime.

Verifying without a full environment

Two checks run against real Cloudflare without needing Supabase, auth or seed data. Signaling and host controls — boots the room server, connects a host and a guest, and exercises the lobby, chat, hand raise, force mute and removal:
Media — bundles the real client modules, opens two browser peers and proves audio and video traverse the SFU. Tracks are synthesised from a canvas and an oscillator, so no camera or microphone permission is needed:
Then open http://127.0.0.1:7898/?peer=a and ?peer=b. Each tab reports bytesReceived and framesDecoded, and renders the other peer’s video.

Two contract details worth remembering

Both of these were found only by running against the live API, and both produce confusing errors if reintroduced:
  • Read transceiver.mid only after setLocalDescription(). It is null before that, and publishing without it fails with 406 tracks[0]: Missing mid in track.
  • Do not send a sessionDescription when pulling remote tracks. Cloudflare returns the offer, which the client answers via sfu.renegotiate. Sending an empty SDP is rejected as a malformed event.
Creating a session with no local offer must also send no request body at all; {} and {autoDiscover: true} are both rejected.

Attributing remote tracks

An inbound track arrives with an opaque id, so nothing on the RTCPeerConnection says who published it. Attribution comes from the track name, which is deliberately <deviceIdentity>-<kind>: the subscribe response returns a mid per track, the client maps mid → userId (the device identity, not the account ID), and the track event is matched against that map. This is load-bearing. Keying inbound streams by the browser’s track id instead means the UI cannot tell participants apart, and every tile ends up rendering whichever stream arrived first. One MediaStream is accumulated per participant, since their audio and video arrive as separate tracks but belong to one tile. The map and the streams are both cleared on reconnect, because mids are only meaningful within a session.

Reconnection

Signaling drops are expected: servers restart, networks blip, laptops sleep. The client reconnects with exponential backoff and full jitter, so a room server restart does not produce a synchronised retry stampede from every participant. Two details make it actually work:
  • Each attempt fetches a fresh join token from Meet’s Cloudflare-local POST /api/meet-call/<meetingId>/token, preserving the current device identity. Join tokens are short-lived and calls are not, so reusing the original token would let a client reconnect for ten minutes and then fail forever on “Reconnecting…”. Membership is re-checked on every refresh.
  • A reconnect rebuilds room state. The client re-announces itself with presence.join and clears its subscription ledger and peer connections, so every remote track is pulled again. Without that it would rejoin a room it could not see.
A clean close (1000) and a host removal (4403) are decisions, not blips, and are never retried. Media survives a signaling drop: the peer connections to the SFU are independent, so audio and video keep flowing while signaling is down.
That check stops the room server mid-session and asserts the client recovers, mints a new token for the retry, and stays closed when the close was intentional.

Meeting codes

Every meeting has a shareable code and a /r/<code> link, surfaced by the Copy invite button in the call header. The code is a reversible Crockford base32 encoding of the meeting id rather than a stored random string, so there is no schema change, no collision risk and no lookup table — an invalid code is rejected before any query runs. Decoding tolerates case, spaces, dashes, and the I/L/O/U characters Crockford omits, folding them onto the digits the sender meant. Only signed-in accounts with a verified @tuturuuu.com address can create online meetings. Other signed-in users can follow an invitation and ask the host to admit them. Sign-in preserves the room link, and an existing profile display name is reused. A missing name is saved to the profile before joining.

Joining and admission feedback

Meet distinguishes connecting to signaling from waiting for host approval. A confirmed waiting response means the request was sent; the lobby shows approval copy and a cancel-and-leave action without a spinner. After 60 seconds it explains that approval is still outstanding and suggests contacting the host or returning later. It does not resend the request, auto-admit the participant, or treat a slow host response as a connection failure. Host presence is not inferred from the absence of a presence snapshot in a waiting response. The signaling readiness deadline is 10 seconds per attempt, including refreshed token resolution and the server’s ready response. Automatic recovery is limited to 12 retries. The budget resets only after the socket stays open for 30 seconds after protocol-ready; repeated ready/disconnect cycles still exhaust it. The first pending admission reconnect uses normal backoff immediately; subsequent attempts are limited to one per minute, including failed token refreshes. Both the stability window and throttle use a monotonic clock, unaffected by wall-clock changes. The server currently re-announces pending admission on every connection, even for an existing waiting entry; this client limit bounds those notifications but does not provide server-side deduplication across tabs or page reloads. Late responses from expired attempts cannot revive them. Connection errors offer retry and leave; room-capacity refusals cannot leave a disabled Connecting spinner. Preparing the page or controls for more than 30 seconds replaces its spinner with an explanation and a way back. Retrying a join starts from the invitation; it does not grant access. Admitted participants see confirmation before choosing Join now. Declined requests and host removal show distinct messages with a way back, rather than claiming the participant voluntarily left. Ended rooms retain the existing ended screen. Invalid or inaccessible room links show a privacy-preserving unavailable message; room-page failures offer retry. Meet’s retained Next routes and native router use the same feedback components. Shared Meet/Parley call controls ship English and Vietnamese copy; admission and workspace access rules are unchanged. Regression coverage: join-flow.test.tsx exercises pending-to-admitted, pending-to-denied, prolonged waiting, removal, recovery, and loading deadlines. signaling-readiness.test.tsx covers hung token resolution, missing protocol-ready, bounded retries, cancellation and late-event fences. room-route-feedback.test.tsx covers the native route’s bilingual missing/error feedback. These fixtures do not establish successful hosted SFU/TURN connectivity or reproduce a specific user’s production account failure. Meet’s first-paint theme initializer is a literal script, not a serialized function. Worker bundlers that preserve function names can inject __name helpers inside functions; serializing such a function with toString() loses those helper bindings. The Meet theme provider leaves the upstream initializer inert and keeps its hydrated theme context, while its own initializer applies the same configured theme before hydration. Native and retained Next providers both use it; other satellites keep their existing provider. theme-provider.test.tsx reproduces the helper failure against the installed upstream script and executes the replacement with saved/system/forced themes. Require exact-commit CI and deployed first-paint verification; this console repair alone does not prove an authenticated join works.

Call controls and admission

The sending and receiving arrow badges open connection details. The displayed latency is the selected SFU connection’s round-trip time, not a measurement to another participant. Unavailable measurements show a dash. A device stays busy until its SFU publication and peer connection are ready; a local preview alone does not prove another participant receives media. Choose automatic, grid, spotlight or sidebar layouts. Each camera and shared screen can be focused independently or opened fullscreen. Videos fit their frames without cropping. Fullscreen uses a viewport-sized fallback when the browser denies native fullscreen. Camera color filters and soft portrait processing run locally and affect the transmitted video; no image is sent to an AI provider for these effects. Soft portrait is subtle whole-frame smoothing and lighting, not face-aware cosmetic placement. Reactions are temporary and rate limited. The host’s People panel contains persistent approved participants. Admission adds a signed-in identity to this room’s approved list, so reconnects and later visits bypass the lobby. Forgetting approval requires admission on the next visit; removing a current participant also removes their saved approval. Transcript and notes sharing is private by default. The host can enable it for admitted and approved participants, including guests outside the workspace. Every non-host notes read checks the signed room policy on the Durable Object; removing permission blocks subsequent reads. Existing downloaded information cannot be recalled. Transcription mutations remain host-only.

Who can read transcripts and notes

Product decision (30 October 2026): a meeting’s transcript and AI notes are readable only by participants of that call and by workspace admins of the meeting’s own workspace. Membership alone is not enough, and an outside guest reads only the calls they were a participant of. Sharing only narrows participant access; it can never grant access to anyone who is not a participant or an admin. A participant is an identity the room service recorded as present or approved for that meeting; there is no workspace-wide participant list. A guest’s access is scoped to the meeting whose room approved them and is not carried to another meeting or workspace. Surfaces: the call and ended-call notes panels, the meeting detail page, the /api/v1/workspaces/{wsId}/meetings/{meetingId}/review route used by web and mobile, and the Meet AI session routes all decide through the room policy plus the admin check, and fail closed (a policy or admin lookup failure never grants access). The recordings list returns a recording transcript only to the identity recorded on that session, the host, and workspace admins. meet_ai_sessions, meet_ai_chunks and private.recording_transcripts hold no client read grant or policy; only the service role behind these gates reads them. Recorded audio carries the same spoken content, so it follows the same rule: the playback route (chunk listing and one-hour signed URLs, which are also the download URLs) and the audio_chunks listing are limited to the identity recorded on the session, the host and workspace admins. A non-participant member and an outsider are denied, and a failed admin lookup fails closed. Signed URLs already issued stay valid until they expire. Exceptions: the recordings list and audio playback are stricter than the call rule, because the database records only the recording identity, not every call participant. Other participants read that call through the ended-call notes and do not get the audio. Cost and token breakdowns remain host-only. Regression coverage: packages/meet-core/src/features/meeting-ai/server/room-access.test.ts, packages/meet-core/src/features/meeting-ai/server/notes-admin.test.ts, apps/web/src/app/api/v1/workspaces/[wsId]/meetings/[meetingId]/review/route.test.ts, apps/web/src/app/api/v1/workspaces/[wsId]/meetings/[meetingId]/recordings/route.test.ts apps/database/supabase/tests/meet-transcript-notes-read-boundary.sql, and for audio the playback route test under legacy-api-routes/.../recordings/[sessionId]/play/ and apps/database/supabase/tests/meet-audio-chunk-read-boundary.sql. Leaving stops local capture and signaling immediately while recording/notes finalization continues on the post-call screen. The host can leave alone or end the room for everyone. Ending is durable, disconnects participants and prevents rejoining that room. Approved participants can still read shared post-call notes while the host’s sharing setting permits it. Room policy and approval state live in Cloudflare Durable Object storage; no self-hosted server or new database migration is required.

Missing microphone or camera

If enabling a device reports that none was found, the browser returned NotFoundError before media reached Cloudflare. Connect an input device or use a browser that can see it. Permission denial and a device that cannot start are reported separately; check browser/system privacy settings for denied access, and other apps using the device for startup failures. Repeated activation uses one notification per device rather than stacking identical warnings.

Recording

A host can record from inside the call. The control creates a row in recording_sessions through the existing /record endpoint, captures local audio with MediaRecorder, uploads through the existing /upload endpoint, and broadcasts recording.state so every participant sees the indicator. Because it reuses those endpoints, a call recording is indistinguishable from one started on the meeting page and feeds the same transcription pipeline.

Cloudflare hosting

Meet’s frontend is packaged with OpenNext and runs on the tuturuuu-meet Worker. Realtime runs on tuturuuu-meet-realtime, with one Durable Object per meeting. Browsers send audio, camera and screen media directly to Cloudflare Realtime SFU. Neither Worker requires an internal host, Docker or a Tunnel. Shared platform authentication and APIs remain at tuturuuu.com, with the existing managed Supabase database; this does not move platform API ownership. apps/meet/wrangler.jsonc configures frontend assets, image optimization, self-reference and the tuturuuu-meet-cache R2 bucket. apps/meet/open-next.config.ts configures the incremental cache. Build with bun run build:cloudflare from apps/meet; use NEXT_WEBPACK_BUILD=1 for the existing local Webpack fallback. The normal CI build uses Turbopack. bun run preview:cloudflare runs the built frontend in workerd for runtime verification. Run bun run cf:typegen after binding changes and bun run cf:typecheck to validate the generated environment with Worker types separately from browser types. CI runs this check after building. The frontend requires NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY, SUPABASE_SECRET_KEY and MEET_REALTIME_TOKEN_SECRET. Configure a separate app-coordination signing secret only if the platform uses one. Public Supabase values are also build inputs; private secrets belong only in Worker runtime configuration. Realtime requires its dedicated token secret and the two Cloudflare SFU credentials from its README. Direct Worker verification requires a wss:// endpoint; ws:// is accepted only for loopback development (localhost, 127.0.0.1, or [::1]). Endpoints must not include credentials, a query, or a fragment. Connected participants get a ten-minute heartbeat grace period for throttled tabs; the Worker closes and removes silent connections after that bound so ghost participants cannot persist. All call token issuers and verifiers must share MEET_REALTIME_TOKEN_SECRET; Supabase service keys are not used for call tokens. .github/workflows/meet-cloudflare.yaml validates both Workers and deploys realtime before the frontend on production. It uses MEET_CLOUDFLARE_API_TOKEN, or the existing account deployment credential COLAB_CLOUDFLARE_API_TOKEN while bootstrapping. The deployment token needs Workers Scripts:Edit and Workers R2 Storage:Edit on the Tuturuuu account, plus Workers Routes:Edit limited to tuturuuu.com. Store the dedicated token in the meet-production GitHub environment, restricted to the production branch. A Worker-only token can upload signaling but fails when OpenNext populates the R2 cache, so verify the complete frontend deployment and authenticated smoke check. Automatic Vercel Meet production deployment is disabled in the CI switchboard. Preserve the Vercel deployment until the canonical Cloudflare sign-in and call checks pass so rollback remains available. Before cutover, deploy the frontend with an explicit verification configuration that has no custom-domain routes. Verify public rendering and API guards there, then deploy the checked canonical configuration. Sign-in returns to the registered canonical hostname, so complete signed-in navigation, API proxying and an authenticated two-participant call after cutover. Do not change DNS until both Worker versions and runtime secrets are verified, and confirm the active versions after deployment. For a direct Worker protocol check, set MEET_CHECK_REALTIME_URL to its WebSocket endpoint when running integration-check.ts. The same variable works with media-check.ts --call-controller; localhost then hosts only the test page, not signaling. The check uses a fresh synthetic room and performs no database meeting writes. A health response alone cannot detect broken SFU calls. Native fetch must retain its global receiver in Workers. The SFU client binds globalThis.fetch to globalThis; calling an unbound native fetch as an object method throws Illegal invocation, even though Bun-based checks pass.

URL shapes

Meet matches the other satellites: workspace routes sit directly under the workspace segment, with no /workspace prefix. plans and r are static segments, which is what frees the bare /<wsId> position — public plan pages previously occupied it, so /<wsId> and /<planId> could not coexist. Every retired shape is permanently redirected in proxy.ts: /workspace/* → /*, /call/<wsId>/<meetingId> and /join/<code> → /r/<code>, and a bare 32-hex segment → /plans/<planId>, so shared plan links keep working.

Hosting eligibility and call verification

The meetings POST handler checks the creator’s current confirmed authentication email and personal subscription. Verified Plus, Pro, and Enterprise accounts can host regardless of email domain; verified @tuturuuu.com accounts remain eligible on Free. Workspace membership is required, and Calendar scheduling additionally requires Calendar permission. Both instant creation and scheduled creation use a trusted server writer only after these checks; the actor is always server supplied. The older restrictive direct-client INSERT policy remains in place. No production migration is needed to unlock paid hosting through the application API. Existing workspace members may still join calls regardless of their email domain. Signed-in invitees outside the workspace can join a meeting whose creator is an eligible verified host. They wait in the lobby until the host admits them; the invite grants no workspace membership, recordings, or archived transcript access. Guests see a transcription capability notice before joining and in the call. Meeting-only access is rechecked by the Cloudflare-local POST /api/meet-call/:meetingId/token route whenever the call reconnects. The creator must still belong to the workspace to receive host privileges. Anonymous invite navigation preserves /r/<code> (including locale) through the central login and token verification handoff. The call page resolves invite access directly rather than redirecting non-members through a workspace dashboard. If a login seems to lose the invite, inspect the nested returnUrl/nextUrl chain and the call authorization separately. Signed-in root navigation opens online meetings. Hosts can start an instant meeting, and members can enter a room code or paste a same-origin room link. Legacy UUID room links remain accepted. The client keeps an audio playback element mounted when cameras are off, selects the screen track while someone presents, and restores the camera after a share ends. Publishing uses stable senders with replaceTrack for device toggles and share restarts. Subscribe negotiations are serialized and retried because track announcements can arrive before the SFU has finished publishing. A ten-second heartbeat keeps presence alive. Reconnect refreshes the token, preserves the member’s speaker role for calls, and republishes enabled media. To test the actual React call controller and participant tile against the live Cloudflare SFU, run the local-only harness:
Open http://127.0.0.1:7898/?peer=a and http://127.0.0.1:7898/?peer=b in separate tabs. Peer A is a host and B is a speaker. The harness supplies synthetic audio, camera, and screen sources; it does not capture the desktop or microphone. Verify nonzero received audio energy, zero energy while muted, decoded video frames, screen-to-camera transitions, two-way chat, host mute, and reconnect. Stop the harness after testing. It uses scratch room identities and does not create application database records. This proves the media path through the real SFU, but does not certify production authentication, OS device permissions, physical devices, or restrictive corporate networks. Those require additional checks on the canonical deployed app. The restrictive insert policy protects authenticated direct database writes. Satellite app-session requests use a service-role client, so their creation gate is the API check against the current confirmed email fetched from Supabase Auth; service-role requests bypass RLS. Neither path trusts handoff or user-editable profile metadata for the creation domain. The policy is not an additional RLS layer on service-role inserts. If a browser blocks remote autoplay, use Play meeting audio. Any keyboard or pointer interaction also retries playback. The controller harness deliberately rebuilds its client bundle on each reload so ongoing controller edits are exercised without restarting its signaling room. Presence stays active while its Durable Object WebSocket is open, even when a background browser delays heartbeats beyond the normal expiry window. Close/error handlers remove disconnected participants. The direct Worker integration check includes a 40-second idle interval to verify presence and host controls survive browser timer throttling. The meet-production GitHub environment must allow only the production branch and contain MEET_REALTIME_TOKEN_SECRET matching the Workers and platform API. Deployment checks required Worker secret names before rollout, then runs the signed-token realtime/SFU integration check against the canonical endpoint.

Gemini transcript and post-call notes

Meet’s own Cloudflare frontend Worker serves /api/meet-ai/:wsId/:meetingId and its /chunks endpoint. These routes do not depend on a self-hosted recording server. Configure GOOGLE_GENERATIVE_AI_API_KEY as a secret on tuturuuu-meet; without it, the panel reports that Gemini is not configured. Do not put the key in public environment variables or the browser. Existing deployment credentials do not grant permission to transfer a Gemini key to a new destination. Apply 20260907040000_meet_ai_sessions.sql through the normal database release process before enabling the feature. A missing schema produces a visible unavailable state without breaking the call. Browser reads require workspace membership; only the meeting creator can start transcription, submit audio, or finalize notes. The server independently verifies the signed Meet app session, workspace membership, meeting ownership, and same-origin mutation requests. The host opens Transcript & notes, tells participants that audio will be sent to Gemini, then starts live transcription. The panel and call header show an active-session indicator to members. The browser mixes its current microphone track with received participant audio; it does not request another microphone or capture unrelated desktop audio. Muted tracks contribute silence. AudioWorklet emits independent mono 16 kHz WAV chunks about every ten seconds, skipping silence. Transcripts arrive after Gemini processes each chunk. This is chunked live transcription, not word-by-word captions, and overlapping speech is not assigned to guessed speaker identities. Stop & generate notes flushes pending audio and generates a summary, decisions, action items, and open questions. Leaving through the call’s leave control does this automatically for the capturing host. Keep the page open while it finishes. If finalization fails, leaving still works and notes can be recovered from meeting details. Abrupt tab closure or a browser crash can lose the current unsubmitted chunk; return to meeting details and finish the active session to generate notes from saved text. Interrupted or failed captures are identified as incomplete. A stuck notes attempt becomes eligible for manual recovery after two minutes; this can incur another provider charge and the earlier unknown usage remains visible in statistics. Notes are AI generated and should be reviewed. Only text, notes, and provider usage are retained in Supabase, not audio files. After a transcription provider result has been generated and settled, the server retries only its same-attempt transcript write up to three total times, including returned storage errors and rejected save promises. The transcript, structured speaker usage, and cost stay identical; this retry does not regenerate audio or repeat settlement. A missing attempt row is a conflict, and failed-attempt cleanup only changes a still-processing row, protecting an uncertain completed commit. Exhaustion returns a safe failure, not a completed receipt. This is request-local persistence recovery: a later request may reserve a new failed attempt and incur another provider charge; there is no cross-request charging guarantee. Synthetic chunk regressions cover thrown/returned errors, CAS conflict, exhaustion, and the completed-row cleanup predicate; they do not prove live database or provider behavior. The capture queue holds up to 60 chunks in browser memory and retries each stable chunk ID for up to five minutes. A recovery status shows pending chunks. Tab closure still loses buffered audio; keep the page open until uploads finish. Overload and unrecoverable chunks remain explicit gaps. Sessions retain the three-hour/1,080-chunk limit. Apply 20260909153000_meet_transcription_recovery.sql before enabling server retries: row locks and attempt leases prevent overlapping provider calls and stale writes. Up to five provider attempts are allowed; uncertain prior costs remain unpriced. The default model is gemini-3.1-flash-lite. Costs are informational estimates in USD, computed on the server from Gemini usage metadata and a dated price snapshot: text input $0.25, audio input $0.50, and output including thinking $1.50 per million tokens (standard API pricing checked September 7, 2026). The panel separates transcription and notes costs, submitted audio minutes, and input/output tokens. Missing provider usage and interrupted attempts are shown explicitly; totals are not invoice totals and do not debit workspace AI credits. Review Google’s pricing when changing the model or rates. Never log raw audio, transcript text, or credentials. Validation: use the Meet AI API/audio tests, AI package usage tests, a real browser AudioWorklet check, and the disposable database runner with --test supabase/tests/meet-ai.sql --typegen packages/types/src/supabase.ts. The disposable runner copies Git-tracked files; new migrations and tests must be tracked before running it. Run the Meet Cloudflare build before deployment and verify a signed-in live transcript plus persisted notes against Gemini afterward.

Production upload and provider diagnostics

If an audio chunk is rejected before reaching the Worker, inspect the request’s Ray ID in Cloudflare Security Analytics. Binary WAV uploads can trigger OWASP 949110: Inbound Anomaly Score Exceeded. Meet has a logged managed-rule exception (6bcb3c2398ca4f2896222b76bc1ae9b2) before the managed rulesets, skipping only rule 6179ae15870a4bb7b2d480d4843b323c in the OWASP ruleset for this expression:
Keep the other managed rules active. The Worker still checks the signed actor, meeting ownership, same-origin request, byte limit, canonical WAV format, and atomic usage reservation. Do not replace this with a domain-wide WAF bypass. Provider failures return a safe 502 response. Worker logs contain only a fixed failure category and provider HTTP status, never provider messages, request URLs, credentials, audio, or transcripts. Use those categories to distinguish invalid keys, disabled APIs, quota, access, location, timeout, and runtime failures. An accepted chunk is not automatically resubmitted to the provider; preserve its unknown-cost record when investigating a failed attempt. Google’s request logs identified the production synthetic failure as 412 FAILED_PRECONDITION: User location is not supported for the API use. The Meet frontend Worker therefore uses placement.region: aws:ap-southeast-1 to run near Singapore, which Google lists as a supported Gemini API region. Signaling and media remain on the separate globally distributed realtime Worker and Cloudflare SFU. This is a Cloudflare placement hint, not an AWS deployment or a strict geographic guarantee; verify the provider path after each release. See Cloudflare placement and Gemini supported regions. Missing keys are classified as missing_configuration and return a safe 500; provider failures return a safe 502 with fixed-category diagnostics.

Media connection readiness

SFU_HTTP_425 means the SFU rejected an operation before the session was ready. After the first offer/answer exchange, subsequent publish and subscribe operations wait for the browser peer connection to reach connected (up to 12 seconds). SFU_CONNECTION_TIMEOUT and SFU_CONNECTION_FAILED distinguish a stalled or failed transport from device permissions. Rejoining creates fresh peer connections. Receiving a successful SDP response alone does not prove playback: the client reconciles negotiated receiver tracks with participant ownership and only marks live, attached tracks subscribed. This also handles receiver reuse without another browser track event. Production verification should include late guest admission, rapid microphone/camera activation, and media flowing in both directions.

Muted tracks and media reconnection

Cloudflare expires tracks after 30 seconds without media packets. Keep muted microphone/camera tracks attached with enabled = false, which sends silence or black frames, so unmuting resumes the existing subscription. Detaching a sender with replaceTrack(null) silently invalidates that track after the timeout. Stopped screen shares are explicitly closed at the SFU with force: true, removed from the publication ledger, and published afresh when sharing resumes. Media connectivity is independent of the signaling WebSocket. Meet rebuilds a failed peer after one second, a disconnected peer after ten seconds, or an unfinished connection handshake after twenty seconds. Brief disconnections that recover within the grace period retain the existing connection. Publisher recovery republishes enabled sources; subscriber recovery clears its ledger and pulls the current room tracks again.

Diagnosing media on another device

Open Connection in the call header to inspect outgoing and incoming media transport states and packet counts. Reconnect media rebuilds both media connections without leaving the room. Copy diagnostics includes only transport states, aggregate media counters, and receiver track states; it omits SDP, IP addresses, track identifiers, participant names, and authentication data. A working local camera preview proves capture succeeded, but does not prove other participants can receive it. If enabling a device shows a transmission warning, record its diagnostic code and the action that triggered it. SFU_HTTP_* identifies an upstream SFU rejection; SIGNALING_* identifies the signaling connection or request timeout; RTC_* identifies a browser peer-connection failure. The code contains no raw provider message, SDP, credential, or meeting content. A successful retry clears the previous warning for that device. Reproduce on the affected device before changing transport settings; synthetic sources in another browser do not establish physical-device compatibility. Browser audio probes should attach the received stream to a playing media element before measuring decoded audio energy; packet counts or an unattached analyser alone do not establish working playback.

Participant names and admission

Meet reuses the participant’s saved profile display name. If it is missing, the invite page suggests an available account name and asks the participant to confirm or edit it before opening the realtime session. It saves the name through the current-user profile API, and refreshes the same invite. Later joins reuse the saved display name. A failed save leaves the form open for retry; a temporary profile lookup failure does not invite the user to overwrite an existing name. The lobby separates connection progress from waiting for host admission. Guests can leave while waiting, and their preview is not published until they join. Host admission actions are labeled, and a muted participant has one mute status indicator rather than an additional redundant mute action.

Cloudflare browser integrations

Meet must not mount Vercel Analytics or Speed Insights: their /_vercel/* script endpoints are not hosted by the Cloudflare Worker. A 404 for those scripts is separate from SFU media delivery. The platform user-config API uses the shared satellite app-session audience list in apps/web/src/lib/api-auth-audiences.ts. Keep Meet in that list and in the Rust current-user session targets. A successful local Meet call-token refresh does not verify authorization for proxied platform APIs such as SHOW_VERSION_BADGE. A recovered publisher announces a new SFU session for its stable microphone, camera, or screen track name. The room retires older registrations for that participant and track and broadcasts track.closed before the replacement track.published, so listeners discard obsolete receivers. Snapshot consumers also deduplicate old session registrations left by earlier Worker versions.

SFU commit boundary

Both realtime transports use MeetCommandExecutor. SFU operations are serialized per participant, while other participants and leave/end controls stay responsive. A track registration or closure is persisted and broadcast only after Cloudflare returns success without per-track errors. The completed operation is applied to the latest room snapshot so concurrent host settings are preserved; a participant who left during the request cannot be resurrected. Closing a track requires the registered owner to match the signed caller. Recording finalization targets the specific session with an idempotent status update, never another toggle. SPA unmount and leave share the same finalizer. The post-call navigation buttons wait for finalization while capture and signaling are already stopped. A hard browser/process termination can still interrupt an upload; do not claim a saved recording without its storage receipt.

Screen audio and in-call controls

Screen sharing requests audio separately from the microphone, including browser hints for window and system audio. The browser picker remains authoritative: capabilities depend on browser, operating system, and the chosen source. Select Share audio when offered. The in-call badge reports whether captured audio is available; a video-only capture still works. Remote screen tiles play shared audio, and muting the microphone does not mute shared audio. Stopping screen sharing stops both its audio and video tracks. The host’s shared audio is included in the existing consent-controlled transcription mixer. The host can click the meeting title to rename it in a dialog, without changing the meeting time or calendar linkage, and hover or keyboard-focus a participant’s name to mute their microphone. Touch layouts keep the action visible. Participants cannot remotely unmute someone. Raised hands use a prominent orange tile badge and outline; reactions use larger, labeled overlays with reduced-motion support. Transcript and Notes have separate tabs, with expandable usage and session notes. Receiving badges distinguish an enabled remote device from an arriving track. A connected SFU receiver with a live track is not proof of packet delivery. Meet checks receiving counters with a 20-second grace period and rebuilds only the subscriber when an expected stream never delivers packets or remains muted and stalled. Automatic rebuilds are limited to one per minute. Missing statistics, normal silence/DTX, and a static screen with an unmuted track do not trigger this recovery. This is a recovery mechanism, not evidence that an affected physical network or device is fixed; verify each audio/video direction on those devices. Packet counters that are unavailable or invalid remain unknown rather than being counted as zero. The tile indicator distinguishes unknown packet statistics from confirmed receiving and waiting states. Ended tracks are excluded from stall recovery. Persisted title announcements are acknowledged by the room and retained across signaling reconnects until delivery; intentional leave clears that outbox. A browser can keep its signaling socket in CLOSING while waiting for a remote close handshake. Removal, admission denial, and meeting-end messages stop retries immediately, before the delayed policy close event arrives. Meet watches that state and retries after a five-second grace period instead of waiting for the browser’s longer timeout. Events from the old socket cannot change the replacement connection or settle its pending requests. Intentional leave and promptly received policy-close codes still stop retries.

Media timeout and managed TURN fallback

SFU_CONNECTION_TIMEOUT means ICE/DTLS did not connect to the media service. A working local microphone preview does not prove that audio can reach the SFU. Meet now obtains expiring Cloudflare TURN credentials with each admitted SFU session and installs them on both publishing and receiving peer connections before negotiation. UDP, TCP, and TLS on port 443 provide alternative network paths; no self-hosted relay is required. Port 53 is filtered because browsers restrict it. Direct connections remain eligible, avoiding unnecessary relaying. Create a dedicated Meet TURN app in Cloudflare and install only CLOUDFLARE_TURN_KEY_ID and CLOUDFLARE_TURN_API_TOKEN in the tuturuuu-meet-realtime Worker before deploying this change. The permanent key never reaches the browser or logs. Only authenticated, admitted participants can create sessions and receive the 24-hour credentials. Each replacement session receives fresh credentials. Missing configuration or generation failures log a fixed warning and preserve direct ICE connectivity. Production checks still fail if relay credentials are unavailable, so degraded coverage stays visible. To verify restrictive-network behavior, run the isolated call-controller harness against the production Worker and open peers with &relay=1. Confirm a selected relay candidate, incoming audio energy and decoded video in both directions; repeat with &relay=tls to allow only TLS/443 and rule out direct UDP success. Synthetic media avoids recording real microphones or cameras. This does not substitute for checking previously affected physical devices.

Audio works but a remote camera stays blank

The receiving watchdog also handles video tracks with no initial RTP report when another incoming track proves that the browser exposes inbound statistics. WebRTC may create an inbound report only after the first packet, so an absent video report is not always an unsupported-statistics case. A live, muted video track with enabled remote camera and working sibling inbound stats gets a 20-second grace period before subscriber recovery. With no usable inbound statistics at all, the watchdog continues to avoid guessing. Video that receives packets but reports zero decoded frames also gets a separate 20-second first-frame deadline. Incoming byte growth does not reset this deadline. A successfully decoded static camera or screen does not trigger this condition. Disabling media, replacing a publication, or ending its track clears the deadline. Recovery rebuilds the subscriber connection while preserving local publishing and room membership, using the existing one-minute recovery throttle. This corrects missed recovery cases; it does not identify a codec or network fault on a device that has not been inspected.

Decoded video with an avatar-only tile

If incoming diagnostics show decoded video frames and a live, unmuted receiver, inspect presentation before resetting the SFU. Tile readiness uses a React external store so an unmute event between rendering and listener installation cannot leave an old avatar visible. Audio and muted video have separate media elements; camera replacement preserves the audio element’s stream. Playback retries on metadata and can-play events after a replaced stream interrupts play(). The incoming-video retry action preserves local publishing. Rejected subscription responses and stale publisher sessions cannot take ownership of a replacement receiver. Unexpected socket closure retains presence and SFU tracks for a 30-second grace period. A resumed device keeps its media connections; intentional leave still releases them immediately.

Call alerts and post-meeting privacy

Join, admission-request, chat and raised-hand changes display actionable in-call notifications. Short sound cues are unlocked by browser interaction, rate-limited, and can be muted from the header. Initial snapshots, reconnect snapshots and the participant’s own actions do not produce notification bursts. Live notes sharing and post-meeting sharing are separate room settings. Both are disabled by default. The host may enable post-meeting access for approved people from the transcript panel or centered ended-meeting screen. Ended invite/detail pages perform a signed read-only room-state check and never open a call socket. Notes open on demand with transcript and notes tabs; there is no join button for an ended room. The authenticated host-only sharing mutation reaches the room Worker over signed HTTP and also works after its WebSocket has closed. AI read responses use explicit field projections. Non-admin readers receive no raw provider usage and null cost/token fields; cost totals and breakdowns render only for the room admin. Sharing permission is rechecked on every notes request.

Room settings, recordings, and devices

The in-call settings dialog contains audio/video input, supported audio output, connection diagnostics, notification sounds, and admin-only cost breakdowns. Changing an input replaces its track on the existing call. Output selection uses HTMLMediaElement.setSinkId where supported; unsupported browsers explain the limitation instead of presenting a non-working selector. Each browser call generates a device ID. The server derives a signed device identity from the account and device IDs; media and SFU sessions use that identity, while admission approvals use the account ID. Joining another device offers a switch or an additional connection. Switching only removes that account’s other connections. Keep microphones or speakers muted on nearby devices to avoid echo. Recording permissions start disabled for participants. Admins can independently share saved recordings and allow participants to start/stop recording. The Durable Object holds one recording lease for the room; a remote stop cannot be overwritten by a delayed start. The recording device composites its available video and mixes audio locally, and must remain available until saving completes. Recordings stop at 55 MiB and upload directly to the creator’s personal Drive using a scoped signed URL, followed by server-side metadata verification. Failed saves offer a local copy. This is browser capture, not a Cloudflare server-side recording service.

Chat and Mira

Room chat supports Markdown, avatars, and up to five 25 MiB attachments per message. Chat saving defaults on; admins can stop retaining new messages in Settings without deleting earlier messages. The recent 500-message snapshot survives Worker eviction; chat retries reuse a stable message ID to avoid duplicates after lost acknowledgements. Files use the creator’s personal Drive provider and storage capacity checks. Room admission controls attachment access; a signed URL is short-lived. Mention @Tuturuuu or @ttr (case-insensitive) to ask Mira using the recent 40 room messages. Uploaded file contents are not automatically read by Mira. The mention author’s personal AI allowance is checked and reported tokens are deducted from that author’s quota. Mira uses the plan-default Google language model for checks, generation, and deduction. It has current-time, admitted-participant, and Google Search tools; web replies include source links. The browser supplies the requester timezone, and participant counts distinguish people from multiple devices. All generation steps and reported search queries count toward the requester quota. Search cost estimates use paid list rates before project-wide free allowances; missing provider usage or grounding coverage stays incomplete. Cost estimates use its catalog prices and reported cached input; missing cache prices or tiered pricing remain incomplete. Requests are deduplicated by chat message ID. The internal server-service token scope is never included in browser join tokens.

Bandwidth and cost attribution

Balanced mode caps camera sending at 900 kbps / 24 fps and screen sharing at 1.8 Mbps / 15 fps. Data saver lowers camera resolution and frame rate, while screen sharing retains text resolution at a lower frame rate. Higher-quality camera mode allows 1.6 Mbps. These are ceilings, not promised usage or minimum bandwidth. Speech has a separate 48 kbps ceiling; screen audio uses 96 kbps. Video shares the available outgoing bandwidth estimate with a reserve for audio. Congestion reduces quality promptly, while recovery requires four measured healthy samples to avoid oscillation. Missing statistics never trigger an upgrade. Video pauses when its share of estimated capacity is too small after reserving speech bandwidth. Sending pauses when the participant is alone and resumes when an audience joins. Sender parameter failures preserve the working transport rather than forcing a reconnect. Initial encodings are capped before negotiation. Admins see client-reported received RTP bytes and server request counters in settings. Estimates use SFU pricing and Durable Object request pricing, checked September 8, 2026, before account-wide allowances or discounts. TURN-to-SFU traffic is not charged twice. Reports are cumulative and deduplicated across samples. Per-device accounting retains at most 4,096 identities per room; reports beyond that limit are marked as incomplete rather than growing storage without bound. These are partial usage estimates, not invoices. RTP payload counters omit transport overhead and reports can miss disconnected devices. Worker CPU, Durable Object duration/storage, R2 operations/storage, and shared plan charges are not yet attributed per room and are shown as unallocated. Reconcile against Cloudflare account billing before using these estimates as a complete budget. Unknown AI costs are shown as missing coverage rather than zero-cost requests. Merely reading a cost panel does not force a Durable Object snapshot write.

Diagnosing call preparation

The centered Preparing your call screen checks the account’s device session before opening the lobby. Its request is POST /api/meet-call/<meetingId>/token; a failure here is distinct from a media or WebSocket connection failure inside the call. The browser bounds each preparation request to 12 seconds and retries transient failures twice. Switching an existing device is not automatically retried, because it can disconnect that device. The token handler checks the realtime Worker’s /room-device endpoint with a five-second timeout. An unavailable upstream returns HTTP 503 with MEET_REALTIME_UNAVAILABLE and Retry-After: 2, without bypassing device policy. Use the request status and Cloudflare Ray ID to distinguish an edge block from an unavailable Worker; a successful /health check alone does not prove the authenticated device endpoint works. Never collect cookies or bearer tokens. Cloudflare’s Meet-only managed-rule exception skips cookie-character checks 942420 and 942421 for GET and POST requests to meet.tuturuuu.com. Other rules and request-body inspection remain active, and exception matches are logged. Inspect the contributing rules before changing this scope; do not disable an entire ruleset to resolve a preparation failure. After the lobby opens, a WebSocket that remains in its initial connecting state for ten seconds is replaced through the normal bounded reconnect policy. Late events from the abandoned socket cannot replace the current connection.

Room policy verification

bun apps/meet-realtime/src/room-controls-check.ts verifies lobby chat privacy, one room-wide recording, private recording/cost access, and same-account device switching against the Worker. Set MEET_CHECK_REALTIME_URL and MEET_REALTIME_TOKEN_SECRET; the default endpoint is local port 8799. The check uses a fresh synthetic room, ends it after success, and never uploads files or calls Gemini. The production deployment workflow runs this after the existing signaling/SFU check. Each room-controls connection has a ten-second deadline; failed or closed attempts dispose their listeners and socket without retrying. The signaling check’s ALL CHECKS PASSED line does not establish room-controls success: require its own assertions and the complete workflow result. Lifecycle regressions live in apps/meet-realtime/src/check-socket.test.ts. Abandoned Mira requests release their per-account pending limit after two minutes; late provider costs can still be recorded.

Assistant identity and panel interactions

Mira replies carry a server-only assistant marker and reserved sender identity. The Nova avatar and official badge require both; choosing the display name Mira does not grant either. Standalone @Tuturuuu and @ttr mentions (case-insensitive) open the assistant mini profile on hover, keyboard focus, or tap. Code and existing Markdown links are not rewritten into mentions. Paragraphs containing raw HTML are also inert for assistant requests and highlighting. Chat message toasts are suppressed while chat is open; admission and other call alerts remain active. Desktop chat, participants, and transcript panels have a draggable separator. Focus the separator and use left/right arrows to adjust it with a keyboard. The meeting settings button is last in the call header. Returning from the post-call screen invalidates cached meeting lists and room state before navigation, including personal-workspace aliases, so newly created meetings are fetched.

Private workspace tool reviews in Meet

When composing an assistant mention, choose the workspace for task, calendar, finance, and time-tracking tools. Personal workspace is the default; billing always uses the requester personal AI allowance. Meet reuses the platform tool definitions and executors, checks membership and permissions on every generation, and only sends detailed schemas for selected tools. Workspace tool calls require the requester to review and approve the exact inputs. Their results remain in a private Mira review; Share with meeting is a separate explicit action. Other participants cannot read, approve, or share those drafts. Draft continuations remain server-side in the room Durable Object and are omitted from room broadcasts and browser review responses. Revisions and an execution claim prevent duplicate approvals from repeating a mutation. If a request remains in an uncertain executing state, do not repeat it; inspect the platform result first. Failed or timed-out reviews can be discarded without making their actions executable again, including after the room ends. Web search runs in an isolated request containing only the explicit question; model-supplied query rewrites and recent room history are not forwarded. Questions containing known participant names or room-title terms are blocked at that boundary. Missing grounded sources produce an unavailable-search result. Web search is disabled while continuing private workspace results. Unsupported platform-only tools such as E2EE key setup, file conversion, and client UI controls are not exposed in the Meet tool set. Verify private reviews with the production React compiler enabled: let a review arrive inline while its detail query is still loading, then deny the action. The list can load before the detail query, so callback inputs such as the revision must remain guarded during that interval. An uncompiled development fixture alone does not cover compiler-generated callback dependency reads. Private reviews appear inline beneath the request that created them. Approval, denial, and the separate sharing decision stay requester-only; orphaned reviews whose request has left the retained chat history appear at the end of the thread. Provider citation IDs are resolved only against their exact returned sources. Older unmapped references show an unavailable-reference badge rather than a guessed link, and the source footer is displayed as an expandable list of source cards. Source metadata is limited to eight URLs and 4,000 characters within the 16,000-character message limit; oversized prose is safely truncated. Empty final synthesis reuses only an available grounded public-search answer, without another paid call. Otherwise generation fails visibly after incurred usage is accounted for; pending approvals stay private. Legacy sources-only replies display an incomplete-answer notice. Sources alone never count as a completed answer.

Fast return to ended meetings

Meeting cards remember an observed ended state on the current browser, scoped to its signed-in account. Only meeting IDs and observation timestamps are retained, for up to 30 days and 256 rooms per account. Returning from an ended call updates this hint immediately, so known ended cards do not queue another room-state request or poll the realtime Worker. Active and unknown rooms still refresh. The cache is only a display hint: opening notes or transcripts always performs server permission checks. No room names, chat, transcripts, or access grants are stored in this cache, and unavailable browser storage does not block navigation.

Echo and microphone processing

Settings → Devices exposes native echo cancellation, background noise reduction, and automatic voice leveling. All three default on. Changes apply to the current microphone and subsequent device switches in that call, without unmuting it. The status under each control comes from the captured track’s getSettings(); a requested setting is not proof that a browser or device applied it. Screen capture excludes whole-system audio and requests restrictOwnAudio to avoid returning meeting playback to peers. These are browser hints, so browser compatibility still matters. Share a browser tab with audio when presenting media. Each remote camera or screen feed also has a local mute control: it silences only that feed on the listener’s device, without changing room participant permissions. For persistent echo, identify the participant or shared-audio feed returning the sound. Enable echo cancellation on the source device, use headphones, or mute additional microphones and speakers in the same physical room. Native processing cannot guarantee cancellation across multiple nearby devices. Verify actual speaker/microphone calls separately from synthetic audio and unit tests.

Nearby participants and same-room audio

Meet automatically pauses the later device’s microphone when the same verified account already has an active microphone in the call. For different accounts, a local audio monitor looks for repeated matching speech dynamics and changing frequency patterns between existing microphone tracks. Only the later device backs off, using join time and device ID as a stable tie-breaker. This avoids two devices muting each other. The microphone is disabled before a compact audio-control popover suggests Share nearby audio. Closing the popover keeps the microphone paused. Confirming sharing also silences this device’s meeting playback, voice assistant and notifications; video, chat and notes remain available. Use my microphone, or explicitly pressing the microphone control, restores this device’s own audio. Device replacement and pending permission requests cannot reopen a paused mic. If the identified primary device leaves, local playback resumes and the microphone stays paused until the user chooses to speak. Analysis runs only on already-enabled microphone tracks, never screen-share audio. It creates no extra capture, cloned tracks, recordings or network payloads, and compares at most eight incoming microphones. Several consecutive matching windows are required; silence and steady tones are insufficient. An explicit override suppresses the current device pair. Unsupported Web Audio falls back to native browser echo cancellation and the manual sharing control. This is a conservative audio-overlap heuristic, not proof of physical proximity or speaker identity. It does not use IP addresses or merge independent recordings on the server. Headphones and one primary microphone/speaker pair remain useful for rooms where acoustic conditions defeat detection. The meeting deadline appears as a compact clock button with a circular countdown ring. Hover or focus shows the remaining time and localized deadline; tap opens the same details. The ring represents the time remaining when this device joined, while the deadline always follows the server’s room expiry. Resource-limit errors still appear as actionable notices.

Audio recovery and meeting previews

In call settings, Restart microphone reacquires the selected input and reconnects media without leaving the room. The control remains disabled while muted and never intentionally unmutes the user. Use this when the browser input degrades; synthetic playback tests do not verify physical microphone acoustics. Transcription retries receive their five-minute window when the upload starts, not while waiting behind another upload. Credit admission failures stop immediately and require the user to check AI Hub; they do not advertise a five-minute network reconnection. Other buffered retry feedback describes transcription retry rather than claiming the connection failed. Notes generation failures retain saved transcript chunks for an explicit retry. Empty saved speech completes with empty notes, zero cost and no provider invocation, even when capture is incomplete; missing-audio warnings alone do not admit generation. Real saved speech still generates with its incomplete warning. This applies to Meet and Parley notes finalization and is covered by server/sessions.test.ts. The failure UI uses localized fixed guidance with allowlisted HTTP status and provider category diagnostics; raw provider bodies, credentials and meeting identities are excluded. Regression coverage: chunk-recovery.test.ts, use-meeting-ai.test.ts, server/sessions.test.ts and failure-toast.test.tsx. The per-session notes warning identifies incomplete results without repeating a global warning after the call. Room preview images are generated from the same explicitly public meeting policy as link metadata. Image responses use private, no-store; revoked public access falls back to generic branding on the next request. External chat clients can retain their own old previews. Preview descriptions omit repeated titles and UTC times because crawlers cannot know the recipient’s timezone. Opened meeting pages and the rightmost call timer use the viewer’s browser timezone and locale. For intermittent outgoing speech stutter, capture connection diagnostics before refreshing. Audio streams include jitter (milliseconds), reported packet loss, and incoming concealment counters when the browser exposes them. Outgoing values come from the SFU’s remote-receiver feedback; they describe that transport hop, not every listener’s playback. Unsupported metrics remain unknown, not zero. Compare successive reports to distinguish ongoing loss from historical totals. This report includes no audio, addresses, SDP, or raw track identifiers. Stuttering is distinct from low microphone volume; reconnection is a recovery action, not proof of the underlying cause.

Worker rendering verification

The pinned OpenNext adapter patch preserves component function names during its server bundling step. React uses these names to resume partially prerendered boundaries; renaming a component can trigger React 419 recovery even when the HTTP response is 200. Keep this patch when updating the adapter until its bundler preserves names upstream. scripts/ci/meet-cloudflare-render.test.js reproduces the failure with a real React prerender/resume cycle and checks the installed adapter configuration. After a Meet deployment, verify a fresh browser load as well as the signaling checks. Check page errors and the rendered interface; a successful HTTP response or a page that recovers after refreshing does not prove server rendering passed.

Mobile screen sharing and notifications

Native Meet presents join, room chat, and host admission-request notices with a shortcut to the relevant sheet. Initial presence and replayed chat stay quiet; reconnecting does not repeat previously seen messages or pending requests. Hidden browser tabs retain at most the newest 50 notices and display them once when the tab becomes visible; disconnect, room end and lost admission discard that queue. Obsolete admission requests are omitted. Notification sound can be disabled separately from meeting audio. OS volume, silent mode, and Do Not Disturb may suppress the sound. The floating navigation bar owns Leave. Hosts retain the leave/end decision; ending the meeting remains restricted by server authorization. Run synthetic fixtures without joining real meetings or publishing real screen, voice, or chat. Validate failed publication, interrupted consent, removal, room end, denied admission, reconnect, and repeated start/stop. Device testing must use controlled fixtures and explicit capture consent. No signing entitlement or developer-account capability is added. A returned WebRTC track alone does not prove ReplayKit produced frames; verify actual receiver frames before declaring native capture acceptance complete. References: Android media projection, Apple ReplayKit, and browser display capture. The installed WebRTC socket implementation previously left a listener open when capture stopped before its first connection. ios/MeetSocketConnection.m is a repository-owned copy with bounded cleanup changes. The Podfile selects it in the generated flutter_webrtc target and fails closed if the expected source layout changes; it does not mutate shared Pub cache files. Revalidate this patch when upgrading the plugin. Native CI must compile/link this replacement. The iOS reader requires a minimum 10 KiB body to avoid overreading coalesced tiny frames. Meet pads JPEG trailing bytes; an independent reader model passed 100 synthetic fragmentation/coalescing cases with 10 frames each. The installed macOS CoreImage runtime decoded a synthetic 8×8 JPEG padded from 707 bytes to 10 KiB and rendered the expected red pixel. iOS receiver-frame decoding and interruption on physical devices remain release acceptance gates. The draft parity implementation at fe6b020b95b2799fad8314969784d06ccd7fadda passed exact-head Android, iOS simulator, macOS, Linux, and Windows CI builds, mobile analysis/tests, TypeScript type checks, and lint. Android artifact 11182160313 belongs to run 36899884533; iOS artifact 11181837360 belongs to run 36899884528. Build success confirms native compilation and linkage, but does not establish capture acceptance. Revalidate subsequent changes at their own exact head. Those development CI artifacts use the default localhost API/Supabase settings and do not inject a Supabase public key. They have no authenticated synthetic Meet room or SFU fixture. Two-peer acceptance therefore requires a separate fixture artifact configured for an isolated test service, synthetic host/guest sessions, and its existing authorized SFU credentials. Do not create accounts, change production settings, or substitute a real meeting to bypass this gate. The available local runtime inventory contains an Android 11/API 30 image and iOS 27 simulators, with no attached physical devices. Android 14+ OS-chip/lock revocation and physical iPhone ReplayKit background/rotation/receiver-frame acceptance remain explicit release gates. Android’s installed WebRTC plugin ignores MediaProjection.Callback.onStop. The repository-owned android/overlays/flutter_webrtc capturer forwards that callback to the Meet bridge and releases the capture. Each capturer claims an immutable owner; repeated callbacks and callbacks from a previous share cannot stop a new share. The assistant’s separate projection service does not claim this owner. Gradle copies plugin Java sources into generated build storage, replacing exactly this file after checking the reviewed version and upstream SHA256. It fails closed on an upstream change and never modifies the Pub cache. Revalidate this overlay when upgrading WebRTC, and require exact-head Android CI plus OS Stop/lock/restart fixture acceptance for overlay changes. Capture stops are generation-scoped and preempt media negotiation. Native revoke and track-ended events trigger one cleanup per capture; a stalled screen track does not discard the camera. Android engine detach stops its claimed capturer. The iOS listener accepts one broadcast connection and closes owned descriptors with Core Foundation socket ownership enabled. Browser Stop retains its capture until unpublication succeeds, so a failed unpublish can be retried. Regression coverage lives in meet_screen_capture_test.dart, meet_media_health_test.dart, local-media-controls.test.ts and use-call-notifications.test.ts. The iOS notice uses an app-owned short WAV loaded through Flutter’s asset lookup and registered as a UI System Sound, rather than a private numeric system-tone identifier. It plays only while the app is active and does not change the call’s audio session. Apple’s System Sound Services owns sound-effects policy. Device mute/Focus behavior remains part of the user’s post-merge physical acceptance.

Collaborative programming

The in-call coding control opens the shared Learn programming/playground workbench with file cursors and transient pointers. Hosts select the resource; the server checks admission and resource access before issuing tickets. A verified @tuturuuu.com admin elevates admitted participants only within that room. See programming playgrounds for Drive ownership, limits and the intentional entitlement exception. Web screen capture depends on browser getDisplayMedia support. Android and iOS browsers that lack it can receive screens and co-program, but cannot open a native screen picker. Missing support and capture denials now display guidance. See mobile capture diagnostics.

Native mobile sharing

Tuturuuu mobile calls use Android MediaProjection with a foreground capture service and iOS ReplayKit with the broadcast extension. The in-app Share screen control starts native OS consent and publishes the resulting track through the same meeting SFU. Native stop, meeting exit and revoked admission stop capture. On unsupported mobile browsers, Open mobile app preserves the room code in a verified platform app link. The app opens the native call; capture still requires a separate user action and OS consent. Tokens and capture permission are never carried in the link.

Collaborative documents and mobile parity

Every admitted Meet participant can edit the room’s private rich-text document. This document does not require a paid playground entitlement. It belongs to the meeting workspace and is created once per meeting. Cloudflare merges and persists Yjs updates, then checkpoints the rich-text content to the private workspace Document. Cursor labels and selected ranges use authenticated account identity; multiple connections from the same account remain separate sessions. Editing the workspace Document through another surface creates a conflict: a room checkpoint never overwrites that external edit silently. The durable room state is retained for reconciliation. Web and native editors display a backup conflict warning; transient checkpoint failures display a delayed-backup warning. Editing continues, and a successful checkpoint clears the warning. Rejoining restores the current backup status. The Worker rejects client-authored backup status notifications. The room Durable Object posts each checkpoint to the platform API from a Worker fetch, which carries no User-Agent. The web API proxy treats an anonymous /api/* request without one as scanner traffic (HTTP 400, with an egress IP block on repeat), so an unidentified checkpoint never reaches its route and the room keeps retrying and re-announcing “backup delayed”. Behind that guard, the proxy’s account-assurance step treats every other bearer token as a Supabase access token and answers 401 before the route runs, which rejects the Worker’s HMAC ticket just the same. Every Worker-to-platform call therefore sends the shared service headers from packages/realtime/src/core/service-request.ts, and the proxy’s default trusted-bypass rules exempt exactly the two checkpoint routes for a POST whose bearer verifies as an unexpired realtime service ticket with the audience and kind bound to that route (tuturuuu.channels / document-checkpoint for documents; tuturuuu.collaboration / checkpoint or runner-files for programming; packages/utils/src/realtime-service-ticket.ts). Other methods, path variants, audiences, kinds and ordinary JWTs fall through to the normal proxy checks. The route verifies the same ticket again together with its topic, ids and version. A ticket may be replayed within its 30 s lifetime; the route’s checks decide the outcome and tickets are not single-use. The checkpoint route passes the private RPC status through (a 40001 conflict is HTTP 409, not 503) so the room reports a real conflict instead of an endless delay. Regression coverage: apps/meet-realtime/src/channel-checkpoint-request.test.ts (request identity and status mapping in the Durable Object), realtime-service-ticket.test.ts (proxy exemption bounds) and apps/web/src/app/api/v1/realtime/documents/checkpoint/route.test.ts. The shared document surface is the panel itself: padding lives on the editable area, the writing surface fills the panel height so a click below the last line still places the caret, and the surface shows no focus ring (the caret, remote cursors and toolbar state convey focus). document-panel.test.tsx asserts that class contract because jsdom computes no layout. Native mobile opens the same shared web rich-text and programming editors inside an authenticated app-owned WebView. This preserves Yjs, formatting, remote cursors, pointers, selections and reconnect behavior across web and mobile. The app bridges a small allowlist of meeting-scoped API actions; it never sends platform tokens to editor JavaScript or runner preview content. A separate loopback capability serves private preview assets. Android denies mixed content; Chromium treats loopback as trustworthy, preserving the private 127.0.0.1 preview exception. External navigation and previews cannot invoke the bridge; calls continue. Native collaboration pins the account separately from the admitted device ID. Account/device changes, revoked admission and ended calls suppress late results; preview capabilities never transfer access. Server admission remains authoritative. Task cubits cancel subscriptions and close only default-owned clients/APIs; injected clients remain caller-owned. Refresh cleanup failures reconnect; disposal fences late cleanup. Navigation denies malformed, hostless and non-HTTP schemes without throwing. One-shot broadcasts preserve ticket/handshake errors; subscribed channels reconnect. Per-send socket setup remains a tradeoff; shared-channel reuse is not implemented. Coverage: meet_collaboration_owner_test.dart, meet_preview_proxy_test.dart, meet_collaboration_policy_test.dart, task_broadcast_lifecycle_test.dart and cloudflare_channel_test.dart. Both clients refresh short-lived channel tickets on the existing socket. Actual reconnects reauthorize admission and replay missing document updates. Expired access closes quiet connections; disconnected editing and persistence failures must remain visible. Room programming editors can save and run the selected resource; only its owner can grant permanent collaborators outside the meeting. Regression coverage includes the real local Cloudflare document/channel probes, shared caret/range tests, mobile channel tests and meeting admission/entitlement checks. Native WebView bridge, private-preview and MediaProjection/ReplayKit acceptance additionally require configured exact-commit mobile CI artifacts.

In-call document workspace

Meet and Parley explicitly enable the rich-text editor collaboration binding with the room Yjs document and provider. This applies to every admitted editor: passing a provider without enabling the binding must never create a local-only document. document-panel.test.tsx guards the binding; shared provider tests cover peer updates and reconnect reconciliation. Hosted multi-user verification remains a separate delivery gate. Meet and Parley render the shared document editor edge to edge, without a separate Shared document heading or padding outside the editor. The editor keeps its own text padding. Backup status sits in the editor toolbar; secondary formatting, media, and copy controls open from Tools, while bold, italic, and strike remain directly available. Other shared-editor surfaces keep their full toolbar. Healthy live synchronization stays quiet. Reconnecting is announced as a status; delayed or conflicted backups use a keyboard-focusable warning icon with an explanatory tooltip, without pushing the editor downward. Document and programming controls use the same icon-button tooltips as media controls, and meeting action triggers expose consistent tooltips. No room access, checkpoint behavior, or document broadcasting policy changes. Regression coverage: document-panel.test.tsx checks the toolbar status placement and seamless editor container; fixed-toolbar-contents.test.tsx exercises actual Tools opening and formatting actions alongside the unchanged full toolbar. document-status.test.tsx verifies quiet healthy state, accessible reconnection, and distinct backup-warning labels without inline alerts.

Programming preparation and resource discovery

Meet web hosts open Programming into a blank Python project when the room has no selected resource. Opening an existing selection reuses it; participants only join the host’s selection and never create a project automatically. The existing authorized collaboration ticket, hosted runtime, Run, and problem Test controls remain responsible for connecting and executing code. Creating a meeting project keeps its runnable entry file empty; standalone playground template defaults are unchanged. Internal checkpoint uploads explicitly permit zero-byte source files only with valid positive Drive quota and verified usage accounting; over-quota or unknown quota still blocks creation. Signed browser uploads retain their nonempty file policy. empty-playground-storage.test.ts exercises actual capacity guards for both Drive providers, including blank creation, quota denial, and cleanup. Hosts choose existing personal projects or authorized workspace problems through shared UI dropdowns. Problem discovery supports cursor pagination, while project discovery checks the response actor against the signed-in account. The native collaboration bridge retains its existing explicit resource-ID selection until that bridge provides authenticated discovery metadata. Regression coverage lives in prepare-programming.test.ts and playground-service.test.ts.

Private My day panel

During a Meet call, My day shows the current authenticated account’s assigned Tasks feed and next seven days of its personal workspace Calendar. It is closed by default and local to that participant; it is never room state, signaling, or shared document content. Team meeting workspaces are never used as a fallback. Account identity is verified before and after reads, and queries are scoped by account ID. Calendar collection GET accepts a signed Meet session only for that actor’s personal workspace, with MEMBER and manage_calendar permission checks. Team-workspace reads and Calendar mutations do not gain Meet-session admission. Temporary failures retain same-actor, same-workspace source snapshots with an explicit stale-source notice; definitive access denials clear that source. Dedicated Tasks and Calendar links open the full apps separately. Parley does not show this Meet-only action. personal-tools.test.ts covers personal scope, actor changes, missing personal workspaces, and partial source availability. calendar-event-meet-read.test.ts uses signed sessions to cover personal scope, actor/audience/signature/expiry denials, membership and permission failures; Calendar events route tests keep the audience opt-in exclusive to GET.

Meeting-list startup and hosting eligibility

The signed-in Meet workspace heading renders after session and workspace membership are verified. Hosting eligibility is resolved inside the content Suspense boundary, so a slow verified-identity or account-tier lookup does not block the heading. Creation controls remain unavailable until that lookup confirms eligibility; lookup errors retain the unavailable notice and never grant creation. Session and workspace redirects stay outside streaming boundaries. This applies to the workspace Meetings list, not room admission or meeting creation authorization. Regression evidence: authorized-meetings-content.test.tsx exercises deferred identity reads, workspace denial, eligible accounts, restriction notices, and failed eligibility reads. Measure signed-in server response and meaningful-content times separately. A fast public homepage is not evidence that an authenticated app is fast, and a Suspense skeleton is not a loaded meeting list. Re-measure the deployed source after rollout.

Meeting-detail policy and permission reads

After the signed-in actor, joined workspace, and workspace-scoped meeting are verified, Meet details starts the read-only room-policy lookup and workspace permission lookup together. The policy still decides the ended-room shortcut before any Calendar event access; Calendar links continue to require the existing manage_calendar permission. Permission failures are captured immediately, ignored for ended rooms as before, and propagated only after a successful active-room policy lookup. Policy failures retain precedence. No reads start before the workspace and meeting gates, and this changes neither room admission nor creation. page-loading.test.tsx covers deferred overlap, scoped identity, ended-room bypass, and rejection precedence; meeting-calendar-event.test.ts retains denied-access coverage. This source change still requires deployed meaningful-content timing to establish a customer-visible latency improvement.

Collaboration layout and document presence

Opening Meet’s shared document or Programming keeps participant cameras and shared screens visible alongside the active content. The existing layout selector also controls collaboration: automatic/sidebar uses a compact desktop rail, spotlight emphasizes content with a narrower rail, and grid balances content and participants. Narrow screens keep a horizontally scrollable filmstrip beneath the content. These choices are local to the active call; they never alter admission, media publication, or another participant’s layout. The same media stage and keyed camera/screen tiles stay mounted when content, layout, or focus changes. Local playback mute state and media element identity remain intact, and no second stage duplicates audio. Regression evidence: collaboration-stage-layout.test.tsx exercises real participant tiles and camera/screen audio elements through content and layout transitions. Shared-document avatars represent users in that document provider’s actual collaboration awareness, not everyone connected to the meeting. Clients with the same authenticated identity appear once; departures and provider switches remove old identities, and disconnected document transport hides the roster until it reconnects. Four avatars are shown in the editor toolbar beside backup status, with an accessible overflow count. document-panel.test.tsx verifies that the live provider awareness is connected to that toolbar roster. document-presence.test.tsx uses real awareness updates for join, rename, departure, duplicate clients, malformed identity fields, and provider/disconnect transitions. Source fixtures do not establish deployed multi-client delivery.

Recovering the final AI notes write

Meet retries a failed final notes persistence step at most twice after its initial attempt, reusing the already generated and settled notes, usage and cost. These retries retain the same session/attempt predicate; they do not repeat provider generation, credit admission or billing. A missing compare-and-set row is a conflict and stops immediately. Failure cleanup only changes a still-processing row, so an uncertain transport response cannot demote already completed notes. If every persistence attempt fails, the request reports failure without claiming that notes were saved. Saved transcript chunks remain unchanged. This is bounded recovery within one request, not durable cross-request output recovery: a later explicit retry can generate and bill again. The existing unpriced-attempt counter also remains conservative when generation succeeded but persistence failed; this packet does not reconcile that counter against settled billing runs. server/sessions.test.ts reproduces successful generation followed by a returned storage error or a thrown unknown-commit error, and covers equal output/usage/cost, same attempt predicates, exhausted recovery, safe errors and immediate conflict. These source fixtures do not identify the original incident’s provider status or establish deployed database/provider delivery.

Transcription capture scope

Transcription capture and its pending uploads belong to one verified actor, workspace and meeting lifetime. Admission changes only when that lifetime commits; a suspended or abandoned render cannot revoke the still-committed capture. A cleanup epoch also rejects operations held across lifecycle cleanup/replay. Queued overflow finalizers and duration callbacks check their captured epoch before calling the current finalizer, so an old callback cannot end a replacement capture. Departing that scope disposes local audio and aborts recovery. Returning to the same IDs creates a new lifetime; delayed old responses cannot install capture, alter current state, finalize an old session or refetch into the current view. The call shell also binds its account ID to the verified actor. Requests already admitted by the server are not promised to be cancelled or rolled back. A delayed start allocation received after departure does not issue an authenticated finalizer from the new scope. That server session remains subject to existing reservation expiry and authorized recovery. Same-scope finishing still waits for the final worklet flush, batch drain and queued uploads before generating notes. Explicit retries of ended sessions remain available to the current actor. use-meeting-ai-scope.test.tsx exercises held start/upload responses across actor, workspace and meeting departures, including switches away and back, new capture survival, retained callbacks, missing/mismatched actor admission, suspended transitions, held preparation across cleanup/replay and old overflow/duration callbacks after an Activity hide/show effect replay. Existing capture, batching and recovery tests retain final-audio ordering coverage. This does not establish the original incident’s reconnect/provider cause or real Radix workspace-selector interaction; those runtime checks remain separate.

Public deployment identity

Only exact GET /api/build-info skips Meet app-session refresh so a credential-free deployment probe can read the bundled production identity. The shared API guard still applies, including security and abuse controls. Other methods, trailing-slash or prefix lookalikes, and private APIs keep their existing session admission; this exception grants no meeting or workspace access. The production verifier requires the exact app, full source SHA, production ref, and environment. HTTP failures report only a validated numeric status, never response bodies or raw transport diagnostics. Proxy regressions cover anonymous and stale-session GET admission, the real metadata handler’s no-store response, protected API/method/lookalike denial, and preserved MFA/rate guard failures. A 403 without an app-assurance or challenge marker remains unclassified; these source fixtures do not establish the blocking layer or deployment identity.

Assistant workspace choices

Meet and Parley’s shared assistant picker shows compact loading, lookup-failure, and empty states in English and Vietnamese. Choices stay disabled until private visibility and workspace data are available; guest and hidden workspaces remain excluded. Lookup failures expose a safe localized message and an explicit retry through the existing actor-scoped visibility and workspace queries. They never expose a raw transport/provider error or grant membership. Actual Live dialog regressions cover Radix pointer selection, personal/member context, focus retention, loading/error/empty feedback, explicit retry, and a held account A→B→A lookup. Pointer selection passes this fixture; the demonstrated repair is missing state feedback, not proof of the reported production portal failure. Browser/provider runtime verification remains separate.

Offline room document checkpoint budget

Meet’s private rich-text document keeps its durable unsaved state when checkpoint callbacks fail. Without a currently valid editor connection, it reserves at most three automatic attempts within two minutes, at least 30 seconds apart. Reservations persist before callback I/O; duplicate alarms, restarts and failed completion writes cannot replenish them. Exhaustion or age expiry stops automatic checkpoint work. An authorized editor rejoin can recover the retained document; a viewer rejoin cannot restart the stopped job. Active editor ticket leases keep their existing checkpoint behavior. This rule applies to meeting-document channel rooms, not programming project checkpoints, media cleanup or a platform spending cap. Regression evidence: channel-retry-budget.test.ts counts operations and tests failure/clock/restart/recovery boundaries; test-fixtures/channel-retry verifies reservation persistence against real local Worker SQLite with controlled callback failures. Hosted alarm delivery and actual workspace storage remain separate gates.

Host identity and transcript reads

A meeting’s creator is immutable after insertion, including when a member edits other meeting fields. Clients cannot transfer or clear that host identity. Meet and Parley transcript sessions and chunks are read through the application API. Direct anonymous and authenticated Data API reads are denied, including for hosts. The API retains its host, workspace, room-sharing, and participant checks before service-role reads. All first-party transcript consumers use that API; room sharing remains the authority for non-host access. Database coverage: meeting-identity-transcript-access.sql and meet-ai.sql. Application coverage: packages/meet-core/src/features/meeting-ai/server/access.test.ts.

Private transcription evaluation

The M1 transcription evaluation CLI lives at packages/ai/src/meetings/eval; its README defines the private manifest, environment variables, bounds and scoring contract. Run it only with consented clips and UTF-8 references stored outside Git. Reports also stay outside Git and contain aggregate counts and scores without transcript or glossary text. Gemini Flash-Lite and an explicitly configured Flash candidate are evaluated through an evaluation-local SttProvider boundary. OpenAI gpt-4o-transcribe requires explicit evaluation opt-in and is never wired to production traffic. The harness does not choose or change the production model. D1 requires real consented corpus results and coverage review; synthetic regression tests establish the metrics and privacy boundaries, not Vietnamese provider quality.

Legacy meeting and recording access

Legacy meeting lists and recording session metadata remain readable by full workspace members. Audio chunks (listing and playback) are limited to the recorded identity, the meeting host and workspace admins; see Who can read transcripts and notes. Anonymous users have no direct privileges on these legacy tables; guests and removed members do not gain access through ownership. Private recording transcripts continue through the existing service-role API boundary. Meeting UPDATE and DELETE require the meeting creator or the existing workspace admin permission. Recording session INSERT, UPDATE and DELETE require the recording creator (user_id) or that workspace permission. This recording admin exception is intentional; ownership does not grant writes after membership ends. Existing sessions cannot change their creator or parent meeting. In-call room recordings in Drive and Meet AI sharing rules use their existing access gates. The legacy recording controls show Stop only for a creator or admin. Both the recording toggle and direct status/delete APIs verify permission before writing and require a returned row. A denied or zero-row update returns HTTP 403; the UI keeps the active recording and explains that it has not stopped. Active-session queries include the creator/admin capability rather than inferring it from workspace membership. Web and mobile consumers keep the existing read response fields; can_manage is additive. Regression coverage: legacy-meet-rls.sql, the meeting identity/transcript pgTAP suite, the recording route unit tests and meeting-actions.test.tsx. Rollbacks drop only the five meeting/session write policies, the recording identity trigger and its function. Privilege revokes, the authenticated audio policy target and creator/admin audio write fences remain in place, including during rollback. Legacy audio chunk INSERT, UPDATE and DELETE require the parent recording creator or the workspace admin permission. Audio chunk SELECT is fenced by the restrictive policy legacy_audio_chunk_select_participant (20261030110000_restrict_audio_chunk_reads; the rollback only drops that policy), and the playback API applies the same check before it lists or signs audio. The chunk and complete-recording upload APIs check the write capability before parsing or storing audio. Meet mounts recording capture only for that capability. A viewer who did not take part can read permitted recording metadata but not the audio. Coverage is in legacy-meet-rls.sql, meet-audio-chunk-read-boundary.sql, the playback route test and the upload route unit tests.

Transcription room protocol

Meet and Parley share the transcription protocol in packages/realtime/src/meet. This protocol milestone adds no capture or UI behavior. A host sends transcription.state {state: {active, sessionId, startedBy, language, consentRequired}}. Language is auto | vi | en | vi+en; consent is required. Active state requires a session UUID and the initiating host’s participant UUID. Only admitted hosts may change it, regardless of delegated recording or participant-management scopes. An admitted participant sends transcription.consent {userId, allowed} using their own participant/device UUID, including for guests. Account IDs and other participants’ IDs cannot stand in for that identity. Missing consent means excluded; false withdraws consent. A new session clears the prior roster. Only that participant and admitted hosts receive live consent decisions. Client snapshots contain only the participant’s own decision and aggregate counts, including for hosts. Transcription state reaches admitted participants and replays on admission or reconnection. Waiting sockets receive neither state nor consent. The existing room Durable Object validates these messages and executes the shared reducer through its serialized command executor. Its broadcast path excludes unadmitted sockets; consent uses direct delivery to admitted hosts rather than the broader participant-manager audience. Existing clients ignore unknown server types in the call reducer’s default branch, so realtime can ship first. Regression coverage lives in transcription.test.ts, transcription-routing.test.ts, and transcription-compat.test.ts. In-room consent survives DO hibernation only under the active session UUID and is cleared on session or room end. This is not the legal consent record; private.meet_ai_consents belongs to M1-B/M1-C. WebSocket messages over 256 KiB (UTF-8 bytes for text) are rejected before JSON parsing. Legal consent persistence, audio filtering and notices belong to subsequent milestones.