docs: merge post-v1 email plans and gated build rows
This commit is contained in:
1 parent
9182134e59
commit
b096a16cce
3 files changed
+401
No files matched your search
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
Reference in new issue
Block a user