fix(ui): keep late-approval fast path cross-channel; surface identity errors in chat

PR #27 scoped the lock-free parked-checkpoint answer to exact checkpoint
task/session equality. Company gate cards are raised by role work-item
tasks but answered from the run's anchor chat, whose task id only
appears in payload["task_ids"] — the exact-match guard silently
disabled the fast path for precisely the answers it exists for and
re-opened the project-0012 late-approval lock wedge. Scope by the same
linkage set _find_parked_checkpoint_for_deferred_resume uses (checkpoint
task/session plus payload waiting_task_id/task_ids), keep rejecting
unrelated channels, and keep legacy checkpoints without linkage
deliverable.

The new fail-closed identity errors in _process_session_message raised
out of fire-and-forget background tasks (_track_session), where they are
only logged and the user's message silently vanishes. Surface them as a
visible system chat error and stop instead of raising.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
LZH-YS1998
2026-08-06 15:37:00 +08:00
parent f6b4fc3683
commit 57610e57a6
3 changed files with 205 additions and 54 deletions
+37 -14
View File
@@ -2061,16 +2061,28 @@ class TestWSHandlerSessionSend(unittest.IsolatedAsyncioTestCase):
return_value="wrong-active-org"
)
with self.assertRaises(ServiceError) as context:
await self.handler._process_session_message(
self.task_id,
"approve",
session_id=self.session_id,
)
# Fail closed without raising: this coroutine usually runs as a
# fire-and-forget background task where an escaping ServiceError is
# only logged and the user's message silently vanishes. The rejection
# must instead surface as a visible chat error.
await self.handler._process_session_message(
self.task_id,
"approve",
session_id=self.session_id,
)
self.engine.process_message.assert_not_called()
self.handler.services_context.get_active_saved_org_name.assert_not_awaited()
self.assertEqual(context.exception.code, "company_runtime_identity_mismatch")
errors = [
msg["payload"].get("content", "")
for msg in self.broadcasts
if msg.get("type") == "session_message"
and str(msg.get("payload", {}).get("sender", "")) == "system"
]
self.assertTrue(
any("Company runtime identity could not be resolved" in text for text in errors),
errors,
)
async def test_process_session_message_rejects_runtime_org_without_durable_org_id(self) -> None:
runtime_session_id = "runtime-org-missing-id-session"
@@ -2104,16 +2116,27 @@ class TestWSHandlerSessionSend(unittest.IsolatedAsyncioTestCase):
return_value="wrong-active-org"
)
with self.assertRaises(ServiceError) as context:
await self.handler._process_session_message(
role_task.id,
"approve",
session_id=role_task.session_id,
)
# Same fail-closed-without-raising contract as the identity-mismatch
# case above: reject visibly instead of raising out of a background
# task.
await self.handler._process_session_message(
role_task.id,
"approve",
session_id=role_task.session_id,
)
self.engine.process_message.assert_not_called()
self.handler.services_context.get_active_saved_org_name.assert_not_awaited()
self.assertEqual(context.exception.code, "org_id_required")
errors = [
msg["payload"].get("content", "")
for msg in self.broadcasts
if msg.get("type") == "session_message"
and str(msg.get("payload", {}).get("sender", "")) == "system"
]
self.assertTrue(
any("org_id_required" in text for text in errors),
errors,
)
async def test_lock_free_process_session_message_uses_durable_org_for_role_task(self) -> None:
runtime_session_id = "runtime-org-lock-free-session"