diff --git a/doc/plans/2026-10-24-recurring-server-sleep-stack.md b/doc/plans/2026-10-24-recurring-server-sleep-stack.md index aebb2957e58..e0b072830ee 100644 --- a/doc/plans/2026-10-24-recurring-server-sleep-stack.md +++ b/doc/plans/2026-10-24-recurring-server-sleep-stack.md @@ -1211,3 +1211,14 @@ Parent Discord #17557 at d3187f76a4 and company cleanup #17561 at e8febb30eb now have no failed or pending checks (Paperclip Review skipped). No merge was made. The older #17490 E2E cancellation failure remains unresolved. The entire original inventory stays in scope, with voice the next separate reason after receivers. + + +Receiver discovery is published and attached as [#17572](https://github.com/paperclipai/paperclip/pull/17572) +at 643f4766227e15006b684d2cc7bea7ddd5cc2147, based on #17557. Its live CI run is +40173962833; security and reviews are pending. All 218 selected local cases, +workspace types and build pass. The next audit splits voice into five separate +reasons: live-session recovery, reply push, inbound admission expiry/provider +cleanup, inbound history retention, and call reports. See +[the voice owner audit](2026-10-25-voice-maintenance-sleep-audit.md). +Branch codex/sleep-21-voice-session-recovery starts with the first reason; the +other four remain explicit required slices. No voice migration is implemented yet. diff --git a/doc/plans/2026-10-25-voice-maintenance-sleep-audit.md b/doc/plans/2026-10-25-voice-maintenance-sleep-audit.md new file mode 100644 index 00000000000..5a15c8d21ae --- /dev/null +++ b/doc/plans/2026-10-25-voice-maintenance-sleep-audit.md @@ -0,0 +1,89 @@ +# Voice maintenance sleep migration audit + +Baseline: receiver parent #17572, head 643f4766227e15006b684d2cc7bea7ddd5cc2147. +Branch: codex/sleep-21-voice-session-recovery. +Status: owner audit only. No voice polling removal is implemented or verified. + +## Separate the reasons before changing the timer + +The app invokes `voice.reconcile()` from the remaining one-second chat lane. +The public service is created through `chat-channels/speko/runtime.ts`; the +implementation is `voice/voice-session-service.ts`. Its reconcile method hides +five distinct obligations. Each needs its own PR, preserving the others until +its producer and deadline contracts are implemented. + +| Slice | Existing behavior | Proposed owner and trigger | +| --- | --- | --- | +| Live session recovery (this branch) | Reads experimental settings, scans live sessions for expiry, ending, disabled endpoints/connections, generation mismatch and 30-second active-state checks | Voice session owner; committed lifecycle/authorization changes plus real expiry, cleanup and active-call inspection deadlines | +| Reply push | `pushVoiceReplies` scans for provider delivery work | Existing reply/session records, committed source changes, pending provider delivery and claim/retry deadlines | +| Inbound admission expiry and provider cleanup | Scans guest/approval/denied/expired calls past expiresAt, inspects/ends exact provider sessions | Inbound owner; admission/lifecycle/decision changes, saved expiry and unresolved end retries | +| Inbound history retention | Deletes denied/expired/ended rows without sessionId or intakeIssueId once both expiresAt and updatedAt are seven days old | Retention owner; deadline max(expiresAt, updatedAt) + seven days, mutation invalidation and startup reconstruction | +| Call reports | Fetches reports for terminal sessions with provider IDs within 24 hours of endedAt; follows nextCheckAt backoff | Report owner; terminal/lifecycle/report events and saved report recheck deadlines, preserving late recording/transcript behavior | + +Removing one invocation of the outer reconcile method would drop several owners. +First expose the live-session operation independently, register it with the +existing coordinator, and leave the other four calls in their existing lane. +The outer wrapper can disappear only after the remaining owners migrate. + +## Live-session source of truth and invariants + +Existing chat_voice_sessions, chat_endpoints, tool_connections, access/bindings, +and instance settings define eligibility and current authority. No new table is +needed. Current live states are reserved, creating, creation_unknown, connecting, +active and ending. Terminal states are ended, failed and expired. + +The current pass uses 25 oldest updatedAt rows. A session qualifies when expired, +ending, disabled, generation-mismatched, or when a connecting/active session has +not been inspected for 30 seconds. Active-session checks validate current task, +caller and guest authority, then inspect the exact saved providerSessionId to +recover a hangup without a final browser callback. A live call is concrete work; +its bounded provider-state inspection is not empty-instance discovery. + +Unknown creation remains reserved until expiresAt plus two minutes of teardown +grace. The provider maximum duration is the final bound. Never replay creation +because its response was lost. Ending retains exact provider cleanup debt until +termination is confirmed. An error records cleanup_pending and updatedAt. +Preserve these business boundaries and distinguish a database error from a real +revocation before changing recovery behavior. + +An inactive instance with no live sessions must stop this owner's settings and +session queries after reconstruction. Retained terminal sessions alone must not +create a live-session deadline. Report work for those rows belongs to its own +slice and stays active under its existing contract for now. + +## Required producer audit + +- voice-session-store.reserve, including inbound and guest binding paths; +- service start claim, successful provider binding, unknown creation, failed + creation, concurrent end, and failure to persist provider identity; +- closeProvider and user end; inspect and tool callbacks that change session state; +- inbound lifecycle, guest intake, approval/denial and end paths that write sessions; +- signed lifecycle callbacks and report callbacks (separate saved report debt); +- endpoint/connection pause, removal, generation or credential changes; +- feature disable, membership/access revocation, task reassignment, binding and + environment changes used by authorizeSession; +- restart/resume and the supported single active writer contract. + +Use existing transaction ownership. Add intent before the write; transaction +outcome uncertainty remains the shared database layer's responsibility. Prefer +session/endpoint/company scopes rather than refreshing all sessions per mutation. +Authorization stays with the voice store; provider requests stay with the Speko +provider. Shared scheduling consumes normalized work identities and deadlines. + +## Required proof + +Reproduce recurring SQL in no-session and retained-terminal-history states before +removal. Test first real session creation after quiet; nested transaction commit +and rollback; uncertain creation without duplicate provider calls; expiry/grace; +ending cleanup failure and retry; active hangup recovery; revoked membership, +changed task binding and feature disable; guest isolation; multiple-company +fairness and backlog; failed scoped reads; mutation during sweep; drain/resume; +shutdown joining admitted provider work. Keep all four adjacent obligations and +their existing tests running. Inspect `voice-session-store.integration.test.ts` +and current producer fixtures before inventing new test contracts. + +The receiver parent has 218 selected passing tests, workspace typecheck/build, +and is published/attached. CI run 40173962833 is live on its exact head; Greptile, +security and Paperclip review are pending. This is not stack completion or merge +permission. The full original ledger, #17490 failure and final master +reconciliation remain required.