fix(channels): make runtime provider state authoritative (#3580)

* fix(channels): make runtime provider state authoritative

* make format

* fix(channels): close runtime provider config races and status gaps

Address review findings on the runtime-provider-state change:

- configure/disconnect now re-read the live app.state.channels_config
  after the worker await and mutate only the affected provider key in
  place, so a concurrent mutation for a different provider is no longer
  clobbered by a stale pre-await snapshot.
- disconnect revokes DB connection rows before committing the store and
  cache, so a repo failure cannot leave the store/cache "disconnected"
  while the DB keeps "connected" rows a later re-configure would
  silently reactivate.
- _provider_response preserves non-connected statuses (e.g. revoked)
  when the provider is unavailable, only masking a stale "connected"
  row as not_connected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Nan Gao
2026-06-17 01:45:46 +02:00
committed by GitHub
parent 43dba448ad
commit 926406e0d6
5 changed files with 289 additions and 27 deletions
@@ -177,6 +177,24 @@ class ChannelConnectionRepository:
await session.commit()
return True
async def disconnect_provider_connections(self, *, provider: str) -> int:
"""Revoke all active user connections for an instance-wide provider removal."""
async with self.session_factory() as session:
result = await session.execute(
select(ChannelConnectionRow.id).where(
ChannelConnectionRow.provider == provider,
ChannelConnectionRow.status != "revoked",
)
)
connection_ids = [row_id for row_id in result.scalars()]
if not connection_ids:
return 0
await session.execute(update(ChannelConnectionRow).where(ChannelConnectionRow.id.in_(connection_ids)).values(status="revoked"))
await session.execute(delete(ChannelCredentialRow).where(ChannelCredentialRow.connection_id.in_(connection_ids)))
await session.commit()
return len(connection_ids)
async def store_credentials(
self,
connection_id: str,