Skip to main content

Email-Reactor fail-closed fix — classifier failure / partner mail no longer auto-archived (MC #103815)

Incident / Root Cause

~/system/daemons/email-agent.js was FAIL-OPEN. When Ollama classification failed (request timeout, JSON parse error, or no-JSON-match), ollamaClassify resolved to {category:'INFO', priority:'low'}. The auto-archive block then archived any info/spam/own row. The strategic-partner elevation block only ran when dbCategory === 'ACTION', so a misclassified partner email was never elevated.

Net effect: A revenue email from strategic partner Asmir Merdžanović ("QODY" project, email #9661, 2026-06-13) was silently auto-archived and never answered until he re-sent it 2026-06-17.

Fix (FAIL-CLOSED) — 3 Changes

  1. All three ollamaClassify failure branches now resolve {category:'ACTION', priority:'medium', _classifyFailed:true} with distinct reason (timeout/parse_error/no_json) — an unclassifiable email defaults to actionable, never FYI/archive.
  2. matchStrategicPartner() now runs independent of category (guard if (!ARGS.dryRun)); on a partner match it elevates to ACTION via emailInbox.updateClassification(...,'ACTION','high'), sets partner_tier, fires CEO push.
  3. Auto-archive is guarded by _skipArchiveDueToClassifyFail and partner-elevated rows (cat patched to 'action') never reach the archive branch.

New helper: updateClassification(id, classification, priority) added + exported in ~/system/tools/email-inbox.js.

Verification

  • node --check clean on both files
  • Simulation harness /tmp/evidence-103815/sim.test.js = 39 PASS / 0 FAIL incl. the exact Asmir/QODY regression
  • Independent verification: native verifier (7/7 atomic claims) + Proveo P2P PASS (mesh-thr-95c7fb0b / mesh-msg-008f947c)

Deployment

Daemon com.john.email-agent is StartInterval (spawns fresh node each cycle) → fix is live on the next cycle, no restart needed.

Residual Known Gap (Follow-on MC #103819)#103819 — RESOLVED (verified 2026-08-08)

TwoThe two heuristic INFO fallbacks OUTSIDE ollamaClassify (circuit-now carry _classifyFailed:true:

  • Circuit-breaker-open path, daemons/email-agent.js:2526: {category:'INFO', priority:'low', summary:'Ollama circuit breaker pathopen ~L2161 andfallback promise-to INFO', _classifyFailed:true}
  • Promise-rejection catch ~L2177) do not yet carrypath, _classifyFaileddaemons/email-agent.js:2543;: narrow{category:'INFO', exposurepriority:'low', summary:'Classification failed — fallback to INFO', _classifyFailed:true}

Both were actually implemented same-day as this page's parent fix, in commit 63142eb8cfa152436924f3a0a610e2c4095d0908 (non-partner2026-06-17 email16:00 during+0200, OllamaJohn TCPAI) error /the breaker-task's original line refs (~2161/~2177) were already stale by the time it was picked up again, since the file grew past 3000 lines in the interim. MC #103819 sat open only because the record was never closed with evidence; re-verified 2026-08-08 via grep, node --check (exit 0), and git log -S confirming no heuristicregression keywordsince. match)Evidence: ~/system/evidence/103819/verify-2026-08-08.md.

Lesson

Email triage must FAIL-CLOSED — an email the classifier could not process must never be silently archived; strategic-partner safety net must be category-independent.


Evidence bundle: /tmp/evidence-103815/
MC task: #103815
Date: 2026-06-17