You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug] Roll back rejected Global Ask turn atomically instead of poisoning the session #362
A Global Ask answer is persisted before its citations are reauthorized for the final response. When that post-persist authorization check fails, the API correctly returns a generic 503, but the rejected turn and citation rows remain. The next question then fails the whole session with 409 because the stale citation is now part of session history.
This converts a narrow visibility/provider race into a permanently retired conversation session. Removing session continuity or weakening citation reauthorization is not acceptable.
Root cause
ask_agent currently performs:
persist_global_ask_turn
→ read_authorized_ask_evidence
→ raise 503 when not all citations remain visible
persist_global_ask_turn commits its inserts before the final check. The later 503 cannot roll them back.
Required fix
Acquire one connection and open an outer database transaction.
Persist the new turn and citations inside that transaction.
Reauthorize the persisted citation set using the same knowledge cutoff and tenant scope before the outer transaction exits.
Raise the existing generic 503 inside the outer transaction when evidence changed so all new turn/citation rows roll back.
Preserve existing previous turns and their authorization state.
Keep the post-persist race check; do not replace it with a precheck-only shortcut.
TDD acceptance
RED on current code: force a cited post to lose eligibility between source selection and final reauthorization.
The request returns the current generic 503 without hidden evidence.
The failed answer creates no new global_ask_turn or global_ask_turn_citation rows.
The same session can immediately submit a safe follow-up and does not receive 409.
Previously committed turns remain unchanged.
Successful answers still persist exactly once under retry/idempotency expectations.
Real PostgreSQL integration and unit tests cover commit and rollback paths.
Full backend/API, SAST, security, migration, and exact-head checks pass.
Stack placement
Fix on the active #342 → #357 → #360 line through an ordinary stacked PR or a normal merge into the appropriate branch. Do not force-push, bypass protection, disable Global Ask, drop session persistence, or relax ABAC/cutoff checks.
Origin
Exact-head review thread on PR #342 (7d2654fb19ba7a88815edcefeb0a44f1bd05f619).
Implemented in PR #399 and merged into the non-main stack at 139ecaa17b411451c645f12a888f095df3beb3b2 (parent stack merge, not protected main). The fix re-authorizes cited posts under row locks inside the persistence transaction, rolls back the new turn/citations/evidence on a race, and preserves the conversation for retry. Real PostgreSQL API coverage passed: 110 passed, 6 skipped; migration replay and Global Ask source suites also passed. Issue remains open until the stack reaches protected main.
상태 정리 (2026-08-24 검증): 이 버그는 protected main에는 존재하지 않습니다 — main에는 global_ask_turn 지속화 자체가 없습니다 (git grep global_ask origin/main -- 'backend/app/*.py' 무일치). 버그와 그 수정(#399, 행 잠금 하 재인가 + 원자 롤백)은 모두 미병합 세션 스택(#342→#357→#360, 스택 머지 139ecaa)에 있으며, 139ecaa1은 origin/main의 조상이 아닙니다. 따라서 이 이슈는 그 스택이 main에 착지할 때 함께 닫히는 게 맞고, 착지 전 main 쪽 별도 조치는 불필요합니다.
Closing as resolved-by-rearchitecture on current main.
The issue's root cause targets the retired synchronous Ask flow (persist_global_ask_turn → read_authorized_ask_evidence → 503-after-commit). That flow no longer exists:
The global_ask_turn / global_ask_turn_citation tables are gone from every migration on main; Global Ask state now lives in one durable global_ask_job row.
backend/app/global_ask_queue.py::process_global_ask_job claims the row with a single conditional update ... where job_status_code = 'queued', computes the payload, then settles the same row with exactly one update global_ask_job set job_status_code = .... There is no second transaction after which a visibility failure can leave partial rows behind.
A post-persist visibility change can no longer strand a session: the settled answer is one jsonb column on the same row the reader polls (GET /api/ask/jobs/{id}), so a stale citation cannot enter any session history and the next question cannot 409 off it.
The issue's intent — never let a rejected answer poison the conversation — holds on current main by construction. If a future multi-turn history feature reintroduces turn/citation rows, reopen with the outer-transaction design from this issue's Required-fix section.
Activity
seonghobae commentedon Aug 21, 2026
Implemented in PR #399 and merged into the non-main stack at
139ecaa17b411451c645f12a888f095df3beb3b2(parent stack merge, not protectedmain). The fix re-authorizes cited posts under row locks inside the persistence transaction, rolls back the new turn/citations/evidence on a race, and preserves the conversation for retry. Real PostgreSQL API coverage passed: 110 passed, 6 skipped; migration replay and Global Ask source suites also passed. Issue remains open until the stack reaches protectedmain.fix(ask): restore atomic rollback for revoked Global Ask citations (#362
fix(ask): port the #362 citation-rollback fix to per-post Ask history
seonghobae commentedon Aug 24, 2026
상태 정리 (2026-08-24 검증): 이 버그는 protected main에는 존재하지 않습니다 — main에는 global_ask_turn 지속화 자체가 없습니다 (
git grep global_ask origin/main -- 'backend/app/*.py'무일치). 버그와 그 수정(#399, 행 잠금 하 재인가 + 원자 롤백)은 모두 미병합 세션 스택(#342→#357→#360, 스택 머지 139ecaa)에 있으며, 139ecaa1은 origin/main의 조상이 아닙니다. 따라서 이 이슈는 그 스택이 main에 착지할 때 함께 닫히는 게 맞고, 착지 전 main 쪽 별도 조치는 불필요합니다.Merge main into feat/board-source-detail-state-filter: reconcile ADR …
seonghobae commentedon Aug 25, 2026
Closing as resolved-by-rearchitecture on current
main.The issue's root cause targets the retired synchronous Ask flow (
persist_global_ask_turn→read_authorized_ask_evidence→ 503-after-commit). That flow no longer exists:global_ask_turn/global_ask_turn_citationtables are gone from every migration onmain; Global Ask state now lives in one durableglobal_ask_jobrow.backend/app/global_ask_queue.py::process_global_ask_jobclaims the row with a single conditionalupdate ... where job_status_code = 'queued', computes the payload, then settles the same row with exactly oneupdate global_ask_job set job_status_code = .... There is no second transaction after which a visibility failure can leave partial rows behind.GET /api/ask/jobs/{id}), so a stale citation cannot enter any session history and the next question cannot 409 off it.failedwith a bounded detail string ([Operability] Preserve server diagnostics behind generic orchestrator 503 responses #361's boundary), so nothing stays stuckrunning.The issue's intent — never let a rejected answer poison the conversation — holds on current
mainby construction. If a future multi-turn history feature reintroduces turn/citation rows, reopen with the outer-transaction design from this issue's Required-fix section.