Skip to content

fix: roll back unauthorized Global Ask turns - #399

Merged
seonghobae merged 8 commits into
ContextualWisdomLab:stack/board-source-detail-state-filter-4fdd-20260822from
seonghobae:fix/global-ask-atomic-rollback-362
Aug 21, 2026
Merged

fix: roll back unauthorized Global Ask turns#399
seonghobae merged 8 commits into
ContextualWisdomLab:stack/board-source-detail-state-filter-4fdd-20260822from
seonghobae:fix/global-ask-atomic-rollback-362

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

What changed

  • Re-authorize every cited post inside the existing persist_turn transaction with row-share locks.
  • Roll back the new session/turn/citations/evidence when source authorization changes before commit.
  • Return a stable retry action without exposing the discarded answer, while preserving the conversation for a follow-up.
  • Add the missing Global Ask migration to the real PostgreSQL API fixture and cover the race, rollback, and follow-up path.

Validation

  • uv run --extra dev --extra backend pytest backend/tests/test_api.py -q — 110 passed, 6 skipped
  • uv run --extra dev --extra backend pytest tests/test_global_ask_sources.py -q — 9 passed
  • uv run --with pytest pytest tests/test_migration_replay.py -q — 9 passed
  • python -m compileall and git diff --check passed

Stacked on #398; merge after the tenant-settings parent is accepted.

Closes #362


Open in Devin Review

@seonghobae

Copy link
Copy Markdown
Contributor Author

@claude Please review the exact current head 2150bcc9668bf529fbd9e6e0f92394be065377be for Issue #362. Focus on transaction rollback, row-lock authorization, evidence leakage, and conversation retry semantics. Report only findings tied to this head.

@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 56897146-24e1-4878-80e6-d24b62c7de3d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@seonghobae

Copy link
Copy Markdown
Contributor Author

@claude Review the exact current head b9acb07f831a176b85c5fed4f48223566082bbd3 after the parent #398 update. Recheck Issue #362 transaction rollback, row-lock authorization, evidence rollback, and follow-up session behavior.

@seonghobae

Copy link
Copy Markdown
Contributor Author

@claude Re-review exact current head d1ea448845715a899572a251994ae68679ef991f after ADR 0126 was updated to document the atomic citation boundary. Check implementation and decision consistency.

…ix/global-ask-atomic-rollback-362

# Conflicts:
#	CHANGELOG.md
@seonghobae

Copy link
Copy Markdown
Contributor Author

@claude Review exact current head 825d27618d6d379a11a29dfd62d4bfda3f4d87ce after parent #398 merged into stack commit 202063bca2afa38d100199de3f0520fdd8c15e8c. Recheck the Issue #362 transactional authorization and ADR/changelog consistency.

@seonghobae
seonghobae merged commit 139ecaa into ContextualWisdomLab:stack/board-source-detail-state-filter-4fdd-20260822 Aug 21, 2026
3 of 4 checks passed

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 3 potential issues.

Open in Devin Review

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Rolled-back turn leaves the conversation reusable

For an existing conversation the ordinal is max+1 and the updated_at bump lives inside the transaction, so a rollback leaves no inserted turn, no ordinal gap, and no stale timestamp. A follow-up retry recomputes the ordinal cleanly, which the new test confirms.

(Refers to this code)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +257 to +280
async def _ensure_citations_visible(
conn: asyncpg.Connection,
conversation_id: UUID,
turn_ordinal: int,
cited_post_count: int,
can_see_post: Callable[[asyncpg.Record], bool],
) -> None:
"""Lock and re-authorize new citations before their transaction commits."""
rows = await conn.fetch(
"""
select relation.cited_post_id::text as post_id,
post.post_title, post.visibility_code, post.corporate_entity_id,
post.author_account_id, post.source_detail_state_code
from global_ask_turn_citation relation
join source_post post on post.post_id = relation.cited_post_id
where relation.global_ask_session_id = $1
and relation.turn_ordinal = $2
for share of post
""",
conversation_id,
turn_ordinal,
)
if len(rows) != cited_post_count or any(not can_see_post(row) for row in rows):
raise GlobalAskEvidenceChanged

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Re-authorization covers only cited posts, not sources

The re-check at global_ask_history.py re-authorizes cited posts under FOR SHARE, but not source posts that fed the answer without being cited. A source that becomes unauthorized between retrieval and persist can still have its content baked into the stored answer_text. ADR 0126 states the stored answer stays account-owned transcript data and only cited/source projections are re-filtered on read, so this is intentional.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +382 to +389
if can_see_post is not None:
await _ensure_citations_visible(
conn,
conversation_id,
ordinal,
len(cited_ids),
can_see_post,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Row-lock re-check closes the retrieval/persist race

Sources are gathered on a separate connection, then persist_turn re-reads cited posts with FOR SHARE. Under read-committed this sees the latest committed source_post state and blocks in-flight writers until commit, so a revocation committed after gathering is caught and the whole turn rolls back. The len(rows) != cited_post_count check also catches concurrent deletes. Because the same _can_use_post_for_analysis predicate runs at gather and re-check, the normal path never rolls back spuriously.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

seonghobae added a commit that referenced this pull request Aug 23, 2026
)

This integration branch diverged from the line that originally shipped
#399/#374 before either landed, so it never picked up the fix: a cited
post whose authorization changed between source selection and commit
(visibility flip, corporate-entity move, etc.) would have its facts
served in the Global Ask answer and its citation row persisted even
though the request had no business seeing it anymore.

Restored _ensure_citations_visible (row-share-locked re-check inside
persist_turn's own transaction) and GlobalAskEvidenceChanged -> 503
wiring in ask_agent, byte-identical to the already-reviewed #399 fix.
Added a regression test that forces the race from inside the fake
chat client and confirmed it fails on the pre-fix code (leaks the
revoked post's facts with a 200) before passing on the restored fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011EP69xAyLaJxa6oaF6D9eq
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant