Skip to content

feat(email-writing): compose bounded judged review service - #1535

Draft
seonghobae wants to merge 17 commits into
feat/email-writing-policy-task8-current-stackfrom
feat/email-writing-review-service-task9-current-stack
Draft

feat(email-writing): compose bounded judged review service#1535
seonghobae wants to merge 17 commits into
feat/email-writing-policy-task8-current-stackfrom
feat/email-writing-review-service-task9-current-stack

Conversation

@seonghobae

@seonghobae seonghobae commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Scope

Task 9 only: compose the already-separated Naruon email-writing boundaries into one bounded, fail-closed review service. This is a preparatory Draft lane stacked on the current Task-8 policy head. It does not add the HTTP review API, consume Inkspan, mutate/send email, publish a policy, or bypass the unresolved immutable fast-mlsirm artifact gate.

Runtime integration order remains Task 7 Judge → immutable fast-mlsirm pinning → Task 8 policy → Task 9 review service → Task 10 API. This slice may be developed and verified in parallel, but it must not merge as the canonical runtime descendant ahead of immutable dependency pinning.

Exact ancestry and TDD sequence

Fresh predecessor #1530 is 32c7e33c304d1b500ddc230044e2877edcf411c8. Exact current Task-9 head is c958b9964f925b6dca99e4c2c4db671934511a40. Current head was produced by an ordinary non-force two-parent restack after the Task-5 import-boundary repair propagated through Tasks 6–8; all predecessor checks/reviews are stale.

Task-9-owned TDD sequence retained in the ancestry:

  • RED 9af92412cf71fec51880637dd5fb14ab4274791c → initial bounded review service 38221826b004fc4f0646c645ca391ee42a00c4cd;
  • RED 9b5e015c02fc3e1c13403622ea2ea5e667083ffd → bounded injected Judge execution fix 9183d734ae98f47d60b96892489a29caf4c135f7;
  • RED ac5ed3dd0fa15e4fb63e870b0e8f415083fd4987 → Candidate selector/evidence revalidation and bounded timeout-state evidence fix c421ab284fc07c1bc45f61f1a5a206d90cd40e33;
  • RED 214c1f5eb70ad5aa4ee970451fddca05f5aa347d → bounded rollback cleanup 88cffb54a981a3d7699e524947f81d7659bd71ad;
  • RED 5f3968bd64ac8e4e4748ec269bb77d674e01bc28 → cancellation evidence finalization fix adb730e68ec7cdfc93c7f0a8fc151382e663c0d8.

No Jules/Cursor branch was modified, copied wholesale, force-updated, or used as writable ancestry. External #1402 remains read-only.

Review-service contract

The service executes only this order after server-authoritative context succeeds:

context build → Candidate reviewer → service-owned strict Candidate/bundle revalidation → bounded independent Judge execution → policy admission → deterministic revision/selector binding → one privacy-minimized evidence transaction → response.

Key boundaries:

  • no keyword/regex/phrase-list/sender-domain/recipient-count/language-name/nearest-text semantic fallback;
  • exact request/context revision, projection, language and mode identities are checked before model use;
  • every injected Candidate result is revalidated against the authorized current draft and evidence IDs before any Judge call;
  • Candidate output is never user-facing by itself;
  • every valid Candidate is independently judged through the injected Task-5-compatible bounded worker lane and evaluated through the structured policy contract;
  • any failed/withheld/adjudicated/unsupported Candidate withholds the whole review rather than silently returning a partial review;
  • an empty Candidate set abstains instead of exposing unjudged whole-document guidance;
  • a published admission still requires an explicit calibrated confidence mapping before a browser diagnostic can be emitted;
  • provider/Judge/policy failure yields typed abstention/unavailability and does not affect editing or sending;
  • Candidate/model work, timeout-state evidence finalization, externally-cancelled terminal evidence, and rollback cleanup are bounded separately;
  • persistence failure attempts bounded rollback and raises review_evidence_unavailable; no unrecorded diagnostic is returned;
  • ordinary persisted evidence contains hashes, selectors, criterion-category receipts, policy/workflow IDs and owner scope, not raw email/draft/replacement/explanation/prompt/model/Judge output.

The checked-in Task-8 policy is evaluation-only/withhold, so this stack cannot expose user-facing diagnostics even if the pipeline executes successfully.

Immutable dependency boundary

Fresh inherited dependency truth remains: fast-mlsirm v0.9.1 contains the required Judge symbols in immutable tagged source but its GitHub release has no wheel/sdist asset. Naruon #1385 and fast-mlsirm #1692 are the current artifact/publication owner paths. No mutable fast-mlsirm or Inkspan branch, Git URL, source copy, local stub or workspace path is consumed here.

Before runtime integration, the exact immutable fast-mlsirm distributable must be verified for version, integrity, source provenance, Python 3.14 installation/execution and Naruon hash lock; Task 7 must exercise those released symbols; then the policy/review-service lineage must be regenerated from that admitted predecessor. All predecessor checks/reviews are discarded when heads/bases move.

Current verification boundary

The dedicated Email Writing Review Service TDD workflow requires Python 3.14, the focused Task-9 tests, exact Task-9 statement+branch coverage at 100%, public docstrings, Ruff and compile checks on the current head.

For exact current head c958b9964f925b6dca99e4c2c4db671934511a40, pull-request job 100237071157 / run 33627024385 is queued before executed steps. Same-head sibling job 100237056302 / run 33627020527 terminated cancelled before useful execution. Both are non-passing; no predecessor run is transferable.

Current review evidence must bind to c958b9964f925b6dca99e4c2c4db671934511a40; prior reviews are predecessor evidence after the restack. Any new valid finding must be verified against current source and repaired test-first.

Runner acquisition remains owned by .github#712; the Naruon branch is not churned merely to retrigger that central condition.

Keep Draft. Merge requires a future unchanged integration-current head to satisfy live rulesets/protection, every applicable CI/security/SAST/dependency/coverage/package/SBOM/provenance gate, zero valid unresolved threads, and any qualifying independent post-last-push approval actually required by live governance. No predecessor evidence, self-approval, bypass, force-push, destructive rebase, or gate weakening.

@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

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.

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Please review the exact current Task-9 head 9670ba049ae23e9396051d21ead9194197de55eb. Treat this Draft as preparatory only: verify the bounded review-service composition, Candidate→independent-Judge separation, injected bounded Judge executor, fail-closed policy admission, cancellation/persistence semantics, privacy-minimized evidence, no lexical semantic fallback, and no send/user-facing publication authority. Do not treat queued workflow evidence or predecessor heads as passing.

@coderabbitai

coderabbitai Bot commented Sep 2, 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

head='9670ba049ae23e9396051d21ead9194197de55eb'
base='21fa293d967bac0dd2b7b9a917b9fbce415134a2'

echo '== HEAD AND ANCESTRY =='
git rev-parse HEAD
git show -s --format='commit=%H%nparents=%P%nsubject=%s' "$head"
git merge-base --is-ancestor "$base" "$head" && echo "base_is_ancestor=yes" || echo "base_is_ancestor=no"

echo '== TASK-9 CHANGED PATHS =='
git diff --name-status "$base" "$head"

echo '== CANDIDATE FILES =='
fd -t f . | rg -i '(email.*writing|review.*service|candidate|judge|evidence|policy|workflow)'

echo '== PYTHON OUTLINES FOR CHANGED FILES =='
git diff --name-only "$base" "$head" -- '*.py' | while IFS= read -r f; do
  echo "--- $f"
  ast-grep outline "$f" --items all --type class,function --view expanded || true
done

echo '== CONTRACT-RELATED REFERENCES IN CHANGED PYTHON =='
git diff --name-only "$base" "$head" -- '*.py' | while IFS= read -r f; do
  echo "--- $f"
  rg -n -C 4 -i 'run_judge|to_thread|candidate|judge|policy|persist|evidence|cancel|abstain|diagnostic|send|publish|regex|keyword|language|timeout|deadline|revision|selector' "$f" || true
done

echo '== PR CHECKS FOR REQUESTED HEAD =='
gh pr checks 1535 --repo ContextualWisdomLab/naruon --json name,state,link,workflow 2>&1 || true

Length of output: 82463


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== REVIEW SERVICE: COMPLETE NUMBERED SOURCE =='
cat -n backend/services/email_writing_review_service.py

echo '== REVIEW CONTRACTS OUTLINE AND REVIEW TYPES =='
ast-grep outline backend/services/email_writing_contracts.py --items all --type class,function --view expanded
rg -n -C 8 'class EmailWritingReview(Request|Response)|class EmailWritingDiagnostic|class EmailWritingDocumentGuidance|ReviewStatus|class EmailWritingProvenance' backend/services/email_writing_contracts.py

echo '== POLICY ADMISSION IMPLEMENTATION =='
ast-grep outline backend/services/email_writing_policy.py --items all --type class,function --view expanded
rg -n -C 8 'def evaluate_policy_admission|type AdmissionOutcome|AdmissionOutcome|publish_decision|evaluation_only|withhold|admit' backend/services/email_writing_policy.py backend/policies/email_writing_judge_evaluation_only_v1.json

echo '== EVIDENCE ORM AND MIGRATION =='
ast-grep outline backend/db/email_writing_evidence.py --items all --type class,function --view expanded
cat -n backend/db/email_writing_evidence.py
cat -n backend/alembic/versions/20260812_0001_add_email_writing_review_evidence.py

echo '== TASK-9 WORKFLOW =='
cat -n .github/workflows/email-writing-review-service-tdd.yml

echo '== IMPORTS OR USES OF THE TASK-9 SERVICE =='
rg -n -C 4 'EmailWritingReviewService|email_writing_review_service' backend --glob '*.py'

echo '== DIFF WITH CONTEXT =='
git diff --find-renames --unified=80 21fa293d967bac0dd2b7b9a917b9fbce415134a2 9670ba049ae23e9396051d21ead9194197de55eb -- \
  backend/services/email_writing_review_service.py \
  backend/tests/test_email_writing_review_service.py \
  backend/tests/test_email_writing_review_service_execution.py \
  .github/workflows/email-writing-review-service-tdd.yml

Length of output: 215890


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== CANDIDATE REVIEW: OUTLINE AND VALIDATION PATHS =='
ast-grep outline backend/services/email_writing_candidate_review.py --items all --type class,function --view expanded
rg -n -C 8 'class EmailWritingCandidate(ReviewResult|Diagnostic|Output)|def .*valid|selector|draft|revision|projection|language|evidence|hash|orchestration' backend/services/email_writing_candidate_review.py

echo '== INDEPENDENT JUDGE: OUTLINE AND EVALUATION PATHS =='
ast-grep outline backend/services/email_writing_judge.py --items all --type class,function --view expanded
rg -n -C 8 'class EmailWritingIndependentJudge|def evaluate|candidate|provider|fast_mlsirm|to_thread|executor|send|publish|admission' backend/services/email_writing_judge.py

echo '== TASK-5 BOUNDED EXECUTOR CONTRACTS =='
rg -n -C 8 'def run_judge|run_judge\s*=|class .*Judge.*(Executor|Worker)|JudgeExecutor|bounded.*judge|judge.*bounded' backend --glob '*.py'

echo '== TIMEOUT AND PERSISTENCE CONTROL FLOW =='
sed -n '332,409p' backend/services/email_writing_review_service.py
sed -n '728,935p' backend/services/email_writing_review_service.py
rg -n -C 6 'review_timeout|total_wall|timeout' backend/tests --glob '*email_writing*.py'

echo '== WORKFLOW/PATH AUTHORITY SEARCH =='
rg -n -i -C 3 'email.writing.review|email_writing_review|EmailWritingReviewService|send.*email|publish.*diagnostic|review.*api' backend/api backend/services --glob '*.py'

Length of output: 114260


@seonghobae I reviewed 9670ba049ae23e9396051d21ead9194197de55eb.

I found two issues.

  1. Total wall time is not bounded after a timeout.
    At Line 373, the TimeoutError handler runs after the asyncio.timeout() context at Line 349 has exited. It then calls _finalize_non_admitted() at Line 376. That method calls _persist_review() at Line 762. A stalled session.commit() can therefore delay the response without any remaining deadline. This violates the total-wall-time contract. Apply a bounded persistence deadline or return a typed evidence-unavailable result after rollback. Add a test with a commit operation that blocks after the model-work timeout.

  2. The generic Candidate port bypasses strict draft-relative selector binding.
    EmailWritingReviewCandidatePort accepts an arbitrary EmailWritingCandidateReviewResult, but _validate_candidate_result_binding() at Line 284 checks only orchestration mode and language. The service does not call _validate_candidate_selectors_and_evidence() before it sends diagnostics to the Judge or returns admitted diagnostics. A custom Candidate port can return a schema-valid selector outside bundle.current_draft or unknown evidence IDs. The normal EmailWritingCandidateReviewer performs these checks, but the public injected port does not. Validate every Candidate result against the authorized bundle inside EmailWritingReviewService, or narrow the constructor to accept only the validated reviewer result boundary. Add tests for an injected Candidate port with an out-of-range selector and an unknown evidence ID.

The remaining requested boundaries are present in the reviewed source:

  • The service uses the injected bounded run_judge lane. It has no direct asyncio.to_thread call.
  • Candidate output is withheld on Judge or policy failure.
  • The checked-in Task-8 policy is evaluation-only and returns withhold.
  • Cancellation attempts an abstained persistence transaction and re-raises cancellation.
  • Evidence records store hashes, selectors, receipt categories, policy/workflow data, and owner scope. They do not store raw draft, replacement, explanation, prompt, or Judge output.
  • No new API, send integration, or production import of this Draft service exists.
  • The current task9-review-service check is QUEUED. It is not passing.

You are interacting with an AI system.

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Please re-review exact current Task-9 head 163e943358dcaac53f978064fe5a7a30a7a17423. The prior review found two valid defects. This head adds RED regressions and causal repairs: every injected Candidate result is revalidated against the authorized bundle before Judge execution, and timeout-state evidence persistence is given a bounded post-timeout deadline that fails closed as review_evidence_unavailable after commit cancellation/rollback. Verify those fixes against current source and look for regressions, especially cancellation/rollback behavior, selector/evidence trust boundaries, and deadline semantics. The exact-head workflow run 33600700231 is queued and remains non-passing.

@coderabbitai

coderabbitai Bot commented Sep 2, 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

head='163e943358dcaac53f978064fe5a7a30a7a17423'
base='21fa293d967bac0dd2b7b9a917b9fbce415134a2'

echo '== EXACT HEAD =='
git rev-parse HEAD
git show -s --format='commit=%H%nparents=%P%nsubject=%s' "$head"
git merge-base --is-ancestor "$base" "$head" && echo 'base_is_ancestor=yes' || echo 'base_is_ancestor=no'
git diff --name-status "$base" "$head"

echo '== SERVICE OUTLINE =='
ast-grep outline backend/services/email_writing_review_service.py --items all --type class,function --view expanded

echo '== SERVICE: CANDIDATE VALIDATION, DEADLINES, CANCELLATION, AND PERSISTENCE =='
rg -n -C 12 -i \
  'validate_candidate|validate.*selector|validate.*evidence|run_judge|asyncio\.timeout|TimeoutError|CancelledError|post.*timeout|persist|commit|rollback|review_evidence_unavailable|finalize_non_admitted|candidate_result' \
  backend/services/email_writing_review_service.py

echo '== PRECISE SERVICE RANGES =='
sed -n '1,460p' backend/services/email_writing_review_service.py
sed -n '460,1040p' backend/services/email_writing_review_service.py

echo '== RELATED CONTRACTS AND VALIDATOR IMPLEMENTATIONS =='
ast-grep outline backend/services/email_writing_candidate_review.py --items all --type class,function --view expanded
ast-grep outline backend/services/email_writing_contracts.py --items all --type class,function --view expanded
rg -n -C 10 -i \
  'validate.*selector|validate.*evidence|selector|evidence_id|current_draft|authorized|candidate.*port|candidate.*result' \
  backend/services/email_writing_candidate_review.py backend/services/email_writing_contracts.py

echo '== FOCUSED REGRESSION TESTS =='
for f in backend/tests/test_email_writing_review_service.py backend/tests/test_email_writing_review_service_execution.py; do
  echo "--- $f"
  ast-grep outline "$f" --items all --type class,function --view expanded
  rg -n -C 10 -i \
    'out.of.range|unknown.*evidence|selector|injected.*candidate|timeout|commit|rollback|cancel|review_evidence_unavailable|run_judge' "$f"
done

echo '== CURRENT-HEAD DIFF FOR REPAIRS =='
git diff --find-renames --unified=100 \
  9670ba049ae23e9396051d21ead9194197de55eb "$head" -- \
  backend/services/email_writing_review_service.py \
  backend/tests/test_email_writing_review_service.py \
  backend/tests/test_email_writing_review_service_execution.py \
  .github/workflows/email-writing-review-service-tdd.yml

echo '== RUNTIME AUTHORITY AND EXECUTOR SEARCH =='
rg -n -C 5 -i \
  'EmailWritingReviewService|email_writing_review_service|asyncio\.to_thread|run_judge|send.*email|publish.*diagnostic|review.*api' \
  backend --glob '*.py'

echo '== TASK-9 WORKFLOW AND LIVE PR CHECKS =='
cat -n .github/workflows/email-writing-review-service-tdd.yml
gh pr checks 1535 --repo ContextualWisdomLab/naruon --json name,state,link,workflow 2>&1 || true

Length of output: 173845


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== EXACT REPAIR SOURCE =='
nl -ba backend/services/email_writing_review_service.py | sed -n '240,430p'
nl -ba backend/services/email_writing_review_service.py | sed -n '800,970p'

echo '== SHARED CANDIDATE VALIDATOR =='
rg -n -A120 -B15 \
  '^def _validate_candidate_selectors_and_evidence\b|^def _validate_candidate_result_binding\b' \
  backend/services/email_writing_review_service.py backend/services/email_writing_candidate_review.py

echo '== COMPLETE TASK-9 REGRESSION TEST FILE =='
nl -ba backend/tests/test_email_writing_review_service_review_regressions.py

echo '== FOCUSED TEST NAMES AND ASSERTIONS =='
rg -n -A40 -B5 \
  '^async def test_|^def test_|block_commit|block_rollback|rollbacks|commits|review_evidence_unavailable|candidate_selector_out_of_range|candidate_evidence_unknown|Judge must not run' \
  backend/tests/test_email_writing_review_service.py \
  backend/tests/test_email_writing_review_service_execution.py \
  backend/tests/test_email_writing_review_service_review_regressions.py

echo '== ASYNCIO TIMEOUT IMPLEMENTATION AVAILABLE TO THE ENVIRONMENT =='
python - <<'PY'
import asyncio
import inspect
print(inspect.getsource(asyncio.Timeout))
PY

Length of output: 1583


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== EXACT REPAIR SOURCE: VALIDATION AND TIMEOUT =='
sed -n '240,430p' backend/services/email_writing_review_service.py | cat -n

echo '== EXACT REPAIR SOURCE: FINALIZATION AND PERSISTENCE =='
sed -n '700,980p' backend/services/email_writing_review_service.py | cat -n

echo '== SHARED CANDIDATE VALIDATOR =='
rg -n -A120 -B15 \
  '^def _validate_candidate_selectors_and_evidence\b|^def _validate_candidate_result_binding\b' \
  backend/services/email_writing_review_service.py backend/services/email_writing_candidate_review.py

echo '== COMPLETE TASK-9 REGRESSION TEST FILE =='
cat -n backend/tests/test_email_writing_review_service_review_regressions.py

echo '== FOCUSED TESTS FOR THE REPAIRS =='
rg -n -A55 -B5 \
  '^async def test_|^def test_|block_commit|block_rollback|rollbacks|commits|review_evidence_unavailable|candidate_selector_out_of_range|candidate_evidence_unknown|Judge must not run' \
  backend/tests/test_email_writing_review_service.py \
  backend/tests/test_email_writing_review_service_execution.py \
  backend/tests/test_email_writing_review_service_review_regressions.py

Length of output: 110790


@seonghobae I reviewed exact head 163e943358dcaac53f978064fe5a7a30a7a17423.

I found one remaining issue.

  1. The post-timeout deadline does not bound a blocked rollback.
    At Line 942 in backend/services/email_writing_review_service.py, _persist_review() catches cancellation from session.commit() and awaits asyncio.shield(session.rollback()). If rollback() blocks, the shielded task continues and the service waits for it. The post-timeout asyncio.timeout(evidence_seconds) cannot return review_evidence_unavailable until that rollback finishes. This still violates the bounded total-wall-time contract.

    The new regression test blocks commit() but its rollback() returns immediately. Add a test where commit() blocks and rollback() also blocks. The review must return the typed review_evidence_unavailable result within the bounded evidence deadline. The implementation must start rollback without awaiting it beyond that deadline, or apply a separately bounded cleanup strategy.

The Candidate repair is correct in the reviewed source:

  • _validate_candidate_result_binding() now passes the authorized bundle to _validate_candidate_selectors_and_evidence() before any Judge call.
  • The shared validator checks empty and out-of-range selectors, selector overlap, and unknown evidence IDs.
  • The regression tests use an injected Candidate port and confirm that the Judge does not run for an out-of-range selector or an unknown evidence ID.

The service still uses the injected run_judge executor. It does not add a direct Judge asyncio.to_thread path. The workflow includes the new regression test file. Run 33600700231 remains QUEUED, so it is non-passing.

You are interacting with an AI system.

@seonghobae seonghobae added enhancement New feature or request priority: high High-priority or P1 work status: draft Draft pull request type: feature New or expanded product capability labels Sep 2, 2026 — with ChatGPT Codex Connector

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Please re-review exact current Task-9 head 88cffb54a981a3d7699e524947f81d7659bd71ad. The prior current-head review found the timeout cleanup still waited on a shielded rollback. This head adds RED 214c1f5eb70ad5aa4ee970451fddca05f5aa347d for both blocked commit and blocked rollback, then replaces unbounded shielded rollback with a bounded rollback cleanup (EMAIL_WRITING_REVIEW_ROLLBACK_SECONDS) before propagating cancellation or review_evidence_unavailable. Re-verify deadline/cancellation semantics, no orphaned semantic output, Candidate/bundle binding, and any new regressions. Exact-head workflow run 33601206280 is queued and non-passing.

@coderabbitai

coderabbitai Bot commented Sep 2, 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

head='88cffb54a981a3d7699e524947f81d7659bd71ad'
previous='163e943358dcaac53f978064fe5a7a30a7a17423'
base='21fa293d967bac0dd2b7b9a917b9fbce415134a2'

echo '== HEAD, ANCESTRY, AND CHANGED PATHS =='
git rev-parse HEAD
git show -s --format='commit=%H%nparents=%P%nsubject=%s' "$head"
git merge-base --is-ancestor "$base" "$head" && echo 'base_is_ancestor=yes' || echo 'base_is_ancestor=no'
git diff --name-status "$previous" "$head"

echo '== REVIEW SERVICE OUTLINE =='
ast-grep outline backend/services/email_writing_review_service.py --items all --type class,function --view expanded

echo '== REVIEW SERVICE: DEADLINES, CLEANUP, PERSISTENCE, CANCELLATION =='
rg -n -C 14 -i \
  'ROLLBACK_SECONDS|rollback|commit|persist_review|evidence_seconds|asyncio\.timeout|TimeoutError|CancelledError|shield|create_task|review_evidence_unavailable|finalize_non_admitted|finalize.*cancel|cancel' \
  backend/services/email_writing_review_service.py

echo '== REVIEW SERVICE: CANDIDATE VALIDATION AND JUDGE ORDER =='
rg -n -C 12 \
  '_validate_candidate_result_binding|_validate_candidate_selectors_and_evidence|candidate_reviewer|run_judge|evaluate_policy|diagnostic' \
  backend/services/email_writing_review_service.py

echo '== PRECISE SERVICE RANGES =='
nl -ba backend/services/email_writing_review_service.py | sed -n '1,470p'
nl -ba backend/services/email_writing_review_service.py | sed -n '700,1040p'

echo '== CANDIDATE SHARED VALIDATOR =='
rg -n -A140 -B15 \
  '^def _validate_candidate_selectors_and_evidence\b|^def _validate_candidate_result_binding\b' \
  backend/services/email_writing_candidate_review.py \
  backend/services/email_writing_review_service.py

echo '== FOCUSED TEST OUTLINES AND TIMEOUT/CANCELLATION ASSERTIONS =='
for f in \
  backend/tests/test_email_writing_review_service.py \
  backend/tests/test_email_writing_review_service_execution.py \
  backend/tests/test_email_writing_review_service_review_regressions.py
do
  echo "--- $f"
  ast-grep outline "$f" --items all --type class,function --view expanded
  rg -n -C 10 -i \
    'blocked.*commit|blocked.*rollback|rollback|commit|timeout|deadline|cancel|review_evidence_unavailable|out.of.range|unknown.*evidence|Judge must not run|run_judge' \
    "$f"
done

echo '== REPAIR DIFF =='
git diff --find-renames --unified=100 "$previous" "$head" -- \
  backend/services/email_writing_review_service.py \
  backend/tests/test_email_writing_review_service.py \
  backend/tests/test_email_writing_review_service_execution.py \
  backend/tests/test_email_writing_review_service_review_regressions.py \
  .github/workflows/email-writing-review-service-tdd.yml

echo '== RUNTIME AUTHORITY SEARCH =='
rg -n -C 4 -i \
  'EmailWritingReviewService|email_writing_review_service|asyncio\.to_thread|run_judge|send.*email|publish.*diagnostic|review.*api' \
  backend --glob '*.py'

echo '== LIVE PR CHECKS =='
gh pr checks 1535 --repo ContextualWisdomLab/naruon --json name,state,link,workflow 2>&1 || true

Length of output: 63982


🏁 Script executed:

#!/bin/bash
set -euo pipefail

head='88cffb54a981a3d7699e524947f81d7659bd71ad'
previous='163e943358dcaac53f978064fe5a7a30a7a17423'

echo '== COMPLETE REVIEW SERVICE WITH LINE NUMBERS =='
cat -n backend/services/email_writing_review_service.py

echo '== COMPLETE REGRESSION TEST FILE WITH LINE NUMBERS =='
cat -n backend/tests/test_email_writing_review_service_review_regressions.py

echo '== CANCELLATION TESTS IN ALL FOCUSED TASK-9 TESTS =='
rg -n -A90 -B12 \
  'CancelledError|review_cancelled|cancel\(|shield\(|persist_terminal_review|rollback' \
  backend/tests/test_email_writing_review_service.py \
  backend/tests/test_email_writing_review_service_execution.py \
  backend/tests/test_email_writing_review_service_review_regressions.py

echo '== TIMEOUT AND BLOCKED PERSISTENCE TESTS =='
rg -n -A110 -B12 \
  'block_commit|block_rollback|commit_started|rollback_started|review_evidence_unavailable|TIMEOUT_EVIDENCE|ROLLBACK_SECONDS|total_wall_seconds' \
  backend/tests/test_email_writing_review_service.py \
  backend/tests/test_email_writing_review_service_execution.py \
  backend/tests/test_email_writing_review_service_review_regressions.py

echo '== EXACT REPAIR DIFF =='
git diff --find-renames --unified=120 "$previous" "$head" -- \
  backend/services/email_writing_review_service.py \
  backend/tests/test_email_writing_review_service_review_regressions.py

echo '== WORKFLOW FOCUSED TEST COMMANDS =='
cat -n .github/workflows/email-writing-review-service-tdd.yml

echo '== EXACT PR CHECK STATE =='
gh pr checks 1535 --repo ContextualWisdomLab/naruon --json name,state,link,workflow 2>&1 || true

echo '== CHECKOUT CONTENT ID =='
git rev-parse HEAD
git diff --quiet "$head" -- && echo 'checkout_matches_requested_head=yes' || echo 'checkout_matches_requested_head=no'

Length of output: 171996


@seonghobae I reviewed exact head 88cffb54a981a3d7699e524947f81d7659bd71ad.

I found no new actionable defects.

The rollback repair resolves the prior finding. _persist_review() now calls _rollback_bounded() after commit cancellation or commit failure. _rollback_bounded() applies EMAIL_WRITING_REVIEW_ROLLBACK_SECONDS to session.rollback(). A blocked rollback no longer delays timeout-state finalization without a bound. The timeout path then fails closed with review_evidence_unavailable.

The regression test covers both cases:

  • blocked commit with normal rollback;
  • blocked commit with blocked rollback.

The test requires review_evidence_unavailable within an outer 0.1-second bound. It also confirms that rollback was attempted.

The Candidate trust boundary remains correct. _validate_candidate_result_binding() validates orchestration mode and language. It then calls _validate_candidate_selectors_and_evidence() with the authorized bundle before any run_judge() call. The focused regressions confirm that an injected Candidate result with an out-of-range selector or unknown evidence ID produces an empty rejected response and does not execute the Judge.

The non-admitted paths return no semantic diagnostics or document guidance. The service builds user-facing diagnostics only after independent Judge execution, policy admission, confidence validation, and successful evidence persistence. The inspected service source does not send email or mutate email state.

The checkout matches the requested head. The Task-9 predecessor is an ancestor of this head.

Workflow run 33601206280 remains QUEUED. It is non-passing. The prior run is CANCELLED.

You are interacting with an AI system.

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Please review the exact current Task-9 head 88cffb54a981a3d7699e524947f81d7659bd71ad only. The previous manual request targeted predecessor 9670ba049ae23e9396051d21ead9194197de55eb and is stale. Verify the bounded rollback cleanup repair together with Candidate→independent-Judge separation, Task-5-compatible bounded Judge execution, strict Candidate/bundle validation before Judge use, fail-closed policy admission, cancellation/persistence deadlines, privacy-minimized evidence, no lexical semantic fallback, and no send/user-facing publication authority. Do not treat predecessor, queued, cancelled, status-only, or model-only evidence as passing.

@coderabbitai

coderabbitai Bot commented Sep 2, 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

head='88cffb54a981a3d7699e524947f81d7659bd71ad'
base='21fa293d967bac0dd2b7b9a917b9fbce415134a2'

echo '== EXACT COMMIT AND ANCESTRY =='
echo "checkout=$(git rev-parse HEAD)"
git show -s --format='commit=%H%nparents=%P%nsubject=%s' "$head"
git merge-base --is-ancestor "$base" "$head" && echo 'task8_predecessor_is_ancestor=yes' || echo 'task8_predecessor_is_ancestor=no'
git diff --quiet "$head" -- && echo 'checkout_matches_requested_head=yes' || echo 'checkout_matches_requested_head=no'
echo '== CHANGED PATHS FROM TASK-8 PREDECESSOR =='
git diff --name-status "$base" "$head"

echo '== REVIEW SERVICE OUTLINE =='
ast-grep outline backend/services/email_writing_review_service.py --items all --type class,function --view expanded

echo '== REVIEW SERVICE: ORDER, VALIDATION, JUDGE, POLICY, PERSISTENCE, CANCELLATION =='
nl -ba backend/services/email_writing_review_service.py | sed -n '1,470p'
nl -ba backend/services/email_writing_review_service.py | sed -n '700,1060p'

echo '== SHARED CANDIDATE VALIDATOR =='
rg -n -A150 -B15 \
  '^def _validate_candidate_selectors_and_evidence\b|^def _validate_candidate_result_binding\b' \
  backend/services/email_writing_candidate_review.py \
  backend/services/email_writing_review_service.py

echo '== JUDGE PORT AND POLICY =='
rg -n -C8 \
  'run_judge|asyncio\.to_thread|ThreadPoolExecutor|evaluate_policy_admission|evaluation_only|withhold|admit' \
  backend/services/email_writing_judge.py \
  backend/services/email_writing_policy.py \
  backend/policies/email_writing_judge_evaluation_only_v1.json \
  backend/services/email_writing_review_service.py

echo '== EVIDENCE MODEL AND PERSISTED-FIELD REFERENCES =='
ast-grep outline backend/db/email_writing_evidence.py --items all --type class,function --view expanded
cat -n backend/db/email_writing_evidence.py
rg -n -C6 \
  'EmailWritingReviewService|email_writing_review_service|send_email|send.*email|publish.*diagnostic|review.*api' \
  backend --glob '*.py'

echo '== FOCUSED REGRESSIONS: TIMEOUT, ROLLBACK, CANCELLATION, CANDIDATE BINDING =='
for f in \
  backend/tests/test_email_writing_review_service.py \
  backend/tests/test_email_writing_review_service_execution.py \
  backend/tests/test_email_writing_review_service_review_regressions.py \
  .github/workflows/email-writing-review-service-tdd.yml
do
  echo "--- $f"
  cat -n "$f"
done

echo '== PR CHECK STATE =='
gh pr checks 1535 --repo ContextualWisdomLab/naruon --json name,state,link,workflow 2>&1 || true

Length of output: 6319


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== SERVICE: CONSTANTS, ROLLBACK, CONTEXT, CANDIDATE VALIDATION =='
sed -n '1,347p' backend/services/email_writing_review_service.py | cat -n

echo '== SERVICE: PRIMARY REVIEW CONTROL FLOW =='
sed -n '348,727p' backend/services/email_writing_review_service.py | cat -n

echo '== SERVICE: FINALIZATION AND PERSISTENCE CONTROL FLOW =='
sed -n '728,1040p' backend/services/email_writing_review_service.py | cat -n

echo '== CANDIDATE VALIDATION IMPLEMENTATION =='
rg -n -A150 -B15 \
  '^def _validate_candidate_selectors_and_evidence\b|^def _validate_candidate_result_binding\b' \
  backend/services/email_writing_candidate_review.py \
  backend/services/email_writing_review_service.py

echo '== JUDGE EXECUTION CONTRACT AND POLICY ADMISSION =='
rg -n -C10 \
  'class .*Judge|def evaluate|def run_judge|asyncio\.to_thread|evaluate_policy_admission|evaluation_only|withhold|admit' \
  backend/services/email_writing_judge.py \
  backend/services/email_writing_policy.py \
  backend/policies/email_writing_judge_evaluation_only_v1.json \
  backend/services/email_writing_review_service.py

echo '== EVIDENCE MODEL =='
cat -n backend/db/email_writing_evidence.py

echo '== RUNTIME AUTHORITY SEARCH =='
rg -n -C5 -i \
  'EmailWritingReviewService|email_writing_review_service|send_email|send.*email|publish.*diagnostic|review.*api' \
  backend --glob '*.py'

echo '== FOCUSED TESTS: NAMES AND RELEVANT ASSERTIONS =='
for f in \
  backend/tests/test_email_writing_review_service.py \
  backend/tests/test_email_writing_review_service_execution.py \
  backend/tests/test_email_writing_review_service_review_regressions.py
do
  echo "--- $f"
  rg -n -A80 -B8 \
    '^async def test_|^def test_|block_commit|block_rollback|rollback_started|commit_started|CancelledError|review_evidence_unavailable|run_judge|selector|evidence_id|policy|send|publish' \
    "$f"
done

echo '== TASK-9 FOCUSED WORKFLOW =='
cat -n .github/workflows/email-writing-review-service-tdd.yml

echo '== PR CHECKS (STATE ONLY) =='
gh pr checks 1535 --repo ContextualWisdomLab/naruon --json name,state,link,workflow 2>&1 || true

Length of output: 146683


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== ROLLBACK HELPER AND BINDING VALIDATORS =='
sed -n '250,320p' backend/services/email_writing_review_service.py | cat -n

echo '== REVIEW ENTRY AND TIMEOUT/CANCELLATION HANDLERS =='
sed -n '340,435p' backend/services/email_writing_review_service.py | cat -n

echo '== AUTHORIZED-BUNDLE CANDIDATE/JUDGE/POLICY FLOW =='
sed -n '436,727p' backend/services/email_writing_review_service.py | cat -n

echo '== RESPONSE FINALIZATION AND PERSISTENCE =='
sed -n '728,1000p' backend/services/email_writing_review_service.py | cat -n

echo '== CANDIDATE SELECTOR/EVIDENCE VALIDATOR =='
ast-grep run --pattern 'def _validate_candidate_selectors_and_evidence($$$):
  $$$' --lang python backend/services/email_writing_candidate_review.py

echo '== POLICY ADMISSION DEFINITION =='
ast-grep run --pattern 'def evaluate_policy_admission($$$):
  $$$' --lang python backend/services/email_writing_policy.py
rg -n -C5 '"publish_decision"|"status"|evaluation_only|withhold' \
  backend/policies/email_writing_judge_evaluation_only_v1.json

echo '== SERVICE-SPECIFIC RUNTIME REFERENCES =='
rg -n -C4 \
  'from services\.email_writing_review_service import|import services\.email_writing_review_service|EmailWritingReviewService\s*\(' \
  backend --glob '*.py' \
  -g '!backend/tests/test_email_writing_review_service.py' \
  -g '!backend/tests/test_email_writing_review_service_execution.py' \
  -g '!backend/tests/test_email_writing_review_service_review_regressions.py' || true

echo '== EVIDENCE RECORD CONSTRUCTION FIELDS =='
sed -n '865,970p' backend/services/email_writing_review_service.py | cat -n

Length of output: 35914


@seonghobae I reviewed only 88cffb54a981a3d7699e524947f81d7659bd71ad.

I found one issue.

  1. Cancellation can wait forever for evidence persistence.
    In EmailWritingReviewService.review(), the asyncio.CancelledError handler awaits asyncio.shield(self._persist_terminal_review(...)). _persist_terminal_review() then calls _persist_review(), which awaits session.commit() without a deadline. If commit() blocks, the cancelled review task does not propagate CancelledError.

    The new _rollback_bounded() helper fixes the prior blocked-rollback path after a commit failure. It does not bound a blocked commit in the cancellation handler. The shield prevents the initial cancellation from cancelling that commit.

    Apply a bounded persistence deadline to cancellation-state terminal persistence. If that deadline expires, run the existing bounded rollback cleanup and then re-raise CancelledError. Do not return a response or semantic diagnostics.

    Add a regression test that:

    • starts a review after authorized context exists;
    • cancels the review;
    • blocks the terminal session.commit();
    • verifies cancellation propagates within a bounded outer timeout; and
    • verifies rollback is attempted without exposing semantic output.

The remaining requested boundaries are present in the reviewed source:

  • The service validates injected Candidate orchestration mode, language, selectors, and evidence IDs before run_judge().
  • The service uses the injected run_judge executor. It does not call asyncio.to_thread for Judge execution.
  • Judge and policy failures withhold the full review.
  • The policy admission path is fail-closed.
  • The timeout finalization path applies a separate evidence deadline.
  • Commit failure uses bounded rollback cleanup and returns review_evidence_unavailable.
  • Evidence persistence stores hashes, selectors, criterion-category receipts, policy/workflow identifiers, and owner scope. It does not persist raw draft or model output.
  • The reviewed service has no send or API publication integration.
  • The source contains no lexical semantic fallback.

The checkout matches the requested commit. The task9-review-service check is QUEUED. The prior run is CANCELLED. Neither is passing evidence.

You are interacting with an AI system.

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Please review exact current Task-9 head adb730e68ec7cdfc93c7f0a8fc151382e663c0d8 only. Prior manual requests targeted predecessor heads and are stale. Verify the new external-cancellation regression and bounded terminal-evidence finalization together with the existing Candidate→independent-Judge separation, Task-5-compatible bounded Judge execution, strict Candidate/bundle validation before Judge use, fail-closed policy admission, bounded timeout/rollback persistence behavior, privacy-minimized evidence, no lexical semantic fallback, and no send/user-facing publication authority. Do not treat predecessor, queued, cancelled, status-only, or model-only evidence as passing.

@coderabbitai

coderabbitai Bot commented Sep 2, 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

head='adb730e68ec7cdfc93c7f0a8fc151382e663c0d8'
base='21fa293d967bac0dd2b7b9a917b9fbce415134a2'
previous='88cffb54a981a3d7699e524947f81d7659bd71ad'

echo '== EXACT COMMIT, CHECKOUT, AND ANCESTRY =='
printf 'checkout='; git rev-parse HEAD
git show -s --format='commit=%H%nparents=%P%nsubject=%s' "$head"
git merge-base --is-ancestor "$base" "$head" && echo 'task8_predecessor_is_ancestor=yes' || echo 'task8_predecessor_is_ancestor=no'
git diff --quiet "$head" -- && echo 'checkout_matches_requested_head=yes' || echo 'checkout_matches_requested_head=no'
echo '== DELTA FROM IMMEDIATE PREDECESSOR =='
git diff --name-status "$previous" "$head"

echo '== SERVICE OUTLINE =='
ast-grep outline backend/services/email_writing_review_service.py --items all --type class,function --view expanded

echo '== SERVICE: ENTRY, DEADLINES, CANCELLATION, AND PERSISTENCE =='
nl -ba backend/services/email_writing_review_service.py | sed -n '1,470p'
nl -ba backend/services/email_writing_review_service.py | sed -n '700,1060p'

echo '== SERVICE: CANDIDATE, JUDGE, POLICY, AND RESPONSE FLOW =='
rg -n -C12 \
  '_validate_candidate_result_binding|_validate_candidate_selectors_and_evidence|candidate_reviewer|run_judge|asyncio\.to_thread|evaluate_policy_admission|_persist_terminal_review|_persist_review|asyncio\.wait_for|asyncio\.timeout|CancelledError|TimeoutError|rollback|review_evidence_unavailable|diagnostic' \
  backend/services/email_writing_review_service.py

echo '== CURRENT CANCELLATION REPAIR DIFF =='
git diff --find-renames --unified=120 "$previous" "$head" -- \
  backend/services/email_writing_review_service.py \
  backend/tests/test_email_writing_review_service_review_regressions.py \
  backend/tests/test_email_writing_review_service.py \
  backend/tests/test_email_writing_review_service_execution.py \
  .github/workflows/email-writing-review-service-tdd.yml

echo '== CANDIDATE VALIDATOR AND JUDGE PORT =='
rg -n -A150 -B15 \
  '^def _validate_candidate_selectors_and_evidence\b|^def _validate_candidate_result_binding\b' \
  backend/services/email_writing_candidate_review.py \
  backend/services/email_writing_review_service.py
rg -n -C10 \
  'def run_judge|asyncio\.to_thread|class .*Judge|def evaluate' \
  backend/services/email_writing_judge.py backend/services/email_writing_review_service.py

echo '== POLICY AND EVIDENCE MODEL =='
rg -n -C8 \
  'def evaluate_policy_admission|evaluation_only|withhold|admit|publish_decision' \
  backend/services/email_writing_policy.py backend/policies/email_writing_judge_evaluation_only_v1.json
cat -n backend/db/email_writing_evidence.py

echo '== FOCUSED REGRESSIONS =='
for f in \
  backend/tests/test_email_writing_review_service.py \
  backend/tests/test_email_writing_review_service_execution.py \
  backend/tests/test_email_writing_review_service_review_regressions.py
do
  echo "--- $f"
  rg -n -A100 -B10 \
    '^async def test_|^def test_|external.*cancel|CancelledError|cancel\(|commit_started|rollback_started|block_commit|block_rollback|review_evidence_unavailable|run_judge|selector|evidence_id|semantic output|diagnostic' \
    "$f"
done

echo '== RUNTIME AUTHORITY REFERENCES =='
rg -n -C5 \
  'from services\.email_writing_review_service import|import services\.email_writing_review_service|EmailWritingReviewService\s*\(|send_email|send.*email|publish.*diagnostic|review.*api' \
  backend --glob '*.py' \
  -g '!backend/tests/test_email_writing_review_service.py' \
  -g '!backend/tests/test_email_writing_review_service_execution.py' \
  -g '!backend/tests/test_email_writing_review_service_review_regressions.py' || true

echo '== TASK-9 WORKFLOW AND PR CHECKS =='
cat -n .github/workflows/email-writing-review-service-tdd.yml
gh pr checks 1535 --repo ContextualWisdomLab/naruon --json name,state,link,workflow 2>&1 || true

Length of output: 7446


🏁 Script executed:

#!/bin/bash
set -euo pipefail

head='adb730e68ec7cdfc93c7f0a8fc151382e663c0d8'
previous='88cffb54a981a3d7699e524947f81d7659bd71ad'

echo '== SERVICE: CONSTANTS, PORTS, VALIDATION, ROLLBACK =='
sed -n '1,347p' backend/services/email_writing_review_service.py | cat -n

echo '== SERVICE: REVIEW ENTRY, TIMEOUT, AND EXTERNAL CANCELLATION =='
sed -n '348,443p' backend/services/email_writing_review_service.py | cat -n

echo '== SERVICE: AUTHORIZED BUNDLE, CANDIDATE, JUDGE, AND POLICY =='
sed -n '444,735p' backend/services/email_writing_review_service.py | cat -n

echo '== SERVICE: FINALIZATION AND PERSISTENCE =='
sed -n '736,1040p' backend/services/email_writing_review_service.py | cat -n

echo '== CANCELLATION REPAIR DIFF =='
git diff --unified=120 "$previous" "$head" -- \
  backend/services/email_writing_review_service.py \
  backend/tests/test_email_writing_review_service_review_regressions.py

echo '== CANDIDATE VALIDATION IMPLEMENTATION =='
rg -n -A150 -B15 \
  '^def _validate_candidate_selectors_and_evidence\b|^def _validate_candidate_result_binding\b' \
  backend/services/email_writing_candidate_review.py \
  backend/services/email_writing_review_service.py

echo '== JUDGE EXECUTOR AND POLICY ADMISSION =='
rg -n -C10 \
  'def run_judge|asyncio\.to_thread|class .*Judge|def evaluate|def evaluate_policy_admission|evaluation_only|withhold|admit|publish_decision' \
  backend/services/email_writing_judge.py \
  backend/services/email_writing_review_service.py \
  backend/services/email_writing_policy.py \
  backend/policies/email_writing_judge_evaluation_only_v1.json

echo '== EVIDENCE MODEL =='
cat -n backend/db/email_writing_evidence.py

echo '== FOCUSED EXTERNAL-CANCELLATION AND PERSISTENCE TESTS =='
cat -n backend/tests/test_email_writing_review_service_review_regressions.py
for f in \
  backend/tests/test_email_writing_review_service.py \
  backend/tests/test_email_writing_review_service_execution.py
do
  echo "--- $f"
  rg -n -A90 -B10 \
    'CancelledError|cancel\(|block_commit|block_rollback|commit_started|rollback_started|review_evidence_unavailable|run_judge|selector|evidence_id' \
    "$f"
done

echo '== RUNTIME AUTHORITY REFERENCES =='
rg -n -C5 \
  'from services\.email_writing_review_service import|import services\.email_writing_review_service|EmailWritingReviewService\s*\(|send_email|send.*email|publish.*diagnostic|review.*api' \
  backend --glob '*.py' \
  -g '!backend/tests/test_email_writing_review_service.py' \
  -g '!backend/tests/test_email_writing_review_service_execution.py' \
  -g '!backend/tests/test_email_writing_review_service_review_regressions.py' || true

echo '== WORKFLOW AND CURRENT CHECKS =='
cat -n .github/workflows/email-writing-review-service-tdd.yml
gh pr checks 1535 --repo ContextualWisdomLab/naruon --json name,state,link,workflow 2>&1 || true

Length of output: 142056


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== REVIEW ENTRY AND EXTERNAL-CANCELLATION HANDLER =='
sed -n '340,445p' backend/services/email_writing_review_service.py | cat -n

echo '== AUTHORIZED CANDIDATE, JUDGE, AND POLICY FLOW =='
sed -n '444,705p' backend/services/email_writing_review_service.py | cat -n

echo '== TERMINAL FINALIZATION =='
sed -n '730,872p' backend/services/email_writing_review_service.py | cat -n

echo '== EVIDENCE PERSISTENCE AND BOUNDED ROLLBACK =='
sed -n '870,1045p' backend/services/email_writing_review_service.py | cat -n

echo '== VALIDATION HELPERS =='
sed -n '260,325p' backend/services/email_writing_review_service.py | cat -n
ast-grep run --pattern 'def _validate_candidate_selectors_and_evidence($$$):
  $$$' --lang python backend/services/email_writing_candidate_review.py

echo '== CALL-SITE ORDER =='
rg -n -C8 \
  'await self\.candidate_reviewer\.review|_validate_candidate_result_binding|await self\.judge_executor\.run_judge|self\.policy_evaluator|await self\._persist_review|await self\._persist_terminal_review' \
  backend/services/email_writing_review_service.py

echo '== SERVICE-ONLY IMPORTS OR INSTANTIATIONS OUTSIDE TASK-9 TESTS =='
rg -n -C3 \
  'from services\.email_writing_review_service import|import services\.email_writing_review_service|EmailWritingReviewService\s*\(' \
  backend --glob '*.py' \
  -g '!backend/tests/test_email_writing_review_service.py' \
  -g '!backend/tests/test_email_writing_review_service_execution.py' \
  -g '!backend/tests/test_email_writing_review_service_review_regressions.py' || true

echo '== TASK-9 EVIDENCE RECORD CONSTRUCTION =='
rg -n -A130 -B15 \
  'EmailReviewSession\(|WritingDiagnosticRecord\(' \
  backend/services/email_writing_review_service.py

echo '== TASK-9 LEXICAL-FALLBACK INDICATORS =='
rg -n -i -C3 \
  'regex|keyword|phrase|sender.domain|recipient.count|language.name|nearest.text|fallback' \
  backend/services/email_writing_review_service.py \
  backend/services/email_writing_candidate_review.py \
  backend/services/email_writing_judge.py || true

Length of output: 35312


🏁 Script executed:

#!/bin/bash
set -u

echo '== CANDIDATE SELECTOR AND EVIDENCE VALIDATOR =='
rg -n -A150 -B10 \
  '^def _validate_candidate_selectors_and_evidence\b' \
  backend/services/email_writing_candidate_review.py || true

echo '== JUDGE EXECUTOR PORT AND SERVICE INVOCATION =='
sed -n '110,165p' backend/services/email_writing_review_service.py | cat -n
rg -n -C5 'asyncio\.to_thread|run_judge\(' \
  backend/services/email_writing_review_service.py \
  backend/services/email_writing_judge.py || true

echo '== POLICY ADMISSION IMPLEMENTATION AND CHECKED-IN POLICY =='
rg -n -A180 -B10 \
  '^def evaluate_policy_admission\b' \
  backend/services/email_writing_policy.py || true
rg -n -C5 \
  '"status"|"publish_decision"|evaluation_only|withhold|admit' \
  backend/policies/email_writing_judge_evaluation_only_v1.json || true

echo '== REVIEW-SERVICE RUNTIME INTEGRATION OUTSIDE TASK-9 TESTS =='
rg -n -C3 \
  'email_writing_review_service|EmailWritingReviewService' \
  backend --glob '*.py' \
  -g '!backend/tests/test_email_writing_review_service.py' \
  -g '!backend/tests/test_email_writing_review_service_execution.py' \
  -g '!backend/tests/test_email_writing_review_service_review_regressions.py' || true

echo '== SERVICE-SPECIFIC LEXICAL SEMANTIC FALLBACK TERMS =='
rg -n -i -C3 \
  'keyword|regex|phrase.list|sender.domain|recipient.count|language.name|nearest.text|semantic fallback' \
  backend/services/email_writing_review_service.py || true

echo '== CURRENT CHECK STATE =='
gh pr checks 1535 --repo ContextualWisdomLab/naruon --json name,state,link,workflow 2>&1 || true

Length of output: 29051


@seonghobae I reviewed only adb730e68ec7cdfc93c7f0a8fc151382e663c0d8.

I found no new actionable defects.

The external-cancellation repair resolves the prior finding. In EmailWritingReviewService.review(), the CancelledError path applies asyncio.wait_for() to _persist_terminal_review() with the evidence budget. If session.commit() blocks, the deadline cancels the commit. _persist_review() then invokes _rollback_bounded(). The service re-raises CancelledError and returns no response.

The new regression covers this path. It cancels an authorized review, blocks terminal evidence commit, confirms that commit receives cancellation, and confirms one rollback attempt.

The existing timeout-state cleanup remains bounded. _persist_review() calls _rollback_bounded() after commit cancellation or commit failure. _rollback_bounded() applies EMAIL_WRITING_REVIEW_ROLLBACK_SECONDS. The timeout regression still covers blocked commit with both normal and blocked rollback.

The Candidate trust boundary is correct:

  • The service validates Candidate orchestration mode and language.
  • The service calls _validate_candidate_selectors_and_evidence() with the authorized context bundle before Judge execution.
  • The validator rejects empty and out-of-range selectors, overlapping selectors, and unknown evidence IDs.
  • The focused regression confirms that invalid injected Candidate results do not invoke the Judge.

The Judge separation remains correct. The service invokes the injected EmailWritingReviewJudgeExecutorPort.run_judge() for each Candidate diagnostic. The reviewed service has no direct asyncio.to_thread() Judge path.

The policy path remains fail-closed. evaluate_policy_admission() returns withhold unless the policy is both published and has publish_decision == "publish". The checked-in policy is evaluation_only with publish_decision: "withhold". This head cannot emit admitted diagnostics through that policy.

The evidence transaction stores owner scope, revision and projection identities, hashes, selectors, criterion-category receipts, policy/workflow identifiers, and admission state. It does not store raw draft text, replacements, explanations, prompts, or Judge output.

I found no runtime import or instantiation of EmailWritingReviewService outside the service module and the Task-9 tests. The reviewed service does not expose an API, edit a draft, send email, or publish diagnostics. The service also contains no lexical semantic fallback.

The checkout matches the requested commit. The Task-8 predecessor is an ancestor.

The task9-review-service run for this head is still QUEUED. The other same-head run is CANCELLED. Neither is passing evidence.

You are interacting with an AI system.

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

PR governance metadata gate is not ready for c958b9964f925b6dca99e4c2c4db671934511a40:

  • Draft PR: merge automation is paused.

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

Labels

enhancement New feature or request priority: high High-priority or P1 work status: draft Draft pull request type: feature New or expanded product capability

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant