Operating Procedures

← Back to Operating Procedures media-production.md

Operating Procedures

Operating procedures define HOW to operate what was built. They are distinct from other planning documents:

Document Type Answers Created When Updated When
5P Plan Why are we building this? Who is involved? Before work starts Scope changes
PRD What exactly should be built? Acceptance criteria? After 5P approved Requirements change
Operating Procedure How does the built system run in production? What is verified? After deployment Every time verification status changes

Operating procedures are the only documents that carry verification status. A PRD can claim "the system will do X." An operating procedure must say whether X has actually been tested, and when.

Verification markers:

  • VERIFIED -- tested and confirmed working in production (date + evidence required)
  • UNTESTED -- code exists and builds, but has never executed in production with real data
  • ASSUMED -- believed to work based on code reading, not execution
  • BLOCKED -- known issue prevents this step from working
  • NOT BUILT -- designed or referenced in code, but implementation does not exist

Media Production Operating Procedure

Process: Value-First Media Network show production lifecycle Owner: Marquee (Media BU Leader), delegating to Director agent Platform: media.valuefirstteam.com (apps/media/) Last Verified: 2026-04-12

System Architecture

Sanity CMS (show + episode data)
    |
    v
media.valuefirstteam.com (Astro 5, Vercel serverless)
    |
    +-- Studio Dashboard (host/admin interface)
    |       |
    |       +-- Shows API (/api/studio/shows) -- Sanity query
    |       +-- Episode Prep (/api/studio/episode-prep) -- Sanity query
    |       +-- Stream API (/api/studio/stream) -- Mux live stream CRUD
    |       +-- Guest Token API (/api/studio/greenroom-token) -- HMAC token generation
    |
    +-- Green Room (guest interface, token-gated)
    |       |
    |       +-- Device Selector (camera/mic)
    |       +-- WebRTC Mesh (Socket.io signaling via Fly.io)
    |       +-- Souvenir Postshow (post-episode guest package)
    |
    +-- Canvas Compositor (browser-side 1920x1080 rendering)
    |       |
    |       +-- Layout engine (solo, 2-up, 3-up, panel)
    |       +-- Lower thirds, LIVE indicator, show logo
    |       +-- Audio mixing via Web Audio API
    |       +-- Output: composited MediaStream at 30fps
    |
    +-- Mux WHIP Push (browser -> Mux live stream)
    |
    +-- Post-Show Pipeline
            |
            +-- Transcription (/api/pipeline/transcribe) -- Mux captions + Sanity patch
            +-- Distribution (/api/pipeline/distribute) -- YouTube, website, podcast data structures
            +-- Souvenirs (/api/pipeline/souvenir) -- Guest clip + social copy + quote cards

External dependencies:

  • Sanity CMS (0efm0pow, production dataset) -- show and episode data
  • Mux (live streaming, recording, transcription, MP4 delivery)
  • Fly.io signaling server (vft-greenroom-signal.fly.dev) -- WebRTC coordination
  • VF Team HubSpot (40810431) -- contact lookup for login

Environment Variables (7 confirmed on Vercel)

Variable Purpose Status
JWT_SECRET Session token signing VERIFIED 2026-04-12 -- confirmed on Vercel
GREENROOM_SECRET Guest HMAC token signing VERIFIED 2026-04-12 -- confirmed on Vercel
DIRECTOR_AUTH_KEY Signaling server director authentication VERIFIED 2026-04-12 -- confirmed on Vercel
HUBSPOT_ACCESS_TOKEN Contact lookup for host login VERIFIED 2026-04-12 -- confirmed on Vercel
MUX_TOKEN_ID Mux API authentication VERIFIED 2026-04-12 -- confirmed on Vercel
MUX_TOKEN_SECRET Mux API authentication VERIFIED 2026-04-12 -- confirmed on Vercel
SANITY_API_TOKEN Sanity read queries (shows, episodes) VERIFIED 2026-04-12 -- confirmed on Vercel

Pre-Production

PP-1: Show exists in Sanity

What happens: Show document exists in Sanity with slug, name, accentColor, hosts, format, frequency, scheduleDays, scheduleTime. Who/what does it: Human (Chris) creates shows in Sanity Studio. Slate agent pre-creates episodes. System writes: Sanity CMS Trigger: Manual or /show-prep Status: VERIFIED 2026-04-12 -- Sanity queried live, active shows returned with correct fields. The /api/studio/shows endpoint queries Sanity and returns show data. Fallback inline data also exists but is stale. Evidence: Shows API returns source: "sanity" with full show list when SANITY_API_TOKEN is present.

PP-2: Episode pre-created for today

What happens: Slate agent creates an episode document in Sanity for scheduled shows (based on scheduleDays). Episode includes airDate, show reference, prep materials. Who/what does it: Slate agent (via Canon for Sanity writes) System writes: Sanity CMS Trigger: Weekly slate run or /show-prep Status: ASSUMED -- Slate has been creating episodes for the OBS-era workflow. Whether the episode schema fields match what apps/media/ expects (specifically airDate format used by findEpisodeId() in the post-show pipeline) has not been tested end-to-end on the new platform. Risk: If airDate is stored as a date-only string vs. a datetime, the post-show pipeline's date range query (>= "${date}T00:00:00Z" && < "${date}T23:59:59Z") may not match.

PP-3: Guest token generated and sent

What happens: Director generates an HMAC-signed guest token containing guestId, guestName, showSlug, and 24-hour expiry. The token is embedded in a URL: media.valuefirstteam.com/greenroom/{token}. Who/what does it: Admin calls POST /api/studio/greenroom-token from the Studio Dashboard's GuestPanel component. System writes: None (stateless token generation) Trigger: Host clicks "Generate Link" in Studio Dashboard GuestPanel Status: UNTESTED -- Code exists at src/pages/api/studio/greenroom-token.ts. Token generation uses createHmac('sha256', GREENROOM_SECRET). The GREENROOM_SECRET is confirmed on Vercel. But the endpoint has never been called on production. Token validation at /api/greenroom/validate also untested.

PP-4: Episode prep materials loaded

What happens: The Studio Dashboard fetches episode prep data (talking points, guest context, show notes) from Sanity via /api/studio/episode-prep. Who/what does it: EpisodePrepPanel component queries the API on show selection. System writes: None (read-only) Trigger: Host selects a show in the Studio Dashboard Status: UNTESTED -- Code exists at src/pages/api/studio/episode-prep.ts and src/components/studio/EpisodePrepPanel.tsx. Never executed against production Sanity data through the media platform.


Production (Live Show)

P-1: Admin login

What happens: Admin enters email at /login. If email ends in @valuefirstteam.com, a JWT is generated immediately (no email verification -- domain IS the verification). The JWT is set as an HttpOnly cookie (vf_media_token) with 30-day expiry. Who/what does it: POST /api/auth/login generates token, redirects to GET /api/auth/verify which sets cookie. System writes: Browser cookie Trigger: User visits media.valuefirstteam.com (redirected to /login by middleware) Status: UNTESTED -- Code path is clear: isAdminEmail() checks domain, generateToken() creates JWT, setSession() sets cookie. The middleware at src/middleware.ts reads cookies via getSession(). JWT_SECRET is on Vercel. But nobody has logged in on production. The 302 redirect from / to /login was confirmed (2026-04-12), which proves middleware runs and redirect works, but the login POST and verify GET have not been tested.

P-2: Host login (non-admin)

What happens: Non-admin emails are looked up in VF Team HubSpot via crm/v3/objects/contacts/search. If the contact exists, they get a JWT with role host and their contact ID. If not found, a generic "check your email" message is returned (prevents email enumeration). Who/what does it: POST /api/auth/login calls findContactByEmail(). System writes: Browser cookie (if contact found) Trigger: Host enters email at /login Status: UNTESTED -- HubSpot contact search code is straightforward. HUBSPOT_ACCESS_TOKEN is on Vercel. But the endpoint has never been called on production. Note: Currently there is no actual email-based magic link sent. The login flow for hosts returns a redirect URL directly in the JSON response. This means anyone who knows the email of a HubSpot contact can authenticate as them. This is acceptable for an internal tool but worth noting.

P-3: Studio Dashboard loads shows

What happens: After login, the Studio Dashboard component calls GET /api/studio/shows. The API queries Sanity for all shows, maps slugs between director-format and Sanity-format, and returns the list with hosts, guests, accent colors, and status. Who/what does it: StudioDashboard.tsx fetches on mount. ShowList.tsx renders the show cards. System writes: None (read-only) Trigger: Navigation to /studio (or / after login) Status: UNTESTED -- The Sanity query was tested in isolation (shows exist, PP-1 verified). The full flow through the deployed API route, including the slug mapping between director slugs and Sanity slugs, has not been tested on production.

P-4: Mux live stream creation

What happens: Admin clicks "Go Live" for a show. POST /api/studio/stream creates a Mux live stream with reduced_latency: true and playback_policy: ['public']. Returns streamId, streamKey, playbackId, and RTMP URL. Who/what does it: StreamPanel.tsx calls the API. Stream state is held in-memory on the serverless function (ephemeral). System writes: Mux (new live stream resource) Trigger: Host clicks "Go Live" in StreamPanel Status: UNTESTED -- Code uses Mux REST API v1 with Basic auth (MUX_TOKEN_ID:MUX_TOKEN_SECRET). Credentials confirmed on Vercel. But no live stream has been created through this endpoint. Known concern: Stream state is stored in a module-level let activeStream variable. On Vercel serverless, this is ephemeral -- a cold start loses the state. The code accounts for this ("good enough for single-session use") but it means the GET status endpoint may not find an active stream after a cold start between the POST and a subsequent status check.

P-5: Green Room loads for guest

What happens: Guest opens media.valuefirstteam.com/greenroom/{token}. The page decodes the token payload client-side (name, show) for display, then calls POST /api/greenroom/validate to server-validate the HMAC signature and expiry. Who/what does it: [token].astro page renders GreenRoom.tsx component. DeviceSelector.tsx handles camera/mic selection. System writes: None (token validation is stateless) Trigger: Guest clicks the link from the host Status: UNTESTED -- The page, component, and validation endpoint all exist. GREENROOM_SECRET is on Vercel. The client-side token decode (decodeTokenPayload()) does not validate the HMAC (by design -- validation happens server-side). But no guest has loaded this page on production.

P-6: WebRTC mesh connects participants

What happens: After device setup, the Green Room connects to the Fly.io signaling server (vft-greenroom-signal.fly.dev) via Socket.io. Participants exchange SDP offers/answers and ICE candidates through the signaling server. Mesh topology: every participant has direct P2P connections to every other participant (max 5). Who/what does it: useWebRTC.ts hook manages Socket.io connection and RTCPeerConnection lifecycle. Events: mesh:join, mesh:offer, mesh:answer, mesh:ice-candidate, mesh:participant-joined, mesh:participant-left. System writes: None (P2P connections, signaling server is relay only) Trigger: Guest clicks "Join" after device setup Status: UNTESTED -- Signaling server health check returns 200 (verified 2026-04-12). The Socket.io client library is loaded via CDN (typeof io). The WebRTC hook handles ICE restart on connection failure. But no WebRTC connection has been established through the production deployment. Dependencies: Socket.io CDN script must be included in the greenroom page <head>. ICE uses Google's public STUN server (stun:stun.l.google.com:19302). TURN credentials are received via turn:credentials event from the signaling server if available.

P-7: Studio sees guest states

What happens: The Studio Dashboard connects to the same signaling server as a "director" client. It receives real-time guest state updates (connected, ready, live, audio levels) and displays them in the GuestPanel. Who/what does it: useSignaling.ts hook fetches DIRECTOR_AUTH_KEY from /api/studio/auth-key, then connects to vft-greenroom-signal.fly.dev and emits director:join. Events received: room:guest-list, room:guest-status, room:audio-level. System writes: None (read-only monitoring) Trigger: Admin selects a show in the Studio Dashboard Status: UNTESTED -- DIRECTOR_AUTH_KEY is on Vercel. The signaling server health check passed. But no director connection has been established from the production deployment.

P-8: Canvas compositor renders streams

What happens: CanvasCompositor creates an offscreen 1920x1080 canvas. Participant MediaStreams are drawn into layout positions (solo, 2-up, 3-up, panel -- pixel positions ported from OBS). Lower thirds (name + title, semi-transparent bar with accent line), show logo (top-right), and LIVE indicator (red pill, top-left) are composited on each frame at 30fps via requestAnimationFrame. Audio is mixed via Web Audio API (MediaStreamSource -> destination node). Output: a combined MediaStream (canvas video + mixed audio). Who/what does it: CanvasCompositor.ts class. Layout positions from layouts.ts. Integrated via useCompositor.ts hook in the Studio Dashboard. System writes: None (browser-side rendering) Trigger: Participants join and streams are added via addParticipant() Status: UNTESTED -- All code is client-side JavaScript. The layout pixel positions are mathematically derived from the proven OBS equidistant geometry. The rendering logic (cover-fit video drawing, rounded rectangles, lower thirds) is standard Canvas 2D API usage. But the compositor has never rendered real participant streams on the production deployment. Note: The compositor uses document.createElement('canvas') and document.createElement('video') -- this is browser-only code that cannot run in SSR.

P-9: Mux WHIP push from browser

What happens: MuxStreamPush takes the compositor's output MediaStream and pushes it to a Mux WHIP endpoint via WebRTC. Creates an RTCPeerConnection, adds all tracks, generates an SDP offer, POSTs it to the WHIP endpoint, and sets the answer SDP. The WHIP endpoint URL format: https://global-live.mux.com/api/v1/whip/{stream_key}. Who/what does it: MuxStreamPush.ts class. Triggered from StreamPanel when admin starts the stream. System writes: Mux (live video ingest) Trigger: Admin clicks "Start Stream" after compositor is rendering Status: UNTESTED -- The WHIP client implementation follows the WebRTC-HTTP Ingestion Protocol spec. ICE gathering has a 5-second timeout. Connection state is monitored. But no WHIP push has been attempted on production. Dependencies: Requires a Mux live stream to be created first (P-4) to get the stream key. The WHIP endpoint is constructed from the stream key. Browser must support WebRTC (all modern browsers do). Fallback: If WHIP fails, the StreamPanel shows the RTMP URL (rtmp://global-live.mux.com:5222/app) for OBS-based ingest as a backup path. This fallback display exists in code but has not been tested either.


Post-Production

POST-1: End show triggers post-show pipeline

What happens: Admin clicks "End Show" in StreamPanel. The client calls DELETE /api/studio/stream to signal Mux to complete the stream, then calls POST /api/pipeline/post-show with the streamId, showSlug, and guest list. The post-show endpoint fetches the live stream from Mux, finds the recording asset, looks up the Sanity episode, and returns a pipeline manifest with URLs for each subsequent step. Who/what does it: StreamPanel UI calls the API. post-show.ts orchestrates. System writes: Mux (stream completion signal). Pipeline state in serverless memory. Trigger: Admin clicks "End Show" Status: UNTESTED -- The Mux API calls for stream completion and asset lookup are standard. The Sanity episode lookup uses a date-range query. Pipeline state is in-memory (ephemeral on Vercel -- same cold start concern as P-4).

POST-2: Mux recording asset available

What happens: After the live stream ends, Mux processes the recording into an asset. The asset appears in recent_asset_ids on the live stream object. Asset status transitions from preparing to ready. Playback ID and MP4 URLs become available. Who/what does it: Mux (automatic, no code on our side) System writes: Mux (asset creation) Trigger: Automatic after stream ends Status: UNTESTED -- This is Mux's standard behavior. The post-show pipeline polls for asset readiness (returns 202 "checking" until asset is ready). But we have never had a live stream to produce a recording.

POST-3: Transcription

What happens: POST /api/pipeline/transcribe takes an assetId, calls Mux to get auto-generated caption tracks, parses VTT to plain text, then patches the Sanity episode document with the transcript text, source type, and status. Who/what does it: transcription.ts library. transcribe.ts API route. System writes: Sanity CMS (episode transcript field) Trigger: Called from post-show pipeline manifest or manually Status: UNTESTED -- The Mux captions API and VTT parsing logic exist. The Sanity patch uses the SANITY_API_TOKEN to write directly (note: this bypasses Canon governance -- the code uses raw fetch to the Sanity mutations API). But no transcript has been generated through this path. Governance note: The saveTranscriptToSanity() function writes directly to Sanity via HTTP. This should eventually route through Canon, but since it runs inside a Vercel serverless function (not a Claude Code session), Canon governance does not apply at runtime. The code is correct for its context.

POST-4: Distribution manifest built

What happens: POST /api/pipeline/distribute builds a distribution manifest with three targets: YouTube (upload data structure), website (Sanity episode patch with muxPlaybackId and muxAssetId), and podcast (RSS feed entry with Mux audio URL). Each target starts as "pending." Who/what does it: distribution.ts library. distribute.ts API route. System writes: Depends on which targets are executed Trigger: Called from post-show pipeline manifest Status: UNTESTED -- The data structure builders (buildYouTubeUploadData, buildSanityEpisodePatch, buildPodcastFeedEntry) are pure functions. They produce correct-looking data. But no distribution has been executed.

POST-5: YouTube upload

What happens: The distribution manifest contains YouTube upload data (title, description, tags, category, privacy, MP4 URL). The actual upload is handled by the Broadcast agent using the Google YouTube Data API v3. Who/what does it: Broadcast agent (external to apps/media/) System writes: YouTube Trigger: Agent-initiated from distribution manifest Status: NOT BUILT -- distribution.ts builds the YouTube data structure, but the actual upload code does not exist in apps/media/. The comment says "The actual upload is handled by the Broadcast agent using googleapis." This dependency on the Broadcast agent running outside the platform means YouTube distribution requires agent orchestration that has not been wired up for the new platform.

POST-6: Website episode patch

What happens: The distribution manifest contains a Sanity episode patch (muxPlaybackId, muxAssetId, videoStatus: "ready", duration). When applied, the website episode player renders the Mux recording. Who/what does it: Canon agent applies the patch, or the distribute endpoint applies it directly if execute: true is passed. System writes: Sanity CMS Trigger: Called from distribution manifest Status: UNTESTED -- The patch data structure is correct. Whether the website episode player reads muxPlaybackId to render the Mux player is a website-side concern not tested from the media platform.

POST-7: Podcast feed entry

What happens: The distribution manifest contains a podcast feed entry with audio URL (https://stream.mux.com/{playbackId}/audio.m4a), duration, and metadata. The actual RSS feed update is handled by the content pipeline. Who/what does it: Content pipeline (external to apps/media/) System writes: RSS feed (wherever it is hosted) Trigger: Agent-initiated from distribution manifest Status: NOT BUILT -- distribution.ts produces the data structure and audio URL. There is no code in apps/media/ or elsewhere that takes this data and writes an RSS feed. The podcast distribution pipeline does not exist.

POST-8: Guest souvenir package

What happens: POST /api/pipeline/souvenir builds a souvenir package for each guest: full recording download URL, optional clip URL (Mux time-based clipping), social copy templates (LinkedIn + Twitter/X), and quote card data structures (3 formats: square, landscape, story). The souvenir is displayed in the GreenRoom's postshow view. Who/what does it: souvenir.ts library. souvenir.ts API route. SouvenirPostshow.tsx component. System writes: None (returns data for display) Trigger: Called from post-show pipeline manifest per guest Status: UNTESTED -- The data structures are pure functions. The Mux clip URL (stream.mux.com/{id}.mp4?start=X&end=Y) and full recording URL (stream.mux.com/{id}/high.mp4) are standard Mux URL patterns. The social copy is template text. Quote card rendering is delegated to the Pixel agent. None of this has been executed on production. Note: The souvenir postshow component (SouvenirPostshow.tsx) uses the guest's original Green Room token to access the postshow view. Token validation for souvenirs extends the expiry window to 365 days from issuance (validateTokenForSouvenir()).


Agent Responsibilities

Agent Role in Production Integration Point
Director Platform operation -- show setup, go live, end show, guest links Direct operator of apps/media/ APIs
Prelude Pre-production -- validates episode exists, checks Mux config Upstream of Director
Encore Post-production -- verifies recording arrived, checks asset status Reads post-show pipeline state
Splice Recording linking -- connects Mux asset to Sanity episode Uses distribution manifest data
Canon Sanity writes -- episode transcript patches, distribution patches Governance gateway for CMS writes
Broadcast Distribution -- YouTube upload (when built) Consumes distribution manifest
Pixel Visual assets -- quote card rendering for souvenirs Consumes quote card data structures
Marquee Media BU leader -- scheduling, health, delegation Spawns Director for production tasks

Known Gaps

Gap 1: YouTube and LinkedIn simulcast

Status: NOT BUILT Detail: distribution.ts references YouTube upload as an agent responsibility, but no code exists for the actual YouTube Data API upload, either in apps/media/ or as a standalone script. LinkedIn simulcast is not referenced anywhere in the codebase. The platform currently has no path to distribute live or recorded content to YouTube or LinkedIn.

Gap 2: Podcast RSS feed generation

Status: NOT BUILT Detail: distribution.ts produces a PodcastFeedEntry data structure with a Mux audio URL. No code exists to generate, update, or host a podcast RSS feed.

Gap 3: Serverless state persistence

Status: KNOWN LIMITATION Detail: Both the stream API (activeStream) and the post-show pipeline (pipelineStore) use in-memory module-level variables. On Vercel serverless, these are lost on cold starts. A show that creates a stream (POST) may not find it on a subsequent status check (GET) if the function instance was recycled. This is documented in code comments as "good enough for single-session use" but is a reliability concern for production.

Gap 4: TURN server for restrictive networks

Status: ASSUMED Detail: The signaling server can send TURN credentials via the turn:credentials event. Whether the Fly.io signaling server actually provides TURN credentials, and whether those credentials are configured, is unknown. Without TURN, guests behind restrictive corporate firewalls or symmetric NATs cannot establish WebRTC connections.

Gap 5: Actual email-based magic link for hosts

Status: NOT BUILT Detail: The login flow for non-admin hosts returns the verify URL directly in the JSON response. There is no email sending. The code comments reference "magic link login" but the "magic" part (email delivery) is not implemented. Currently, any request with a HubSpot contact's email gets immediate access.

Gap 6: Souvenir clip selection UI

Status: NOT BUILT Detail: souvenir.ts supports time-based clip generation (clipStart, clipEnd parameters). The SouvenirPostshow.tsx component exists. But there is no UI for a host to select clip start/end times or choose a guest quote for the quote card. These would need to be passed manually via the API.


Test Plan (Priority Order)

Testing should follow the dependency chain of actual production usage. Each test builds on the previous one's success.

Test 1: Login flow

What: Navigate to media.valuefirstteam.com, confirm redirect to /login. Enter a @valuefirstteam.com email. Confirm redirect to Studio Dashboard. Validates: Middleware redirect, login API, token generation, cookie setting, verify endpoint, session read. Dependency: None. This gates everything else.

Test 2: Shows API returns Sanity data

What: After login, confirm the Studio Dashboard displays shows from Sanity (not fallback). Check that show names, accent colors, and host/guest lists are populated. Validates: /api/studio/shows Sanity query, slug mapping, ShowList component rendering. Dependency: Test 1 (must be logged in).

Test 3: Guest token generation

What: From the Studio Dashboard, select a show and generate a guest link via the GuestPanel. Verify the returned URL has the format media.valuefirstteam.com/greenroom/{token}. Validates: /api/studio/greenroom-token endpoint, HMAC token generation, GREENROOM_SECRET usage. Dependency: Test 2 (must have show selected).

Test 4: Green Room loads and validates token

What: Open the guest link from Test 3 in an incognito window. Confirm the Green Room page loads, displays the guest's name and show name (decoded from token), and successfully validates the token server-side. Validates: [token].astro page, GreenRoom.tsx component, DeviceSelector.tsx, /api/greenroom/validate endpoint. Dependency: Test 3 (need a valid token).

Test 5: WebRTC signaling connection

What: With a guest in the Green Room and an admin in the Studio Dashboard, confirm both connect to the signaling server. Verify the Studio shows the guest in the GuestPanel with "connected" state. Validates: Socket.io connection to Fly.io, mesh:join / director:join events, room:guest-list event, DIRECTOR_AUTH_KEY authentication. Dependency: Tests 1 + 4 (admin logged in, guest in Green Room).

Test 6: WebRTC media flow

What: With two participants connected via signaling, confirm video and audio streams flow between them. Verify the Green Room VideoGrid shows the remote participant's video. Validates: SDP offer/answer exchange, ICE candidate exchange, RTCPeerConnection establishment, ontrack event, MediaStream display. Dependency: Test 5 (signaling connected).

Test 7: Canvas compositor renders

What: With at least one participant stream, confirm the canvas compositor renders the correct layout (solo for 1, 2-up for 2). Verify lower thirds, accent color, and LIVE indicator. Validates: CanvasCompositor class, layout positions from layouts.ts, video-to-canvas drawing, captureStream(30). Dependency: Test 6 (need real MediaStreams).

Test 8: Mux stream creation

What: Click "Go Live" in the Studio Dashboard. Confirm the Mux API creates a live stream and returns a stream key. Validates: POST /api/studio/stream, Mux REST API authentication, live stream creation. Dependency: Test 2 (must have show selected). Incurs Mux API usage.

Test 9: WHIP push to Mux

What: With the compositor rendering (Test 7) and a Mux stream created (Test 8), start the WHIP push. Confirm the composited stream appears on the Mux dashboard (stream.new or the Mux Studio). Validates: MuxStreamPush class, WHIP endpoint negotiation, SDP/ICE flow, Mux ingest. Dependency: Tests 7 + 8. This is the core production capability.

Test 10: End show and post-show pipeline

What: End the stream (DELETE). Call the post-show pipeline. Confirm it finds the recording asset from Mux and returns a pipeline manifest with correct URLs and body payloads. Validates: Stream completion, asset discovery, episode lookup, pipeline manifest generation. Dependency: Test 9 (need a completed stream with a recording).

Test 11: Transcription pipeline

What: Trigger transcription from the pipeline manifest. Confirm Mux captions are fetched (or that the "waiting for captions" state is returned if Mux hasn't generated them yet). If captions exist, confirm the Sanity episode is patched with the transcript. Validates: Mux captions API, VTT parsing, Sanity episode patch. Dependency: Test 10 (need a recording asset).

Test 12: Distribution manifest

What: Trigger distribution from the pipeline manifest. Confirm the manifest contains correct YouTube upload data, Sanity episode patch data, and podcast feed entry data. Validates: Data structure builders, Mux URL construction. Dependency: Test 10 (need a recording asset).


Revision History

Date Change Author
2026-04-12 Initial document. Build verified, deployment confirmed, 7 env vars verified, signaling health confirmed. All production flows marked UNTESTED. Q (via Marshal)