WordloopWordloop
WorkMeeting RecordingTechnical Design DocMilestones11 Recording Hardening

Core — Recording Limits

max_duration_seconds enforcement, RecordingDurationWarningEvent and auto-stop, session lock preventing a second active recording per user, and the recording_event_history diagnostics table.

Core — Recording Limits

Owner: Core Engineer Domain: core Complexity: M Prerequisite: Milestone 05 merged

When this slice is complete, a per-start max_duration_seconds can only lower the server's RECORDING_MAX_DURATION_SECONDS ceiling, never raise it; Core warns the client at roughly 80% of the limit via duration_warning.v1 and auto-stops at the limit with stopped.v1 { reason: "duration_limit" }, continuing the recording through the normal finalization pipeline. A second recording start for the same user — a different meeting, or the same meeting from another tab — is refused session_conflict while the original session continues unaffected, and GET /meetings?has_active_recording=true lists every live session. Every lifecycle, health, and stop-reason signal is captured in a newest-first diagnostics history, exposed only to service auth.

Required Capabilities

  • StartRecordingCommand.max_duration_seconds overrides the server ceiling downward only; the effective limit is echoed on started.v1.
  • Core emits com.wordloop.recording.duration_warning.v1 { meeting_id, remaining_seconds, auto_stop_at } at roughly 80% of the limit.
  • Core auto-stops the recording at the limit with stopped.v1 { reason: "duration_limit", last_received_sequence } and no error event, then proceeds through the normal post_processing → completed pipeline.
  • A second recording start for the same user — a different meeting, or the same meeting under a different client_recording_id — is refused with recording.error.v1 { code: "session_conflict", severity: "error" }; the original session is unaffected.
  • GET /meetings?has_active_recording=true lists every meeting with an active or stopping recording for the user; a stopped meeting leaves the list.
  • GET /meetings/{id}/recording/events (service auth only — bearer and impersonated-user auth get 403) returns a newest-first diagnostics history including started, status_changed, health, duration_warning, and stopped entries, each with id, created_at, and a payload object.
  • GET /meetings/{id}/recording/events for an unknown meeting answers 404.

Dependencies

  • Milestone 05 (live-audio-durability-recovery) must be merged — the recordings row and status-transition machinery this slice extends with limits, the session lock, and diagnostics history.

Domain Notes

max_duration_seconds is a ceiling that individual starts may lower but never raise — the test compose sets RECORDING_MAX_DURATION_SECONDS=30 and starts pass 12, never the reverse. The session lock is per-user, not per-meeting: a second tab is refused even when it targets a different meeting than the one already recording.

Test Cases

Test cases map to tests/bets/meeting-recording/test_milestone_11_recording_hardening.py. Run via ./dev test bet meeting-recording.

TestLocationAssertion
test_long_session_reaches_target_or_clear_limit_statetest_milestone_11started.v1 echoes the lowered max_duration_seconds; duration_warning.v1 arrives with 1 <= remaining_seconds <= limit, auto_stop_at == started_at + limit (±1s), at roughly 50–100% of the limit's elapsed time.
test_background_tab_behavior_is_visibletest_milestone_11After a resume command with last_client_sequence, resumed.v1 is received and GET /meetings/{id}/recording shows status: "active" with last_received_sequence matching what the client reported.
test_no_audio_input_warns_or_auto_stopstest_milestone_11Core relays a recording.health.v1 { code: "no_audio_detected", status: "degraded", recoverable: true } event; degraded_reasons on GET /recording includes no_audio_detected; the recording status stays active (warn, not stop); the diagnostics history records a health event with that code.
test_recording_auto_stops_at_hard_limitstest_milestone_11The recording auto-stops with reason: "duration_limit" at the configured limit (±15s tolerance), the warning precedes the stop, next_status is composing_audio or awaiting_gap_upload, and the meeting is no longer listed under has_active_recording=true.
test_multi_tab_guard_blocks_conflicting_recording_controltest_milestone_11A second tab starting the same meeting (or a different one) gets session_conflict with severity: "error"; the original tab keeps recording and its frames continue to be accepted; has_active_recording=true lists the meeting as active.
test_recording_diagnostics_capture_health_and_stop_reasonstest_milestone_11GET /recording/events refuses bearer and impersonated-user auth with 403; the newest-first history includes started, health, duration_warning, and stopped(duration_limit); status transitions include stopping → draining_ml → composing_audio/awaiting_gap_upload → ... → post_processing; an unknown meeting answers 404.

Completion Checklist

  • Code merged and deployed
  • Bet progress tests pass (./dev test bet meeting-recording)
  • Permanent service tests implemented per testing strategy
  • Code review completed
  • API review completed (GET /meetings/{id}/recording/events diagnostics endpoint)
  • Testing review completed
  • Schema review completed (recording_event_history table)
  • System documentation updated (as applicable)

On this page