docs: merge post-v1 email plans and gated build rows

This commit is contained in:
Della committed 2026-10-21 16:43:07 -05:00
1 parent 9182134e59
commit b096a16cce
3 files changed
+401

No files matched your search

+2
View File
@@ -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: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: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.
+1
View File
@@ -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-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: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: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.
+398
View File
@@ -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 `<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. 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 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 `<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](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 <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.
## 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.