docs: plan post-v1 Stack email processing and monitoring

This commit is contained in:
Della committed 2026-10-21 16:12:23 -05:00
1 parent c3d88840c5
commit 191dff32fb
3 files changed
+346

No files matched your search

+4
View File
@@ -5189,3 +5189,7 @@ Before: managed project/thread verbs had no Console conversation area. After: fi
2026-10-20 19:34:28 UTC | Gander row 196 build paused at design mismatch | Before: approved design promised Unix tester cleanup only for the owned inode. After: scratch test shows Node/libuv close unlinks a replacement inode before JS close handlers. Evidence agents/gander/work/queue-196/evidence/tester-cleanup-blocker.txt. Sent Sage/Duckworth gander-0016/0017, recommending explicit same-uid residual with private 0700 parent retained. Interpreter, concurrent peer, principal, human, peer-transport, transport, tester and process single files passed. Partial code uncommitted, tester transport not wired. No suites, mutant batches or live actions.
2026-10-21 18:45 UTC | Gander row 196 preservation before rebase | Sage-2051, Mos order mos-1193, explicitly authorizes commit and fast-forward push of the current build before deferred full verification. Product and evidence preserved together; prior focused checks and stopped gates are recorded in the row196 packet, with no claim of full acceptance. Next: rebase onto a36523713 on new gander/row196-r2, report range-diff/patch-id/conflicts, then await Sage's clear for M1-M29 and all requested suites. No live action, force push, branch deletion or tests during hold.
2026-10-21T21:11:44Z | Della mos-1213 / Vikunja 460 planning phase | Before: PRD 1.10 has no email feature; MP 95b0298 and EP 7a64921 are read-only prior inputs. Scope: independent post-v1 email plan on della/email-plan, no implementation/live access/MVP or queue changes. Gitea issue enrollment belongs to Mos/Sage because this seat has no API credential.
2026-10-21T21:12:18Z | Della mos-1213 / Vikunja 460 planning verification | After: docs/plans/2026-10-21_email-plan-della.md is 341 lines, with 13 post-v1 rows and six Jason questions. Verified 42 pinned citation anchors, ten requested coverage areas, identical prior src trees, append-only records, clean whitespace and repository queue guard. No configured CI pipeline or CI-queue wrapper found in the current foundation; documentation-only validation applies, no product suites or live tests run. Phase B/C and Gitea issue/queue enrollment remain with Mos/Sage. Only della/email-plan is authorized for push.
+1
View File
@@ -1432,3 +1432,4 @@ are never rewritten or removed; corrections are new entries.
2026-10-21T19:46:12Z | Dewey (Opus 5.5, 84fb346c) | row 222 r1 static review (megavolt-0095), c9aa9c2ee, manifest c2121b55 | APPROVE, comment 30138 on #1723, queue rev 2966; rulings: createObject=rename accepted, 5 s human timeout deferred with STATUS codes in the follow-up, credential.helper reset argv-only accepted; notes N1-N5; no tests run
2026-10-21T19:49:37Z | Sage (Opus 5.5, 1ef1e4f8) | row 250 landing, row 196 r5 grant (mos-1198) and resume (mos-1200), F27 row 253 | 250 LANDED 1b6158ce2 (patch-id 29786caa, Filbert rev 2957, Gizmoduck slot green), #1755 closed c30139, Vikunja 450; 196 r5 91e3696ef hunk checked by Duckworth, webui 304/0; my queue add tripped Gander's busy check, not a stop (mos-1200), resume on 05e940117; row 253 #1758 brief 0cdead66c
2026-10-21T20:46:03Z | Sage (Opus 5.5, 1ef1e4f8) | row 222 landing, PRD 1.9 and 1.10, Pocket ID design row 255, row 254 brief, 196 r6 grants | 222 LANDED 0b33f5e9d (megavolt-0098 gate, Dewey rev 2966, patch-id edb60b4f), #1723 closed c30147, done rev 2972; follow-up row 254 (#1759, after 196); PRD 1.9 f48725cfc (REQ-WEB-2, REQ-DEC-3 amended, mos-1202); 458 brief 7d08237e1 (#1760), row 255 rev 2976; PRD 1.10 add0f1aae REQ-INST-2 post-v1 (mos-1204, tasks 97/0, bus req 3/0); Vikunja 458 c478, 416 c479; queue-commit 21def2850 revs 2968-2976; 196 r6 gate gander-0066 2334/0, 38/38 killed; 244 granted sage-2147; 196 r6 resume granted sage-2145 after self-tee busy refusal (mos-1205). Miss: landed 222 before checking 196's manifest; 196 carried results voided. Class rule now (mos-1204, replaces the mos-1200 interface grep): before any landing push, compare every in-review and in-progress row's manifest against the landing's paths.
2026-10-21T21:12:18Z | Della (GPT-6.1-Sol, mos-1213) | Vikunja 460 independent post-v1 email plan, della/email-plan from c3d88840c | 341-line proposal, 13 post-v1 rows, six Jason questions, 42 pinned citation anchors verified. Canonical queue next della: nothing. Planning only, no live mail/service/MVP/queue change; phase B/C with Mos, no Ludwig plan read. Commit and branch push authorized by dispatch.
+341
View File
@@ -0,0 +1,341 @@
# Post-v1 email processing and monitoring plan (Della)
**Status:** proposal for Mos phase A, Vikunja 460, mos-1213. Planning only.
**Goal:** receive mail for named agents and users through Stack-owned connections,
then expose authorized reading, routing and drafting equally to every harness.
**Boundary:** every build row below is **post-v1**. None is required for the
2026-10-27 MVP. This document changes neither PRD 1.10 nor the live Stack.
No service, mailbox connection, live-mail read, account, DNS change or spend is authorized.
R21 binds the specialist: **“The email specialist reads and drafts, never sends.”**
## Evidence and reuse
Citations are pinned, not claims about deployed behavior. Prefixes below identify
the repository and commit for every `file:line` citation. Both prior clones were
inspected read-only. No prior application or test suite was executed.
| Prefix | Pinned evidence |
| --- | --- |
| S | Stack `c3d88840c5b6ffa62d2a6a4aa4385d388234d777`, this worktree |
| MP | `mail-processor` `95b02989f4d4276dded00c90ffd74ebb7d03ae8b` |
| EP | `email-processor` `7a6492190cd376058e29a04fd0593e86bae1a8e5` |
| SM | `origin/huey/system-manager-design` at `2ee68cd69337bb933a8fabeb04bcd71835170171`, Q198 r6, superseding r5 `fd44f7c60` |
**F1 Existing mail code:** recursive comparison found identical `src/` trees in MP
and EP. Reuse once, not as two running products. Both implement normalized messages
and a connector boundary (`src/email_processor/connectors/base.py:8,30`), M365
pagination/rate-limit handling (`connectors/m365.py:81,138`, under that same source
directory), ordered AND/OR rules (`src/email_processor/rule_engine.py:81,124`), and
bounded preview/cold-start safety (`src/email_processor/poller.py:101,122,152`).
Port these concepts and selected synthetic tests into Stack-native modules after
dependency/license review. Neither copy proves IMAP or POP3 support: connector
selection handles only M365 (`src/email_processor/poller.py:48`).
**F2 Prior intent:** EP `docs/PRD.md:25,39` describes a single-organization M365
application, not the Stack add-on. MP `docs/PRD.md:26,66,204,235` specifies generic
Graph/IMAP/POP3 drivers, separated intake/execution, fail-closed activation and
atomic staging/checkpoints. Those are useful design requirements, not implemented
features. Adopt the safety properties and conformance cases, not its independent
tenant/profile product, bootstrap authority or mandatory standalone web setup.
**F3 Drop unsafe coupling:** in both repos, `src/email_processor/poller.py:186,200,301`
checks for duplicates before effects and advances a timestamp to wall-clock now.
`src/email_processor/models.py:266` has indexes, not a unique mailbox/message key.
Replace these with durable provider cursors, unique identities and transactional
delivery. Drop connector caches keyed just by mailbox name (`poller.py:48`), direct
GLPI/ntfy effects (`action_executor.py:30,69,159`) and source category writes
(`connectors/m365.py:170`, `poller.py:359`). Drop direct model calls and content logs
(`classifiers/llm.py:79,105,109,123`), replacing decisions through Q207.
Do not import deployment manifests, credential settings, existing accounts or data.
**F4 Stack enforcement:** S `packages/harness/src/tools.mjs:1` already maps typed
tools through the runner socket with broker checks. Its MCP bridge carries no
capability (`packages/harness/src/mcp-server.mjs:1,34`). Reuse that path for all
harnesses, rather than giving each one mailbox credentials or a monitoring plugin.
S `packages/bus/src/broker.mjs:690,701,1601,1666` supplies human-approved grant
consumption and action authority. `packages/bus/src/credentials.mjs:142,358,379`
supplies scoped references and protected use. Mail kinds and grants do **not** exist:
S `packages/business/src/vocabulary.mjs:109,119` accepts Gitea, Vikunja and host kinds.
Mail requires reviewed extensions, not relabelling mail credentials as an existing kind.
**F5 Product constraints:** S `docs/prd/mosaic-stack.md:432,454,477,496` defines
chief-raised grants, exact human approval, one-time consumption and immediate local
revocation. Its `:507,542,582,607` covers decision evidence, thread binding and hard
waits. PRD “inbox” is the decision inbox, not email. Mail needs a separately approved
post-v1 PRD addendum. S `packages/plugins/src/kinds.mjs:9,32` has no mail-source kind.
Follow REQ-PLUG-1/2 (S PRD `:876,910`) for provider adapters.
**F6 Monitoring precedent:** S `tools/agent-watch/jarvis-email-watch-liveness.sh:8,26,53`
checks timer state, failed execution and staleness. Reuse the distinction between
alive, failed and unknown, not its legacy unit names or fleet wiring. SM
`docs/plans/2026-10-21_q198-system-manager.md:262,291,304,326` supplies protected mode,
independent probes and controlled resume. It is a design dependency, not shipped proof.
## Architecture and sources
**D1 One owner per instance.** Add an optional supervised `packages/mail/` service,
with a single leased owner per configured source. It owns connection pools, OAuth
refresh, scheduling, cursors and intake. A harness can stop, restart or change without
losing monitoring. Multiple accounts are isolated by instance/business/source ID.
“One service” does not mean one global credential, mailbox or shared authorization.
Default installation is disabled and opens no connection.
Use Node modules aligned with the Stack, adapting the Python design rather than
embedding both applications and their separate databases/authentication/UI. A Python
sidecar would reuse more syntax but duplicate operational boundaries. Harness-native
connectors would repeat connection state and fail Jason's cross-harness requirement.
**D2 Source contract.** Propose a reviewed `mail-source` plugin kind, list-selected,
with `descriptor()`, `scan({source,cursor,limits,signal})`,
`fetch({source,item,part,limits,signal})` and `health({source,signal})` exports.
`scan` returns bounded observations, continuation, terminal cursor and identity epoch.
Adapters receive protected credential-use callbacks, never agent-provided secret paths.
Core owns grants, leases, persistence, rule evaluation and audit. No adapter sends,
deletes, labels, moves or marks messages read. Use the existing channel kind only
where its dispatch contract fits presentation, not as a substitute polling interface.
| Source | Proposed implementation and boundary |
| --- | --- |
| Microsoft 365 | Graph read-only access, per-folder delta/continuations and immutable message IDs. Replace timestamp polling. Restrict application rights to named mailboxes and verify effective scope, including any additive Entra permissions. |
| IMAP | TLS with certificate verification, OAuth where supported, otherwise a separately approved service/app credential. Read-only selection and `BODY.PEEK`; UIDVALIDITY plus UID, never sequence number. IDLE is a wake-up hint with periodic reconciliation. |
| Other providers | Prefer a conforming IMAP source initially. Gmail API and JMAP are later adapters only when a named need justifies them. No new provider account is implied. |
| POP3 | Deferred optional row. Require stable UIDL, TLS and read-only retrieval. No `DELE`. Reject automatic activation without safe identity/reconciliation. |
Graph delta is folder scoped and cursors are opaque ([Microsoft delta documentation](https://learn.microsoft.com/en-us/graph/delta-query-messages)).
Immutable IDs survive moves within a mailbox, not archive-mailbox moves or reimports
([Microsoft ID documentation](https://learn.microsoft.com/en-us/graph/outlook-immutable-id)).
Mailbox-scoped Application RBAC and unscoped Entra grants are independent
([Microsoft RBAC documentation](https://learn.microsoft.com/en-us/exchange/permissions-exo/application-rbac)).
IMAP identity, read-only selection and PEEK semantics follow [RFC 9051](https://www.rfc-editor.org/rfc/rfc9051.html).
These are protocol references checked 2026-10-21, not evidence of installed support.
**D3 Addressing.** Version a route table mapping trusted delivery evidence to a stable
Stack user or role-instance ID, never a display name or a newly invented role.
Aliases on an approved ingress mailbox are the recommended inexpensive start.
Plus-addresses are acceptable only after fixtures and a later authorized provider test
prove the exact recipient survives delivery. They are not a security boundary.
Dedicated mailboxes provide stronger provider-side separation for confidential users
or agents, at the cost of account management and possible licenses.
Bind routes to source identity plus provider-controlled original-recipient evidence.
`To`/`Cc`, forwarded headers and arbitrary `Delivered-To` headers are insufficient
unless the enrolled delivery path establishes their provenance. Bcc or ambiguous
alias delivery goes to restricted operator review, never guessed routing. Unknown
addresses do not create agents. Multiple recipients produce independently authorized
deliveries without revealing other recipients' mail. Jason approves domains, names,
aliases, mailboxes and each provider-side change before provisioning. No SMTP/MX
server, catch-all domain or public webhook is proposed for the initial increment.
## Processing, data and retention
**D4 Intake pipeline:** approved source → bounded discovery → bounded parse → durable
observation/dedup → versioned deterministic rules → authorized delivery outbox →
Stack notification/work request. Provider cursor advancement and durable observations
commit together under the source lease/generation. Outbox delivery has a stable key
and a recipient-side unique receipt, so a crash after delivery but before acknowledgment
does not create a second task/message. Never promise exactly-once external effects.
Default candidates: poll every 60 seconds, at most 50 observations and 10 MiB fetched
per slice, fair scheduling between sources. Persist continuation when the slice fills.
Cap each source at 1,000 pending deliveries and 100 MiB of retained content initially.
Persist quota accounting across restarts and slices. Reaching a quota holds new intake
and raises an alert, while read-only health checks continue.
First activation establishes a from-now baseline without actions on old mail. Historical
import is a separate approved time/count window, previewed before activation. Cap
violations, disk-full and unavailable audit storage hold work without advancing past
unstaged mail. These limits are proposed settings to validate, not measured capacities.
Provider identity is scoped to business/source/account: Graph immutable ID, IMAP
folder/UIDVALIDITY/UID or POP3 UIDL. RFC Message-ID, thread ID and content similarity
are secondary correlation signals, never sole proof of uniqueness. Preserve distinct
messages with missing or reused Message-ID. An IMAP move, epoch reset, expired delta
cursor, archive move or reimport enters bounded reconciliation. Record aliases only
with sufficient identity evidence. Otherwise hold possible duplicate actions for review.
An edit is a new observation version, not automatic permission to rerun prior actions.
Preserve dedup tombstones beyond content expiry. Replay needs a new authorized intent,
parent link and current grants. A rule edit never retroactively executes preview work.
**D5 Parsing and rules.** Preserve provenance for normalized sender, recipients,
timestamp, subject, text, thread references and attachment descriptors. Bound MIME
depth to 10, parts to 100 and normalized text to 256 KiB. Decode safely, flag malformed
or truncated content and quarantine failures. Prefer text, sanitize HTML for display,
disable remote images/URLs and never execute active content. Rules use finite allowlisted
fields/operators, stable priority and first-match by default. Reject executable rules
and unbounded regex. Record rule/version and routing reason. Unmatched mail is held
for its authorized owner, not dropped or copied fleet-wide. Routing a user notification
does not launch an agent. Agent wake-up is a separate admission-controlled work request.
Optional classification and any mail-triggered decision go through the Stack's one
decision module under DECISIONS Q207. The mail service neither selects nor falls back
to a model locally. Core policy still determines permitted actions and human hard waits.
Use minimal, explicitly permitted content and an allowlisted result schema. A model
result is advisory data, never a grant. Missing model service leaves deterministic
intake working and model-dependent work held. Summaries/drafts run as ordinary governed
agent work, not an extra mail-service model client.
**D6 Attachments.** Initially enumerate metadata only. An authorized explicit fetch
quarantines bytes under an opaque ID, with a 10 MiB per-part and per-message total cap.
Never trust filenames as paths, unpack archives, execute macros or follow embedded
links. Content extraction requires isolated no-network processing, resource bounds,
scanner success and a separate read right. Failed, encrypted or unsupported files stay
quarantined. A clean scan does not make attachment instructions trustworthy.
**D7 Storage.** Proposed `<dataRoot>/mail/<business>/` contains encrypted content blobs,
SQLite observation/outbox state, encrypted cursors, local drafts and append-only event evidence.
No mail content enters git or images. Service-only directories are 0700, files 0600.
Use scoped keyed fingerprints for private dedup fields. Separate content keys from
provider slots. Keep tokens, delta URLs and authentication errors out of ordinary logs.
Default durable records hold opaque IDs, state, versions and counts, not bodies,
subjects, recipient lists or attachment names. Content access is explicit and audited.
Recommended content policy for an enabled pilot: encrypted bodies and local drafts
for 30 days, explicitly fetched attachments for 7 days, minimal metadata/tombstones for
365 days. Without Jason's approved content/retention policy, persist metadata only and
show that later content fetch requires the source online. Expired content returns an
explicit unavailable result. Define backup/key expiry and restore suppression together:
restoring an old backup must not resurrect expired content or redeliver old work.
Local pruning needs the Stack's `data.delete` approval path or an explicitly approved
future retention authority. Do not silently grant automatic deletion. Write receipts,
preserve legal holds and do not rewrite `<dataRoot>/runs/` or existing audit evidence.
## Access and security
**D8 Identical access contract.** Proposed tools are `mail_list(source?,cursor?,limit)`,
`mail_get(id,view)`, `mail_attachment_get(id,part)`, `mail_route(id,target,expect)`,
`mail_ack(id,expect)`, `mail_draft_create(id,text)` and `mail_health(source?)`.
All map to typed broker verbs via the existing tool socket/MCP bridge. Local CLI and
authenticated Console use the same handlers. List is paginated (default 20, max 100).
`ack` changes Stack-local state only. Drafts are local unsent records, not Graph drafts.
Health returns only authorized source metadata. No arbitrary URL, shell, source secret
path, provider query language or send tool is accepted.
Separate rights for metadata, body, attachment, draft and routing per business,
source, address binding and named principal. Recheck current role/grant on every read,
notification and action, including caches and derived summaries. Revoke immediately
fences new uses, invalidates fetch handles and cancels owned in-flight reads before
returning content. Agents receive only mail explicitly routed to their grant scope.
User mail requires that user's or explicitly delegated access. An email specialist is
not implicitly allowed to read every user's mail. Cross-business IDs return no data.
New mail access grants follow the existing chief → `credential.mint` → authenticated
human Approve chain, with exact target, rights and expiry bound once. Add the mail
grant method and action routing only through a Jason-approved PRD version and reviewed
vocabulary/role changes. Until then new actions remain hard waits. Within-role mail
access can become routine only inside those approved grants. Manual-mode actions still
need per-use approval. A hand-edited grant or approving email creates no authority.
**D9 Credentials and hostile input.** Distinguish a source service principal's
provider connection grant from an agent's mediated mail-access grant. Recommend
dedicated read-only ingress credentials per source/business, held by protected Stack
code and never shared with agents. This system-service ownership is a new approved
extension, not an exception silently taken to REQ-CRED-1. Configuration names only
an approved absolute slot path, for example
`<dataRoot>/credentials/mail/<business>/<source>/oauth`; no value or partial value is
read by a planner, returned through tools or embedded in a plan. Missing/expired/revoked
slots fail closed. Validate owned non-symlink files, rotation and revocation behavior.
Treat mail, headers, filenames, OCR and quoted replies as untrusted external data.
Sender addresses, even authenticated messages, cannot approve decisions, change policy,
mint grants or impersonate the CLI/Console human. Delimit model input and strip tool
authority, but rely on broker denial, sandboxing and least privilege for enforcement.
An email asking an agent to fetch secrets, forward documents or run commands must not
gain those rights. External action proposals use existing approval paths and provenance.
No automatic link fetches. Provider endpoints are enrolled allowlists with TLS and
redirect checks, not values selected from mail content. Logs use reason codes and IDs.
**D10 Sending.** Out of scope: SMTP submission, Graph send, forwarding, auto-replies,
DSNs generated by this service and emailed digests. Block them in adapter interfaces,
credentials and network policy. R21 stays intact even if a future separate sender role
is approved. Human export of an unsent draft is not authorization for Stack delivery.
## Monitoring and system-manager integration
**D11 Health:** report desired/actual connection state, last successful scan and durable
cursor commit, lag, backlog, lease owner, retry-after, credential status, quarantine,
outbox age and disk pressure. Never report “healthy” merely because the process lives.
Candidate stale threshold: three missed scan intervals. Alert once per incident, on
severity change and recovery through existing internal notifications/Console, not mail.
Show alert delivery failure locally. Alerts include IDs and reason codes, no content.
Register dependency scopes `mail-source/<business>/<source>` with Q198's manager.
Network/provider outages pause only affected intake and dependent fetch/dispatch.
Independent sources and authorized stored-mail reads can continue. Disk/audit failure
halts new intake/effects. Auth/config denial stays held for correction, never retries
under another credential. Malformed messages quarantine individually, not as outages.
Apply Q205–Q207 through protected mode, not the human kill switch: mechanical source
probes run independently at 60, 120, 180 seconds, increasing by 60 to a 600-second
ceiling, indefinitely, while respecting longer Retry-After. No JEV/model availability
gate and no model call for a mail health check. Mail health proves TLS/auth and scoped
source access without fetching message bodies. One lease owns retries, so adapter and
manager loops cannot multiply them. Disabled sources make no probes.
On recovery reconcile cursors/uncertain delivery receipts, pass one synthetic pipeline
canary, then resume eligible work in manager-controlled batches without approval per
session. Never send test mail or use a live message as a synthetic canary. Uncertain
external effects stay fenced. Human kill remains human-only to clear. These mail
extensions to SM D13–D15 need contract review and tests before operational reliance.
## Ordered build rows (all post-v1, not current queue edits)
Each row is a separately reviewable deliverable. Write failing synthetic cases first,
implement only that row, then run its named tests and affected existing suites. Commands
below name proposed tests. They are not tests run during this planning assignment.
Shared touch points include `packages/{bus,business,harness,plugins,cli,webui}/`.
No rollout uses an implementation candidate until normal review and re-pin gates pass.
| Row | Deliverable / files | Acceptance evidence |
| --- | --- | --- |
| E01 **post-v1** | PRD addendum and source/data schemas, `docs/prd/`, `packages/mail/schemas/`. Resolve Q1–Q6 before activating corresponding behavior. | Schema fixtures reject unknown fields, missing scopes and secret values. Disabled add-on makes zero connections and v1 startup works without it. Obtain Jason's policy approval, not implementation's implied consent. |
| E02 **post-v1** | Protected source slots and mail-access grants, `packages/bus/`, `packages/business/`. Build D8/D9 on existing grant executor. Depends E01. | `node --test packages/bus/tests/mail-grants.test.mjs`: deny non-chief raises, nonhuman approvals, altered targets, duplicate consumption, hand grants and revoked/in-flight access. Secret sentinel absent from all outputs. Existing grant/approval suites pass. |
| E03 **post-v1** | Durable intake ledger, leases, outbox and disabled supervisor, `packages/mail/src/{store,intake,service}.mjs`. Depends E02. | `node --test packages/mail/tests/intake.test.mjs`: crash at each transaction/outbox boundary, concurrent owners, disk-full, cap exhaustion and restart. No skipped cursor or duplicate recipient effect, no source calls while disabled. |
| E04 **post-v1** | Bounded MIME parsing and quarantine, `packages/mail/src/{parse,attachments}.mjs`. Depends E03. | `node --test packages/mail/tests/parse.test.mjs`: malformed MIME, oversized/deep parts, traversal filenames, remote HTML, archives and encrypted attachments. Limits enforced before expansion, zero execution/network, explicit truncation/quarantine. |
| E05 **post-v1** | M365 driver and first `mail-source` kind, `packages/plugins/src/kinds.mjs`, `packages/plugins/mail-source/m365/`. Depends E03/E04. | `node --test packages/mail/tests/m365.test.mjs`: fake Graph pagination/delta, immutable ID moves, cursor expiry, token expiry, 429, restarts and scoped-denial cases. Read-only request allowlist, no skipped/repeated delivery. Existing plugin tests pass. |
| E06 **post-v1** | IMAP driver, `packages/plugins/mail-source/imap/`. Depends E05 contract. | `node --test packages/mail/tests/imap.test.mjs`: fake TLS/IMAP server, certificate denial, IDLE disconnect, sequence renumbering, UIDVALIDITY reset, folder moves and missed wake-ups. No Seen/STORE/EXPUNGE, uncertain identities hold effects. |
| E07 **post-v1** | Address table, bounded rules and delivery, `packages/mail/src/{addresses,rules,route}.mjs`. Depends E03–E06. | `node --test packages/mail/tests/routing.test.mjs`: aliases, plus tags, Bcc, forged recipient headers, retired agents, multiple recipients, missing/reused Message-ID and duplicate provider pages. No cross-principal disclosure, no duplicate work, no authority from sender. |
| E08 **post-v1** | Broker verbs, MCP/typed tools and local drafts, `packages/bus/`, `packages/harness/`, `packages/mail/src/access.mjs`. Depends E02/E07. | `node --test packages/harness/tests/mail-tools.test.mjs`: identical schema/results/refusals through every supported harness adapter, read/attachment/draft rights separated, manual mode and expiry enforced, send impossible. Existing MCP/tools/approval suites pass. |
| E09 **post-v1** | Decision-module integration, `packages/mail/src/decisions.mjs`, central decision interface. Depends E08 and approved Q198/Q207 interface. | `node --test packages/mail/tests/decisions.test.mjs`: model outage leaves rules active, one central fallback path, schema-invalid suggestions held, no mail-local model client, hostile mail cannot approve/grant/spend/send. Minimal-context privacy and budget checks pass. |
| E10 **post-v1** | Health, alerts and protected-mode recovery, `packages/mail/src/health.mjs`, Q198 manager adapter. Depends E03/E08 and implemented/verified SM D13–D15. | `node --test packages/mail/tests/outage.test.mjs`: fake clock/provider verifies outage scope, full cooldown sequence, Retry-After, parallel probes, no model probe, no outage DLQ, restart continuity, canary/batched resume, uncertain delivery fencing and human kill persistence. |
| E11 **post-v1** | Authorized Console/CLI mail views, retention/export/prune/restore, `packages/{mail,cli,webui}/`. Depends E08/E10 and Q4 authority. | Focused `mail-retention` and `mail-ui` tests prove ACLs on lists/search/export, expired-content denial, exact delete approval, holds, backup-expiry restore suppression and no old-work replay. Decision inbox remains distinct, alerts survive mail outage. |
| E12 **post-v1** | Opt-in operator runbook and acceptance packet under `docs/`. Depends E01–E11. | Offline end-to-end synthetic message → parse → route → authorized tools → local draft, under every supported harness. Demonstrate revoke, outage recovery and disable/rollback preserving evidence. Live pilot is a separate Jason-authorized step after named address/account/scope/retention approvals. No external mail sent. |
| E13 **post-v1 optional** | POP3 adapter, `packages/plugins/mail-source/pop3/`, only for an approved named need. Depends E12. | Fake server tests UIDL absence/reuse, restart and TLS failure, retrieval caps, reconciliation and zero DELE. Unsupported identity refuses activation. Other adapters remain unaffected. |
## Questions for Jason (recommendations, not assumed approvals)
1. **Q1 Source and address pilot:** which provider, domain and existing or new mailbox
should receive agent/user mail? **Recommend** one separately approved read-only
ingress mailbox, aliases for named agent/user recipients, no catch-all or MX change.
Use dedicated mailboxes for confidential separation. Prove preserved delivery
identity before enabling alias/plus routing. New addresses/accounts wait for Jason.
2. **Q2 Grant ownership:** approve a protected source service principal plus separately
scoped per-agent/user mail-access grants? **Recommend yes**, through a post-v1 PRD
amendment and existing chief/human approval model. No agent gets provider secrets
or implicit access to personal mail. Enable only the explicitly requested sources.
3. **Q3 Automation:** may rules route mail and wake a granted agent without per-message
human approval? **Recommend yes** for reviewed within-role routes, subject to manager
admission and manual mode. Reading/drafting does not authorize requested external
effects. Any model-needed decision uses the one Q207 module.
4. **Q4 Content and deletion:** approve the 30-day body/draft, 7-day attachment and
365-day minimal-evidence policy, including encrypted backups and deletion authority?
**Recommend these pilot limits**, metadata-only until approved, with an exact
`data.delete` approval binding for pruning. Do not weaken the hard-waits list.
Jason must identify any business retention/hold requirements before live intake.
5. **Q5 Exposure and costs:** allow provider webhooks or paid mailbox infrastructure?
**Recommend no initially**. Use outbound Graph polling/IMAP, existing internal
notifications and no new exposure. Each future webhook, DNS/MX change or spend
gets its own explicit authorization.
6. **Q6 Sending:** should any future role send email? **Recommend keep sending out
of this add-on**. Retain R21 permanently for the specialist. Any later sender is a
separate design with exact-recipient approval, scoped credentials and effect evidence,
not a toggle that silently turns drafts into delivery.
## Planning receipt and handoff
Verified pinned commits, source-tree equality, cited code boundaries and ten requested
coverage areas. No implementation, dependency installation, legacy test execution,
mail connection or live-stack change occurred. Canonical `queue next della` returned
`nothing` after the linked-worktree refusal. No queue/MVP row changed. Vikunja 460 is
the supplied tracking task. Mos/Sage owns any required Gitea issue and future queue
enrollment because this seat has no Gitea API credential. Phase B review and phase C
synthesis remain with Mos. Della has not read Ludwig's plan. This branch push delivers
the planning artifact only, not approval to build or deploy it.