42 KiB
Post-v1 email processing and monitoring plan
Status: Q217/Q222 revision, mos-1303, 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/Q220 ready rows proceed on 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.
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. Per-source mailboxScope defaults to [] (none), including
agent ingress. A human sets exact mailbox IDs through the audited CLI and later Console; no agent session
may change this setting, even to narrow it. Recheck scope before scan/fetch and fence removed mailboxes
before returning in-flight content. Effective reads intersect source scope, provider rights and current
principal grants. Enrollment never grants agent body access. No personal mailbox is enrolled under Q222.
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). Immutable IDs survive moves within a mailbox, not archive-mailbox moves or reimports (Microsoft ID documentation). Mailbox-scoped Application RBAC and unscoped Entra grants are independent (Microsoft RBAC documentation). IMAP identity, read-only selection and PEEK semantics follow RFC 9051. 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. Q217 selects one agent mailbox on woltje.com at InMotion, created by Jason; its offered
protocol pilots first (IMAP expected, not yet verified). Exact address/slot enrollment awaits Jason. 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. Mail-owned limits/caps/scopes are settings with these defaults, not hardcoded policy; tuning cannot override the Q198 manager contract. Human-only audited CLI, then Console, changes tune settings within resource bounds set by implementers and reviewers in the versioned schema, backed by resource-exhaustion tests in E01/E04; widening bounds needs a reviewed schema/test change. Agents cannot change protective settings; invalid settings fail closed. No gate bypass.
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. Human-tunable settings (D4) default to parserInputBytes: 30 MiB,
mimeDepth: 10, mimeParts: 100, normalizedTextBytes: 256 KiB. E04 tests changed values and bound refusals.
Decode safely, flag malformed/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. unattendedWake defaults false; a human may enable it per route and set its empty-by-default sender
allowlist. When enabled, wake requires an approved route, current access grant 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; human-tunable attachmentPartBytes and attachmentMessageBytes each default to 10 MiB (D4 bounds).
E04 tests changed caps and out-of-bounds refusal. 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 <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. Content keys live at <configDir>/secrets/mosaic-stack/mail-content/<business>/<key-id>,
outside dataRoot and its backup set, separate from provider slots, with 0600/no-symlink checks.
Protected key expiry/retirement metadata also stays outside the mail-backup restore domain. 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.
Content settings default to persistContent: false, bodies/drafts 30 days, explicitly fetched attachments
7 days and minimal metadata/tombstones 365 days. A human may set these through audited CLI, later Console,
within approved retention authority and holds. Until that authority exists, persist metadata only and show
that later content fetch requires the source online. A duration setting never authorizes deletion.
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 <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_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 post-v1 PRD version and reviewed vocabulary/role changes (REQ-WAIT-2/3). Until then hard waits apply.
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 <configDir>/secrets/mosaic-stack/mail/<business>/<source>/. 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/<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. 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). 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-Q7 are settings/review work, not unanswered Jason choices. PRD/grant/deletion gates still apply; Q9/Q10 retain external-action boundaries. Q7 dependency review adds no 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 and external content-key location/lifecycle schemas in docs/prd/, packages/mail/schemas/. Core mail-source registration/policy activation awaits approved addendum/Q4; retention authority awaits E13; settings use Q6 defaults. |
packages/mail/tests/schema.test.mjs: unknown fields, wildcards, secret values, incomplete scopes and mismatched principal/source rejected; key location excludes dataRoot/backups, mode/symlink checks, default mailbox scope empty, 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. Add human-only per-source mailbox-scope CLI writer; Console parity follows in E13. |
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. Default none makes zero reads; human mailbox-scope change audited; agent change (direct/tool/proxied) refused, revoked scope fences in-flight reads, widening creates no agent grant. 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. Missing/expired key fails closed; restored backup with expired key returns unavailable, never plaintext. 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 offline; core registration WAIT Q4 | Graph descriptor/module in packages/plugins/mail-source/m365/ against test-local registry packages/mail/tests/fixtures/mail-source-registry.mjs. After E01/E03/E04. packages/plugins/src/kinds.mjs stays unchanged until E01 addendum approval/Q4; core entry lands together with or after that approved addendum, with its first production module. Q1 selects 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 source writes; test-local registry works with unchanged core registry before approval. Existing plugin suite passes. |
| E06 post-v1 READY; Jason: none, Q7 advisory | IMAP module packages/plugins/mail-source/imap/. After E05's test-local shared contract; no core registration before approved E01/Q4. 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 governance | 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, wake off/empty allowlist by default, human-only wake/route edits, 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 READY offline; governed activation after E08/E09 | 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: disabled wake never launches; enabled human-configured allowlist still requires DMARC/grants/admission; 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 governance/deletion authority | 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: human-only Console mailbox/settings parity with CLI, default metadata-only and 30/7/365 settings, refused agent edits, 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 Jason mailbox provisioning, Q4 governance and live-pilot authorization | 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 <filename>; 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.
Decisions and settings (stable Q1-Q11 identifiers)
Q217/Q222 settle Q1/Q2; mos-1303 separates settings from external decisions. Schema names are proposals. Changes are validated/revisioned/audited with human identity; agent proxies cannot pose as human writers. Configuration never replaces grants, PRD or external authority.
- Q1 CLOSED, Q217: Jason creates/names one agent mailbox on woltje.com at InMotion and fills its protected slot. No agent account creation/catch-all/MX change; aliases after delivery proof. Verify protocol before pilot.
- Q2 CLOSED, Q222: no personal mailbox now. Per-source
mailboxScope: []defaults none; exact IDs are human CLI/Console settings (D1/E02/E13). Later personal enrollment needs user consent/delegation and grants.
Group (a): Q3-Q7 settings and ordinary technical review
| Item | Setting and default | Who may change it; remaining boundary |
|---|---|---|
| Q3 source mutation | sourceMutations: []; sources read-only, drafts/ack local |
Human-only configuration within reviewed adapter capabilities. No mutation capability is implemented in this increment. Enabling provider writes needs a reviewed extension and exact provider authority; this setting cannot enable sending or override R21. |
| Q4 service/access | sourceEnabled: false, mailboxScope: [], access grants empty, accessMode: manual |
Authenticated human changes source settings; chief raises exact per-principal grants and human approves through existing approval boundary. mail.access, service principal, mail-source kind and closed decision schema still require approved PRD/vocabulary extensions before integration. Routing mail.access out of hard waits requires a PRD version Jason approves under REQ-WAIT-2/3: an existing gate, not a new Q number. Q211 is not assumed to waive it. No self-grant or source credential shared with agents. |
| Q5 unattended wake | unattendedWake: false, wakeSenderAllowlist: [], route table empty |
Human-only CLI/Console writer. When enabled, require allowlist AND trusted aligned DMARC, approved route/current grant, restricted profile and Q198 admission. Manual mode prevails; ambiguous/failing mail holds. No per-message Jason question inside this approved envelope. |
| Q6 retention | persistContent: false, bodyDraftDays: 30, attachmentDays: 7, metadataDays: 365, automaticPrune: false |
Human-only writer, respecting holds and approved retention authority. Metadata-only default; encrypted content only within approved authority. Settings never grant data.delete; exact deletion approval or explicitly approved retention authority, backup/key expiry and restore suppression remain required. |
| Q7 dependencies | sourceAdapter unset until human enrollment; reviewed exact-pinned IMAP/MIME modules, initially imapflow/mailparser candidates |
Humans select enrolled adapters via CLI/Console. Implementers/reviewers choose and pin maintained modules under repository license/security checks in E04/E06; not a Jason product question. Paid dependencies or external services go to group (b). |
Group (b): real Jason decisions, only when needed
No new external question is needed for default offline work. Existing Q4 approval is still required; Mos asks one at a time if a later increment requires:
- A new provider account/license, paid dependency/service, new spending, or outside-party permission beyond Q217's Jason-created mailbox. Recommend use the existing InMotion service and free reviewed dependencies.
- New exposure/listener/webhook/DNS/MX/addresses/accounts: recommend none. Q217 creation is Jason’s existing task.
Q4 Jason PRD approval/grants and Q6 deletion gates persist. No live access is authorized; offline work proceeds.
- Q8 Attachments: metadata first; capped raw quarantine fetch. Extraction remains Jason’s choice (E16), with isolation/scanning, no execution/archive expansion. Group (b) is not the full list of Jason gates.
- Q9 Exposure/spend: none initially; later changes need their own authorization as group (b) describes.
- Q10 Sending: remains out of scope. Recommend no sending/forwarding/replying; R21 remains permanent
for the specialist. Any future separate sender needs its own plan and exact-recipient human approval
through
message.external; it is not a configurable bypass of this add-on's boundary. - Q11 Prior repositories/service: preserve histories/USC service; status unverified. Retirement is separate.
Verification and handoff
Sources: Della 191dff32f, Ludwig 4a27b73ac, reviews 9182134e5/143415779, Mos's routing ruling.
Verify coverage, row gates/tests and unchanged originals. No product suite, live mail, secret read or service change.
After Ludwig approval, send Sage this revision for landing/row-gate updates on non-v1 seats, copying Mos.
Q220 authorizes progress without calendar waits, not live access/gate bypass. Q217/Q222 applied; originals unchanged.