feat(email): deterministic dedupe provenance — gate strong fingerprints on genuine Date (naruon#1086) - #1195
Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe email pipeline now tracks Date and Message-ID provenance, persists Date provenance, preserves source bytes, gates strong fingerprints on trusted dates, and classifies duplicates as ChangesEmail deduplication
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant RFC822Message
participant email_parser
participant import_worker
participant email_dedupe_service
participant Email
RFC822Message->>email_parser: provide Date and Message-ID headers
email_parser-->>import_worker: return parsed fields and provenance
import_worker->>email_dedupe_service: provide candidate and source bytes
email_dedupe_service->>Email: compare fingerprints and identity
Email-->>email_dedupe_service: return stored row data
email_dedupe_service-->>import_worker: return dedupe decision
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (5)
backend/services/email_dedupe_service.py (3)
103-104: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
getattrdefault is unnecessary here.Email.date_provenanceis a non-nullable mapped column, and even a transientEmail()returnsNonefor it rather than raising, soemail_row.date_provenance != "parsed"is equivalent and doesn't mask a genuinely unloaded/expired attribute.♻️ Direct attribute access
- if getattr(email_row, "date_provenance", None) != "parsed": + if email_row.date_provenance != "parsed": return None🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/services/email_dedupe_service.py` around lines 103 - 104, In the date provenance check within the email deduplication flow, replace getattr(email_row, "date_provenance", None) with direct access to email_row.date_provenance. Preserve the existing early return when the value is not "parsed".
36-43: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider typing
date_provenancewith a shared Literal.backend/services/email_parser.pyalready definesDateProvenance = Literal["parsed", "missing", "invalid"]; here (and onEmail.date_provenance) it is a barestr, so a mistyped value silently degrades every pair toreview_requiredwith no type error. A sharedLiteral["parsed", "missing", "invalid", "unknown"]alias would make the gate checkable.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/services/email_dedupe_service.py` around lines 36 - 43, Define or reuse a shared DateProvenance Literal including "parsed", "missing", "invalid", and "unknown", then apply it to date_provenance in the shown deduplication model and Email.date_provenance. Update the existing email_parser.py alias as needed so all date provenance fields share the same type and invalid values are caught statically.
222-227: 🚀 Performance & Scalability | 🔵 TrivialNote for the follow-up API wiring: hash the candidate once, and block before comparing. Each pair re-derives the candidate's strong and content fingerprints over the full body, so a corpus of N rows costs 2N candidate-side hashes. When this is wired into the import/IMAP paths, hoist the candidate fingerprints out of the loop and restrict
existing_rowswith a blocking predicate (owner scope + sender/subject or a persisted content-fingerprint index) rather than streaming all stored rows.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/services/email_dedupe_service.py` around lines 222 - 227, Update the deduplication flow around classify_dedupe_decision to compute the candidate’s strong and content fingerprints once before iterating, then filter existing_rows using a blocking predicate based on owner scope plus sender/subject or the persisted content-fingerprint index before pairwise comparison. Preserve the existing auto_link and first review_required selection behavior.backend/tests/test_email_import_service.py (1)
612-620: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winTighten the negative assertion and cover absent provenance.
weak != strongpasses for any differing value; asserting the expected weak fingerprint pins the fallback, and a case with nodate_provenancekey covers the.get()default path used by legacy callers.♻️ Suggested tightening
+ from services.email_service import generate_email_fingerprint + + expected_weak = generate_email_fingerprint( + parsed_fields["subject"], + persisted_date.isoformat(), + parsed_fields["sender"], + parsed_fields["recipients"], + ) for provenance in ("missing", "invalid"): weak = _email_fingerprint( {**parsed_fields, "date_provenance": provenance}, persisted_date ) - assert weak != strong + assert weak == expected_weak != strong + assert _email_fingerprint(dict(parsed_fields), persisted_date) == expected_weak🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/tests/test_email_import_service.py` around lines 612 - 620, Update the _email_fingerprint test’s negative cases to assert the exact expected weak fingerprint rather than merely checking inequality with strong. Include a case where date_provenance is absent from the input mapping, covering the .get() default path used by legacy callers, while retaining the existing parsed provenance assertion.backend/tests/test_email_parser.py (1)
511-524: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winWhitespace-only
Datebranch is documented as verified but untested. Root cause:_extract_date_with_provenancestripsheader_textto classify a blankDateas"missing", and no test exercises that path.
backend/tests/test_email_parser.py#L511-L524: add aparse_eml_bytescase with a whitespace-onlyDateheader assertingdate_provenance == "missing"andheader_date is None.CHANGELOG.md#L2-L10: keep the line 7 whitespace-onlyDateverification claim only once that test exists.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/tests/test_email_parser.py` around lines 511 - 524, The whitespace-only Date handling path lacks coverage. In backend/tests/test_email_parser.py lines 511-524, add a parse_eml_bytes test using _eml_with with a Date header containing only whitespace, asserting date_provenance is "missing" and header_date is None; in CHANGELOG.md lines 2-10, retain the whitespace-only Date verification claim once this test is present.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@backend/services/email_dedupe_service.py`:
- Around line 128-135: Update content_email_fingerprint and
strong_email_fingerprint to include distinct fingerprint-kind discriminators in
the input passed to generate_email_fingerprint, ensuring content and strong
fingerprints cannot produce identical keys even when the strong date value is
empty. Preserve the existing field values and fingerprint persistence/matching
behavior.
In `@backend/services/email_parser.py`:
- Around line 148-173: Update _extract_date_with_provenance to normalize the
datetime returned by parsedate_to_datetime before returning it: when header_date
is naive, assign the appropriate UTC timezone while preserving aware values
unchanged. Return the normalized value for both effective_date and header_date
so the documented timezone-aware contract holds for parsed headers, including
-0000.
In `@backend/services/imap_worker.py`:
- Around line 56-66: Update the IMAP Email construction in
backend/services/imap_worker.py (around lines 56-66 and the Email call near line
88) to persist date_provenance from email_data, defaulting to "unknown", so it
matches the strong_fingerprint gate. Update CHANGELOG.md lines 2-10 to ensure
its provenance-persistence claim is accurate, or make the IMAP fix first so the
existing claim becomes true.
In `@CHANGELOG.md`:
- Around line 7-8: Clarify the verification claims in the adjacent CHANGELOG
bullets so their reported counts are not ambiguous or contradictory. Identify
whether each count reflects a specific commit/state or the final tree, and
include the exact focused verification commands used for each result, especially
for the later domain-core changes.
---
Nitpick comments:
In `@backend/services/email_dedupe_service.py`:
- Around line 103-104: In the date provenance check within the email
deduplication flow, replace getattr(email_row, "date_provenance", None) with
direct access to email_row.date_provenance. Preserve the existing early return
when the value is not "parsed".
- Around line 36-43: Define or reuse a shared DateProvenance Literal including
"parsed", "missing", "invalid", and "unknown", then apply it to date_provenance
in the shown deduplication model and Email.date_provenance. Update the existing
email_parser.py alias as needed so all date provenance fields share the same
type and invalid values are caught statically.
- Around line 222-227: Update the deduplication flow around
classify_dedupe_decision to compute the candidate’s strong and content
fingerprints once before iterating, then filter existing_rows using a blocking
predicate based on owner scope plus sender/subject or the persisted
content-fingerprint index before pairwise comparison. Preserve the existing
auto_link and first review_required selection behavior.
In `@backend/tests/test_email_import_service.py`:
- Around line 612-620: Update the _email_fingerprint test’s negative cases to
assert the exact expected weak fingerprint rather than merely checking
inequality with strong. Include a case where date_provenance is absent from the
input mapping, covering the .get() default path used by legacy callers, while
retaining the existing parsed provenance assertion.
In `@backend/tests/test_email_parser.py`:
- Around line 511-524: The whitespace-only Date handling path lacks coverage. In
backend/tests/test_email_parser.py lines 511-524, add a parse_eml_bytes test
using _eml_with with a Date header containing only whitespace, asserting
date_provenance is "missing" and header_date is None; in CHANGELOG.md lines
2-10, retain the whitespace-only Date verification claim once this test is
present.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: b525377d-beab-430e-9e8a-fc0385f6d7de
📒 Files selected for processing (10)
CHANGELOG.mdbackend/alembic/versions/0018_email_date_provenance.pybackend/db/models.pybackend/services/email_dedupe_service.pybackend/services/email_import_service.pybackend/services/email_parser.pybackend/services/imap_worker.pybackend/tests/test_email_dedupe_service.pybackend/tests/test_email_import_service.pybackend/tests/test_email_parser.py
|
PR governance metadata gate is not ready for
|
|
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. |
Pull request was closed
seonghobae
left a comment
There was a problem hiding this comment.
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.
|
@coderabbitai review Please re-review the current head |
|
✅ Action performedReview finished.
|
seonghobae
left a comment
There was a problem hiding this comment.
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.
Pull request was converted to draft
|
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 |
|
@jules Refresh this PR onto the current Resolve the overlap in
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 |
c14fcae to
d948528
Compare
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.
|
@coderabbitai review Please review exact head |
|
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Pull request overview
OpenCode cannot approve yet because required coverage evidence did not pass.
Review outcome
1. HIGH .github/workflows/opencode-review.yml:1 - Coverage evidence did not prove required test/docstring evidence
-
Problem: The required coverage-evidence job result was
failure, so OpenCode cannot establish approval sufficiency for this head. -
Root cause: Automated approval is only valid when the same-head coverage-evidence job proves supported repository test suites passed and configured docstring gates passed or were advisory, or reports not applicable because no supported source files or package manifests exist. Missing, failed, skipped, unavailable, or unsupported-tooling test evidence is a blocker.
-
Fix: Install or configure the repository test/docstring evidence tooling when source files or package manifests exist, rerun the current-head coverage-evidence job, and approve only after it reports
successwith required evidence or explicit no-source not-applicable evidence. -
Regression test: Keep the approval branch checking
needs.coverage-evidence.result == successbefore posting APPROVE, and publish REQUEST_CHANGES when coverage-evidence blocker states such as cancelled, skipped, failed, unsupported-tooling, or below-100 evidence are present. -
Result: REQUEST_CHANGES
-
Reason: coverage-evidence result was
failure, so required test/docstring evidence was not proven for current head24efad389c236f7490571711f66a0f0bd4448a0e. -
Head SHA:
24efad389c236f7490571711f66a0f0bd4448a0e -
Workflow run: 31021393607
-
Workflow attempt: 1
Coverage evidence
Coverage evidence job did not run or did not publish coverage evidence.
Changed-File Evidence Map
flowchart LR
PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
Evidence --> S1["Changed file: CHANGELOG.md"]
S1 --> I1["repository behavior"]
I1 --> R1["Review risk: Changed file: CHANGELOG.md"]
R1 --> V1["required checks"]
Evidence --> S2["Backend (13 files)"]
S2 --> I2["API and service runtime"]
I2 --> R2["Review risk: Backend (13 files)"]
R2 --> V2["backend tests"]
Evidence --> S3["Docs: email-source-identity-provenance.md"]
S3 --> I3["operator or user guidance"]
I3 --> R3["Review risk: Docs: email-source-identity-provenance.md"]
R3 --> V3["docs review"]
OpenCode Review Overview
Pull request overviewOpenCode cannot approve yet because required coverage evidence did not pass. Review outcome1. HIGH .github/workflows/opencode-review.yml:1 - Coverage evidence did not prove required test/docstring evidence
Coverage evidenceCoverage evidence job did not run or did not publish coverage evidence. Changed-File Evidence Mapflowchart LR
PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
Evidence --> S1["Backend (13 files)"]
S1 --> I1["API and service runtime"]
I1 --> R1["Review risk: Backend (13 files)"]
R1 --> V1["backend tests"]
Evidence --> S2["Docs: email-source-identity-provenance.md"]
S2 --> I2["operator or user guidance"]
I2 --> R2["Review risk: Docs: email-source-identity-provenance.md"]
R2 --> V2["docs review"]
|
|
@opencode-agent @cwl-noema-review @coderabbitai review Review exact current head only. Verify that collection timestamps never become duplicate identity, raw RFC 822 bytes or a deterministic canonical source projection bind fallback fingerprints, parsed Date provenance alone gates strong fingerprints, the Alembic migration/backfill is conservative, and focused/full tests plus 100% changed production coverage/docstrings pass. |
🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '== Current HEAD and pull request state =='
git rev-parse HEAD
git show -s --format='commit=%H%nparents=%P%nsubject=%s' HEAD
gh pr view 1195 --repo ContextualWisdomLab/naruon \
--json state,headRefOid,baseRefOid,mergeStateStatus,reviewDecision,statusCheckRollup 2>&1 || true
printf '%s\n' '== Aggregate scope from merge base =='
base="$(git merge-base HEAD origin/develop 2>/dev/null || git merge-base HEAD develop)"
printf 'merge-base=%s\n' "$base"
git diff --name-status "$base" HEAD
git diff --check "$base" HEAD
printf '%s\n' '== Relevant outlines =='
for file in \
backend/services/email_parser.py \
backend/services/email_dedupe_service.py \
backend/services/email_import_service.py \
backend/services/imap_worker.py \
backend/services/pop3_worker.py \
backend/db/models.py \
backend/alembic/versions/0018_email_date_provenance.py \
backend/tests/test_email_parser_provenance.py \
backend/tests/test_email_dedupe_service.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/email-source-identity-provenance.md
do
if [ -f "$file" ]; then
printf '\n--- %s ---\n' "$file"
ast-grep outline "$file" --items all || true
fi
done
printf '%s\n' '== Identity, provenance, and ingestion call paths =='
rg -n -C 5 --glob '*.py' \
'(def (_extract_date|_extract_date_with_provenance|_email_fingerprint|source_email_fingerprint|canonical_email_source_content|process_fetched_email|_message_bytes)|date_provenance|header_date|message_id_provenance|source_content|strong_email_fingerprint|generate_email_fingerprint|process_fetched_email\s*\()' \
backend/services backend/db backend/tests
printf '%s\n' '== Migration graph declarations =='
rg -n -C 1 --glob '*.py' \
'^(revision|down_revision|branch_labels|depends_on)\s*=' \
backend/alembic/versions
printf '%s\n' '== Current check runs =='
gh pr checks 1195 --repo ContextualWisdomLab/naruon \
--json name,state,conclusion,workflow,link,completedAt 2>&1 || trueLength of output: 162700 🏁 Script executed: #!/bin/bash
set -euo pipefail
show_range() {
local file="$1" start="$2" end="$3"
printf '\n--- %s, Lines %s-%s ---\n' "$file" "$start" "$end"
nl -ba "$file" | sed -n "${start},${end}p"
}
show_range backend/services/email_import_service.py 173 235
show_range backend/services/email_import_service.py 320 375
show_range backend/services/email_import_service.py 811 915
show_range backend/services/imap_worker.py 27 115
show_range backend/services/imap_worker.py 325 365
show_range backend/services/pop3_worker.py 91 205
show_range backend/services/email_dedupe_service.py 49 305
show_range backend/alembic/versions/0018_email_date_provenance.py 1 100
show_range backend/tests/test_source_bound_email_dedupe.py 1 240
show_range backend/tests/test_imap_worker.py 50 140
show_range backend/tests/test_pop3_worker.py 85 195
show_range docs/doctoring/email-source-identity-provenance.md 1 120
printf '%s\n' '== All Email fingerprint duplicate lookups =='
rg -n -C 4 --glob '*.py' \
'(Email\.fingerprint|fingerprint\s*==|_find_existing_email\s*\(|_message_id_for\s*\(|_fallback_message_id\s*\()' \
backend/services
printf '%s\n' '== Application CI workflow and backend job evidence =='
gh run view 31016153740 --repo ContextualWisdomLab/naruon --json \
databaseId,headSha,conclusion,status,event,workflowName,jobs 2>&1 || true
printf '%s\n' '== Application CI backend job log excerpts =='
gh run view 31016153740 --repo ContextualWisdomLab/naruon --job 92340607242 --log 2>&1 \
| rg -n -i -C 3 '(pytest|ruff|coverage|passed|failed|warning|error)' || true
printf '%s\n' '== Coverage-evidence job log =='
gh run view 31016152969 --repo ContextualWisdomLab/naruon --job 92340782638 --log 2>&1 \
| tail -n 220 || true
printf '%s\n' '== Metadata gate failure summary =='
gh api repos/ContextualWisdomLab/naruon/check-runs/92340668375 2>&1 \
| jq '{name,conclusion,output,status,details_url}' || trueLength of output: 2321 🏁 Script executed: #!/bin/bash
set -euo pipefail
show_range() {
local file="$1" start="$2" end="$3"
printf '\n--- %s, Lines %s-%s ---\n' "$file" "$start" "$end"
awk -v start="$start" -v end="$end" \
'NR >= start && NR <= end { printf "%d\t%s\n", NR, $0 }' "$file"
}
show_range backend/services/email_import_service.py 173 235
show_range backend/services/email_import_service.py 320 375
show_range backend/services/email_import_service.py 811 915
show_range backend/services/imap_worker.py 27 115
show_range backend/services/imap_worker.py 325 365
show_range backend/services/pop3_worker.py 91 205
show_range backend/services/email_dedupe_service.py 49 305
show_range backend/alembic/versions/0018_email_date_provenance.py 1 100
show_range backend/tests/test_source_bound_email_dedupe.py 1 240
show_range backend/tests/test_imap_worker.py 50 140
show_range backend/tests/test_pop3_worker.py 85 195
show_range docs/doctoring/email-source-identity-provenance.md 1 120
printf '%s\n' '== All Email fingerprint duplicate lookups =='
rg -n -C 4 --glob '*.py' \
'(Email\.fingerprint|fingerprint\s*==|_find_existing_email\s*\(|_message_id_for\s*\(|_fallback_message_id\s*\()' \
backend/services
printf '%s\n' '== Application CI workflow and backend job evidence =='
gh run view 31016153740 --repo ContextualWisdomLab/naruon --json \
databaseId,headSha,conclusion,status,event,workflowName,jobs 2>&1 || true
printf '%s\n' '== Application CI backend job log excerpts =='
gh run view 31016153740 --repo ContextualWisdomLab/naruon --job 92340607242 --log 2>&1 \
| rg -n -i -C 3 '(pytest|ruff|coverage|passed|failed|warning|error)' || true
printf '%s\n' '== Coverage-evidence job log =='
gh run view 31016152969 --repo ContextualWisdomLab/naruon --job 92340782638 --log 2>&1 \
| tail -n 220 || true
printf '%s\n' '== Metadata gate failure summary =='
gh api repos/ContextualWisdomLab/naruon/check-runs/92340668375 2>&1 \
| jq '{name,conclusion,output,status,details_url}' || trueLength of output: 84480
I found no new code blocker in the requested areas.
Available CI evidence is positive for this exact head:
I could not independently confirm the claimed 100% changed-production coverage. The coverage-evidence job log endpoint returned HTTP 404, and the Application CI test command does not collect coverage. The merge gate remains blocked. The PR is behind
|
There was a problem hiding this comment.
Pull request overview
OpenCode cannot approve yet because required coverage evidence did not pass.
Review outcome
1. HIGH .github/workflows/opencode-review.yml:1 - Coverage evidence did not prove required test/docstring evidence
-
Problem: The required coverage-evidence job result was
failure, so OpenCode cannot establish approval sufficiency for this head. -
Root cause: Automated approval is only valid when the same-head coverage-evidence job proves supported repository test suites passed and configured docstring gates passed or were advisory, or reports not applicable because no supported source files or package manifests exist. Missing, failed, skipped, unavailable, or unsupported-tooling test evidence is a blocker.
-
Fix: Install or configure the repository test/docstring evidence tooling when source files or package manifests exist, rerun the current-head coverage-evidence job, and approve only after it reports
successwith required evidence or explicit no-source not-applicable evidence. -
Regression test: Keep the approval branch checking
needs.coverage-evidence.result == successbefore posting APPROVE, and publish REQUEST_CHANGES when coverage-evidence blocker states such as cancelled, skipped, failed, unsupported-tooling, or below-100 evidence are present. -
Result: REQUEST_CHANGES
-
Reason: coverage-evidence result was
failure, so required test/docstring evidence was not proven for current head596aeb654a6960fdc08b176ae386211d1100d51d. -
Head SHA:
596aeb654a6960fdc08b176ae386211d1100d51d -
Workflow run: 31844037887
-
Workflow attempt: 1
Coverage evidence
Coverage evidence job did not run or did not publish coverage evidence.
Changed-File Evidence Map
flowchart LR
PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
Evidence --> S1["Backend (13 files)"]
S1 --> I1["API and service runtime"]
I1 --> R1["Review risk: Backend (13 files)"]
R1 --> V1["backend tests"]
Evidence --> S2["Docs: email-source-identity-provenance.md"]
S2 --> I2["operator or user guidance"]
I2 --> R2["Review risk: Docs: email-source-identity-provenance.md"]
R2 --> V2["docs review"]
This current-head REQUEST_CHANGES state was caused solely by a transient/ordering coverage-evidence result cited from workflow run 31844037887. The unchanged head 596aeb6 has later authoritative exact-head coverage-source-tree and coverage-evidence check runs terminal-success, and all source review threads are resolved. Dismissing this superseded infrastructure/evidence state removes only the stale blocker; it is not approval and does not waive current-base refresh or independent last-pusher approval.
There was a problem hiding this comment.
Pull request overview
OpenCode cannot approve yet because required coverage evidence did not pass.
Review outcome
1. HIGH .github/workflows/opencode-review.yml:1 - Coverage evidence did not prove required test/docstring evidence
-
Problem: The required coverage-evidence job result was
failure, so OpenCode cannot establish approval sufficiency for this head. -
Root cause: Automated approval is only valid when the same-head coverage-evidence job proves supported repository test suites passed and configured docstring gates passed or were advisory, or reports not applicable because no supported source files or package manifests exist. Missing, failed, skipped, unavailable, or unsupported-tooling test evidence is a blocker.
-
Fix: Install or configure the repository test/docstring evidence tooling when source files or package manifests exist, rerun the current-head coverage-evidence job, and approve only after it reports
successwith required evidence or explicit no-source not-applicable evidence. -
Regression test: Keep the approval branch checking
needs.coverage-evidence.result == successbefore posting APPROVE, and publish REQUEST_CHANGES when coverage-evidence blocker states such as cancelled, skipped, failed, unsupported-tooling, or below-100 evidence are present. -
Result: REQUEST_CHANGES
-
Reason: coverage-evidence result was
failure, so required test/docstring evidence was not proven for current head26dfa2260365e4691f5aecbddf0ace61fb101f69. -
Head SHA:
26dfa2260365e4691f5aecbddf0ace61fb101f69 -
Workflow run: 31873648915
-
Workflow attempt: 1
Coverage evidence
Coverage evidence job did not run or did not publish coverage evidence.
Changed-File Evidence Map
flowchart LR
PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
Evidence --> S1["Backend (13 files)"]
S1 --> I1["API and service runtime"]
I1 --> R1["Review risk: Backend (13 files)"]
R1 --> V1["backend tests"]
Evidence --> S2["Docs: email-source-identity-provenance.md"]
S2 --> I2["operator or user guidance"]
I2 --> R2["Review risk: Docs: email-source-identity-provenance.md"]
R2 --> V2["docs review"]
There was a problem hiding this comment.
Pull request overview
OpenCode cannot approve yet because required coverage evidence did not pass.
Review outcome
1. HIGH .github/workflows/opencode-review.yml:1 - Coverage evidence did not prove required test/docstring evidence
-
Problem: The required coverage-evidence job result was
failure, so OpenCode cannot establish approval sufficiency for this head. -
Root cause: Automated approval is only valid when the same-head coverage-evidence job proves supported repository test suites passed and configured docstring gates passed or were advisory, or reports not applicable because no supported source files or package manifests exist. Missing, failed, skipped, unavailable, or unsupported-tooling test evidence is a blocker.
-
Fix: Install or configure the repository test/docstring evidence tooling when source files or package manifests exist, rerun the current-head coverage-evidence job, and approve only after it reports
successwith required evidence or explicit no-source not-applicable evidence. -
Regression test: Keep the approval branch checking
needs.coverage-evidence.result == successbefore posting APPROVE, and publish REQUEST_CHANGES when coverage-evidence blocker states such as cancelled, skipped, failed, unsupported-tooling, or below-100 evidence are present. -
Result: REQUEST_CHANGES
-
Reason: coverage-evidence result was
failure, so required test/docstring evidence was not proven for current headbee6519b733703432f38eadefb6097470996b47f. -
Head SHA:
bee6519b733703432f38eadefb6097470996b47f -
Workflow run: 31893636177
-
Workflow attempt: 1
Coverage evidence
Coverage evidence job did not run or did not publish coverage evidence.
Changed-File Evidence Map
flowchart LR
PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
Evidence --> S1["Backend (13 files)"]
S1 --> I1["API and service runtime"]
I1 --> R1["Review risk: Backend (13 files)"]
R1 --> V1["backend tests"]
Evidence --> S2["Docs: email-source-identity-provenance.md"]
S2 --> I2["operator or user guidance"]
I2 --> R2["Review risk: Docs: email-source-identity-provenance.md"]
R2 --> V2["docs review"]
|
@opencode-agent Re-review the unchanged exact current head |
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
There was a problem hiding this comment.
Pull request overview
OpenCode cannot approve yet because required coverage evidence did not pass.
Review outcome
1. HIGH .github/workflows/opencode-review.yml:1 - Coverage evidence did not prove required test/docstring evidence
-
Problem: The required coverage-evidence job result was
failure, so OpenCode cannot establish approval sufficiency for this head. -
Root cause: Automated approval is only valid when the same-head coverage-evidence job proves supported repository test suites passed and configured docstring gates passed or were advisory, or reports not applicable because no supported source files or package manifests exist. Missing, failed, skipped, unavailable, or unsupported-tooling test evidence is a blocker.
-
Fix: Install or configure the repository test/docstring evidence tooling when source files or package manifests exist, rerun the current-head coverage-evidence job, and approve only after it reports
successwith required evidence or explicit no-source not-applicable evidence. -
Regression test: Keep the approval branch checking
needs.coverage-evidence.result == successbefore posting APPROVE, and publish REQUEST_CHANGES when coverage-evidence blocker states such as cancelled, skipped, failed, unsupported-tooling, or below-100 evidence are present. -
Result: REQUEST_CHANGES
-
Reason: coverage-evidence result was
failure, so required test/docstring evidence was not proven for current head510ec77582aefbd6e76c6fca42c2f5cc28843ae5. -
Head SHA:
510ec77582aefbd6e76c6fca42c2f5cc28843ae5 -
Workflow run: 31997968368
-
Workflow attempt: 1
Coverage evidence
Coverage evidence job did not run or did not publish coverage evidence.
Changed-File Evidence Map
flowchart LR
PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
Evidence --> S1["Backend (13 files)"]
S1 --> I1["API and service runtime"]
I1 --> R1["Review risk: Backend (13 files)"]
R1 --> V1["backend tests"]
Evidence --> S2["Docs: email-source-identity-provenance.md"]
S2 --> I2["operator or user guidance"]
I2 --> R2["Review risk: Docs: email-source-identity-provenance.md"]
R2 --> V2["docs review"]
There was a problem hiding this comment.
Pull request overview
OpenCode cannot approve yet because required coverage evidence did not pass.
Review outcome
1. HIGH .github/workflows/opencode-review.yml:1 - Coverage evidence did not prove required test/docstring evidence
-
Problem: The required coverage-evidence job result was
failure, so OpenCode cannot establish approval sufficiency for this head. -
Root cause: Automated approval is only valid when the same-head coverage-evidence job proves supported repository test suites passed and configured docstring gates passed or were advisory, or reports not applicable because no supported source files or package manifests exist. Missing, failed, skipped, unavailable, or unsupported-tooling test evidence is a blocker.
-
Fix: Install or configure the repository test/docstring evidence tooling when source files or package manifests exist, rerun the current-head coverage-evidence job, and approve only after it reports
successwith required evidence or explicit no-source not-applicable evidence. -
Regression test: Keep the approval branch checking
needs.coverage-evidence.result == successbefore posting APPROVE, and publish REQUEST_CHANGES when coverage-evidence blocker states such as cancelled, skipped, failed, unsupported-tooling, or below-100 evidence are present. -
Result: REQUEST_CHANGES
-
Reason: coverage-evidence result was
failure, so required test/docstring evidence was not proven for current headf6e60ef85e6877df7e978a6f86f019c2c32cf795. -
Head SHA:
f6e60ef85e6877df7e978a6f86f019c2c32cf795 -
Workflow run: 32014151311
-
Workflow attempt: 1
Coverage evidence
Coverage evidence job did not run or did not publish coverage evidence.
Changed-File Evidence Map
flowchart LR
PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
Evidence --> S1["Backend (13 files)"]
S1 --> I1["API and service runtime"]
I1 --> R1["Review risk: Backend (13 files)"]
R1 --> V1["backend tests"]
Evidence --> S2["Docs: email-source-identity-provenance.md"]
S2 --> I2["operator or user guidance"]
I2 --> R2["Review risk: Docs: email-source-identity-provenance.md"]
R2 --> V2["docs review"]
|
@coderabbitai review |
|
…ce-qyxe67 Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
|
@coderabbitai review Please review the unchanged exact current head |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
expected='f98c6fe4f7b452c988f8fae3bb963c97aa8da43b'
printf '%s\n' '== Exact requested commit =='
printf 'checked_out='; git rev-parse HEAD
git cat-file -t "$expected"
git show -s --format='commit=%H%nparents=%P%nsubject=%s' "$expected"
printf '%s\n' '== Merge parents and changed scope =='
git diff-tree --no-commit-id --name-status -r "$expected"
git diff --check "${expected}^1" "$expected"
printf '%s\n' '-- Delta from first parent --'
git diff --stat "${expected}^1" "$expected"
printf '%s\n' '-- Delta from second parent --'
git diff --stat "${expected}^2" "$expected"
printf '%s\n' '== Aggregate scope from current develop merge base =='
base="$(git merge-base "$expected" origin/develop 2>/dev/null || git merge-base "$expected" develop)"
printf 'merge_base=%s\n' "$base"
git diff --name-status "$base" "$expected"
git diff --check "$base" "$expected"
printf '%s\n' '== Focused source identity and provenance symbols =='
rg -n -C 4 --glob '*.py' \
'(def (_extract_date|_extract_date_with_provenance|_email_fingerprint|source_email_fingerprint|canonical_email_source_content|process_fetched_email|_message_bytes)|date_provenance|header_date|message_id_provenance|source_content|strong_email_fingerprint|generate_email_fingerprint|resolve_candidate_disposition|classify_dedupe_decision|process_fetched_email\s*\()' \
backend/services backend/db backend/tests || true
printf '%s\n' '== Relevant file outlines =='
for f in \
backend/services/email_parser.py \
backend/services/email_dedupe_service.py \
backend/services/email_import_service.py \
backend/services/imap_worker.py \
backend/services/pop3_worker.py \
backend/db/models.py \
backend/alembic/versions/0018_email_date_provenance.py \
backend/tests/test_email_parser_provenance.py \
backend/tests/test_email_dedupe_service.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
do
if [ -f "$f" ]; then
echo "--- $f"
ast-grep outline "$f" --items all || true
fi
done
printf '%s\n' '== Migration graph declarations =='
rg -n -C 1 --glob '*.py' \
'^(revision|down_revision|branch_labels|depends_on)\s*=' \
backend/alembic/versions || true
printf '%s\n' '== Workflow changes in aggregate diff =='
git diff --name-status "$base" "$expected" -- '.github/workflows/**' || true
printf '%s\n' '== Current PR state and checks =='
gh pr view 1195 --repo ContextualWisdomLab/naruon \
--json state,headRefOid,baseRefOid,mergeStateStatus,reviewDecision,statusCheckRollup 2>&1 || true
gh pr checks 1195 --repo ContextualWisdomLab/naruon \
--json name,state,conclusion,workflow,link,completedAt 2>&1 || trueLength of output: 156685 🏁 Script executed: #!/bin/bash
set -euo pipefail
show_range() {
local file="$1" start="$2" end="$3"
printf '\n--- %s, Lines %s-%s ---\n' "$file" "$start" "$end"
nl -ba "$file" | sed -n "${start},${end}p"
}
show_range backend/services/email_dedupe_service.py 49 320
show_range backend/services/email_import_service.py 173 365
show_range backend/services/email_import_service.py 805 925
show_range backend/services/imap_worker.py 27 115
show_range backend/services/imap_worker.py 315 365
show_range backend/services/pop3_worker.py 85 210
show_range backend/services/email_parser.py 166 280
show_range backend/alembic/versions/0018_email_date_provenance.py 1 100
show_range backend/tests/test_source_bound_email_dedupe.py 1 230
show_range backend/tests/test_imap_worker.py 50 140
show_range backend/tests/test_pop3_worker.py 85 195
printf '%s\n' '== Every direct process_fetched_email call =='
rg -n -C 4 --glob '*.py' '\bprocess_fetched_email\s*\(' backend || true
printf '%s\n' '== Email fingerprint lookup predicates =='
rg -n -C 5 --glob '*.py' \
'(select\(Email\)|Email\.fingerprint|Email\.message_id|fingerprint\s*==|message_id\s*==)' \
backend/services/email_import_service.py backend/services/imap_worker.py || true
printf '%s\n' '== Parser compatibility wrapper and references =='
rg -n -C 3 --glob '*.py' '\b_extract_date\s*\(' backend || true
printf '%s\n' '== Current checks, supported fields =='
gh pr checks 1195 --repo ContextualWisdomLab/naruon \
--json name,state,workflow,link,completedAt,description 2>&1 || true
printf '%s\n' '== Coverage-evidence status, if present =='
gh pr view 1195 --repo ContextualWisdomLab/naruon \
--json statusCheckRollup --jq '.statusCheckRollup[] | select(.name == "coverage-evidence" or .name == "backend (Python 3.14)") | {name, status, conclusion, detailsUrl, startedAt, completedAt}' \
2>&1 || trueLength of output: 2261 🏁 Script executed: #!/bin/bash
set -euo pipefail
show_range() {
local file="$1" start="$2" end="$3"
printf '\n--- %s, Lines %s-%s ---\n' "$file" "$start" "$end"
awk -v start="$start" -v end="$end" \
'NR >= start && NR <= end { printf "%d\t%s\n", NR, $0 }' "$file"
}
show_range backend/services/email_dedupe_service.py 49 320
show_range backend/services/email_import_service.py 173 365
show_range backend/services/email_import_service.py 805 925
show_range backend/services/imap_worker.py 27 115
show_range backend/services/imap_worker.py 315 365
show_range backend/services/pop3_worker.py 85 210
show_range backend/services/email_parser.py 166 280
show_range backend/alembic/versions/0018_email_date_provenance.py 1 100
show_range backend/tests/test_source_bound_email_dedupe.py 1 230
show_range backend/tests/test_imap_worker.py 50 140
show_range backend/tests/test_pop3_worker.py 85 195
printf '%s\n' '== Every direct process_fetched_email call =='
rg -n -C 4 --glob '*.py' '\bprocess_fetched_email\s*\(' backend || true
printf '%s\n' '== Email fingerprint lookup predicates =='
rg -n -C 5 --glob '*.py' \
'(select\(Email\)|Email\.fingerprint|Email\.message_id|fingerprint\s*==|message_id\s*==)' \
backend/services/email_import_service.py backend/services/imap_worker.py || true
printf '%s\n' '== Parser compatibility wrapper and references =='
rg -n -C 3 --glob '*.py' '\b_extract_date\s*\(' backend || true
printf '%s\n' '== Current checks, supported fields =='
gh pr checks 1195 --repo ContextualWisdomLab/naruon \
--json name,state,workflow,link,completedAt,description 2>&1 || true
printf '%s\n' '== Coverage-evidence status, if present =='
gh pr view 1195 --repo ContextualWisdomLab/naruon \
--json statusCheckRollup --jq '.statusCheckRollup[] | select(.name == "coverage-evidence" or .name == "backend (Python 3.14)") | {name, status, conclusion, detailsUrl, startedAt, completedAt}' \
2>&1 || trueLength of output: 73545
I found no new code blocker in the requested scope.
I verified that the aggregate PR delta contains no workflow changes.
I did not emit an approval action. You are interacting with an AI system. |
There was a problem hiding this comment.
Pull request overview
OpenCode cannot approve yet because required coverage evidence did not pass.
Review outcome
1. HIGH .github/workflows/opencode-review.yml:1 - Coverage evidence did not prove required test/docstring evidence
-
Problem: The required coverage-evidence job result was
failure, so OpenCode cannot establish approval sufficiency for this head. -
Root cause: Automated approval is only valid when the same-head coverage-evidence job proves supported repository test suites passed and configured docstring gates passed or were advisory, or reports not applicable because no supported source files or package manifests exist. Missing, failed, skipped, unavailable, or unsupported-tooling test evidence is a blocker.
-
Fix: Install or configure the repository test/docstring evidence tooling when source files or package manifests exist, rerun the current-head coverage-evidence job, and approve only after it reports
successwith required evidence or explicit no-source not-applicable evidence. -
Regression test: Keep the approval branch checking
needs.coverage-evidence.result == successbefore posting APPROVE, and publish REQUEST_CHANGES when coverage-evidence blocker states such as cancelled, skipped, failed, unsupported-tooling, or below-100 evidence are present. -
Result: REQUEST_CHANGES
-
Reason: coverage-evidence result was
failure, so required test/docstring evidence was not proven for current headf98c6fe4f7b452c988f8fae3bb963c97aa8da43b. -
Head SHA:
f98c6fe4f7b452c988f8fae3bb963c97aa8da43b -
Workflow run: 32068450034
-
Workflow attempt: 1
Coverage evidence
Coverage evidence job did not run or did not publish coverage evidence.
Changed-File Evidence Map
flowchart LR
PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
Evidence --> S1["Backend (13 files)"]
S1 --> I1["API and service runtime"]
I1 --> R1["Review risk: Backend (13 files)"]
R1 --> V1["backend tests"]
Evidence --> S2["Docs: email-source-identity-provenance.md"]
S2 --> I2["operator or user guidance"]
I2 --> R2["Review risk: Docs: email-source-identity-provenance.md"]
R2 --> V2["docs review"]
|
@opencode-agent @cwl-noema-review please independently re-review exact current head |
Summary
Prevent synthetic collection timestamps from becoming strong duplicate evidence.
date_provenance,header_date, andmessage_id_provenancefrom the email parser while preserving the existing effectivedatecontract;-0000dates to timezone-aware UTC;date_provenanceonemail_recordsthrough Alembic revision0018_email_date_provenance, conservatively backfilling existing rows tounknown;auto_link,review_required, anddistinctclassification plus 1:N disposition resolution.Data and compatibility boundary
date_provenance(two-wordsnake_case).unknown, which can widen clerical review but cannot manufacture an automatic duplicate.datebehavior remains parsed-header-or-fallback.Dateheader was genuinely parsed.Current exact candidate
f6e60ef85e6877df7e978a6f86f019c2c32cf795.develop):f98c6fe4f7b452c988f8fae3bb963c97aa8da43b.develop:dd8d15191338b841f9e6f3a06507c6a5643b95d0(includes feat(calendar): prevent status-weighted double booking #1367).f6e60ef85e6877df7e978a6f86f019c2c32cf795+dd8d15191338b841f9e6f3a06507c6a5643b95d0.0018_email_date_provenance(develop still ended at0017_merge_newsdom_carddav_heads; no retarget required).ddd05c5a: none. The merge was conflict-free and did not change dedupe-provenance product code.f98c6fe4f7b452c988f8fae3bb963c97aa8da43b: 101 passed, 0 failed in 0.36s. NoTimeout/Fatal/Warn/Deniedoutput.The exact current product diff remains fourteen durable files:
backend/alembic/versions/0018_email_date_provenance.pybackend/db/models.pybackend/services/email_dedupe_service.pybackend/services/email_import_service.pybackend/services/email_parser.pybackend/services/imap_worker.pybackend/services/pop3_worker.pybackend/tests/test_email_dedupe_service.pybackend/tests/test_email_import_service.pybackend/tests/test_email_parser_provenance.pybackend/tests/test_imap_worker.pybackend/tests/test_pop3_worker.pybackend/tests/test_source_bound_email_dedupe.pydocs/doctoring/email-source-identity-provenance.mdVerification boundary
Predecessor-head test, check, and review results do not transfer. This exact head must establish its own complete repository CI, migration/single-head, coverage, security, container, and current-head review evidence. Queued, skipped, stale, predecessor-head, author-only, or model-only evidence is non-passing.
Focused contracts cover parsed/missing/invalid Date provenance,
-0000normalization, source-bound identities, collection-time independence, import/IMAP/POP3 parity, conservative migration/backfill behavior, deterministic 1:N disposition, and distinct raw messages collected at the same instant.Customer next action
Do not merge from this reconcile. Wait for exact-head product CI on
f98c6fe4f7b452c988f8fae3bb963c97aa8da43b, then obtain an independent non-author APPROVE. Author, merge-bot, and predecessor-head approvals are not sufficient.Reference
Fellegi, I. P., & Sunter, A. B. (1969). A theory for record linkage. Journal of the American Statistical Association, 64(328), 1183–1210. https://doi.org/10.1080/01621459.1969.10501049
Refs #1086
Merge gate
Merge only after every required unchanged exact-head check is terminal-success, all actionable current-head review threads are resolved, a qualifying independent non-author APPROVE exists where live organization policy requires it, and normal protected-branch rules accept the head without bypass. Predecessor evidence does not transfer. The central trusted-
uvmaterializer repair remains an external control-plane prerequisite for freshcoverage-evidence; an infrastructure failure must not be represented as a source-code finding.