mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-30 21:49:18 +01:00
fix(server): fence late voice binding during expiry recovery
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
f4b99ec0fb
commit
8f5e861dbd
5 files changed
+202
-7
No files matched your search
@@ -0,0 +1,97 @@
|
||||
# Voice session recovery without idle discovery
|
||||
|
||||
The live-session worker replaces the session-discovery portion of the recurring
|
||||
voice maintenance call. It uses the existing delivery coordinator, transaction
|
||||
signals and shared scheduler. It adds no tables or migrations. Reply delivery,
|
||||
inbound admission cleanup, inbound history retention and call reports are separate
|
||||
obligations; their existing maintenance remains until their own migrations land.
|
||||
|
||||
## Work and deadlines
|
||||
|
||||
Existing `chat_voice_sessions` rows are authoritative. Startup reconstructs live
|
||||
sessions in UUID pages of 100, joined to their same-company endpoint and connection.
|
||||
Terminal history does not create a live-session recovery obligation. With no live
|
||||
sessions, the worker also avoids reading experimental settings after reconstruction.
|
||||
|
||||
| State | Next obligation |
|
||||
| --- | --- |
|
||||
| No provider identity yet | Existing `expiresAt` plus two minutes of teardown grace; no provider creation replay |
|
||||
| Known provider, ending or invalidated configuration | Immediate cleanup; a recorded cleanup failure waits 30 seconds |
|
||||
| Connecting or active call | Earlier of session expiry and `updatedAt + 30 seconds` for current authorization and missed-hangup inspection |
|
||||
| Other live state with provider identity | Saved session expiry |
|
||||
| Ended, failed or expired | No live-session work; reports have a separate owner |
|
||||
|
||||
Provider maximum duration remains the final bound for uncertain creation. A
|
||||
late provider response can bind identity while expiry is being considered. The
|
||||
expiration write requires that provider identity is still absent, so a newly
|
||||
known call retains exact-call cleanup rather than being marked terminal prematurely.
|
||||
|
||||
Each due call is reloaded before execution. The voice store checks current
|
||||
company, endpoint, credentials, caller, task, guest sandbox and binding authority.
|
||||
A 403/404 means authority no longer permits the call. A transient lookup error
|
||||
retains a retry; it does not authorize ending an otherwise valid call. The existing
|
||||
voice service owns provider inspection and termination, including single-flight
|
||||
termination and ambiguous-response confirmation. Recovery never creates a call.
|
||||
|
||||
## Producers and transaction ownership
|
||||
|
||||
| Owner | Changes that wake live-session recovery |
|
||||
| --- | --- |
|
||||
| `voice-session-store.ts` | Reservation, guest promotion, connecting-to-active transition and reply cursor writes that change the inspection timestamp |
|
||||
| `voice-session-service.ts` | Creation claim, binding, known/unknown creation failure, user end, cleanup outcome and foreground inspection |
|
||||
| `voice-inbound-service.ts` | Guest session creation, guest denial and signed terminal lifecycle callbacks |
|
||||
| `voice-reply-push.ts` | Confirmed remote end and unknown delivery outcome that changes session state/timestamp |
|
||||
| Chat configuration helper | Endpoint configuration, pause, reconnect, generation/credential changes, connection hints and company removal |
|
||||
| Generic tool/access services | Voice connection API mutation, removal and credential revocation during access removal |
|
||||
| Instance settings | Explicit chat-connector feature enable/disable |
|
||||
|
||||
Existing transactions register the awaited signal before the session/configuration
|
||||
write. Standalone session updates use the small `withVoiceSessionChange` helper.
|
||||
Provider requests remain outside database transactions. Nested writes retain outer
|
||||
commit ownership, rollback does not dispatch work, and uncertain commit settlement
|
||||
stays with the shared database protocol.
|
||||
|
||||
Membership changes, task reassignment, phone preferences and other authorization
|
||||
inputs that do not mutate the session or connection remain checked on the existing
|
||||
30-second active-call inspection deadline, as well as on use. This slice does not
|
||||
promise instant cleanup for every authority mutation or add cross-cutting voice
|
||||
hooks throughout those owners. Session activity that changes `updatedAt` retains
|
||||
the existing inspection-time semantics. The session expiry is always an upper
|
||||
bound for authorized active work.
|
||||
|
||||
The generic chat connection hint does not always carry a transport. It can cause
|
||||
a bounded empty voice lookup after a chat configuration write. It does not create
|
||||
idle polling or a recurring deadline. The shared helper contains only routing;
|
||||
voice eligibility, authority and provider policy stay with the voice owner.
|
||||
|
||||
## Failure and lifecycle behavior
|
||||
|
||||
Refreshes coalesce by scope, with a full refresh covering already pending narrower
|
||||
hints. Changes arriving during refresh remain dirty. Failed refreshes block use of
|
||||
stale cached entries in that scope. Per-scope and per-call failure backoff starts
|
||||
at 30 seconds and caps at five minutes. New hints do not erase failure backoff.
|
||||
Batches rotate across companies, process at most 25 calls, and continue an already
|
||||
due backlog after one second. A failed provider does not stall independent calls.
|
||||
|
||||
Drain and standby retain dirty work without passive SQL. Resume processes that
|
||||
work, and restart reconstructs deadlines from the rows. Shutdown joins admitted
|
||||
provider work and removes the coordinator's subscriptions and timers. Retained
|
||||
terminal history becomes quiet after any already committed hints settle.
|
||||
|
||||
Process-local signals support the existing single active database writer and its
|
||||
shared clients. Independent SQL writers or another replica need an explicit wake
|
||||
contract. This change neither expands Cloud alarm coverage nor relaxes sleep
|
||||
eligibility. Active calls and their real deadlines remain obligations; local worker
|
||||
quiet is not proof of host suspension or fleet savings.
|
||||
|
||||
## Verification
|
||||
|
||||
The voice integration suite exercises actual service producers, guest intake,
|
||||
signed callbacks, current authority, unknown creation and adjacent reply/report
|
||||
behavior. The recovery-owner suite uses real PostgreSQL and controlled provider
|
||||
operations for outer commit/rollback, drain, restart, scope failure, mutation during
|
||||
refresh, paging, company fairness, shutdown and the late-provider-binding race.
|
||||
The latter race failed before the write-time guard and passes afterward.
|
||||
|
||||
The dated audit records exact commands, results, initial fixture failures and
|
||||
remaining acceptance work: [voice migration audit](plans/2026-10-25-voice-maintenance-sleep-audit.md).
|
||||
@@ -141,3 +141,26 @@ Outstanding before publication: complete producer/authorization-change audit,
|
||||
current-state races, cross-company fairness and failed-scope coverage, affected
|
||||
shared producer consumers, full workspace verification and remote gates. The
|
||||
other four voice duties and every remaining original stack item remain required.
|
||||
|
||||
|
||||
## Producer and race audit completion
|
||||
|
||||
See [the live-session contract](../idle-voice-session-recovery.md) for the complete
|
||||
producer mapping, existing transaction ownership and retained active authorization
|
||||
inspection contract. All production session writers are covered. Phone preferences,
|
||||
membership and task authority remain checked on the existing active-call deadline
|
||||
and on use; no new recurring empty inventory scan replaces their absence of signals.
|
||||
|
||||
A late provider binding between grace-period discovery and expiration was reproduced:
|
||||
the previous update marked a connecting call expired despite its new provider ID.
|
||||
The expiration update now requires provider identity to remain absent. The regression
|
||||
proves the known call stays live for exact-provider cleanup. All 11 recovery lifecycle
|
||||
cases pass, including company fairness and failed-scope fencing.
|
||||
|
||||
Expanded real-service coverage passes for endpoint pause, generic voice connection
|
||||
disable, task reassignment and signed guest lifecycle settlement. Shared consumer
|
||||
verification passes 469 tool-access, 18 access-removal, and 207 settings/provider
|
||||
cases. Workspace typechecking passes. A fresh final voice/receiver/startup selection
|
||||
and the full local test command are running; build/publication/remote gates remain.
|
||||
No full local matrix result is claimed from targeted tests. Initial fixture errors
|
||||
and the reproduced provider-binding race remain recorded above and in test logs.
|
||||
Reference in new issue
Block a user