Add required human review gate to the Agent pipeline.
Docker Release / build-and-push (push) Successful in 1m10s
Docker Release / release (push) Skipped

Agent web jobs now stop after Brain consolidation and enter needs_review
with a persisted review queue (blocking: high-severity, low-confidence,
sensitive-category findings; audit sample of clean clusters). Humans
decide confirm/reject/unsure/needs_clarification via new review API and
frontend queue; a finalizer applies decisions (rejections suppressed with
reason codes), performs bounded targeted reruns for clarifications,
drafts RFIs only for kept issues, and only then marks the job done and
sends the final email. Two-phase email (review-required, then final
report), per-decision feedback labels with redacted aggregate metrics,
restart recovery from job artifacts, and CLI --no-review bypass.
Classic pipeline unchanged. 65 non-LLM tests.
This commit is contained in:
John Wilganowski
2026-07-28 19:23:57 +00:00
parent ac328d34fd
commit 1c1d2ff21b
27 changed files with 3344 additions and 22 deletions
+106
View File
@@ -0,0 +1,106 @@
"""Tests for the two-phase email flow: review-required notice, then final report."""
import threading
import backend.jobs
from backend.email_sender import send_review_required
class _SyncThread:
"""Drop-in threading.Thread replacement that runs the target inline."""
def __init__(self, target=None, args=(), **kwargs):
self._target = target
self._args = args
def start(self):
self._target(*self._args)
def test_review_required_email_skips_without_smtp(monkeypatch):
monkeypatch.setattr("backend.email_sender._smtp_ready", lambda: False)
assert send_review_required("user@example.com", {"source": "set.pdf", "summary": {}}, "http://localhost:8099/?job=abc") is False
def test_review_required_email_sends_with_smtp(monkeypatch):
"""With SMTP ready, the message goes out with recipient, review URL, and
the required-item count (0 when the report has no review summary)."""
sent = []
monkeypatch.setattr("backend.email_sender._smtp_ready", lambda: True)
monkeypatch.setattr("backend.email_sender._send",
lambda msg: sent.append(msg) or True)
review_url = "http://localhost:8099/?job=abc"
report = {"source": "set.pdf", "summary": {"review": {"required": 3}}}
assert send_review_required("user@example.com", report, review_url) is True
assert len(sent) == 1
msg = sent[0]
assert msg["To"] == "user@example.com"
assert "review" in msg["Subject"].lower()
body = msg.get_content()
assert "review" in body.lower()
assert review_url in body
assert "3" in body
# Missing review summary -> required count defaults to 0.
sent.clear()
assert send_review_required("user@example.com", {"source": "set.pdf", "summary": {}}, review_url) is True
assert "0" in sent[0].get_content()
def test_agent_needs_review_sends_review_email_not_report(monkeypatch, tmp_path):
"""An agent job entering needs_review emails the review-required notice
exactly once and never sends the final conflict report."""
review_emails = []
report_emails = []
monkeypatch.setattr("backend.config.OUTPUT_DIR", str(tmp_path))
monkeypatch.setattr("backend.jobs.run_agent_pipeline", lambda pdf_path, **kw: {
"summary": {"agent_status": "needs_review"}, "conflicts": [],
})
monkeypatch.setattr("backend.jobs.send_review_required",
lambda *a, **kw: review_emails.append((a, kw)))
monkeypatch.setattr("backend.jobs.send_conflict_report",
lambda *a, **kw: report_emails.append((a, kw)))
monkeypatch.setattr(threading, "Thread", _SyncThread)
pdf = tmp_path / "upload.pdf"
pdf.write_bytes(b"%PDF-1.4 dummy")
job_id = backend.jobs.create_job(str(pdf), source_filename="set.pdf",
email="arch@example.com", pipeline_mode="agent")
try:
job = backend.jobs.get_job(job_id)
assert job["status"] == "needs_review"
assert len(review_emails) == 1
args, _ = review_emails[0]
assert args[0] == "arch@example.com"
assert f"/?job={job_id}" in args[2]
assert report_emails == []
finally:
backend.jobs._jobs.pop(job_id, None)
def test_classic_job_sends_only_conflict_report(monkeypatch, tmp_path):
"""Classic pipeline is untouched: only the final report email fires."""
review_emails = []
report_emails = []
monkeypatch.setattr("backend.config.OUTPUT_DIR", str(tmp_path))
monkeypatch.setattr("backend.jobs.run_pipeline", lambda pdf_path, **kw: {
"summary": {}, "conflicts": [],
})
monkeypatch.setattr("backend.jobs.send_review_required",
lambda *a, **kw: review_emails.append((a, kw)))
monkeypatch.setattr("backend.jobs.send_conflict_report",
lambda *a, **kw: report_emails.append((a, kw)))
monkeypatch.setattr(threading, "Thread", _SyncThread)
pdf = tmp_path / "upload.pdf"
pdf.write_bytes(b"%PDF-1.4 dummy")
job_id = backend.jobs.create_job(str(pdf), source_filename="set.pdf",
email="arch@example.com", pipeline_mode="classic")
try:
assert backend.jobs.get_job(job_id)["status"] == "done"
assert len(report_emails) == 1
assert review_emails == []
finally:
backend.jobs._jobs.pop(job_id, None)