Skip to content

feat(ask): persist per-post conversation history - #675

Merged
seonghobae merged 2 commits into
docs/adr-0133-0137-decomposition-matrixfrom
feat/post-ask-conversation-history
Aug 26, 2026
Merged

feat(ask): persist per-post conversation history#675
seonghobae merged 2 commits into
docs/adr-0133-0137-decomposition-matrixfrom
feat/post-ask-conversation-history

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Contract

Implements the focused ADR 0136 gap as superseding ADR 0228 without replaying closed PR #490. LineageWeave persists account-owned post Ask transcripts; it does not perform psychometric or statistical arithmetic.

  • 3NF session, turn, source, and citation tables with account-leading hot-post-safe index
  • account + post scoped list/load/append APIs; citation authorization is rechecked inside the commit transaction
  • list/select/new UI with localized copy, Storybook seeded/saved/mobile scenes, and 44px controls
  • gap baseline and exact local k6 observation updated

Verification

  • uv run --extra dev --extra backend pytest -q tests/test_post_ask_history.py (7 passed)
  • focused real-PostgreSQL chat regressions (3 passed)
  • migration/documentation regressions (17 passed)
  • corepack pnpm run lint and corepack pnpm run build
  • focused ChatPanel interaction (1 passed)
  • Storybook build plus desktop/mobile screenshots inspected
  • k6 5 VU / 15 s: 0 failed requests; deployed-stack reader p95 43.26 s remains an explicit non-exact-head performance gap

The broader local App suite reached 82 passes and 13 existing 5-second timeouts without assertion failures; hosted exact-head checks remain authoritative.

Stacked on #671 so the product-gap matrix lands before this implementation.


Open in Devin Review

@coderabbitai

coderabbitai Bot commented Aug 26, 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: 8831663e-39a3-4fcf-82cc-b804a19ffda7

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
seonghobae merged commit 85a1109 into docs/adr-0133-0137-decomposition-matrix Aug 26, 2026
1 of 4 checks passed
@seonghobae seonghobae moved this from Done to In Progress in naruon Platform Roadmap Aug 26, 2026

@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 5 potential issues.

Open in Devin Review

Comment on lines +143 to +144
post.post_title, post.visibility_code, post.corporate_entity_id,
post.author_account_id, post.source_detail_state_code

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Reopening a saved conversation crashes process-unit-scoped readers

The source and citation reauthorization queries select visibility_code and corporate_entity_id but not process_unit_id. For a private post, _can_see_post then reads post["process_unit_id"] whenever the reader is scoped to process units, raising KeyError on the row and returning 500. Any saved turn that cites or sources a private post breaks the transcript endpoint for such readers. The citation query at post_ask_history.py has the same omission.

Prompt for agents
Both queries in _visible_post_ids_batch (the source branch and the citation branch) omit post.process_unit_id from their select lists. The can_see_post callback passed in is _can_see_post from main.py, which for a private post evaluates post["process_unit_id"] whenever the account has a non-empty process_unit_ids set. Because the column is absent from the fetched asyncpg Record, this raises KeyError and the fetch_conversation endpoint returns 500. Add post.process_unit_id to the select list in both the source and citation query branches, matching how gather_chat_sources selects process_unit_id in backend/app/post_chat_ingestion.py.
Open in Devin Review

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

Comment on lines +293 to +294
post.post_title, post.visibility_code, post.corporate_entity_id,
post.author_account_id, post.source_detail_state_code

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Asking a question crashes process-unit-scoped readers citing private posts

The citation reauthorization query selects visibility_code and corporate_entity_id but not process_unit_id. Inside the persist_turn commit, _can_see_post reads post["process_unit_id"] for a private cited post when the reader is scoped to process units, raising KeyError on the row. The error escapes uncaught, so the chat POST returns 500 and no turn is saved.

Suggested change
post.post_title, post.visibility_code, post.corporate_entity_id,
post.author_account_id, post.source_detail_state_code
post.post_title, post.visibility_code, post.corporate_entity_id,
post.author_account_id, post.source_detail_state_code, post.process_unit_id
Open in Devin Review

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

Comment on lines +290 to +305
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 post_ask_turn_citation relation
join source_post post on post.post_id = relation.cited_post_id
where relation.post_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 PostAskEvidenceChanged

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: Write-path citation recheck skips draft/deleted eligibility

_ensure_citations_visible applies only can_see_post, while _visible_post_ids_batch also applies SOURCE_POST_ELIGIBILITY_SQL. A cited post that is ABAC-visible but draft/deleted would pass the commit-time recheck yet be dropped on read. Citations come from gather_chat_sources, which already excludes ineligible posts, so the asymmetry is currently harmless but inconsistent.

Open in Devin Review

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

Comment thread backend/app/main.py
Comment on lines +3058 to +3067
conversation_id = await _persist_post_ask_turn(
conn,
account,
post_id,
request.conversation_id,
question,
stored["answer_text"],
source_ids,
list(stored["cited_post_ids"]),
)

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: Cache-hit asks with no conversation id spawn new conversations each time

Every request persists a turn, including the stored cache-hit path. With request.conversation_id null, persist_turn creates a fresh post_ask_session per call, so repeatedly re-asking the same seeded question (e.g. clicking a suggestion chip) accumulates many single-turn conversations in the history list.

Open in Devin Review

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

Comment thread backend/app/main.py
Comment on lines 3127 to +3138
async with pool.acquire() as conn:
await persist_post_chat(conn, post_id, question, answer.answer_text, cited_ids)
conversation_id = await _persist_post_ask_turn(
conn,
account,
post_id,
request.conversation_id,
question,
answer.answer_text,
source_ids,
cited_ids,
)

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: Cache write and history write are not atomic

In the live path persist_post_chat commits before _persist_post_ask_turn opens its own transaction. If the turn persist raises (e.g. 503 on changed evidence), the post_chat_result cache row is committed while the account-history turn is not, leaving the two stores diverged. Impact is low because the cache is additive to history.

Open in Devin Review

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

1 participant