Skip to content

feat(email): deterministic dedupe provenance — gate strong fingerprints on genuine Date (naruon#1086) - #1195

Draft
seonghobae wants to merge 97 commits into
autoresearch/frontend-sec-bumpfrom
claude/contextualwisdomlab-audit-governance-qyxe67
Draft

seonghobae wants to merge 97 commits into
autoresearch/frontend-sec-bumpfrom
claude/contextualwisdomlab-audit-governance-qyxe67

Conversation

@seonghobae

@seonghobae seonghobae commented Jul 30, 2026 •

Copy link
Copy Markdown
Contributor

Current authority — 2026-09-22

Refs #1086, #1498, #1717.

  • protected root: develop@042b0c70531b229af3acbd0421a2f23098d848b3
  • dependency-security prerequisite/base: #1623@509be4c1d9b6c7ba239a108656e2382681a85341
  • workspace/Alembic owner that must precede final migration reconciliation: #1503@9151c75568c582c8147cfee6757cd00a9b4d60b7
  • exact current head: 52d2cc6fc136c931dda609a0129e3cd78048eebb
  • lifecycle: Draft / canonical email identity-dedupe + POP3 collection-progress owner / interrupted-RETR starvation source repair present / Alembic lineage reconciliation pending / exact-head hosted + independent-review evidence pending / do not merge

The latest ordinary-forward generation adds one focused regression file, a two-line production repair in backend/services/pop3_worker.py, and code-current doctoring. It does not create a new migration, dependency owner, workflow, or POP3 implementation.

Canonical domain contract

Sender-authored message identity evidence and provider collection progress remain separate. Canonical message provenance is still date_provenance plus existing Message-ID/source-fingerprint semantics. POP3 UIDL is provider progress identity only and never replaces message identity or dedupe evidence.

The parallel #1656 date_evidence / message_id_evidence vocabulary and sibling email_metadata_provenance_1086 migration remain rejected. #1656 is provenance only and must ordinary/non-force follow this owner tree.

Alembic lineage finding

This branch still contains branch-local 0018_email_date_provenance → 0019_pop3_observed_uidl, while canonical workspace owner #1503 owns 0018_workspace_registry → 0019_email_read_state_repair → 0020_workspace_organization_binding → 0021_workspace_personal_owner_binding, both from protected historical 0017_merge_newsdom_carddav_heads ancestry.

Therefore the revision identifiers/down-revisions in this branch are not final merge authority. Do not create a merge revision against absent #1503 source, copy #1503 migrations here, or independently merge either parallel head. After #1503 reaches protected ancestry, #1195 must ordinary/non-force adopt that protected lineage and rechain/renumber its message-provenance and POP3-progress schema after the then-current protected Alembic head. Acceptance requires one head plus fresh PostgreSQL upgrade from an empty DB and historical protected migration point.

The current POP3 retry repairs deliberately extend the existing branch-local 0019_pop3_observed_uidl schema instead of adding another parallel migration.

Existing email identity lineage

  • zone-less Date RED/fix/doctoring: 66273d51... → 37429ccb... → 70bf46ee...;
  • complete sender/recipients/subject/body gate: 9fdb1207... → d2ae9d6d...;
  • direct-import and fetched-email boundaries: 5d68f08b... → c39f5174..., fd4cd4e4... → d8a08deb...;
  • sync-count disposition repairs ending at 908a254d... prevent duplicate IMAP/POP3 messages from inflating imported counts.

POP3 durable progress repairs

The first durable-progress repair handled standards-conforming message-level -ERR: RED 5d40f83ff48e7dd9c3bf6deb93995130c5a1470a and fix 7406cb63923003df2c3a5d21a0104203f78b90bc persist collection_disposition={observed,retryable} plus nullable retry_after, prioritize never-attempted UIDLs before due retries, keep provider network I/O outside long-lived DB transactions, and leave successfully persisted UIDLs observed. This prevents a set of persistent negative tail messages from monopolizing every poll.

Fresh review found a second cross-poll counterexample. _retrieve_uidl_batch() stopped the remaining batch on a malformed protocol response or OSError, but returned the currently attempted UIDL with no disposition. Candidate selection then treated that same newest UIDL as never attempted on reconnect and put it ahead of lower fresh backlog again. A persistently interrupted newest UIDL could therefore recreate the starvation that the durable progress model was intended to remove.

  • RED 5be27e6d11e918a37dbba3d199c4015c731e63c1 covers malformed-protocol and transport interruption plus next-poll selection;
  • causal fix c6462af326721da39497666d78556ec8e31432dd marks only the currently attempted UIDL retryable before terminating the untrusted session; identities never attempted after the interruption remain fresh;
  • doctoring 52d2cc6fc136c931dda609a0129e3cd78048eebb records the distinction: retryable is attempt state, not proof that a transport failure was message-specific.

No DELE is introduced. UIDL-unavailable/malformed LIST fallback remains compatibility-only without an eventual-progress claim.

Evidence boundary

The source repair above is ordinary-forward evidence, not execution GREEN. Historical CodeRabbit approval from 2026-09-17 predates both the persistent-negative and interrupted-RETR generations and does not transfer. A post-last-push independent review is required.

Before merge, the final migration-reconciled exact head must independently prove:

  • repository-local/required workflow GREEN;
  • PostgreSQL fresh/historical migration acceptance and one Alembic head;
  • repeated-poll + restart behavior with more than the cap of persistent negative RETRs ahead of valid unseen messages;
  • reconnect behavior where a newest UIDL repeatedly causes malformed protocol or transport interruption while lower fresh backlog still advances;
  • retryable failures remain retryable while valid backlog advances and duplicate counts stay stable;
  • large-mailbox provider-history profile and applicable security/coverage evidence;
  • qualifying current-head independent review.

Do not synthesize evidence with no-op commits, temporary base manipulation, predecessor receipts, blind reruns, self-approval, force push, destructive rebase, copied source, synthetic statuses, or gate weakening.

Merge gate

Keep Draft until #1623 settles, #1691 enables normal stacked admission, #1503's migration lineage reaches protected ancestry, this owner is ordinarily restacked and rechained to one Alembic head, exact integrated PostgreSQL/test/security/coverage is terminal GREEN, #1717 acceptance is satisfied, and no causal finding remains unresolved.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026 •

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The change adds email date provenance tracking, source-bound fallback fingerprinting, and three-way deduplication across parsing, persistence, import workers, tests, and documentation. It also changes import embedding generation with larger uploads, chunk-window batching, empty-input handling, and partial-result completion.

Changes

Email deduplication

Layer / File(s) Summary
Parse header provenance
backend/services/email_parser.py, backend/tests/test_email_parser_provenance.py
EmailData now carries header_date, date_provenance, and message_id_provenance. Date parsing distinguishes parsed, missing, and invalid, including zone-less header handling and Message-ID provenance tests.
Persist provenance and source bytes
backend/alembic/versions/0018_email_date_provenance.py, backend/db/models.py, backend/services/email_import_service.py, backend/services/imap_worker.py, backend/services/pop3_worker.py, backend/tests/test_email_import_service.py, backend/tests/test_imap_worker.py, backend/tests/test_pop3_worker.py, backend/tests/test_source_bound_email_dedupe.py, docs/doctoring/...
The schema adds Email.date_provenance. Import code now stores that field, passes raw message bytes from IMAP and POP3, and builds fingerprints from trusted parsed dates or source-bound fallbacks. POP3 message reconstruction now includes the final CRLF terminator. Tests and docs cover source-bound fingerprint behavior.
Classify and resolve duplicate candidates
backend/services/email_dedupe_service.py, backend/tests/test_email_dedupe_service.py
The dedupe service adds DedupeDecision, source and content fingerprint helpers, message-ID lookup normalization, pairwise auto_link / review_required / distinct classification, and multi-row resolution with precedence tests.

Import embeddings

Layer / File(s) Summary
Batch and complete import embeddings
backend/services/email_import_service.py
The upload limit increases to 64 MiB. Embedding generation now chunks parsed body and attachment content in windows of 32, skips unsupported attachments, returns empty output for empty input, uses zero vectors for empty sources, and completes partial batch results with per-request fallback generation.

Priority: ⬇️ Low

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

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant IMAPWorker
  participant POP3Worker
  participant process_fetched_email
  participant email_dedupe_service
  participant Email
  IMAPWorker->>process_fetched_email: import raw_message
  POP3Worker->>process_fetched_email: import reconstructed message bytes
  process_fetched_email->>email_dedupe_service: compute strong or source fingerprint
  email_dedupe_service-->>process_fetched_email: return fingerprint inputs
  process_fetched_email->>Email: persist fingerprint and date_provenance
Loading

Merge Risk: 🟡 Moderate · up to 449a6

Unsupported Date zones can incorrectly trigger automatic email linking, and unrelated import and embedding changes complicate safe validation and rollback. Resolve both before merging.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: deterministic email deduplication provenance with strong fingerprints gated on genuine Date data.
✨ 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/contextualwisdomlab-audit-governance-qyxe67

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 changed the title feat(email): expose Date/Message-ID provenance in the parser (naruon#1086 foundation) feat(email): Date/Message-ID provenance + strong-fingerprint dedupe gating (naruon#1086) Jul 30, 2026
@seonghobae seonghobae changed the title feat(email): Date/Message-ID provenance + strong-fingerprint dedupe gating (naruon#1086) feat(email): deterministic dedupe provenance — gate strong fingerprints on genuine Date (naruon#1086) Jul 30, 2026
coderabbitai[bot]

This comment was marked as resolved.

@github-actions

github-actions Bot commented Jul 30, 2026 •

Copy link
Copy Markdown
Contributor

PR governance metadata gate is not ready for 0b74befc6d4ccdd49db3a33b7565e4b224c4a30b:

  • Required check strix is FAILURE on the current head.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 30, 2026
@seonghobae
seonghobae enabled auto-merge July 31, 2026 12:48
@seonghobae

Copy link
Copy Markdown
Contributor Author

Closing after Loop drain: permanently blocked — branch was updated onto develop for mergeability, which cleared prior APPROVED robot evidence; re-review (CodeRabbit/OpenCode) and/or central gate jobs (metadata-only gate evaluation, coverage-evidence) remained pending/stuck without a re-runnable workflow handle. Not force-merging (merge-gate policy). Re-open a focused PR when robot capacity is available. Related product security fixes that reimplemented cleanly remain on branch goal/carddav-path-traversal-decode (#1206) for relaunch.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Current-head provenance and deduplication revalidation completed: parser evidence classification, import and IMAP persistence, conservative migration backfill, strong-fingerprint gating, and review-required decisions are covered. Refresh central review evidence for this exact head.

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Please re-review the current head c14fcae24b99a65d7755a2d4db7eaf93bf431f6e. All actionable findings are resolved or withdrawn with code-path evidence; five required workflows are green and the current-head container validation is completing.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026 •

Copy link
Copy Markdown
Contributor

@seonghobae I will re-review the current head c14fcae24b99a65d7755a2d4db7eaf93bf431f6e. I will evaluate the current diff and the resolved findings. The completing container validation remains a separate verification signal.

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

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Current head c14fcae24b99a65d7755a2d4db7eaf93bf431f6e has all repository-local required workflows successful and every current review thread resolved. Re-triggering central current-head OpenCode review and merge scheduling after its RFC 5322 prerequisite PR.

@seonghobae
seonghobae marked this pull request as draft August 3, 2026 05:07
auto-merge was automatically disabled August 3, 2026 05:07

Pull request was converted to draft

@seonghobae
seonghobae marked this pull request as ready for review August 3, 2026 05:07
@seonghobae
seonghobae marked this pull request as draft August 3, 2026 05:27

Copy link
Copy Markdown
Contributor Author

Temporarily sequencing this provenance/deduplication PR behind #1192 because both modify RFC 5322 date parsing and email-ingest contracts. Keeping this PR in draft prevents an overlapping merge race. After #1192 lands, update onto current develop, resolve the shared parser changes without weakening timezone-awareness or provenance classification, rerun migration/email/dedupe coverage, then mark ready for independent review and guarded merge.

Copy link
Copy Markdown
Contributor Author

@jules Refresh this PR onto the current develop head 0484ac3c137dcd31b02f6718773f276476015444 now that #1192 is merged.

Resolve the overlap in backend/services/email_parser.py by preserving every RFC 5322/RFC 2047 correctness change already on develop and layering the provenance contract on top:

  • keep Unicode display-name handling, attachment/body fallbacks, provider-byte parsing, and timezone-aware -0000 behavior;
  • add header_date, date_provenance, and message_id_provenance without changing the effective date semantics;
  • retain a backward-compatible _extract_date wrapper if the provenance implementation introduces a new helper;
  • a missing/whitespace Date must be missing, an unparseable Date invalid, a valid or -0000 Date parsed; only genuine parsed metadata may seed strong fingerprints;
  • keep the Alembic history single-headed and the date_provenance database name unchanged.

Use test-first conflict resolution. Preserve all current #1192 tests and add the provenance regressions in a separate focused test module if that avoids destructive edits. Rebase or rebuild the branch so it is zero commits behind develop, contains only the intended ten-file provenance scope (or an equivalent non-destructive test split), has no temporary workflow, and passes Ruff, Alembic/model reconciliation, focused email/import/IMAP/dedupe tests, and 100% coverage for newly introduced production branches. Do not merge the PR.

@seonghobae
seonghobae force-pushed the claude/contextualwisdomlab-audit-governance-qyxe67 branch from c14fcae to d948528 Compare August 4, 2026 06:35
@seonghobae
seonghobae dismissed coderabbitai[bot]’s stale review August 4, 2026 06:35

All actionable findings were addressed or explicitly withdrawn in resolved threads. The branch is now rebuilt as one current-develop commit at d948528, preserving later address-display hardening and isolating provenance tests in a focused module.

@seonghobae
seonghobae marked this pull request as ready for review August 4, 2026 06:36

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Please review exact head d9485287b0115d0b9dbbd0bef9d9144fd416e72c, rebuilt as one current-develop commit. All prior inline findings are resolved or withdrawn; the merge preserves later address-display hardening and moves provenance regressions into a focused test module.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026 •

Copy link
Copy Markdown
Contributor

@seonghobae I will review pull request #1195 at head d9485287b0115d0b9dbbd0bef9d9144fd416e72c. I will check the provenance contract, deduplication gate, migration consistency, and preservation of the parser hardening.

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

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Please review the exact current integration only: base #1623@509be4c1d9b6c7ba239a108656e2382681a85341 → head 3c734b54112b241c12f9d4a35335caba2e089a53. Focus on the new POP3 UIDL provider-progress model/migration, bounded unseen selection, idempotent observation persistence, UIDL-unavailable fallback, partial RETR preservation, QUIT cleanup, transaction boundaries, and the corresponding tests/doctoring. Please return a formal APPROVED or CHANGES_REQUESTED verdict for this exact head; do not transfer predecessor review evidence.

seonghobae added a commit that referenced this pull request Sep 17, 2026
@coderabbitai

coderabbitai Bot commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo='ContextualWisdomLab/naruon'
base='509be4c1d9b6c7ba239a108656e2382681a85341'
head='3c734b54112b241c12f9d4a35335caba2e089a53'

printf '%s\n' '== Exact integration identity =='
printf 'checked_out='; git rev-parse HEAD
git cat-file -t "$base"
git cat-file -t "$head"
git show -s --format='base=%H%nsubject=%s' "$base"
git show -s --format='head=%H%nparents=%P%nsubject=%s' "$head"
git merge-base --is-ancestor "$base" "$head" && echo 'base_is_ancestor=yes' || echo 'base_is_ancestor=no'

printf '%s\n' '== Aggregate effective diff =='
git diff --name-status "$base" "$head"
git diff --stat "$base" "$head"
git diff --check "$base" "$head"

printf '%s\n' '== POP3 UIDL candidate files =='
git diff --name-only "$base" "$head" | rg -n -i '(pop3|uidl|provider|migration|doctor|email)' || true

printf '%s\n' '== Outlines of changed POP3/migration/tests/docs modules =='
while IFS= read -r file; do
  case "$file" in
    *.py|*.md)
      if git cat-file -e "$head:$file" 2>/dev/null; then
        printf '\n--- %s ---\n' "$file"
        git show "$head:$file" > "/tmp/review-$(basename "$file")"
        case "$file" in
          *.py) ast-grep outline "/tmp/review-$(basename "$file")" --items all || true ;;
          *.md) rg -n '^(#|##|###)|UIDL|RETR|QUIT|transaction|unseen|progress|idempot' "/tmp/review-$(basename "$file")" || true ;;
        esac
      fi
      ;;
  esac
done < <(git diff --name-only "$base" "$head")

printf '%s\n' '== UIDL, RETR, QUIT, progress, transaction, and count references =='
git grep -n -i -C 7 -E \
  '(UIDL|uidl|RETR|retr|QUIT|quit|provider.progress|provider_progress|progress|unseen|observation|idempot|transaction|begin_nested|commit|rollback|imported_count|persist_fetched_email|process_fetched_email)' \
  "$head" -- backend docs 2>/dev/null || true

printf '%s\n' '== Alembic graph declarations =='
git grep -n -C 2 -E '^(revision|down_revision|branch_labels|depends_on)\s*=' \
  "$head" -- backend/alembic/versions || true

printf '%s\n' '== Current PR status and checks =='
gh pr view 1195 --repo "$repo" \
  --json headRefOid,baseRefOid,state,isDraft,mergeStateStatus,reviewDecision,statusCheckRollup 2>&1 || true
gh pr checks 1195 --repo "$repo" \
  --json name,state,workflow,link,completedAt,description 2>&1 || true

Length of output: 6938


🏁 Script executed:

#!/bin/bash
set -euo pipefail

base='509be4c1d9b6c7ba239a108656e2382681a85341'
head='3c734b54112b241c12f9d4a35335caba2e089a53'

show_range() {
  local file="$1" start="$2" end="$3"
  if git cat-file -e "$head:$file" 2>/dev/null; then
    printf '\n--- %s, Lines %s-%s ---\n' "$file" "$start" "$end"
    git show "$head:$file" | awk -v start="$start" -v end="$end" \
      'NR >= start && NR <= end { printf "%d\t%s\n", NR, $0 }'
  fi
}

printf '%s\n' '== Changed Python, migration, test, and doctoring files =='
git diff --name-status "$base" "$head" | rg -i '(pop3|uidl|provider|migration|doctor|models|email)' || true

printf '%s\n' '== Exact changes since integration base for POP3 relevant files =='
git diff --find-renames --unified=55 "$base" "$head" -- \
  backend/services/pop3_worker.py \
  backend/services/imap_worker.py \
  backend/db/models.py \
  backend/alembic/versions \
  backend/tests \
  docs \
  || true

printf '%s\n' '== Targeted production ranges =='
show_range backend/services/pop3_worker.py 1 420
show_range backend/db/models.py 1 220
show_range backend/db/models.py 760 900

printf '%s\n' '== Targeted migrations =='
for file in $(git diff --name-only "$base" "$head" -- backend/alembic/versions); do
  show_range "$file" 1 180
done

printf '%s\n' '== Targeted tests and doctoring =='
for file in $(git diff --name-only "$base" "$head" -- backend/tests docs); do
  case "$file" in
    *pop3*|*uidl*|*provider*|*doctor*|*.md) show_range "$file" 1 460 ;;
  esac
done

printf '%s\n' '== POP3 external API calls and exception paths =='
git grep -n -C 12 -E \
  '(\.uidl\(|\.retr\(|\.quit\(|except |finally:|rollback\(|commit\(|flush\(|begin\(|begin_nested\(|Provider|provider)' \
  "$head" -- backend/services/pop3_worker.py backend/tests 2>/dev/null || true

printf '%s\n' '== Every Provider progress model query/write =='
git grep -n -C 10 -i -E \
  '(pop3.*(uidl|progress)|uidl.*(progress|provider)|Provider.*(uidl|progress)|provider.*(uidl|progress))' \
  "$head" -- backend 2>/dev/null || true

printf '%s\n' '== Syntax parse changed production Python files =='
while IFS= read -r file; do
  case "$file" in
    backend/*.py)
      git show "$head:$file" > "/tmp/syntax-$(basename "$file")"
      python - "/tmp/syntax-$(basename "$file")" <<'PY'
import ast
import sys
path = sys.argv[1]
ast.parse(open(path, encoding="utf-8").read(), filename=path)
print(f"{path}: AST_OK")
PY
      ;;
  esac
done < <(git diff --name-only "$base" "$head")

Length of output: 203174


🏁 Script executed:

#!/bin/bash
set -euo pipefail

head='3c734b54112b241c12f9d4a35335caba2e089a53'

show_file() {
  local file="$1"
  printf '\n--- %s ---\n' "$file"
  git show "$head:$file" | nl -ba
}

show_file backend/services/pop3_worker.py
show_file backend/db/pop3_collection_models.py
show_file backend/alembic/versions/0019_pop3_observed_uidl.py
show_file backend/tests/test_pop3_uidl_progress.py
show_file backend/tests/test_pop3_partial_retrieval.py
show_file backend/tests/test_pop3_quit_resilience.py
show_file backend/tests/test_pop3_uidl_migration_contract.py

printf '%s\n' '== POP3-related test names in all focused test modules =='
git grep -n -E '^(async )?def test_' "$head" -- \
  backend/tests/test_pop3_worker.py \
  backend/tests/test_pop3_uidl_progress.py \
  backend/tests/test_pop3_partial_retrieval.py \
  backend/tests/test_pop3_quit_resilience.py \
  backend/tests/test_pop3_uidl_migration_contract.py || true

printf '%s\n' '== Exact migration declarations and migration-model identifiers =='
git grep -n -C 3 -E \
  '(pop3_observed_messages|Pop3ObservedMessage|tenant_config_id|provider_uidl|observed_at|0019_pop3_observed_uidl)' \
  "$head" -- backend/alembic/versions/0019_pop3_observed_uidl.py backend/db/pop3_collection_models.py backend/services/pop3_worker.py || true

printf '%s\n' '== POP3 doctoring sections =='
git show "$head:docs/doctoring/email-source-identity-provenance.md" \
  | rg -n -C 6 -i '(pop3|uidl|retr|quit|observ|progress|transaction)' || true

printf '%s\n' '== Static Python parse for reviewed production files =='
for file in backend/services/pop3_worker.py backend/db/pop3_collection_models.py backend/alembic/versions/0019_pop3_observed_uidl.py; do
  temp="/tmp/$(basename "$file")"
  git show "$head:$file" > "$temp"
  python - "$temp" <<'PY'
import ast
import sys
path = sys.argv[1]
ast.parse(open(path, encoding="utf-8").read(), filename=path)
print(f"{path}: AST_OK")
PY
done

Length of output: 67679


🏁 Script executed:

#!/bin/bash
set -euo pipefail

head='3c734b54112b241c12f9d4a35335caba2e089a53'

show_range() {
  local file="$1" start="$2" end="$3"
  printf '\n--- %s, Lines %s-%s ---\n' "$file" "$start" "$end"
  git show "$head:$file" | awk -v start="$start" -v end="$end" \
    'NR >= start && NR <= end { printf "%d\t%s\n", NR, $0 }'
}

show_range backend/tests/test_pop3_worker.py 130 380
show_range backend/tests/test_pop3_uidl_progress.py 100 190

printf '%s\n' '== Focused test assertions for transaction and UIDL observation failure =='
git grep -n -i -C 10 -E \
  '(rollback|commit|persist.*(fail|error)|observation|observed|uidl|duplicate|created_record|imported_count)' \
  "$head" -- backend/tests/test_pop3_worker.py backend/tests/test_pop3_uidl_progress.py backend/tests/test_pop3_partial_retrieval.py backend/tests/test_pop3_quit_resilience.py || true

printf '%s\n' '== Alembic graph heads at exact head =='
tmpdir="$(mktemp -d)"
git archive "$head" backend/alembic/versions | tar -x -C "$tmpdir"
python - "$tmpdir/backend/alembic/versions" <<'PY'
import ast
import pathlib
import sys

root = pathlib.Path(sys.argv[1])
revisions = {}
parents = set()
for path in root.glob("*.py"):
    tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
    values = {}
    for node in tree.body:
        if isinstance(node, ast.Assign):
            for target in node.targets:
                if isinstance(target, ast.Name) and target.id in {"revision", "down_revision"}:
                    try:
                        values[target.id] = ast.literal_eval(node.value)
                    except (ValueError, TypeError):
                        pass
    revision = values.get("revision")
    if isinstance(revision, str):
        revisions[revision] = path.name
        down = values.get("down_revision")
        if isinstance(down, str):
            parents.add(down)
        elif isinstance(down, tuple):
            parents.update(value for value in down if isinstance(value, str))
heads = sorted(set(revisions) - parents)
print(f"heads={heads}")
print(f"0019_file={revisions.get('0019_pop3_observed_uidl')}")
PY
rm -rf "$tmpdir"

printf '%s\n' '== Changed workflow files =='
git diff --name-status '509be4c1d9b6c7ba239a108656e2382681a85341' "$head" -- '.github/workflows/**'

printf '%s\n' '== Exact GitHub review/check status =='
gh pr view 1195 --repo ContextualWisdomLab/naruon \
  --json headRefOid,baseRefOid,isDraft,mergeStateStatus,reviewDecision,statusCheckRollup \
  --jq '{headRefOid,baseRefOid,isDraft,mergeStateStatus,reviewDecision,checks:[.statusCheckRollup[] | {name,status,conclusion,completedAt}]}' 2>&1 || true

Length of output: 55253


@seonghobae APPROVED for exact integration #1623@509be4c1d9b6c7ba239a108656e2382681a85341 → #1195@3c734b54112b241c12f9d4a35335caba2e089a53.

I found no code blocker in the requested current-head scope.

  • Pop3ObservedMessage stores UIDL progress separately from email identity.

  • The model scopes a UIDL by tenant_config_id.

  • The model limits provider_uidl to 70 characters.

  • The migration creates the same table, foreign key, unique constraint, and index as the model.

  • 0019_pop3_observed_uidl follows 0018_email_date_provenance.

  • The Alembic graph has one head: 0019_pop3_observed_uidl.

  • The worker loads observed UIDLs before it starts POP3 network I/O.

  • The worker filters observed UIDLs before it selects messages.

  • The worker selects at most MAX_POP3_FETCH_MESSAGES unseen messages.

  • The worker uses the current session message number to retrieve each UIDL.

  • This preserves progress after POP3 message-number renumbering.

  • If UIDL is unsupported or malformed, the worker uses the bounded highest-number LIST fallback.

  • The fallback assigns no provider_uidl.

  • The fallback does not claim durable backlog progress.

  • The worker preserves messages retrieved before a later RETR failure.

  • The worker stops the current batch after the failing RETR.

  • The failed UIDL remains unobserved and eligible for retry.

  • If QUIT fails, the worker retains retrieved messages and attempts close().

  • _import_messages() writes the email record and UIDL observation in the same session transaction.

  • The worker inserts the UIDL only after persist_fetched_email() returns.

  • The PostgreSQL conflict boundary makes UIDL observation idempotent.

  • If persistence or observation fails, the worker rolls back the transaction.

  • POP3 imported counts increase only when created_record is true.

The focused tests cover unseen selection after a newer window, renumbering, UIDL-unavailable fallback, partial RETR handling, failed QUIT cleanup, UIDL persistence, and duplicate imported-count behavior. The doctoring document describes the same collection-progress and retry boundaries.

I performed static inspection only. I did not execute repository code or tests.

You are interacting with an AI system.

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Please review exact current integration #1623@509be4c1d9b6c7ba239a108656e2382681a85341 → #1195@fcef1780986bdd0d0986342e71206b303e0dd27c, including the latest POP3 partial-RETR changes (d9c87082... → 38803cc5... → 7606387c... → a5d7d210...) and doctoring fcef1780.... Treat predecessor approvals as stale. Please submit a formal current-head APPROVED or REQUEST_CHANGES verdict; in particular verify that only RFC-style -ERR continues the bounded batch, while transport loss or malformed/unexpected protocol response stops it.

@coderabbitai

coderabbitai Bot commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo='ContextualWisdomLab/naruon'
base='509be4c1d9b6c7ba239a108656e2382681a85341'
head='fcef1780986bdd0d0986342e71206b303e0dd27c'

printf '%s\n' '== Exact integration identity =='
printf 'checked_out='; git rev-parse HEAD
git cat-file -t "$base"
git cat-file -t "$head"
git show -s --format='base=%H%nsubject=%s' "$base"
git show -s --format='head=%H%nparents=%P%nsubject=%s' "$head"
git merge-base --is-ancestor "$base" "$head" && echo 'base_is_ancestor=yes' || echo 'base_is_ancestor=no'

printf '%s\n' '== Aggregate effective diff =='
git diff --name-status "$base" "$head"
git diff --stat "$base" "$head"
git diff --check "$base" "$head"

printf '%s\n' '== Changes since prior POP3-reviewed head =='
git log --format='%H %s' '3c734b54112b241c12f9d4a35335caba2e089a53..'"$head"
git diff --name-status '3c734b54112b241c12f9d4a35335caba2e089a53' "$head"

printf '%s\n' '== Changed POP3 candidates and outlines =='
git diff --name-only "$base" "$head" | rg -i '(pop3|uidl|doctor|migration|email)' || true
for file in \
  backend/services/pop3_worker.py \
  backend/tests/test_pop3_worker.py \
  backend/tests/test_pop3_partial_retrieval.py \
  backend/tests/test_pop3_quit_resilience.py \
  backend/tests/test_pop3_uidl_progress.py \
  docs/doctoring/email-source-identity-provenance.md \
  backend/alembic/versions/0019_pop3_observed_uidl.py \
  backend/db/pop3_collection_models.py
do
  if git cat-file -e "$head:$file" 2>/dev/null; then
    git show "$head:$file" > "/tmp/$(basename "$file")"
    printf '\n--- %s ---\n' "$file"
    case "$file" in
      *.py) ast-grep outline "/tmp/$(basename "$file")" --items all || true ;;
      *.md) rg -n -i -C 3 '(POP3|RETR|QUIT|partial|transport|malformed|-ERR|progress)' "/tmp/$(basename "$file")" || true ;;
    esac
  fi
done

show_range() {
  local file="$1" start="$2" end="$3"
  if git cat-file -e "$head:$file" 2>/dev/null; then
    printf '\n--- %s, Lines %s-%s ---\n' "$file" "$start" "$end"
    git show "$head:$file" | awk -v start="$start" -v end="$end" \
      'NR >= start && NR <= end { printf "%d\t%s\n", NR, $0 }'
  fi
}

printf '%s\n' '== POP3 implementation and focused tests =='
show_range backend/services/pop3_worker.py 1 460
show_range backend/tests/test_pop3_partial_retrieval.py 1 460
show_range backend/tests/test_pop3_worker.py 1 460
show_range backend/tests/test_pop3_quit_resilience.py 1 300

printf '%s\n' '== RETR error classification and exact call sites =='
git grep -n -i -C 10 -E \
  '(retr\(|RETR|error_proto|-\s*ERR|^-ERR|transport|EOF|malformed|unexpected|partial|except |continue|break|close\(|quit\()' \
  "$head" -- backend/services/pop3_worker.py backend/tests 2>/dev/null || true

printf '%s\n' '== Focused diff since prior reviewed head =='
git diff --find-renames --unified=80 '3c734b54112b241c12f9d4a35335caba2e089a53' "$head" -- \
  backend/services/pop3_worker.py \
  backend/tests/test_pop3_partial_retrieval.py \
  backend/tests/test_pop3_worker.py \
  backend/tests/test_pop3_quit_resilience.py \
  docs/doctoring/email-source-identity-provenance.md

printf '%s\n' '== Migration lineage and exact PR state =='
git grep -n -C 2 -E '^(revision|down_revision|branch_labels|depends_on)\s*=' \
  "$head" -- backend/alembic/versions || true
gh pr view 1195 --repo "$repo" \
  --json headRefOid,baseRefOid,state,isDraft,mergeStateStatus,reviewDecision,statusCheckRollup 2>&1 || true
gh pr checks 1195 --repo "$repo" \
  --json name,state,workflow,link,completedAt,description 2>&1 || true

printf '%s\n' '== Static syntax check of POP3 worker =='
git show "$head:backend/services/pop3_worker.py" > /tmp/pop3_worker.py
python - <<'PY'
import ast
ast.parse(open("/tmp/pop3_worker.py", encoding="utf-8").read(), filename="/tmp/pop3_worker.py")
print("backend/services/pop3_worker.py: AST_OK")
PY

Length of output: 204294


🏁 Script executed:

#!/bin/bash
set -euo pipefail
head='fcef1780986bdd0d0986342e71206b303e0dd27c'

show() {
  local file="$1" start="$2" end="$3"
  printf '\n--- %s, Lines %s-%s ---\n' "$file" "$start" "$end"
  git show "$head:$file" | awk -v start="$start" -v end="$end" \
    'NR >= start && NR <= end { printf "%d\t%s\n", NR, $0 }'
}

show backend/services/pop3_worker.py 250 390
show backend/tests/test_pop3_partial_retrieval.py 1 180

printf '%s\n' '== All helper-specific tests and malformed status inputs =='
git grep -n -i -C 6 -E \
  '(_is_negative_pop3_response|-\s*ERR|ERROR|ERR[A-Za-z0-9]|unexpected response|malformed)' \
  "$head" -- backend/tests/test_pop3_partial_retrieval.py backend/tests/test_pop3_worker.py backend/tests/test_pop3_quit_resilience.py || true

printf '%s\n' '== Exact partial-RETR commits =='
git show -s --format='%H%nparents=%P%nsubject=%s' \
  d9c87082355fd133a5f4ed9715c560eaee28b211 \
  38803cc59be9cee2f72be5f48a9c3c9f1aca402e \
  7606387c829e27845633d180642369547d23336b \
  a5d7d21011cf96548cce18b8119a553123a25ab5
git diff --unified=50 7606387c829e27845633d180642369547d23336b a5d7d21011cf96548cce18b8119a553123a25ab5 -- \
  backend/services/pop3_worker.py backend/tests/test_pop3_partial_retrieval.py

Length of output: 35480


🤖 web_search executed:

RFC 1939 POP3 response status indicator grammar -ERR space text exact syntax

Length of output: 7532


@seonghobae REQUEST_CHANGES for exact integration #1623@509be4c1d9b6c7ba239a108656e2382681a85341 → #1195@fcef1780986bdd0d0986342e71206b303e0dd27c.

I found one blocking issue in backend/services/pop3_worker.py.

_is_negative_pop3_response() uses startswith("-ERR") for text and startswith(b"-ERR") for bytes. A malformed response such as -ERROR or -ERRX passes this check. The UIDL and LIST fallback retrieval loops then continue the bounded batch.

A POP3 negative status indicator must be exactly -ERR, followed by the end of the response or a valid separator before optional response text. A prefix-only match does not enforce that boundary.

This conflicts with the required contract: only an RFC-style -ERR can continue the batch. A malformed or unexpected protocol response must stop the batch.

Update _is_negative_pop3_response() to validate the status-token boundary. For example, accept only:

  • -ERR
  • -ERR followed by response text
  • the equivalent byte responses

Reject -ERROR, -ERRX, and similar malformed prefixes.

Add regressions for both UIDL and LIST fallback retrieval paths. Each regression should raise poplib.error_proto("-ERROR ...") or poplib.error_proto(b"-ERRX ...") for an intermediate RETR call and verify that the worker does not retrieve later selected messages.

I verified the intended paths otherwise:

  • A valid -ERR no such message continues the UIDL and LIST fallback batches.
  • An OSError stops the batch and preserves earlier retrieved messages.
  • A generic unexpected protocol response stops the batch.
  • The doctoring document describes the intended failure boundary.
  • 0019_pop3_observed_uidl follows 0018_email_date_provenance.
  • The requested base is an ancestor of the requested head.

I performed static inspection only. I did not execute repository tests.

You are interacting with an AI system.

Copy link
Copy Markdown
Contributor Author

@coderabbitai Please perform a fresh formal review of exact #1623@509be4c1d9b6c7ba239a108656e2382681a85341 → #1195@fcef1780986bdd0d0986342e71206b303e0dd27c, including the current POP3 UIDL/RETR changes and the newly documented cross-owner Alembic-lineage blocker with #1503. Treat predecessor approvals as historical. Please return a formal APPROVED or REQUEST_CHANGES verdict for this exact source head; do not infer hosted test/coverage evidence where the repository-local exact-head workflow inventory is empty.

Copy link
Copy Markdown
Contributor Author

Additional current-head finding after the exact review request: the recent RETR -ERR repair is correct within one selected batch, but it does not yet prove #1717's cross-poll eventual-progress invariant. If the highest ten unseen UIDLs all return persistent standards-conforming -ERR, none is observed and the next poll selects the same ten again, indefinitely starving lower unseen UIDLs. Marking them observed would turn transient rejection into silent loss, so this needs bounded durable retry/progress state rather than another local continue/break tweak. I recorded the counterexample and acceptance requirement on #1717. Any persistence revision must also respect the newly verified #1503/#1195 Alembic-lineage repair; do not add another parallel head. Keep this PR Draft.

@coderabbitai

coderabbitai Bot commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo='ContextualWisdomLab/naruon'
base='509be4c1d9b6c7ba239a108656e2382681a85341'
head='fcef1780986bdd0d0986342e71206b303e0dd27c'

printf '%s\n' '== Exact integration identity =='
printf 'checked_out='; git rev-parse HEAD
git cat-file -t "$base"
git cat-file -t "$head"
git show -s --format='base=%H%nsubject=%s' "$base"
git show -s --format='head=%H%nparents=%P%nsubject=%s' "$head"
git merge-base --is-ancestor "$base" "$head" && echo 'base_is_ancestor=yes' || echo 'base_is_ancestor=no'

printf '%s\n' '== Aggregate effective diff =='
git diff --name-status "$base" "$head"
git diff --check "$base" "$head"

printf '%s\n' '== Relevant structures =='
for file in \
  backend/services/pop3_worker.py \
  backend/db/pop3_collection_models.py \
  backend/alembic/versions/0018_email_date_provenance.py \
  backend/alembic/versions/0019_pop3_observed_uidl.py \
  backend/tests/test_pop3_partial_retrieval.py \
  backend/tests/test_pop3_uidl_progress.py \
  backend/tests/test_pop3_worker.py \
  backend/tests/test_pop3_quit_resilience.py \
  docs/doctoring/email-source-identity-provenance.md
do
  if git cat-file -e "$head:$file" 2>/dev/null; then
    git show "$head:$file" > "/tmp/$(basename "$file")"
    printf '\n--- %s ---\n' "$file"
    case "$file" in
      *.py) ast-grep outline "/tmp/$(basename "$file")" --items all || true ;;
      *.md) rg -n -i -C 3 '(`#1503`|alembic|lineage|pop3|uidl|retr|quit|partial|transport|malformed|-ERR)' "/tmp/$(basename "$file")" || true ;;
    esac
  fi
done

show_range() {
  local file="$1" start="$2" end="$3"
  printf '\n--- %s, Lines %s-%s ---\n' "$file" "$start" "$end"
  git show "$head:$file" | awk -v start="$start" -v end="$end" \
    'NR >= start && NR <= end {printf "%d\t%s\n", NR, $0}'
}

printf '%s\n' '== POP3 failure classification and retrieval boundaries =='
show_range backend/services/pop3_worker.py 1 440
show_range backend/tests/test_pop3_partial_retrieval.py 1 260
git grep -n -i -C 10 -E \
  '(_is_negative_pop3_response|-\s*ERR|retr\(|error_proto|continue|break|transport|malformed|unexpected)' \
  "$head" -- backend/services/pop3_worker.py backend/tests/test_pop3_partial_retrieval.py backend/tests/test_pop3_worker.py || true

printf '%s\n' '== POP3 progress transaction paths =='
git grep -n -i -C 10 -E \
  '(Pop3ObservedMessage|uidl|observed|begin_nested|commit|rollback|flush|persist_fetched_email|created_record|imported_count|quit\(|close\()' \
  "$head" -- backend/services/pop3_worker.py backend/db/pop3_collection_models.py backend/tests/test_pop3_uidl_progress.py backend/tests/test_pop3_worker.py backend/tests/test_pop3_quit_resilience.py || true

printf '%s\n' '== Alembic declarations and graph heads =='
git grep -n -C 2 -E '^(revision|down_revision|branch_labels|depends_on)\s*=' \
  "$head" -- backend/alembic/versions || true
tmpdir="$(mktemp -d)"
git archive "$head" backend/alembic/versions | tar -x -C "$tmpdir"
python - "$tmpdir/backend/alembic/versions" <<'PY'
import ast
import pathlib
import sys
root = pathlib.Path(sys.argv[1])
revisions = {}
parents = set()
for path in root.glob("*.py"):
    tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
    values = {}
    for node in tree.body:
        if isinstance(node, ast.Assign):
            for target in node.targets:
                if isinstance(target, ast.Name) and target.id in {"revision", "down_revision"}:
                    try:
                        values[target.id] = ast.literal_eval(node.value)
                    except (ValueError, TypeError):
                        pass
    revision = values.get("revision")
    if isinstance(revision, str):
        revisions[revision] = path.name
        down = values.get("down_revision")
        if isinstance(down, str):
            parents.add(down)
        elif isinstance(down, tuple):
            parents.update(parent for parent in down if isinstance(parent, str))
print("heads=", sorted(set(revisions) - parents))
print("0018=", revisions.get("0018_email_date_provenance"))
print("0019=", revisions.get("0019_pop3_observed_uidl"))
PY
rm -rf "$tmpdir"

printf '%s\n' '== Documented `#1503` and local workflow inventory =='
git grep -n -i -C 8 -E '(`#1503`|alembic|lineage|cross-owner|blocker)' \
  "$head" -- docs backend .github 2>/dev/null || true
git ls-tree -r --name-only "$head" .github/workflows 2>/dev/null || true
gh pr view 1195 --repo "$repo" --json headRefOid,baseRefOid,isDraft,reviewDecision,statusCheckRollup 2>&1 || true

printf '%s\n' '== Static syntax parse =='
for file in backend/services/pop3_worker.py backend/db/pop3_collection_models.py backend/alembic/versions/0019_pop3_observed_uidl.py; do
  git show "$head:$file" > "/tmp/$(basename "$file")"
  python - "/tmp/$(basename "$file")" <<'PY'
import ast
import sys
path=sys.argv[1]
ast.parse(open(path, encoding="utf-8").read(), filename=path)
print(f"{path}: AST_OK")
PY
done

Length of output: 205376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

head='fcef1780986bdd0d0986342e71206b303e0dd27c'

show_matches() {
  local pattern="$1"
  printf '\n== %s ==\n' "$pattern"
  git grep -n -i -C 15 -E "$pattern" "$head" -- \
    backend/services/pop3_worker.py \
    backend/tests/test_pop3_partial_retrieval.py \
    backend/tests/test_pop3_worker.py \
    backend/tests/test_pop3_uidl_progress.py \
    backend/tests/test_pop3_quit_resilience.py \
    docs/doctoring/email-source-identity-provenance.md \
    || true
}

printf '%s\n' '== POP3 worker, Lines 240-400 =='
git show "$head:backend/services/pop3_worker.py" \
  | awk 'NR >= 240 && NR <= 400 {printf "%d\t%s\n", NR, $0}'

show_matches '_is_negative_pop3_response'
show_matches '(-ERROR|-ERRX|-ERR[[:space:]]|error_proto)'
show_matches '(uidl\(|retr\(|list\(|continue|break)'
show_matches '(`#1503`|cross-owner|Alembic.*lineage|lineage.*Alembic)'

printf '%s\n' '== All `#1503` references in tracked files =='
git grep -n -i -C 12 '`#1503`' "$head" -- ':!docs/superpowers/**' || true

printf '%s\n' '== Targeted migrations =='
for file in backend/alembic/versions/0018_email_date_provenance.py backend/alembic/versions/0019_pop3_observed_uidl.py; do
  printf '\n--- %s ---\n' "$file"
  git show "$head:$file" | nl -ba
done

printf '%s\n' '== Changed files since prior POP3 review =='
git diff --name-status '3c734b54112b241c12f9d4a35335caba2e089a53' "$head"
git diff --unified=30 '3c734b54112b241c12f9d4a35335caba2e089a53' "$head" -- \
  backend/services/pop3_worker.py \
  backend/tests/test_pop3_partial_retrieval.py \
  docs/doctoring/email-source-identity-provenance.md

printf '%s\n' '== Exact test functions =='
git grep -n -E '^(async )?def test_' "$head" -- \
  backend/tests/test_pop3_partial_retrieval.py \
  backend/tests/test_pop3_uidl_progress.py \
  backend/tests/test_pop3_worker.py \
  backend/tests/test_pop3_quit_resilience.py || true

Length of output: 139653


@seonghobae REQUEST_CHANGES for exact integration #1623@509be4c1d9b6c7ba239a108656e2382681a85341 → #1195@fcef1780986bdd0d0986342e71206b303e0dd27c.

I found one blocking POP3 protocol-classification defect.

In backend/services/pop3_worker.py, Line 394 and Line 395, _is_negative_pop3_response() accepts any response that starts with -ERR. Therefore, malformed responses such as -ERROR and -ERRX return True.

The UIDL path continues after this result at Line 329. The LIST fallback path continues after this result at Line 368. The worker can retrieve later messages after a malformed protocol response.

Only an RFC-style negative response must continue the batch. Accept -ERR and -ERR followed by response text. Apply the same rule to bytes. Reject -ERROR, -ERRX, and other invalid prefixes. Stop the remaining batch when the response is malformed.

Add regressions for both retrieval paths:

  1. A UIDL retrieval where an intermediate RETR raises poplib.error_proto("-ERROR ...").
  2. A LIST fallback retrieval where an intermediate RETR raises poplib.error_proto(b"-ERRX ...").

Each regression must verify that the worker does not call RETR for later selected messages.

The current test covers a valid -ERR no such message and an unrelated "unexpected response". It does not falsify the prefix-matching behavior.

The migration files are sequential in this head: 0019_pop3_observed_uidl follows 0018_email_date_provenance. However, I found no #1503 reference in the inspected current-head doctoring or tracked source files. I cannot confirm the claimed cross-owner Alembic-lineage blocker from this source evidence.

I performed static inspection only. I did not infer test or coverage execution from the returned hosted status contexts.

You are interacting with an AI system.

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Please review exact current integration #1623@509be4c1d9b6c7ba239a108656e2382681a85341 -> #1195@630d5461d1eb8b59b392e4170dec66ce1edf034c, including source-order RED 5d40f83ff48e7dd9c3bf6deb93995130c5a1470a, causal fix 7406cb63923003df2c3a5d21a0104203f78b90bc, and doctoring 630d5461d1eb8b59b392e4170dec66ce1edf034c.

Focus on the durable POP3 retry/progress invariant: persistent standards-conforming negative RETR responses must remain retryable but must not monopolize bounded polls while never-attempted UIDLs exist; provider I/O must remain outside DB transaction scope; successful persistence must transition the same UIDL to observed; and this branch must not create a new Alembic revision before #1503 migration ancestry is reconciled.

Please return a formal current-head APPROVED or REQUEST_CHANGES rather than treating predecessor reviews/checks as transferable.

@coderabbitai

coderabbitai Bot commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo='ContextualWisdomLab/naruon'
base='509be4c1d9b6c7ba239a108656e2382681a85341'
head='630d5461d1eb8b59b392e4170dec66ce1edf034c'
red='5d40f83ff48e7dd9c3bf6deb93995130c5a1470a'
fix='7406cb63923003df2c3a5d21a0104203f78b90bc'

printf '%s\n' '== Exact integration identity =='
printf 'checked_out='; git rev-parse HEAD
for commit in "$base" "$head" "$red" "$fix"; do
  git cat-file -t "$commit"
  git show -s --format='commit=%H%nparents=%P%nsubject=%s' "$commit"
done
git merge-base --is-ancestor "$base" "$head" && echo 'base_is_ancestor=yes' || echo 'base_is_ancestor=no'
git merge-base --is-ancestor "$red" "$fix" && echo 'red_is_ancestor_of_fix=yes' || echo 'red_is_ancestor_of_fix=no'
git merge-base --is-ancestor "$fix" "$head" && echo 'fix_is_ancestor_of_head=yes' || echo 'fix_is_ancestor_of_head=no'

printf '%s\n' '== Effective delta and post-fix delta =='
git diff --name-status "$base" "$head"
git diff --stat "$base" "$head"
git diff --check "$base" "$head"
printf '%s\n' '-- Source-order commits --'
git log --format='%H %s' "$red^..$head"
printf '%s\n' '-- RED to causal fix --'
git diff --find-renames --unified=70 "$red" "$fix" -- backend/services/pop3_worker.py backend/db backend/alembic/versions backend/tests docs || true
printf '%s\n' '-- Causal fix to head --'
git diff --find-renames --unified=70 "$fix" "$head" -- backend/services/pop3_worker.py backend/db backend/alembic/versions backend/tests docs || true

printf '%s\n' '== Candidate files and outlines =='
git diff --name-only "$base" "$head" | rg -i '(pop3|uidl|retry|progress|migration|doctor|email)' || true
for file in \
  backend/services/pop3_worker.py \
  backend/db/pop3_collection_models.py \
  backend/alembic/versions/0018_email_date_provenance.py \
  backend/alembic/versions/0019_pop3_observed_uidl.py \
  backend/tests/test_pop3_uidl_progress.py \
  backend/tests/test_pop3_partial_retrieval.py \
  backend/tests/test_pop3_worker.py \
  backend/tests/test_pop3_quit_resilience.py \
  docs/doctoring/email-source-identity-provenance.md
do
  if git cat-file -e "$head:$file" 2>/dev/null; then
    git show "$head:$file" > "/tmp/$(basename "$file")"
    echo "--- $file"
    case "$file" in
      *.py) ast-grep outline "/tmp/$(basename "$file")" --items all || true ;;
      *.md) rg -n -i '^(#|##|###)|uidl|retry|progress|retr|transaction|alembic|1503' "/tmp/$(basename "$file")" || true ;;
    esac
  fi
done

show_range() {
  local file="$1" start="$2" end="$3"
  if git cat-file -e "$head:$file" 2>/dev/null; then
    printf '\n--- %s, Lines %s-%s ---\n' "$file" "$start" "$end"
    git show "$head:$file" | awk -v start="$start" -v end="$end" \
      'NR >= start && NR <= end { printf "%d\t%s\n", NR, $0 }'
  fi
}

printf '%s\n' '== POP3 implementation, models, migrations, tests, doctoring =='
show_range backend/services/pop3_worker.py 1 520
show_range backend/db/pop3_collection_models.py 1 260
show_range backend/alembic/versions/0019_pop3_observed_uidl.py 1 220
show_range backend/tests/test_pop3_uidl_progress.py 1 520
show_range backend/tests/test_pop3_partial_retrieval.py 1 360
show_range backend/tests/test_pop3_worker.py 1 480
show_range backend/tests/test_pop3_quit_resilience.py 1 260
show_range docs/doctoring/email-source-identity-provenance.md 1 340

printf '%s\n' '== All POP3 retry, DB transaction, observation, and I/O references =='
git grep -n -i -C 10 -E \
  '(retry|attempt|uidl|observed|retr\(|list\(|quit\(|close\(|commit\(|rollback\(|flush\(|begin\(|begin_nested\(|session|transaction|persist_fetched_email|created_record|MAX_POP3_FETCH_MESSAGES|continue|break)' \
  "$head" -- backend/services/pop3_worker.py backend/db backend/tests docs || true

printf '%s\n' '== All new Alembic declarations and heads =='
git grep -n -C 2 -E '^(revision|down_revision|branch_labels|depends_on)\s*=' \
  "$head" -- backend/alembic/versions || true
tmpdir="$(mktemp -d)"
git archive "$head" backend/alembic/versions | tar -x -C "$tmpdir"
python - "$tmpdir/backend/alembic/versions" <<'PY'
import ast
import pathlib
import sys
root = pathlib.Path(sys.argv[1])
revisions, parents = {}, set()
for path in root.glob("*.py"):
    tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
    vals = {}
    for node in tree.body:
        if isinstance(node, ast.Assign):
            for target in node.targets:
                if isinstance(target, ast.Name) and target.id in {"revision", "down_revision"}:
                    try:
                        vals[target.id] = ast.literal_eval(node.value)
                    except (ValueError, TypeError):
                        pass
    revision = vals.get("revision")
    if isinstance(revision, str):
        revisions[revision] = path.name
        down = vals.get("down_revision")
        if isinstance(down, str):
            parents.add(down)
        elif isinstance(down, tuple):
            parents.update(x for x in down if isinstance(x, str))
print("heads=", sorted(set(revisions) - parents))
print("revisions=", sorted(revisions.items()))
PY
rm -rf "$tmpdir"

printf '%s\n' '== Exact current PR state =='
gh pr view 1195 --repo "$repo" \
  --json headRefOid,baseRefOid,state,isDraft,mergeStateStatus,reviewDecision,statusCheckRollup 2>&1 || true
gh pr checks 1195 --repo "$repo" \
  --json name,state,workflow,link,completedAt,description 2>&1 || true

Length of output: 205657


🏁 Script executed:

#!/bin/bash
set -euo pipefail
head='630d5461d1eb8b59b392e4170dec66ce1edf034c'
red='5d40f83ff48e7dd9c3bf6deb93995130c5a1470a'
fix='7406cb63923003df2c3a5d21a0104203f78b90bc'

show() {
  local f="$1" s="$2" e="$3"
  printf '\n--- %s, Lines %s-%s ---\n' "$f" "$s" "$e"
  git show "$head:$f" | awk -v s="$s" -v e="$e" 'NR>=s && NR<=e {printf "%d\t%s\n", NR, $0}'
}

printf '%s\n' '== Exact changed paths only =='
git diff --name-status '509be4c1d9b6c7ba239a108656e2382681a85341' "$head"

printf '%s\n' '== POP3 worker full targeted file =='
git show "$head:backend/services/pop3_worker.py" | nl -ba

printf '%s\n' '== Retry model and migration =='
git show "$head:backend/db/pop3_collection_models.py" | nl -ba
git show "$head:backend/alembic/versions/0019_pop3_observed_uidl.py" | nl -ba

printf '%s\n' '== Focused retry and protocol tests =='
for f in \
  backend/tests/test_pop3_uidl_progress.py \
  backend/tests/test_pop3_partial_retrieval.py \
  backend/tests/test_pop3_worker.py \
  backend/tests/test_pop3_quit_resilience.py
do
  if git cat-file -e "$head:$f" 2>/dev/null; then
    echo "--- $f"
    git show "$head:$f" | nl -ba
  fi
done

printf '%s\n' '== RED-to-fix diff, retry-specific =='
git diff --unified=45 "$red" "$fix" -- \
  backend/services/pop3_worker.py \
  backend/db/pop3_collection_models.py \
  backend/tests/test_pop3_uidl_progress.py \
  backend/tests/test_pop3_partial_retrieval.py

printf '%s\n' '== New migration paths after source-order RED =='
git diff --name-status "$red" "$head" -- backend/alembic/versions
git log --format='%H %s' "$red..$head" -- backend/alembic/versions

printf '%s\n' '== Doctoring POP3 paragraphs =='
git show "$head:docs/doctoring/email-source-identity-provenance.md" \
 | rg -n -i -C 6 '(pop3|uidl|retry|retr|transaction|1503|alembic)' || true

printf '%s\n' '== Static parse of reviewed production files =='
for f in backend/services/pop3_worker.py backend/db/pop3_collection_models.py backend/alembic/versions/0019_pop3_observed_uidl.py; do
  git show "$head:$f" > "/tmp/$(basename "$f")"
done
python - <<'PY'
import ast
for f in ("/tmp/pop3_worker.py", "/tmp/pop3_collection_models.py", "/tmp/0019_pop3_observed_uidl.py"):
    ast.parse(open(f, encoding="utf-8").read(), filename=f)
    print(f"{f}: AST_OK")
PY

Length of output: 96652


🏁 Script executed:

#!/bin/bash
set -euo pipefail
head='630d5461d1eb8b59b392e4170dec66ce1edf034c'

printf '%s\n' '== Dedicated durable retry regression =='
git show "$head:backend/tests/test_pop3_retry_progress.py" | nl -ba

printf '%s\n' '== POP3 migration contract regression =='
git show "$head:backend/tests/test_pop3_uidl_migration_contract.py" | nl -ba

printf '%s\n' '== Exact malformed-response coverage and implementation =='
git grep -n -i -C 8 -E '(-ERROR|-ERRX|_is_negative_pop3_response|unexpected response|error_proto)' \
  "$head" -- \
  backend/services/pop3_worker.py \
  backend/tests/test_pop3_retry_progress.py \
  backend/tests/test_pop3_partial_retrieval.py \
  backend/tests/test_pop3_worker.py \
  backend/tests/test_pop3_uidl_progress.py || true

printf '%s\n' '== Exact `#1503` / ancestry references in POP3 scope =='
git grep -n -i -C 6 -E '(`#1503`|alembic.*(rechain|ancestry|canonical)|migration.*(rechain|ancestry|canonical))' \
  "$head" -- \
  backend/alembic/versions/0019_pop3_observed_uidl.py \
  backend/tests/test_pop3_uidl_migration_contract.py \
  docs/doctoring/pop3-durable-retry-progress.md \
  docs/doctoring/email-source-identity-provenance.md || true

printf '%s\n' '== Current POP3-related migration changes relative to base =='
git diff --unified=35 '509be4c1d9b6c7ba239a108656e2382681a85341' "$head" -- \
  backend/alembic/versions/0019_pop3_observed_uidl.py \
  backend/tests/test_pop3_uidl_migration_contract.py \
  docs/doctoring/pop3-durable-retry-progress.md

Length of output: 44385


@seonghobae REQUEST_CHANGES for exact integration #1623@509be4c1d9b6c7ba239a108656e2382681a85341 → #1195@630d5461d1eb8b59b392e4170dec66ce1edf034c.

I found two blocking issues.

  1. backend/services/pop3_worker.py still accepts malformed POP3 error responses.

    At Lines 540-547, _is_negative_pop3_response() uses startswith("-ERR") and startswith(b"-ERR"). Therefore, malformed responses such as -ERROR and -ERRX are treated as a valid RFC-style -ERR response.

    The UIDL path continues at Line 469. The LIST fallback path continues at Line 520. This violates the stated protocol boundary. A malformed response must stop the remaining batch.

    Accept only an exact -ERR token, with either end-of-response or a space after it. Apply the same check to bytes. Add regressions for -ERROR and -ERRX in both the UIDL and LIST fallback retrieval paths. Each regression must verify that no later selected message receives RETR.

  2. The required Alembic ancestry reconciliation has not occurred.

    backend/alembic/versions/0019_pop3_observed_uidl.py is still a new revision in this effective delta. It sets down_revision = "0018_email_date_provenance" at Line 21. Its own module documentation states that this ancestry is branch-local and that #1195 must rechain it after the protected migration head is available.

    docs/doctoring/pop3-durable-retry-progress.md also states that #1503 lineage reconciliation remains required before merge. This head therefore cannot provide the requested canonical migration state. Rechain 0019_pop3_observed_uidl only after the protected #1503 migration lineage is available. Do not add another parallel revision.

The durable retry/progress repair is otherwise consistent in static inspection:

  • backend/services/pop3_worker.py Lines 376-398 select unattempted UIDLs before due retryable UIDLs.
  • backend/tests/test_pop3_retry_progress.py demonstrates that a persistent rejected tail does not hide lower unattempted UIDLs.
  • Provider I/O completes before _import_messages() opens its database session.
  • Successful persistence transitions the same UIDL to observed.
  • A rejected or unparsable UIDL remains retryable.
  • retry_after delays retries without making the UIDL terminal.

I did not execute repository tests. The current GitHub status contexts are skipped reviews, not current-head execution evidence.

You are interacting with an AI system.

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Please review exact current head 52d2cc6fc136c931dda609a0129e3cd78048eebb, with particular attention to the new interrupted-RETR durable-progress generation (5be27e6d... RED → c6462af3... causal fix → doctoring). Verify that marking only the currently attempted UIDL retryable on malformed protocol/transport interruption cannot hide unattempted backlog or misstate observed delivery, and that no predecessor approval is transferred.

@coderabbitai

coderabbitai Bot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo='ContextualWisdomLab/naruon'
head='52d2cc6fc136c931dda609a0129e3cd78048eebb'
red='5be27e6d'
fix='c6462af3'

printf '%s\n' '== Exact head and source-order commits =='
printf 'checked_out='; git rev-parse HEAD
git cat-file -t "$head"
git show -s --format='head=%H%nparents=%P%nsubject=%s' "$head"
for prefix in "$red" "$fix"; do
  resolved="$(git rev-parse "${prefix}^{commit}")"
  git show -s --format='commit=%H%nparents=%P%nsubject=%s' "$resolved"
  printf '%s_ancestor_of_head=' "$prefix"
  git merge-base --is-ancestor "$resolved" "$head" && echo yes || echo no
done
red_full="$(git rev-parse "${red}^{commit}")"
fix_full="$(git rev-parse "${fix}^{commit}")"
printf 'red_ancestor_of_fix='
git merge-base --is-ancestor "$red_full" "$fix_full" && echo yes || echo no

printf '%s\n' '== Effective change scope and whitespace =='
base='509be4c1d9b6c7ba239a108656e2382681a85341'
git merge-base --is-ancestor "$base" "$head" && echo 'base_ancestor=yes' || echo 'base_ancestor=no'
git diff --name-status "$base" "$head"
git diff --check "$base" "$head"

printf '%s\n' '== Changed POP3-related files since predecessor reviewed head =='
prior='630d5461d1eb8b59b392e4170dec66ce1edf034c'
git log --format='%H %s' "$prior..$head"
git diff --name-status "$prior" "$head"
git diff --find-renames --unified=80 "$prior" "$head" -- \
  backend/services/pop3_worker.py \
  backend/db/pop3_collection_models.py \
  backend/alembic/versions \
  backend/tests \
  docs

printf '%s\n' '== Relevant outlines =='
for file in \
  backend/services/pop3_worker.py \
  backend/db/pop3_collection_models.py \
  backend/alembic/versions/0019_pop3_observed_uidl.py \
  backend/tests/test_pop3_retry_progress.py \
  backend/tests/test_pop3_partial_retrieval.py \
  backend/tests/test_pop3_uidl_progress.py \
  backend/tests/test_pop3_worker.py \
  backend/tests/test_pop3_quit_resilience.py \
  docs/doctoring/pop3-durable-retry-progress.md \
  docs/doctoring/email-source-identity-provenance.md
do
  if git cat-file -e "$head:$file" 2>/dev/null; then
    echo "--- $file"
    git show "$head:$file" > "/tmp/$(basename "$file")"
    case "$file" in
      *.py) ast-grep outline "/tmp/$(basename "$file")" --items all || true ;;
      *.md) rg -n -i '^(#|##|###)|interrupted|RETR|retry|observed|transaction|transport|malformed|unattempted' "/tmp/$(basename "$file")" || true ;;
    esac
  fi
done

show_range() {
  local file="$1" start="$2" end="$3"
  if git cat-file -e "$head:$file" 2>/dev/null; then
    printf '\n--- %s, Lines %s-%s ---\n' "$file" "$start" "$end"
    git show "$head:$file" | awk -v start="$start" -v end="$end" \
      'NR >= start && NR <= end { printf "%d\t%s\n", NR, $0 }'
  fi
}

printf '%s\n' '== POP3 implementation and focused tests =='
show_range backend/services/pop3_worker.py 1 620
show_range backend/db/pop3_collection_models.py 1 300
show_range backend/tests/test_pop3_retry_progress.py 1 520
show_range backend/tests/test_pop3_partial_retrieval.py 1 420
show_range backend/tests/test_pop3_uidl_progress.py 1 420
show_range backend/tests/test_pop3_worker.py 1 460
show_range docs/doctoring/pop3-durable-retry-progress.md 1 360

printf '%s\n' '== Retrieval, state transition, selection, and transaction references =='
git grep -n -i -C 10 -E \
  '(retr\(|retryable|observed|unattempted|attempt|retry_after|provider_uidl|Pop3ObservedMessage|_import_messages|persist_fetched_email|session|commit\(|rollback\(|flush\(|begin\(|begin_nested\(|continue|break|transport|malformed)' \
  "$head" -- backend/services/pop3_worker.py backend/db/pop3_collection_models.py backend/tests docs || true

printf '%s\n' '== Migration declarations and graph heads =='
git grep -n -C 2 -E '^(revision|down_revision|branch_labels|depends_on)\s*=' "$head" -- backend/alembic/versions || true
tmpdir="$(mktemp -d)"
git archive "$head" backend/alembic/versions | tar -x -C "$tmpdir"
python - "$tmpdir/backend/alembic/versions" <<'PY'
import ast
import pathlib
import sys
root = pathlib.Path(sys.argv[1])
revisions, parents = {}, set()
for path in root.glob("*.py"):
    tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
    vals = {}
    for node in tree.body:
        if isinstance(node, ast.Assign):
            for target in node.targets:
                if isinstance(target, ast.Name) and target.id in {"revision", "down_revision"}:
                    try:
                        vals[target.id] = ast.literal_eval(node.value)
                    except (ValueError, TypeError):
                        pass
    revision = vals.get("revision")
    if isinstance(revision, str):
        revisions[revision] = path.name
        down = vals.get("down_revision")
        if isinstance(down, str):
            parents.add(down)
        elif isinstance(down, tuple):
            parents.update(x for x in down if isinstance(x, str))
print("heads=", sorted(set(revisions) - parents))
PY
rm -rf "$tmpdir"

printf '%s\n' '== Static syntax check for inspected production modules =='
for file in backend/services/pop3_worker.py backend/db/pop3_collection_models.py; do
  if git cat-file -e "$head:$file" 2>/dev/null; then
    temp="/tmp/$(basename "$file")"
    git show "$head:$file" > "$temp"
    python - "$temp" <<'PY'
import ast
import sys
path = sys.argv[1]
ast.parse(open(path, encoding="utf-8").read(), filename=path)
print(f"{path}: AST_OK")
PY
  fi
done

printf '%s\n' '== Exact PR state =='
gh pr view 1195 --repo "$repo" \
  --json headRefOid,baseRefOid,state,isDraft,mergeStateStatus,reviewDecision,statusCheckRollup 2>&1 || true

Length of output: 205896


🏁 Script executed:

#!/bin/bash
set -euo pipefail
head='52d2cc6fc136c931dda609a0129e3cd78048eebb'
red="$(git rev-parse '5be27e6d^{commit}')"
fix="$(git rev-parse 'c6462af3^{commit}')"

range_for_pattern() {
  local file="$1" pattern="$2" before="${3:-12}" after="${4:-45}"
  local line
  line="$(git grep -n -m1 -E "$pattern" "$head" -- "$file" | sed -E 's/^[^:]+:[^:]+:([0-9]+):.*/\1/' || true)"
  if [ -n "$line" ]; then
    local start=$(( line > before ? line - before : 1 ))
    local end=$(( line + after ))
    printf '\n--- %s around /%s/, Lines %s-%s ---\n' "$file" "$pattern" "$start" "$end"
    git show "$head:$file" | awk -v s="$start" -v e="$end" 'NR>=s && NR<=e {printf "%d\t%s\n", NR, $0}'
  else
    printf '\n--- no match: %s /%s/ ---\n' "$file" "$pattern"
  fi
}

printf '%s\n' '== Exact source-order evidence =='
git show -s --format='RED %H%n%s' "$red"
git show -s --format='FIX %H%n%s' "$fix"
git log --format='%H %s' "$red^..$head" -- \
  backend/services/pop3_worker.py backend/tests docs/doctoring
printf '%s\n' '== RED to FIX diff =='
git diff --unified=55 "$red" "$fix" -- \
  backend/services/pop3_worker.py \
  backend/tests/test_pop3_retry_progress.py \
  backend/tests/test_pop3_partial_retrieval.py \
  docs/doctoring/pop3-durable-retry-progress.md

printf '%s\n' '== Key POP3 worker definitions and call sites =='
git grep -n -E \
  '^(async )?def |^class |retryable|observed|retry_after|retr\(|_import_messages|_select|_mark|_is_negative' \
  "$head" -- backend/services/pop3_worker.py

for pattern in \
  '^async def _sync' \
  '^def _select' \
  '^async def _import_messages' \
  '^async def _mark' \
  '^def _is_negative' \
  'client\.retr' \
  'retry_after' \
  'state.*retryable|retryable.*state' \
  'state.*observed|observed.*state'
do
  range_for_pattern backend/services/pop3_worker.py "$pattern" 15 70
done

printf '%s\n' '== Focused test functions and their bodies =='
for file in \
  backend/tests/test_pop3_retry_progress.py \
  backend/tests/test_pop3_partial_retrieval.py \
  backend/tests/test_pop3_uidl_progress.py
do
  if git cat-file -e "$head:$file" 2>/dev/null; then
    echo "--- $file test definitions"
    git grep -n -E '^(async )?def test_' "$head" -- "$file"
    echo "--- $file interrupted/retry/transport excerpts"
    git grep -n -i -C 18 -E \
      '(interrupt|transport|malformed|retryable|unattempted|observed|retr\(|OSError|error_proto|later|backlog)' \
      "$head" -- "$file" || true
  fi
done

printf '%s\n' '== Model state fields and persistence queries =='
git grep -n -C 10 -E \
  '(class Pop3ObservedMessage|state|retry_after|attempt|observed_at|provider_uidl|UniqueConstraint|Index)' \
  "$head" -- backend/db/pop3_collection_models.py backend/services/pop3_worker.py || true

printf '%s\n' '== Doctoring interrupted-RETR contract =='
for file in docs/doctoring/pop3-durable-retry-progress.md docs/doctoring/email-source-identity-provenance.md; do
  if git cat-file -e "$head:$file" 2>/dev/null; then
    echo "--- $file"
    git grep -n -i -C 10 -E \
      '(interrupt|RETR|retryable|unattempted|observed|transport|malformed|transaction)' \
      "$head" -- "$file" || true
  fi
done

Length of output: 155552


@seonghobae REQUEST_CHANGES for exact head 52d2cc6fc136c931dda609a0129e3cd78048eebb.

The interrupted-RETR durable-progress repair is correct in the reviewed code.

  • The requested RED commit 5be27e6d... precedes fix c6462af3....
  • On malformed protocol or transport interruption, backend/services/pop3_worker.py adds only the current UIDL to retryable_uidls.
  • The worker stops the current network batch after that failure.
  • The worker does not mark later unattempted UIDLs as retryable or observed.
  • Candidate selection prefers UIDLs with no state before due retries.
  • Therefore, the interrupted UIDL cannot remain falsely fresh and monopolize later polls.
  • The worker sets observed only after persist_fetched_email() returns.
  • Provider I/O completes before _import_messages() opens its persistence session.

Two existing blockers remain in this exact head:

  1. backend/services/pop3_worker.py, Lines 542-549 still uses startswith("-ERR") and startswith(b"-ERR").

    This accepts malformed response tokens such as -ERROR and -ERRX. The UIDL retrieval path then treats them as valid per-message negative responses and continues the batch. Accept only -ERR or -ERR , with equivalent byte handling. Reject malformed prefixes and stop the batch. Add UIDL and LIST fallback regressions.

  2. backend/alembic/versions/0019_pop3_observed_uidl.py still has branch-local ancestry from 0018_email_date_provenance.

    docs/doctoring/pop3-durable-retry-progress.md states that #1503 ancestry reconciliation remains required before merge. Do not add another revision. Rechain 0019_pop3_observed_uidl after the protected migration lineage becomes available.

I performed static inspection only. I did not execute repository tests.

You are interacting with an AI system.

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

현재 exact head 52d2cc6fc136c931dda609a0129e3cd78048eebb만 기준으로 재검토해 주세요. 특히 5be27e6d... → c6462af... interrupted RETR progress repair가 현재 POP3 durable-progress invariant를 완전하게 보존하는지, unattempted lower UIDLs가 fresh 상태로 남는지, branch-local Alembic lineage가 #1503 보호 계보 도착 전 비권위 상태라는 문서·코드 경계가 일치하는지 확인해 주세요. 이전 head의 approval/check receipt는 current-head acceptance로 이전하지 않습니다.

@coderabbitai

coderabbitai Bot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

base='509be4c1d9b6c7ba239a108656e2382681a85341'
head='52d2cc6fc136c931dda609a0129e3cd78048eebb'
red="$(git rev-parse '5be27e6d^{commit}')"
fix="$(git rev-parse 'c6462af3^{commit}')"

printf '%s\n' '== Exact revisions =='
printf 'checked_out='; git rev-parse HEAD
for commit in "$base" "$head" "$red" "$fix"; do
  git cat-file -t "$commit"
  git show -s --format='commit=%H%nparents=%P%nsubject=%s' "$commit"
done
git merge-base --is-ancestor "$base" "$head" && echo 'base_is_ancestor=yes' || echo 'base_is_ancestor=no'
git merge-base --is-ancestor "$red" "$fix" && echo 'red_precedes_fix=yes' || echo 'red_precedes_fix=no'
git merge-base --is-ancestor "$fix" "$head" && echo 'fix_is_in_head=yes' || echo 'fix_is_in_head=no'
git diff --check "$base" "$head"

printf '%s\n' '== Changed POP3-related files since prior reviewed head =='
prior='630d5461d1eb8b59b392e4170dec66ce1edf034c'
git log --format='%H %s' "$prior..$head"
git diff --name-status "$prior" "$head"

printf '%s\n' '== Outlines =='
for file in \
  backend/services/pop3_worker.py \
  backend/db/pop3_collection_models.py \
  backend/alembic/versions/0019_pop3_observed_uidl.py \
  backend/tests/test_pop3_retry_progress.py \
  backend/tests/test_pop3_partial_retrieval.py \
  backend/tests/test_pop3_uidl_progress.py \
  docs/doctoring/pop3-durable-retry-progress.md
do
  if git cat-file -e "$head:$file" 2>/dev/null; then
    echo "--- $file"
    git show "$head:$file" > "/tmp/$(basename "$file")"
    case "$file" in
      *.py) ast-grep outline "/tmp/$(basename "$file")" --items all || true ;;
      *.md) rg -n -i -C3 '(interrupted|retr|retryable|unattempted|observed|alembic|1503|lineage)' "/tmp/$(basename "$file")" || true ;;
    esac
  fi
done

show() {
  local file="$1" start="$2" end="$3"
  printf '\n--- %s, Lines %s-%s ---\n' "$file" "$start" "$end"
  git show "$head:$file" | awk -v s="$start" -v e="$end" 'NR >= s && NR <= e {printf "%d\t%s\n", NR, $0}'
}

printf '%s\n' '== Interrupted RETR, selection, persistence, and protocol classification =='
git grep -n -i -C10 -E \
  '(retryable|unattempted|retry_after|observed|retr\(|_import_messages|_is_negative_pop3_response|startswith\("-ERR|startswith\(b"-ERR|persist_fetched_email|commit\(|rollback\()' \
  "$head" -- backend/services/pop3_worker.py backend/db/pop3_collection_models.py || true
show backend/services/pop3_worker.py 330 610

printf '%s\n' '== Focused test evidence =='
for file in \
  backend/tests/test_pop3_retry_progress.py \
  backend/tests/test_pop3_partial_retrieval.py \
  backend/tests/test_pop3_uidl_progress.py
do
  if git cat-file -e "$head:$file" 2>/dev/null; then
    show "$file" 1 520
  fi
done

printf '%s\n' '== RED-to-fix diff =='
git diff --unified=55 "$red" "$fix" -- \
  backend/services/pop3_worker.py \
  backend/tests/test_pop3_retry_progress.py \
  backend/tests/test_pop3_partial_retrieval.py \
  docs/doctoring/pop3-durable-retry-progress.md

printf '%s\n' '== Migration and lineage documentation =='
show backend/alembic/versions/0019_pop3_observed_uidl.py 1 180
git grep -n -i -C8 -E '(`#1503`|branch-local|non-authoritative|rechain|ancestry|lineage)' \
  "$head" -- \
  backend/alembic/versions/0019_pop3_observed_uidl.py \
  docs/doctoring/pop3-durable-retry-progress.md \
  docs/doctoring/email-source-identity-provenance.md || true

printf '%s\n' '== Alembic heads =='
tmpdir="$(mktemp -d)"
git archive "$head" backend/alembic/versions | tar -x -C "$tmpdir"
python - "$tmpdir/backend/alembic/versions" <<'PY'
import ast
import pathlib
import sys
root = pathlib.Path(sys.argv[1])
revisions, parents = {}, set()
for path in root.glob("*.py"):
    tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
    values = {}
    for node in tree.body:
        if isinstance(node, ast.Assign):
            for target in node.targets:
                if isinstance(target, ast.Name) and target.id in {"revision", "down_revision"}:
                    try:
                        values[target.id] = ast.literal_eval(node.value)
                    except (TypeError, ValueError):
                        pass
    revision = values.get("revision")
    if isinstance(revision, str):
        revisions[revision] = path.name
        parent = values.get("down_revision")
        if isinstance(parent, str):
            parents.add(parent)
        elif isinstance(parent, tuple):
            parents.update(item for item in parent if isinstance(item, str))
print("heads=", sorted(set(revisions) - parents))
print("0019=", revisions.get("0019_pop3_observed_uidl"))
PY
rm -rf "$tmpdir"

printf '%s\n' '== Static syntax =='
for file in backend/services/pop3_worker.py backend/db/pop3_collection_models.py; do
  git show "$head:$file" > "/tmp/$(basename "$file")"
  python - "/tmp/$(basename "$file")" <<'PY'
import ast
import sys
path = sys.argv[1]
ast.parse(open(path, encoding="utf-8").read(), filename=path)
print(f"{path}: AST_OK")
PY
done

Length of output: 118779


@seonghobae REQUEST_CHANGES for exact head 52d2cc6fc136c931dda609a0129e3cd78048eebb.

The interrupted-RETR repair is correct.

  • c6462af... follows the requested RED commit 5be27e6d....
  • On malformed protocol or transport interruption, backend/services/pop3_worker.py marks only the current UIDL as retryable.
  • The worker stops the current batch after that failure.
  • Later UIDLs receive no collection state update.
  • Therefore, later UIDLs remain fresh.
  • Candidate selection prioritizes fresh UIDLs before due retryable UIDLs.
  • The current interrupted UIDL cannot repeatedly outrank unattempted lower UIDLs after reconnect.
  • The worker transitions a UIDL to observed only after persist_fetched_email() returns.
  • Provider I/O completes before _import_messages() opens its database session.

Two blocking issues remain.

  1. Malformed POP3 error tokens continue the batch.

    In backend/services/pop3_worker.py, Lines 542-549, _is_negative_pop3_response() accepts every string or byte sequence that starts with -ERR. This accepts malformed values such as -ERROR and -ERRX.

    The UIDL path then continues at Line 469. The LIST fallback path then continues at Line 522. A malformed response must stop the remaining batch.

    Accept only an exact -ERR token or -ERR followed by text. Apply the same boundary check to bytes. Add regressions for both paths that verify no later RETR call occurs after -ERROR or -ERRX.

  2. The Alembic lineage is explicitly non-authoritative.

    backend/alembic/versions/0019_pop3_observed_uidl.py still declares down_revision = "0018_email_date_provenance".

    Its module documentation and docs/doctoring/pop3-durable-retry-progress.md both state that this parent is branch-local pending #1503. The documentation and code boundary agree, but the current migration cannot be accepted as canonical until #1503 reaches protected ancestry and this migration is rechained. Do not add a parallel revision.

I performed static inspection only. I did not run repository tests or treat predecessor checks as current-head evidence.


✏️ Learnings added
Learnt from: seonghobae
URL: https://github.com/ContextualWisdomLab/naruon/pull/1195

Timestamp: 2026-09-21T18:13:14.118Z
Learning: For PR `#1195` POP3 durable retry work, `backend/alembic/versions/0019_pop3_observed_uidl.py` has branch-local, non-authoritative ancestry until `#1503`'s protected workspace migration lineage is available. The revision must be rechained after the protected head before merge, without adding a parallel Alembic revision.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

You are interacting with an AI system.

This branch has not been deployed

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

Labels

area: ci-cd CI, GitHub Actions, checks, release, or supply chain enhancement New feature or request priority: medium Normal-priority or P2 work status: needs-review Open pull request requiring current-head review or checks type: feature New or expanded product capability

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants