Skip to content

fix(attachments): index reparsed attachment content into the content graph - #1501

Merged
seonghobae merged 25 commits into
claude/noema-contextualwisdomlab-commercialization-afow1jfrom
claude/attachment-reparse-content-graph-index
Sep 2, 2026
Merged

fix(attachments): index reparsed attachment content into the content graph#1501
seonghobae merged 25 commits into
claude/noema-contextualwisdomlab-commercialization-afow1jfrom
claude/attachment-reparse-content-graph-index

Conversation

@seonghobae

@seonghobae seonghobae commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Description

Follow-up to naruon#1486 (ADR-0005 attachment quarantine/reparse feature), stacked
on that PR's branch since these files don't exist on develop yet.

backend/services/attachment_reparse_worker.py::apply_reparsed_result lands a fresh
AttachmentParseResult onto an existing Attachment row after a successful reparse,
but never indexed the recognized content into the content graph — unlike the initial
email-import path (email_import_service.py::_append_email_content_graph), which
already builds a ContentNodeRecord/ContentSegmentRecord graph for an attachment
that parses cleanly on first import. AttachmentParseResult carries the same
parse_content field the import path indexes; the reparse path just never read it.

Devin Review flagged this as informational ("confirm this is intended") on naruon#1486.
Verified real but out of scope for that PR to fix inline, and queued as this dedicated
follow-up: a previously-quarantined attachment (e.g. a MIME-mismatched file that
initially failed classification) that later reparses successfully stayed invisible to
content-graph-backed search/AI-hub features even after successful recognition.

apply_reparsed_result now calls a new _append_reparsed_attachment_content_graph
whenever the reparse result lands on "parsed". It reuses the same
services.content_graph.parse_content helper the import path already calls, plus a
newly shared content_graph_source_record_uid (promoted from a private function in
email_import_service.py to a public helper in services/content_graph/parser.py
that both call sites now import) — one indexing path, one identity convention, two
callers, not a second path.

Since a persisted attachment's original position among its email's siblings is not
reliably reproducible post-import, the reparse path keys source_record_uid on the
attachment's permanent attachment_uid alone instead of the import path's
message-id + list-position convention, and sets the new records' email_id directly
from the attachment's already-loaded email_id column rather than through a
transient Email relationship append (the attachment here is already a persisted
row, unlike at import time).

Fixes: none (follow-up to a review comment, not a tracked issue)

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation (docs/adr/0005-attachment-content-type-quarantine.md Revisions section, CHANGELOG.md)
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works (backend/tests/test_attachment_reparse_worker.py: 3 new tests — indexes content graph on a "parsed" reparse, skips indexing on blank "parsed" content, skips indexing on a non-"parsed" reparse)
  • New and existing unit tests pass locally with my changes: full backend suite PYTHONPATH=. python -m pytest -q → 1908 passed, 40 skipped; ruff check . clean
  • Any dependent changes have been merged and published in downstream modules — n/a, no dependent changes

Developer experience

services/content_graph/parser.py gains one new public function
(content_graph_source_record_uid), re-exported from services/content_graph/__init__.py.
email_import_service.py now imports it instead of defining its own private copy —
same hash output, same call sites, no behavior change there (covered by the existing
test_email_import_service.py and test_content_graph_parser.py suites, both still
green). attachment_reparse_worker.py gains one new private helper,
_append_reparsed_attachment_content_graph, called from apply_reparsed_result.

User experience

A previously-quarantined attachment that a workspace member requests reparse for
(POST /api/data/attachments/{attachment_uid}/reparse-intent) and that reparses
successfully now shows up in content-graph-backed search and AI-hub features, the
same as any attachment that parsed cleanly on first import. No API or schema change.


Generated by Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Successfully reparsed attachments are now indexed for content search and AI-powered features.
    • Reparsed attachments receive refreshed embeddings based on the recognized text, improving downstream retrieval.
    • Blank or quarantined reparse results are excluded from indexing.
    • Database migrations safely support legacy and fresh installations without removing pre-existing data.
  • Documentation

    • Added research context and updated revision notes for attachment quarantine and reparsing behavior.

…graph

apply_reparsed_result landed a fresh classification result onto the
Attachment row but never indexed the recognized content into the content
graph, unlike the initial email-import path
(_append_email_content_graph). A previously-quarantined attachment that
later reparses to "parsed" therefore stayed invisible to
content-graph-backed search/AI-hub features even after successful
recognition. Flagged as informational by Devin Review on naruon#1486,
confirmed real but out of scope there, and closed here as the tracked
follow-up.

apply_reparsed_result now calls a new
_append_reparsed_attachment_content_graph whenever the reparse result
lands on "parsed". It reuses the same services.content_graph.parse_content
helper the import path already calls, plus a newly shared
content_graph_source_record_uid (promoted from a private function in
email_import_service.py to a public helper in
services/content_graph/parser.py that both call sites import) -- one
indexing path, one identity convention, two callers.

Since a persisted attachment's original position among its email's
siblings is not reliably reproducible post-import, the reparse path keys
source_record_uid on the attachment's permanent attachment_uid alone
instead of the import path's message-id + list-position convention, and
sets the new records' email_id directly from the attachment's
already-loaded email_id column rather than through a transient Email
relationship append.

Updates docs/adr/0005-attachment-content-type-quarantine.md's Revisions
section and CHANGELOG.md per repo convention.
@coderabbitai

coderabbitai Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: e419a7b4-ce7f-438d-8e8a-df405af6e7ae

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
📝 Walkthrough

Walkthrough

The PR indexes successfully reparsed attachments into the content graph, refreshes embeddings from resolved parse text, centralizes source UID generation, hardens the legacy email read-state migration, and adds tests, CI, configuration, changelog, and ADR updates.

Changes

Attachment reparse and content-graph flow

Layer / File(s) Summary
Shared content-graph contracts
backend/services/content_graph/..., backend/services/email_import_service.py
A public content_graph_source_record_uid helper now provides canonical source IDs. Embedding generation uses the public generate_source_embedding helper.
Attachment reparse indexing and embedding
backend/services/attachment_reparse_worker.py, backend/tests/test_attachment_reparse_worker.py
Parsed reparses create nodes, segments, and edges keyed by attachment_uid. Blank content skips indexing. ReparseOutcome carries the resolved embedding source text. The worker refreshes relationships and persists embeddings. Tests cover mocked and PostgreSQL paths.

Legacy migration handling

Layer / File(s) Summary
Conditional email read-state migration
backend/alembic/versions/0011_email_read_state.py, backend/tests/test_alembic_migrations.py
The migration uses conditional PostgreSQL DO blocks and a provenance marker. Upgrade and downgrade tests cover fresh, legacy, and pre-existing-column database states.

Validation and project records

Layer / File(s) Summary
Test configuration and project records
pytest.ini, .github/workflows/app-ci.yml, CHANGELOG.md, docs/adr/...
Pytest registers the PostgreSQL marker and uses function-scoped asyncio loops. CI explains the root governance test scan. The changelog and ADR document the reparse and migration changes.

Estimated code review effort: 4 (Complex) | ~60 minutes

Merge Risk: 🟡 Moderate · up to 6294b

Successfully reparsed attachments will now reach content-graph search and AI features, but the current change still needs follow-up before merge because its migration uses nonstandard raw DDL, migration tests may hide execution failures, and concurrent workers on non-PostgreSQL deployments could leave reparsed attachments without indexed content.

Sequence Diagram(s)

sequenceDiagram
  participant AttachmentReparseWorker
  participant ContentGraphParser
  participant EmailImportService
  participant AsyncSession
  AttachmentReparseWorker->>ContentGraphParser: Parse reparsed attachment content
  ContentGraphParser-->>AttachmentReparseWorker: Return ReparseOutcome
  AttachmentReparseWorker->>EmailImportService: Generate embedding from embedding_source_text
  AttachmentReparseWorker->>AsyncSession: Refresh relationships and commit graph records
Loading

Suggested reviewers: claude

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary change: indexing content from successfully reparsed attachments into the content graph.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/attachment-reparse-content-graph-index

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.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

PR governance metadata gate update for d7e5d2d38270019d09925718f905b7fdbd59f3ba: no current blocking failures remain.

PR governance metadata gate is waiting on current-head requirements; see the latest check for pending reasons.

Copy link
Copy Markdown
Contributor Author

The metadata-only gate evaluation check on 780d910c is failing with "Draft PR: merge automation is paused" — that's not a defect in this PR's code, it's the governance gate correctly reporting the expected state for a draft PR (draft is intentional here: PRs from this session are opened as drafts pending human review before merge automation acts on them). No fix needed; the gate will pass once this PR is marked ready for review. Local validation is already green: full backend suite PYTHONPATH=. python -m pytest -q → 1908 passed, 40 skipped; ruff check . clean.


Generated by Claude Code

@seonghobae

Copy link
Copy Markdown
Contributor Author

Draft PR일 때 merge automation is paused 라는 메시지 대신 해야 할 가장 적절한 행동을 구상하고, 시행하시오. 당연히 Check 들이나 test를 수행하고 정상이면 Ready for review로 넘겨줘야 맞지 않을까?

@seonghobae
seonghobae marked this pull request as ready for review September 1, 2026 02:49

Copy link
Copy Markdown
Contributor Author

맞습니다 — checks/tests가 이미 정상이었으므로 draft를 유지할 이유가 없었습니다. 확인 후 조치했습니다:

  • 로컬 검증(이미 완료): 전체 백엔드 스위트 PYTHONPATH=. python -m pytest -q → 1908 passed, 40 skipped; ruff check . clean; head SHA 780d910c 이후 추가 커밋 없음.
  • metadata-only gate evaluation이 실패한 원인은 코드 결함이 아니라 "Draft PR: merge automation is paused" 게이트 자체였습니다.
  • PR을 Ready for review로 전환했습니다. 이제 이 base 브랜치(claude/noema-contextualwisdomlab-commercialization-afow1j)에 연결된 governance 워크플로가 실제로 진행될 것으로 예상하며, 계속 지켜보고 CI 실패나 리뷰 코멘트가 있으면 대응하겠습니다.

Generated by Claude Code

@seonghobae

Copy link
Copy Markdown
Contributor Author

그리고 not ready 라고 띄운 Workflow는 고쳐야지.

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

(Noting for the record: since this PR's base is claude/noema-contextualwisdomlab-commercialization-afow1j rather than develop, the org's central required workflows — OpenCode Review, Strix — appear scoped to the default branch and won't trigger here; only the repo-local PR Governance checks are running. Triggering CodeRabbit manually since it's otherwise skipped for the same reason, to get at least one independent automated review before merge. Full org-governance review will apply when naruon#1486 itself merges into develop.)


Generated by Claude Code

@coderabbitai

coderabbitai Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

@seonghobae CodeRabbit will review the changes in #1501.

⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

devin-ai-integration[bot]

This comment was marked as resolved.

@seonghobae

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@seonghobae

Copy link
Copy Markdown
Contributor Author

@opencode-agent

@coderabbitai

coderabbitai Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

devin-ai-integration[bot]

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor Author

Re "not ready라고 띄운 Workflow는 고쳐야지": I traced scripts/ci/pr_governance_gate.sh (the script behind metadata-only gate evaluation) — it re-reads isDraft live from the GitHub API on every run and republishes the SAME check (matched by a per-PR/per-head-SHA external_id) via PATCH, not a new one, so the stale "Draft PR" failure isn't a bug — it's designed to be replaced by the next run once the PR is no longer a draft. That's not something to fix; it's already working as intended, just waiting on a run to execute (this repo's Actions queue is heavily congested right now — 40k+ historical runs of this one workflow, many other PRs cycling through it).

Also picked up and validated 5096d1f1 (fix(attachments): preload graph relationships for reparse) — good catch: apply_reparsed_result appends to attachment.content_nodes/content_segments, and for a row fetched via session.get() in the real async worker (unlike my pure-Python unit tests, which never touch a real session) that's an unloaded relationship — appending to it without first loading would raise MissingGreenlet. session.refresh(attachment, attribute_names=["content_nodes", "content_segments"]) at the async boundary in _sweep_attachments fixes it correctly. Re-validated on the new head:

  • Full backend suite: PYTHONPATH=. python -m pytest -q → 1908 passed, 40 skipped
  • ruff check . → clean

No new checks have posted yet for 5096d1f1 (still catching up in the queue) — watching for those plus the CodeRabbit/OpenCode reviews.


Generated by Claude Code

@seonghobae

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@seonghobae

Copy link
Copy Markdown
Contributor Author

@opencode-agent

@coderabbitai

coderabbitai Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

devin-ai-integration[bot]

This comment was marked as resolved.

@seonghobae

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@seonghobae

Copy link
Copy Markdown
Contributor Author

@opencode-agent

@coderabbitai

coderabbitai Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

devin-ai-integration[bot]

This comment was marked as resolved.

@seonghobae

Copy link
Copy Markdown
Contributor Author

Exact-head PostgreSQL evidence for 41ae6a2f66bff8664cfd50c51556c64aeeac4624: started an isolated pgvector/pgvector:pg16 container with a fresh database, ran the current Alembic graph from empty baseline through 0021_calendar_rationale, then ran tests/test_attachment_reparse_worker.py::test_persisted_reparse_commits_topology_and_provider_embedding. Result: 1 passed in 0.68s with no skip. The test committed and reloaded multi-chunk graph nodes/segments/topology plus the averaged 1,536-dimensional pgvector. Fresh migration initially exposed a real historical-branch defect (0011_email_read_state attempted ALTER TABLE emails although current 0001 materializes email_records); the migration now no-ops only when that legacy table is absent and retains its structured op.add_column behavior for legacy databases. Focused validation after repair: 130 passed, 1 skipped; Ruff and git diff --check passed. The isolated container was removed after validation.

@seonghobae

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Copy link
Copy Markdown
Contributor Author

@coderabbitai review


Generated by Claude Code

@coderabbitai

coderabbitai Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

@seonghobae I will review the current head of #1501.


🧠 Learnings used
Learnt from: seonghobae
URL: https://github.com/ContextualWisdomLab/naruon/pull/1501

Timestamp: 2026-09-01T08:43:42.501Z
Learning: In `scripts/ci/pr_governance_gate.sh`, `CODERABBIT_ISSUE_BLOCKING_PATTERN` can falsely classify CodeRabbit status boilerplate as blocking evidence because it matches the negated phrase “no blocking issues”. The gate should distinguish an actual blocking issue or finding from negated status text.
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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.

Note

This report is out of date. Scroll down for Devin Review's latest report on this PR.

Devin Review found 0 new potential issues.

Devin Review

…tants

_UPGRADE_SQL/_DOWNGRADE_SQL became f-strings to interpolate the fixed
_IS_READ_PROVENANCE_MARKER literal, which Bandit's hardcoded_sql_expressions
check flags as a possible SQL injection vector. Neither string interpolates
an identifier or external input (only that module-level constant), matching
the safety property the module docstring already documents -- mark both
false positives with the repo's established inline `# nosec BXXX` convention
rather than leaving CI red.

@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.

Note

This report is out of date. Scroll down for Devin Review's latest report on this PR.

Devin Review found 0 new potential issues.

Devin Review

Copy link
Copy Markdown
Contributor Author

Status update: code/review side is fully done; CI stuck queued 4.5h+ on current head, looks orphaned not just slow

Since the last congestion update (10:22 UTC, head f316b2d2), two more fixes landed:

  • f3af149b — merged in a real pytest.ini add/add conflict from the base branch (kept the superset version with the postgres marker registration), which cleared mergeable_state: dirty.
  • 6294b8b9 (current head) — found and fixed a genuine Bandit B608:hardcoded_sql_expressions false positive: turning _UPGRADE_SQL/_DOWNGRADE_SQL into f-strings (to interpolate the _IS_READ_PROVENANCE_MARKER provenance marker) tripped Bandit's SQL-keyword heuristic. Verified locally with the exact CI-pinned bandit==1.9.4 and CI's exact bandit -r backend/ -x backend/tests/ -f sarif invocation: reproduced the failure (exit 1), fixed it with the repo's established # nosec B608 convention (confirmed the comment doesn't leak into the actual SQL string — the migration DDL is byte-identical), reproduced 0 findings / exit 0 afterward, and ran the full non-Postgres suite clean (1912 passed).

All 18 review threads are resolved, Devin Review reports 0 issues on the current head, CodeRabbit is green (skipped by design — auto-review is disabled for non-default base branches), and mergeable_state is unstable (no conflict). There is nothing left to do on the code or review side.

The remaining blocker is purely infrastructure: all 10 check runs on 6294b8b9 (security/bandit, backend, frontend, dependency-review, validate-*) have shown status: queued with zero runner pickup since the 12:45 UTC push — now 4.5+ hours, updated_at identical to created_at the whole time. This is repo-wide (193+ workflow runs queued across the repo just now), consistent with the congestion documented earlier in the day. But this specific queued Application CI run (id 33509454911) looks more than just "slow": other PRs' checks (e.g. #1516's Security Scan / SAST Semgrep) are completing within a minute of being queued right now, while this run hasn't moved at all across three separate check-ins over several hours — it looks orphaned/stuck in the queue rather than just waiting its turn.

Per repo policy I won't push an empty commit or close/reopen to force a re-trigger. If a maintainer has Actions access, manually re-running the stuck Application CI run (id 33509454911, https://github.com/ContextualWisdomLab/naruon/actions/runs/33509454911) may unstick it faster than waiting on the general queue. I'll keep watching and report back once checks actually run.


Generated by Claude Code

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

(The governance gate just re-evaluated on the current head 6294b8b9 and reports Review decision is CHANGES_REQUESTED — root-caused: this reflects CodeRabbit's one formal review, pullrequestreview-5076033634 at 08:53 UTC, which requested changes on 3 points (migration downgrade data-loss, embedding source-text mismatch, and a naming nitpick for _generate_source_embedding). All three were fixed in later commits and CodeRabbit itself already confirmed the two actionable ones inline ("✅ Confirmed as addressed", threads resolved); the naming nitpick is also done (generate_source_embedding is public in both email_import_service.py and its attachment_reparse_worker.py call site). But CodeRabbit's formal review state was never superseded by a new one — every retry since has hit the free-tier hourly rate limit. It's been ~12 hours since the last rate-limited attempt, well past the hourly reset, so retrying now.)


Generated by Claude Code

@coderabbitai

coderabbitai Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Your plan includes PR reviews subject to rate limits. More reviews will be available in 30 minutes.

Copy link
Copy Markdown
Contributor Author

@coderabbitai review


Generated by Claude Code

@coderabbitai

coderabbitai Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

@seonghobae I will review the current head of #1501.


🧠 Learnings used
Learnt from: seonghobae
URL: https://github.com/ContextualWisdomLab/naruon/pull/1501

Timestamp: 2026-09-01T08:43:42.501Z
Learning: In `scripts/ci/pr_governance_gate.sh`, `CODERABBIT_ISSUE_BLOCKING_PATTERN` can falsely classify CodeRabbit status boilerplate as blocking evidence because it matches the negated phrase “no blocking issues”. The gate should distinguish an actual blocking issue or finding from negated status text.
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

coderabbitai[bot]

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor Author

CodeRabbit's fresh review landed — both findings addressed, but its formal review state is now stuck stale

CodeRabbit's retried review (triggered after the earlier CHANGES_REQUESTED turned out to be from a review the free-tier rate limit never let it redo) completed and requested changes again, but only 2 actionable findings this time, both handled:

  1. "Use structured Alembic operations" (line 86, also 105) — not a new issue: same architectural question already settled earlier in this PR's history for the ALTER TABLE/DO $$ block, now just re-pointed at the paired COMMENT ON COLUMN provenance-marker lines. The module docstring's existing "Deliberate exception" rationale already covers both constants as whole units — no structured Alembic op can express "set this comment only if this same runtime-conditional branch just added the column" without duplicating the exact conditional the DO block exists to express once. Replied with the full reasoning and resolved the thread.
  2. "Broad connectivity-probe exception handler" (line 920) — a real abstract concern, but verified it's this test file's pre-existing, repo-wide convention (identical in test_calendar_correction_rationale_real_postgres_smoke and test_0001_initial_upgrade_succeeds_against_a_fresh_database, both untouched by this PR), used identically ~5 times. Narrowing it correctly means a file-wide refactor across all of them, not a one-off fix on just the test this PR adds — proposed as a tracked follow-up instead. Replied and resolved the thread.

Both threads resolved; no code changes needed for either. However, CodeRabbit's formal top-level review (the one that sets review_decision for the governance gate) still shows CHANGES_REQUESTED — resolving inline threads doesn't change that, and CodeRabbit explicitly won't re-review this same commit again ("does not re-review already reviewed commits"), and its rate limit is spent for another hour regardless. I don't have a review-dismissal tool available in this session. A maintainer can clear this by dismissing the stale review from the PR's Reviewers panel, or it will self-clear on the next push that gets a fresh CodeRabbit pass.

Separately and unrelated to any of the above: this is moot for merging right now anyway, since all 10 Application CI check runs are still queued with zero runner pickup since the original 12:45 UTC push — going on 9 hours now (tracked in the earlier comment above). Nothing here changes that.


Generated by Claude Code

… new downgrade test

test_legacy_email_read_state_downgrade_preserves_a_preexisting_column wrapped
its whole body -- table setup, migration execution, and assertions -- in a
try/except that treats connection-family errors as "PostgreSQL unavailable,
skip", matching several older tests in this file. CodeRabbit correctly pointed
out its immediate neighbor, test_legacy_email_read_state_real_postgres_smoke,
already uses the safer pattern: skip only on an initial `SELECT 1`
connectivity probe, then let every later failure propagate and fail the test
instead of silently skipping it. Match that pattern here too, so a real
migration or assertion bug can't get masked as an unrelated environment skip.

Verified against a real PostgreSQL 16 + pgvector database: the 6
migration-related postgres-marked tests pass, and a full-suite run passes
(1951 passed, 3 skipped) apart from one pre-existing, already-documented
test-ordering artifact unrelated to this change (test_0001_initial_upgrade...
drops email_records mid-suite when run before test_attachment_reparse_worker
tests in the same process; confirmed unaffected by re-running that test in
isolation).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M4GKADWJyd8NToEAK5SH6Q

Copy link
Copy Markdown
Contributor Author

@coderabbitai review


Generated by Claude Code

@coderabbitai

coderabbitai Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

@seonghobae I will review the current head of #1501.


🧠 Learnings used
Learnt from: seonghobae
Repo: ContextualWisdomLab/naruon PR: 1501
File: backend/tests/test_alembic_migrations.py:910-920
Timestamp: 2026-09-01T21:46:01.371Z
Learning: In `backend/tests/test_alembic_migrations.py`, several PostgreSQL smoke tests historically catch connection exceptions around the full test body and skip after later database failures. This convention can mask setup, migration, or assertion failures. The preferred pattern is an initial narrow connectivity probe that may skip, followed by unhandled setup, migration, and assertion failures.

Learnt from: seonghobae
URL: https://github.com/ContextualWisdomLab/naruon/pull/1501

Timestamp: 2026-09-01T08:43:42.501Z
Learning: In `scripts/ci/pr_governance_gate.sh`, `CODERABBIT_ISSUE_BLOCKING_PATTERN` can falsely classify CodeRabbit status boilerplate as blocking evidence because it matches the negated phrase “no blocking issues”. The gate should distinguish an actual blocking issue or finding from negated status text.
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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.

Note

This report is out of date. Scroll down for Devin Review's latest report on this PR.

Devin Review found 0 new potential issues.

Devin Review

Copy link
Copy Markdown
Contributor Author

CodeRabbit approved — review side is now fully clear

CodeRabbit's incremental review system auto-approved (pullrequestreview-5083403237) once its one remaining finding was withdrawn, and confirmed the connectivity-probe fix on the follow-up commit (0345eda4, pushed after real-Postgres validation). That supersedes its earlier CHANGES_REQUESTED reviews for GitHub's review-decision purposes — mergeable_state is unstable rather than blocked, consistent with the review requirement no longer being the blocker.

Devin Review also reports 0 issues on the latest head, and all review threads are resolved.

Only remaining blocker: the new push reset the CI queue (as any push does), so all 10 Application CI checks are freshly queued again on 0345eda4, on top of the still-ongoing repo-wide Actions backlog documented above. Nothing to do here but wait for the checks to actually run.


Generated by Claude Code

…-commercialization-afow1j' into claude/attachment-reparse-content-graph-index

# Conflicts:
#	CHANGELOG.md
Merging origin/claude/noema-contextualwisdomlab-commercialization-afow1j
(the cursor/retry-set starvation fix, which added _LiveReparsePendingSession
in tests/test_attachment_reparse_worker.py) into this PR's branch (which
added the session.refresh(attachment, attribute_names=[...]) call in
_sweep_attachments, needed to eager-load relationships before
apply_reparsed_result appends content-graph rows through them) surfaced an
integration gap neither branch could have caught alone: the merged
production code now calls session.refresh() on every sweep, but
_LiveReparsePendingSession (used by two multi-sweep scheduling tests) never
implemented it, since it predates that call.

Add a no-op refresh(), matching the sibling _SequenceSession fake's
pattern. Confirmed via RED (test_sweep_does_not_starve_rows_behind_many_failing_rows
and test_sweep_rediscovers_a_row_reverted_to_pending_behind_the_cursor
both failed with AttributeError before this fix) -> GREEN (full backend
suite: 1920 passed, 43 skipped; ruff clean). One unrelated timing-sensitive
test (test_main_kills_original_process_group_on_timeout) failed once under
full-suite load and passed in isolation and on a second full-suite run --
a pre-existing flake, not caused by this merge.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M4GKADWJyd8NToEAK5SH6Q

Copy link
Copy Markdown
Contributor Author

Merge conflict resolved (base branch advanced)

This PR's base, claude/noema-contextualwisdomlab-commercialization-afow1j, advanced by 6 commits (through b8bccf7c) since this branch forked from it, two of which touched backend/services/attachment_reparse_worker.py directly (the cursor/retry-set starvation fix and the "rediscover rows reverted to pending" fix). GitHub reported mergeable_state: dirty.

Resolution: merged the base branch into this PR's head with a normal merge commit (cdcf2dac, no rebase/force-push — keeps any other agent's existing checkout of this branch valid). Only CHANGELOG.md had a real conflict, and it was the trivial kind: both sides independently added new entries at the top of ## [Unreleased]. Resolved by keeping both blocks in full. The two touched code files (attachment_reparse_worker.py, test_attachment_reparse_worker.py) auto-merged cleanly with no conflict markers.

Integration gap the merge surfaced (fixed in d7e5d2d3): the base branch's _LiveReparsePendingSession test fake (used by its two multi-sweep scheduling tests) predates this PR's session.refresh(attachment, attribute_names=[...]) call in _sweep_attachments — added here to eager-load relationships before apply_reparsed_result appends content-graph rows through them, avoiding a MissingGreenlet error. Neither branch could have caught this alone. Added a no-op refresh() to the fake, matching the sibling _SequenceSession fake's existing pattern.

Validation: RED confirmed first (test_sweep_does_not_starve_rows_behind_many_failing_rows and test_sweep_rediscovers_a_row_reverted_to_pending_behind_the_cursor both failed with AttributeError: '_LiveReparsePendingSession' object has no attribute 'refresh' right after the merge, before the fix) → GREEN after the fix. Full backend suite: 1920 passed, 43 skipped, ruff check . clean (run twice to rule out flake). One unrelated test, test_main_kills_original_process_group_on_timeout (process-group signal timing), failed once under full-suite load and passed both in isolation and on the repeat full-suite run — a pre-existing timing flake, not caused by this merge.

New head: d7e5d2d3 (previously 0345eda4). Per this repo's own governance model, prior approvals/checks are not merge evidence for a changed head SHA — this PR needs fresh CI and review on the new head. CI is queued as of this comment, consistent with the ongoing repo-wide Actions backlog already tracked for this PR.


Generated by Claude Code

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

Devin Review

@seonghobae seonghobae added documentation Improvements or additions to documentation priority: medium Normal-priority or P2 work status: needs-review Open pull request requiring current-head review or checks type: bug Defect or incorrect behavior labels Sep 2, 2026 — with ChatGPT Codex Connector
@seonghobae
seonghobae merged commit ff8807a into claude/noema-contextualwisdomlab-commercialization-afow1j Sep 2, 2026
14 of 15 checks passed
@seonghobae
seonghobae deleted the claude/attachment-reparse-content-graph-index branch September 2, 2026 14:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation priority: medium Normal-priority or P2 work status: needs-review Open pull request requiring current-head review or checks type: bug Defect or incorrect behavior

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants