Summary
POST /api/v1/conversations/{id}/accept never makes the caller the owner when called with a JWT. The endpoint passes the caller's user id (JWT sub) where the switchboard expects an agent id; the capacity resolver finds no Agent with that id, and the switchboard returns Success = false, "Agent has no capacity.". The endpoint still answers HTTP 200, so the Web agent workspace shows the "accepted" toast while the conversation stays Offered.
Found by code reading on main @ 3f4590ed (v2.23.0); not reproduced at runtime.
Call chain
src/Verbara.Platform.Api/Endpoints/ConversationEndpoints.cs:145-146 — GetCurrentAgentId(context) is passed to AcceptAsync as the agent id.
ConversationEndpoints.cs:978-990 — GetCurrentAgentId returns the JWT sub, which is the user id.
src/Verbara.Platform.Switchboard/ConversationSwitchboard.cs:92-94 — HasCapacityAsync(tenantId, agentId, …); when false it returns OwnershipResult(false, …, "Agent has no capacity.").
src/Verbara.Platform.Queues/Services/InMemoryAgentCapacityService.cs:59-61 → AgentCapacityResolver.cs:28-30 — IAgentStore.GetByIdAsync(tenantId, agentId) returns null for a user id, which the resolver treats as "agent does not exist", so there is no capacity.
- Agent ids never equal user ids: the only
Agent creation site is src/Verbara.Platform.Api/Endpoints/AdminEndpoints.cs:453 (AgentId = EntityId.New()).
ConversationEndpoints.cs:147 returns TypedResults.Ok(result) whatever result.Success is.
- Web:
Verbara.Platform.Web/src/core/api/hooks/use-conversations.ts:127-139 treats any 2xx as success and shows toasts.conversations.accepted.
The comment at ConversationEndpoints.cs:980-986 explains that reading sub first stopped JWT agents from getting a random id. The id is now stable, but it is still a user id, not the agent id.
Why tests stay green
tests/Verbara.Platform.Api.Tests/ConversationEndpointTests.cs:51-58 (AcceptConversation_ShouldReturn200_WhenSwitchboardCallSucceeds) posts with a placeholder id and accepts 200, 400 or 500, so it cannot detect a failed ownership change.
Suggested direction
- Resolve the caller's
Agent in the tenant and pass its AgentId; return a 4xx when the caller has no agent.
- Map
OwnershipResult.Success == false to a non-2xx response (or have the Web check success), so a failed accept is visible.
- Add an integration test: a JWT user with an
Agent row accepts an offered conversation, and the owner becomes that AgentId.
- Other handlers in the same file also treat
GetCurrentAgentId as an agent id (:120,523,768,800,815); they are worth checking for the same mismatch.
Summary
POST /api/v1/conversations/{id}/acceptnever makes the caller the owner when called with a JWT. The endpoint passes the caller's user id (JWTsub) where the switchboard expects an agent id; the capacity resolver finds noAgentwith that id, and the switchboard returnsSuccess = false,"Agent has no capacity.". The endpoint still answers HTTP 200, so the Web agent workspace shows the "accepted" toast while the conversation staysOffered.Found by code reading on
main@3f4590ed(v2.23.0); not reproduced at runtime.Call chain
src/Verbara.Platform.Api/Endpoints/ConversationEndpoints.cs:145-146—GetCurrentAgentId(context)is passed toAcceptAsyncas the agent id.ConversationEndpoints.cs:978-990—GetCurrentAgentIdreturns the JWTsub, which is the user id.src/Verbara.Platform.Switchboard/ConversationSwitchboard.cs:92-94—HasCapacityAsync(tenantId, agentId, …); when false it returnsOwnershipResult(false, …, "Agent has no capacity.").src/Verbara.Platform.Queues/Services/InMemoryAgentCapacityService.cs:59-61→AgentCapacityResolver.cs:28-30—IAgentStore.GetByIdAsync(tenantId, agentId)returnsnullfor a user id, which the resolver treats as "agent does not exist", so there is no capacity.Agentcreation site issrc/Verbara.Platform.Api/Endpoints/AdminEndpoints.cs:453(AgentId = EntityId.New()).ConversationEndpoints.cs:147returnsTypedResults.Ok(result)whateverresult.Successis.Verbara.Platform.Web/src/core/api/hooks/use-conversations.ts:127-139treats any 2xx as success and showstoasts.conversations.accepted.The comment at
ConversationEndpoints.cs:980-986explains that readingsubfirst stopped JWT agents from getting a random id. The id is now stable, but it is still a user id, not the agent id.Why tests stay green
tests/Verbara.Platform.Api.Tests/ConversationEndpointTests.cs:51-58(AcceptConversation_ShouldReturn200_WhenSwitchboardCallSucceeds) posts with a placeholder id and accepts 200, 400 or 500, so it cannot detect a failed ownership change.Suggested direction
Agentin the tenant and pass itsAgentId; return a 4xx when the caller has no agent.OwnershipResult.Success == falseto a non-2xx response (or have the Web checksuccess), so a failed accept is visible.Agentrow accepts an offered conversation, and the owner becomes thatAgentId.GetCurrentAgentIdas an agent id (:120,523,768,800,815); they are worth checking for the same mismatch.