diff --git a/BUILD-LOG.md b/BUILD-LOG.md index 22b54ee58..3b93b90c7 100644 --- a/BUILD-LOG.md +++ b/BUILD-LOG.md @@ -5195,3 +5195,5 @@ Before: managed project/thread verbs had no Console conversation area. After: fi 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. 2026-10-21T21:37:07Z | Della mos-1220 / Vikunja 460 phase B cross-review | Before: independent plans Della 191dff32f and Ludwig 4a27b73ac delivered. After: docs/plans/2026-10-21_email-review-by-della.md reviews all 218 Ludwig plan lines, records ten findings, five grafts, six retained strengths, nine disagreements and eleven merged Jason questions. Ludwig review 143415779 read in full after ludwig-0002 arrived; reconciliation records new mail-access action, no agent reroute and correction of Della dataRoot credential example. Original plan unchanged. Planning only, no product/MVP/queue/live change. + +2026-10-21T21:43:02Z | Della mos-1223 / Vikunja 460 phase C synthesis | Before: independent plans/reviews complete, Mos selected Della base. After: docs/plans/2026-10-21_email-plan.md merges Ludwig grafts and accepted corrections, with 16 post-v1 rows (eight READY, eight WAIT/optional), eleven deduplicated Jason questions, per-row test paths and explicit Q210 non-v1 scheduling. Original Della plan/review preserved; Ludwig originals remain on their branch. Static checks cover graft/row/question/citation anchors and original blobs. No source code, suite, mailbox, secret file, service, MVP or queue change. Candidate goes to Ludwig before Sage landing/queue handoff, copying Mos. diff --git a/docs/SESSIONS.md b/docs/SESSIONS.md index d1a61194d..b9cd58b39 100644 --- a/docs/SESSIONS.md +++ b/docs/SESSIONS.md @@ -1434,3 +1434,4 @@ are never rewritten or removed; corrections are new entries. 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. 2026-10-21T21:37:07Z | Della (GPT-6.1-Sol, mos-1220) | Vikunja 460 phase B email cross-review, della/email-plan | Ludwig plan 4a27b73ac and review 143415779 read in full. Cross-review has ten findings and eleven merged questions; original Della plan unchanged. Canonical queue next della: nothing. Static verification only; review push followed by direct della-0002 handoff to both threads under Q210. Phase C base choice remains with Mos. +2026-10-21T21:43:02Z | Della (GPT-6.1-Sol, mos-1223) | Vikunja 460 phase C merged email plan, della/email-plan | Mos-selected base plus Ludwig grafts; original plans/reviews unchanged. Sixteen post-v1 rows, eight READY and eight WAIT/optional; eleven Jason questions. Canonical queue next della: nothing. Docs-only checks. Commit/push then immediate della-0003 handoff to Ludwig/Mos; Sage landing/queueing follows approval. diff --git a/docs/plans/2026-10-21_email-plan.md b/docs/plans/2026-10-21_email-plan.md new file mode 100644 index 000000000..2c906d04b --- /dev/null +++ b/docs/plans/2026-10-21_email-plan.md @@ -0,0 +1,398 @@ +# Post-v1 email processing and monitoring plan + +**Status:** phase C candidate, mos-1223, Vikunja 460. Ludwig reviews before Sage lands it. +**Goal:** Stack-owned mail intake, monitoring, authorized reading/routing and local drafting for every harness. +**Boundary:** all rows post-v1, none required for 2026-10-27. No PRD 1.10 or live change here. +Q210 ready rows go to non-v1 seats after handoff. No ready label authorizes live connections, +addresses/accounts, DNS/MX, exposure, spend or external mail. Original plans/reviews stay unchanged. +R21: **“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, including identical +tests. MP contains EP plus three commits. Reuse once. 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 all 36 rule-engine and 15 predeployment safety test cases +as synthetic JS cases after dependency/license review. Remove subject logging (`rule_engine.py:147`). 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. Deployment manifests target USC, not proof of a currently running service. Do not enroll an employer +tenant by default or change any existing service. 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:187-195,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. +Graph already selects `body` and normalizes HTML (`connectors/m365.py:27,129`), although `body_text` is empty. +Do not describe the prior connector as body-free. + +**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. S +`packages/discord/src/connector.mjs:1-12` and `journal.mjs:60,71` provide authorize → journal → engine → +deliver and intent/result reconciliation precedents. Reuse principles, not Discord nonce assumptions or its +old approval-button path for mail authorization. + +**F7 Secret-scan evidence:** Ludwig reports a pattern-based scan of both prior trees and full histories with +no candidate credential value found (his plan `4a27b73ac`, `docs/plans/2026-10-21_email-plan-ludwig.md:35`). +Della did not independently repeat that scan. It proves neither absence of secrets nor that rotation is +unnecessary. Any later finding is reported by path and commit only, never by value or partial value. + +## 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 `Mail.Read`, 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 | No build row until Jason names a POP-only need. A future adapter requires stable UIDL, TLS, read-only retrieval and safe identity/reconciliation. No `DELE`. | + +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. The org file holds this human-approved table +through its versioned audited write path. No agent reroutes messages or widens its own route. A route grants +no read right. The first source follows Q1’s provider. Offline adapters need not wait for that choice. Review +maintained exact-pinned IMAP/MIME dependencies in their owning rows, initially Ludwig’s +`imapflow`/`mailparser` candidates. Do not write either protocol/parser in-house. 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 Q198-admitted +work request. Wake requires an approved route, current access grant, sender allowlist and trusted aligned +DMARC authentication. Strip/ignore sender-supplied Authentication-Results. Ambiguous forwarded/list mail or +failed authentication goes to restricted review. + +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 a reviewed mail decision schema inside SM D12’s 4 KiB +request limit (`:503-512`). Initial input is metadata only: opaque eligible route IDs, work/capability/error +enums and bounded numeric state. Sender domain/auth fields or any new mail-derived field need explicit schema +approval and redaction tests; no free-form rule summaries or body. If metadata cannot decide, hold/unknown +rather than invent confidence. Body-based summaries and drafts are separate governed work, not mail-local +routing decisions. 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. Raw quarantine fetch needs no scanner. It never +implies permission to expose extracted content. Read-only files are not execution prevention. +Scanner/extraction is gated E16. 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 `/mail//` 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. Use tested +backup-copy deletion or crypto-erasure with expiry of every relevant key copy/escrow, plus restore +suppression. Encryption or one key deletion alone proves neither. 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 `/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_thread(id,cursor?,limit?)`, `mail_attachment_get(id,part)`, `mail_ack(id,expect)`, +`mail_draft_create(id,text)` and `mail_health(source?)`. Thread views recheck every message and omit +unauthorized members/recipients. No reroute tool. 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. + +Routing is human/system-only. Separate metadata, body, attachment and draft rights per business/source 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 mediated access uses proposed `mail.access`, distinct from provider credential creation through +`credential.mint`. The chief raises it to the authenticated human Approve path, with exact +principal/target/rights/expiry bound once. Add its access grant schema and action routing only through a +Jason-approved PRD version and reviewed vocabulary/role changes. Until then new actions remain hard waits. +Grant creation stays human-only. Read/draft actions become routine only inside approved grants. Manual mode needs +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 +under `/secrets/mosaic-stack/mail///`. Proposed service-principal schema: `{kind: +"mail-service", business, source, provider, mailbox, scopes, slotPath, rotateBy, approvalId, revision}`. +Identity comes from approved configuration, not an agent argument. It grants source reads only, not human +input, grant issuance or general bus writes. Reject wildcard/unknown fields, expired approval, changed source +identity and slots inside repo/dataRoot. This extends existing schemas explicitly (S +`packages/business/src/credentials.mjs:95-102`, `packages/webui/src/sso.mjs:33`). 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. Mail tool results carry `untrusted: true` plus a notice modeled on S +`packages/bus/src/broker.mjs:109`. Sanitize hidden HTML, comments and control-character tricks for display +without treating sanitization as authorization. Test body, subject, display-name, filename, hidden-text and +spoofed-internal-sender injection. Mail-woken work uses a fresh isolated profile, intersected with its +existing grants: mail reads/draft, scoped fleet messages and decision.raise only, no git/credential/ +external-message tools. Broker enforcement matches the tool list. Decisions and onward messages retain +`source: mail`; downstream seats cannot launder mail into authority. A powerful existing thread is never +resumed as the restricted mail turn. + +**D9a Service delivery:** add a protected business-scoped delivery principal/API that accepts only approved +recipients and stable mail receipt keys, with fixed actor `mail`. S `packages/bus/src/principal.mjs:8` has no +mail principal today. Do not impersonate a user/agent or call an unauthenticated general bus write. Journal +delivery acceptance and recipient receipts atomically in the bus through this API, with grant/revoke +checks. The separate local outbox retries the same receipt key after a crash, not a distributed transaction. + +**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//` 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. Permit at most one refresh/retry of a rejected authentication attempt, then +stop and alert to prevent account lockout. Missing grants/config and explicit scope denials get no retry. +Malformed messages quarantine individually. + +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/folder access without bodies: IMAP login/NOOP alone and Graph directory `GET +/users/{id}` do not prove it. The latter needs directory permissions, not Mail.Read ([Microsoft user +API](https://learn.microsoft.com/en-us/graph/api/user-get?view=graph-rest-1.0)). Use minimal scoped mailbox +operations and test denied folders/mailboxes. Retention or provider deletion can lose source mail during +outages; detect/report gaps, never promise losslessness. 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 without approval per session in manager-controlled batches at +most five with 30-second healthy intervals, and current capacity/admission checks (SM `:326-339`). After three +consecutive failed automatic exits, persist `recovery-escalated`; probes continue, work does not. Only the +authorized `system.recovery.release` path with incident/revision and diagnosis clears that latch, never the +kill switch (SM `:341-358`). 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) + +READY means no Jason decision blocks the stated offline deliverable. It does not mean its code prerequisites +have landed. Sage queues it now and assigns an available non-v1 seat when prerequisites/review gates pass, +without a scheduled start or calendar wait. WAIT Qn identifies the positive authority needed before that row's +governed integration. Unanswered questions never block independent READY work. All rows remain disabled by +default, use synthetic mail/fake providers and run named tests plus affected existing suites. No ready row +enables a real source or weakens current grants/hard waits. Q3/Q9/Q10 propose keeping restrictive defaults, +not gates on code that preserves them. Q7 is a technical dependency review, not a new permission gate. Q11 +affects neither build nor pilot because no old repository/service is altered. Queue enrollment/landings belong +to Sage after Ludwig approves this plan, with the v1 path and protected files preserved. + +| Row / gate | Deliverable and dependencies | Acceptance tests (proposed filenames, not run here) | +| --- | --- | --- | +| E01 **post-v1 READY**; Jason: none for draft | PRD addendum draft, source/data/access/service schemas in `docs/prd/`, `packages/mail/schemas/`. Policy activation awaits E02/Q4; content policy awaits E13/Q6. | `packages/mail/tests/schema.test.mjs`: unknown fields, wildcards, secret values, incomplete scopes and mismatched principal/source rejected; disabled schema compatible with v1. Every requirement maps to a row. | +| E02 **post-v1 WAIT Q4** | Protected slot reader, source-service enrollment/credential lifecycle and separate `mail.access` grant action in `packages/{bus,business}/`. After E01 and approved PRD. No real credential enrollment in this build row. | `packages/bus/tests/mail-grants.test.mjs`, `packages/bus/tests/mail-principal.test.mjs`: non-chief/nonhuman approval, modified targets, expiry, duplicate consume, hand grants, failed issuance undo, manual mode, immediate revoke/in-flight fencing and secret sentinel leakage. Reject repo/dataRoot slots; existing grant tests pass. | +| E03 **post-v1 READY**; Jason: none | Durable scoped ledger, encrypted state, leases, cursor/continuation, outbox and quota accounting in `packages/mail/src/{store,intake,service}.mjs`. After E01. No dependency on live grants. | `packages/mail/tests/intake.test.mjs`, `packages/mail/tests/store.test.mjs`: crash every staging/delivery boundary, concurrent owners, missing/reused Message-ID, source-scoped identity collisions, 1,000/100-MiB quotas, disk/audit failure, restart and zero-connection disabled startup. No skips or duplicate recipient effects. | +| E04 **post-v1 READY**; Jason: none, Q7 advisory | Bounded MIME parsing/metadata quarantine in `packages/mail/src/{parse,attachments}.mjs`. After E01; independently of grants. Review license/advisories and exact-pin maintained MIME dependency. | `packages/mail/tests/parse.test.mjs`, `packages/mail/tests/parser-corpus.test.mjs`: multipart, bad charset, missing IDs, S/MIME, 30-MiB input, depth/part/text limits, traversal, hidden HTML, comments, bidi/zero-width display tricks, archive and encrypted attachment quarantine. Fuzz corpus; no expansion/execution/network, no content logs. | +| E05 **post-v1 READY**; Jason: none for fake source | `mail-source` registry/descriptor plus Graph module in `packages/plugins/{src/kinds.mjs,mail-source/m365/}`. After E01/E03/E04; first kind lands with its first module. Q1 selects eventual pilot, not offline work. | `packages/mail/tests/m365.test.mjs`, `packages/mail/tests/source-contract.test.mjs`: fake Graph delta/paging, immutable moves, token/cursor expiry, 401 single refresh, 429/5xx scheduling, restart, denied mailbox, additive-scope check and alias/plus/Bcc provenance. No body-free claim about old code, no source writes. Existing plugin suite passes. | +| E06 **post-v1 READY**; Jason: none, Q7 advisory | IMAP module `packages/plugins/mail-source/imap/`. After E05's shared contract; review/license/exact-pin maintained IMAP dependency. | `packages/mail/tests/imap.test.mjs`: local fake TLS server, certificate denial, selected-folder scope denial, BODY.PEEK/no Seen or STORE/EXPUNGE, IDLE drop/poll fallback, renumbering, UIDVALIDITY/move reconciliation and alias/plus/Bcc/forged-header provenance. Ambiguity holds effects. | +| E07 **post-v1 READY**; Jason: none for pure evaluation | Address/rule evaluator `packages/mail/src/{addresses,rules,route}.mjs`. After E01/E03/E04; no org-file activation or delivery privilege. | `packages/mail/tests/rules.test.mjs`, `packages/mail/tests/routing.test.mjs`: port all 36 prior rule cases; bounded operators, rule version/order, previews never execute, history import window, recipient ambiguity, multiple recipients, retired agents, repeated identical mail and no content logs. | +| E08 **post-v1 WAIT Q4/Q5** | Approved org route writer and narrow service delivery API/principal in `packages/{bus,business}/`, `packages/mail/src/delivery.mjs`. After E02/E03/E07. No agent route writer/reroute. | `packages/bus/tests/mail-delivery.test.mjs`, `packages/bus/tests/mail-routes.test.mjs`: exact human approval and versioned changes, fixed actor/business, recipient receipt idempotency, spoofed principal refused, route addition grants no read, revoke before dispatch fences content, no human-input impersonation. | +| E09 **post-v1 WAIT Q4** | Mediated typed/MCP tools, per-message thread view and local drafts in `packages/{bus,harness}/`, `packages/mail/src/access.mjs`. After E02/E08. | `packages/harness/tests/mail-tools.test.mjs`, `packages/mail/tests/access.test.mjs`: same schema/results/refusals on all supported harnesses, separate metadata/body/attachment/draft rights, page caps, thread filtering, cross-business ID denial, revoke/cache races, manual mode, local ack/draft and absence of send/reroute tools. | +| E10 **post-v1 WAIT Q5** | Restricted mail-triggered launch profile, sender-auth gate and origin propagation in `packages/{mail,harness,bus}/`. After E08/E09 and Q198 admission interface. | `packages/mail/tests/mail-turn.test.mjs`, `packages/mail/tests/injection.test.mjs`: fresh context/grant intersection, no git/credential/external tools at broker and harness, body/subject/display-name/filename injection, forged auth headers, aligned DMARC/allowlist, forwarded ambiguity, no resume of powerful thread, no authority laundering through fleet messages/decisions. | +| E11 **post-v1 WAIT Q4** (mail decision schema approval) | `packages/mail/src/decisions.mjs` using the one approved Q207 decision API; no local model client. After E07/E09 and implemented Q198 decision module. | `packages/mail/tests/decisions.test.mjs`: 4-KiB closed schema, no body/free text, extra metadata denied until approved, out-of-enum/missing/uncalibrated confidence held, central fallback only, unavailable model leaves deterministic intake working; useful metadata-only outcomes or explicit unknown. | +| E12 **post-v1 READY**; Jason: none for synthetic recovery | Health/manager adapter `packages/mail/src/health.mjs`. After E03/E08 and implemented/verified SM D13-D15; fixtures can precede integration. | `packages/mail/tests/health.test.mjs`, `packages/mail/tests/outage.test.mjs`: mailbox/folder scope proof, full 60..600 cooldown and Retry-After, independent probes/one retry owner, bad-auth single retry then stop, no model probes or outage DLQ, synthetic canary, five-item/30-second batches, three-exit latch/release, human kill, alert-once/recovery and source gap reporting. | +| E13 **post-v1 WAIT Q4/Q6** | Authorized CLI/Console mail/draft/digest views and content/export/retention handlers in `packages/{mail,cli,webui}/`. After E09/E12. No live purge; keep `data.delete` hard wait. | `packages/mail/tests/retention.test.mjs`, `packages/cli/tests/mail.test.mjs`, `packages/webui/tests/mail.test.mjs`: per-user ACL on every item/export, separate decision inbox, exact deletion approval/holds/receipts, expiry of backup content and all relevant keys, restore suppression/tombstones, local draft ownership and metadata-only digest. | +| E14 **post-v1 READY**; Jason: none for offline proof | Operator runbook/acceptance packet in `docs/`; offline end-to-end harness test. After E01-E13. | `packages/mail/tests/predeploy-safety.test.mjs`, `packages/mail/tests/end-to-end.test.mjs`: port all 15 safety-gate cases; synthetic intake → route → authorized tools → local draft across supported adapters, zero source connections while disabled, from-now baseline, bounded preview, revoke/outage/restart and disable/rollback with evidence preserved. | +| E15 **post-v1 WAIT Q1/Q4/Q5/Q6**, plus Q2 if personal mail | Explicit separately authorized live pilot: enrolled agent ingress source, approved grant/route/content policy and reviewed re-pin. After E14. No v1 gate or external test mail. | `packages/mail/tests/pilot-reconcile.test.mjs` validates a redacted identity-level acceptance manifest; authorized operator supplies live receipts for delivered/held/missing identities, recipient proof, health, restart/revoke and bounded catch-up. Counts alone prove neither loss nor duplication. Complete on coverage/evidence, not a mandatory week or scheduled wait. | +| E16 **post-v1 OPTIONAL WAIT Q8**, plus Q4 access | Isolated scanner/content extractor only if Jason chooses attachment content support, `packages/mail/src/extract.mjs`. After E04/E09, separate reviewed dependency/resource boundary. | `packages/mail/tests/extract.test.mjs`: no-network/resource isolation, scan failure/unavailable/encrypted/unsupported hold, execution/archive expansion prohibited, hostile extracted text marked untrusted, access/revoke checked before release. No scanner implies no extracted-content feature. | + +Run proposed Node files with `node --test `; owning reviewers select affected existing suites and +require green evidence before commit/landing. Pure store/parser/rule rows can proceed independently of +approval work. Later adapters Gmail API/JMAP/POP3 require a named need and separate rows, not a committed +expansion of this add-on. + +## Questions for Jason (merged list, recommendations are not approvals) + +1. **Q1 Pilot provider/domain/addresses:** which controlled domain, provider and agent ingress mailbox, + aliases or plus-tags? Recommend one approved non-USC agent mailbox, aliases after recipient proof, no + catch-all/MX change. Dedicated mailbox only for approved isolation. Its protocol pilots first; build both + adapters offline meanwhile. + +2. **Q2 Personal mail scope:** which existing user mailbox, if any, may be read? Recommend none initially. + Later enrollment names user/mailbox and exact consent or delegation, separately from agent ingress. + +3. **Q3 Source mutation:** mark read, flag, label, move or save provider drafts? Recommend no, read-only + sources and Stack-local drafts/acknowledgments. + +4. **Q4 Service/access grants:** approve protected source-service credential ownership, distinct + per-agent/user `mail.access` approval and the closed mail decision schema? Recommend yes through reviewed + post-v1 PRD/vocabulary changes, chief/human approval, exact targets/rights/expiry/revoke. New access action + stays a hard wait until the approved PRD explicitly routes it. Provider credential creation remains + distinct. + +5. **Q5 Unattended wake:** which senders/routes may wake granted agents without per-message approval? + Recommend allowlist plus trusted aligned DMARC, approved route, restricted fresh profile and Q198 + admission. Manual mode overrides this. Ambiguous/failing mail quarantines. Neither identity nor email + approves external effects. + +6. **Q6 Content/retention/deletion:** what may be retained, for how long, with what holds? Recommend encrypted + 30-day bodies/drafts, 7-day explicitly fetched attachments, 365-day minimal evidence/tombstones, + metadata-only until approved. Keep exact `data.delete` authorization, backup/key expiry and restore + suppression. + +7. **Q7 Dependencies:** maintained IMAP/MIME packages after license/security review and exact pins rather than + custom clients/parsers? Recommend yes, investigate Ludwig's imapflow/mailparser candidates in owning rows. + Routine technical review unless an existing repository policy adds an approval gate; no calendar wait for + package review. + +8. **Q8 Attachments:** allow bytes/extraction and require scanning? Recommend metadata first, explicit capped + raw fetch into quarantine, no scanner for opaque quarantine. Extracted content waits for isolated + no-network processing, resource limits and a scanner. Do not enable arbitrary execution or automatic + archive expansion. + +9. **Q9 Exposure/spend:** new listener/webhook, DNS/MX, paid infrastructure or model spend? Recommend none + initially. Outbound monitoring/internal alerts suffice. Every later exposure, address/account change or + spend needs its own authorization. + +10. **Q10 Sending:** should this add-on send/forward/reply? Recommend no. R21 remains permanent for the + specialist. A future separate sender needs its own approved plan and exact-recipient human approval + through `message.external`. + +11. **Q11 Prior repositories/service:** is the configured USC deployment still running, and should either + repo/service be archived or retired? Recommend no operational change here. Preserve both histories, use MP + as consolidated design reference. Any archival/retirement is a separate authorized task after actual-state + verification. + +## Verification and handoff + +Sources: Della 191dff32f, Ludwig 4a27b73ac, reviews 9182134e5/143415779, Mos's routing ruling. +Verify citations, coverage, row gates/tests and unchanged original blobs. No product suite, +live mail, secret-file read, install or service change here. After Ludwig approval, send Sage +this pin for refactor landing and READY-row queueing on non-v1 seats, copying Mos. +Resolve review changes without a calendar wait. Jason-gated work waits on its numbered decision.