diff --git a/00-META/glossary.md b/00-META/glossary.md index 1636123d..b6ca7e64 100644 --- a/00-META/glossary.md +++ b/00-META/glossary.md @@ -772,6 +772,54 @@ it was told, and the servarr apps' own libraries. **Upstream of** nothing. **Dec - **grab** — an engine handing one media release to a download client. It names the work it is for, so the media-importer knows where the copy belongs. +## Mail — the operator's own correspondence, which the mesh holds and reads for them + +**Purpose.** To hold the operator's mail accounts, say each mail on the bus, find and read mail for whoever the +operator granted, note each mail for the operator and tell them of the mail that matters, and send a mail only on +the operator's Approve. **Recorded by** `mesh-mail` (its index database and attachment bucket, ADR 0359 §3; its +account buckets, ADR 0439) and `mesh-mail-reader` (its database of mail entries, mail groups and reading lessons, +ADR 0459). **Downstream of** Identity and access and Operator and conversation; **upstream of** nothing. +**Decided by** ADR 0359, 0361, 0378, 0439, 0440, 0458, 0459, 0460, 0461, 0462. +**Uses:** operator, person, module, seat; grant, identity (Identity and access); ask, warrant, operator channel, +content rule, untrusted input (Operator and conversation). A *judgement* is one call of the model with no tools +and no session (ADR 0278), in its plain sense. + +- **mail account** — one of the operator's mailboxes that `mesh-mail` holds, of a kind of adapter (`imap`, + `imap-oauth`, `graph`), named by its address (ADR 0359 §1). +- **`mail-store`** — the seat holding the mail accounts: `accounts`, `search`, `read`, `send`, `send_status` (ADR + 0378 §2, ADR 0440 §7). Always the seat's name: the bare *store* is Core's. +- **mail event** — what `mesh-mail` publishes on its stream `MAIL` for a new, changed or removed mail: headers, + flags and attachment references, never a body (ADR 0359 §2). +- **mail send** — a mail the mesh sends, which goes out only on the operator's warrant for that one mail (ADR 0361 + §2), with the states `mail_send_status` answers (ADR 0440 §7). Only a caller granted asking to send for that + account may ask for one (ADR 0460). +- **mail web client** — the module `mesh-mail-web`, holder of `mail-webclient`: the operator's mail in a browser on + the private network (ADR 0440 §1); also called *the mail page*. +- **mail notice** — `mesh-mail-notifier`'s desktop pop-up for a new inbox mail (ADR 0378 §7, §9). It retires when + `mesh-mail-notifier` is unassigned everywhere (ADR 0461 §6) and then moves to *mail signal*'s *Not:* line + (issue 1418). +- **mail reading** — the model's reading of each new mail as it arrives, in batches, into its mail group and mail + entry; the seat `mail-reading`, held by `mesh-mail-reader` (ADR 0459). Not *triage* (the issue tracker's + judgement), and not `mail_read`, `mail-store`'s tool that fetches one mail's text. +- **mail entry** — what mail reading keeps about one mail: its mail group, a one-line summary, its mark and why, a + due date; never the body (ADR 0459 §4). Not *catalogue entry*: the catalogue is Delivery's. +- **mark** — what a mail entry says the mail is to the operator: *matters*, *scam warning* or neither, never two + (ADR 0459 §7). Not *kind* (Media's). +- **mail group** — a set of mails the model put together because they belong to one subject. Not *delivery + group* or *account group*. +- **reading lesson** — one correction the operator made on the mail page (a mark or a mail group), kept so later + mail reading follows it (ADR 0459 §8 to §10). Always qualified: the bare *lesson* is Operator and conversation's. +- **mail signal** — the notice on the operator's channel, one `tell` per batch in words only, and the place at the + top of the mail page, that tells the operator mail matters (ADR 0461). Always qualified: the bare *signal* is + Health and repair's. +- **scam warning** — the mark of a mail the model judges an attempt to deceive the operator, with why; shown in + its own place on the mail page and never a mail signal (ADR 0459 §7, ADR 0461 §5). +- **mail conversation** — a thread (ADR 0383) with a mail context, followed on the mail page: the mail questions + and their answers, each answer with the mails it found. The mail context is the framing that a mail is data, the + tools `mail_search` and `mail_read` only, the operator's senders and folders, and the reading lessons (ADR 0462). +- **mail question** — what the operator writes in the mail conversation to find or understand mail. Not *ask* + (Operator and conversation's request for the operator's input). + ## The record — how this repository works Not a domain of the mesh but of the way it is built, listed because its words meet the mesh's. @@ -877,6 +925,7 @@ renaming one sense, the old sense moves to a *Not:* line and becomes mechanical. | store | the one database server | **store** | Core | | | the OCI registry | **artifact store** | Delivery | | | a node's own copy of its last declaration | **last declaration** | Placement | +| | the seat holding the mail accounts | **`mail-store`** | Mail | | record | a numbered decision | **decision record** | The record | | | the literal reader of this repository, searched first for a symptom | **the record** (the `records` module) | Operator and conversation | | | the upgrade policy that sends nowhere | `record` | Module | @@ -903,6 +952,12 @@ renaming one sense, the old sense moves to a *Not:* line and becomes mechanical. | | a file the mesh writes, such as the agent's instruction file or `managed_settings` | the file's own name, or *a file the mesh places* | — | | trusted | a config value that decides what runs or who can reach what | **trusted** config value | Config | | | a sender on the operator's own list | **trusted** sender, its message an **operator message** | Operator and conversation | +| lesson | an agent's proposal after the operator corrects its way of working | **lesson** | Operator and conversation | +| | the operator's correction of mail reading | **reading lesson** | Mail | +| signal | something that must keep happening | **signal** | Health and repair | +| | the notice that mail matters | **mail signal** | Mail | +| conversation | the domain *Operator and conversation* | the domain's name | Operator and conversation | +| | the mail questions and answers on the mail page | **mail conversation** | Mail | ## How this page is kept diff --git a/03-DESIGN/01-to-be/49-the-mesh-in-domains.md b/03-DESIGN/01-to-be/49-the-mesh-in-domains.md index f798c39a..18e6207e 100644 --- a/03-DESIGN/01-to-be/49-the-mesh-in-domains.md +++ b/03-DESIGN/01-to-be/49-the-mesh-in-domains.md @@ -2,21 +2,23 @@ layer: to-be status: in-progress code: [hq 00-META/checks/words.py, mesh-catalog merge-check.sh] -updated: 2026-10-18 +updated: 2026-10-20 decisions: - "ADR 0429" - "ADR 0244" - "ADR 0006" - "ADR 0008" - "ADR 0302" + - "ADR 0354" + - "ADR 0458" --- # 49 — The mesh in domains -**Every concept of the mesh belongs to one of eleven domains, which owns its one word. The glossary is +**Every concept of the mesh belongs to one of thirteen domains, which owns its one word. The glossary is organised by them and is the authority; a word it retires is named in the entry that replaced it, with where it is retired; and two checks hold the documents and the tools' descriptions to it.** -(ADR 0244; the eleventh, Media, ADR 0302.) +(ADR 0244; the eleventh, Media, ADR 0302; the twelfth, Config, ADR 0354; the thirteenth, Mail, ADR 0458.) A **domain** is an area of the mesh that owns a set of concepts: inside it each concept has one word, and the domain decides what that word means. The glossary @@ -38,6 +40,8 @@ shape — what each is for, where its records live, and how they depend on one a | **Connectivity** | to make every node and module reachable by name where it should be, and unreachable where it should not | the controller | | **Operator and conversation** | to let the mesh and its operator tell, ask and answer, and act only on an answer it can trust | the router, and the controller for authorising asks | | **Media** | to find, fetch and file copies of the works a person watches, listens to and reads, each in the library its kind names | the `media-importer` module's settings and memory, and the servarr apps' own libraries | +| **Config** | to hold, check and resolve every value a module reads that someone may change, and say where each resolved value came from | the `mesh-config` module, in its buckets on the bus, with its store as the history behind them | +| **Mail** | to hold the operator's mail accounts, say each mail on the bus, note each mail for the operator and tell them of the mail that matters, and send a mail only on the operator's Approve | `mesh-mail` (its index database, attachment bucket and account buckets) and `mesh-mail-reader` (its database of mail entries, mail groups and reading lessons) | Beside them, **the record** is this repository's own vocabulary — research, decisions, designs, issues, playbooks, checks — listed because its words meet the mesh's. @@ -71,6 +75,11 @@ a change of meaning upstream ripples down, never up. Arrows point downstream. Media stands beside the diagram, below Module alone: its words are about what a person watches, not about the mesh's machinery, and no other domain reads them. +Mail stands below the diagram's foot: it is downstream of Identity and access (the operator grants each mail +account to a caller) and of Operator and conversation (a mail send is an ask, a mail signal is a notice through +the operator channel), and upstream of nothing. It is the operator's own correspondence, which the mesh holds and +reads for them, not the mesh talking about itself, so it is not part of Operator and conversation (ADR 0458). + Delivery sits low because it reads health and data; in time it runs first, and asks Placement to send. An authorising answer acts in whichever domain asked, through that domain's own verb: the conversation owns the asking, never the act. diff --git a/03-DESIGN/01-to-be/59-the-mail-web-client.md b/03-DESIGN/01-to-be/59-the-mail-web-client.md index e53fc903..529940d9 100644 --- a/03-DESIGN/01-to-be/59-the-mail-web-client.md +++ b/03-DESIGN/01-to-be/59-the-mail-web-client.md @@ -2,7 +2,7 @@ layer: to-be status: in-progress code: [mesh-catalog] -updated: 2026-10-19 +updated: 2026-10-20 decisions: - "ADR 0440" - "ADR 0378" @@ -14,6 +14,12 @@ decisions: - "ADR 0439" - "ADR 0201" - "ADR 0436" + - "ADR 0458" + - "ADR 0459" + - "ADR 0460" + - "ADR 0461" + - "ADR 0462" + - "ADR 0278" --- # 59 — The mail web client @@ -22,12 +28,23 @@ decisions: `mesh-mail-web`, holds the seat `mail-webclient`; it renders pages on the server in Go and reaches mail only through `mail-store`'s verbs, under a caller the operator grants account by account. Its route reaches the private network only, under its internal name, and the operator signs in with Keycloak. A mail written there goes out only on the -operator's Approve, as every mail the mesh sends does. Once it works on the laptop and the workstation, Betterbird is removed.** +operator's Approve, as every mail the mesh sends does.** (ADR 0440, building on ADR 0378 §5 and ADR 0379 §2.) +**Then the model reads the mail for the operator.** A module of its own, `mesh-mail-reader`, holds the seat +`mail-reading`: it reads each new mail in batches, keeps a mail entry for it (its mail group, one line, its mark), +tells the operator on Telegram of the mail that matters in a mail signal, shows a scam warning on the page only, and +learns from the operator's corrections as reading lessons; it never changes the mailbox and keeps no mail text. +Reading and asking to send are granted apart first, and the pop-up for every new mail retires once mail signals +work. Later, once the mesh's threads exist, the operator asks questions about their mail in a mail conversation, a +thread with a mail context. Betterbird is removed once mail reading works on the laptop and the workstation. +(Research 066; ADR 0458 to 0462. The words are the glossary's Mail domain.) + **Acceptance.** In a browser on the laptop and on the workstation, the operator signs in, browses, searches and reads the mail of every account `mesh-mail` holds, opens an attachment, and writes a mail that goes out on their Approve on Telegram; -the same page is not reachable from a machine outside the private network. +the same page is not reachable from a machine outside the private network. A new mail that matters reaches the +operator's phone as one mail signal and stands at the top of the mail page in its mail group; a mail that does not +matter shows nothing anywhere. ## 1. The parts @@ -70,7 +87,7 @@ outside. Two things hold it: Measured on 2026-10-19: the laptop and the workstation hold the mesh's authority (`mesh-internal-ca.crt`) as a trust anchor, and Firefox on both reads the system's trust through p11-kit, so the page opens there without a warning. Away from the private network (the laptop travelling without its tunnel) the page is unreachable; Betterbird stays the offline client -until it is removed (§6). +until it is removed (milestone 4, §11). ## 3. Signing in (ADR 0440 §3) @@ -102,16 +119,103 @@ account through the page and get around `grants`. - **The seat's verbs** (ADR 0378 §5): `open` answers the page's address, and with an account and a mail that mail's page; `status` answers whether it serves and which accounts it is granted. -## 5. What the first version leaves out +## 5. Reading and asking to send, granted apart (ADR 0460) -Each is an issue with an owner (ADR 0440 §8): +`mesh-mail`'s trusted setting `grants` gives a caller, per account, **reading** (that account's `MAIL` events, +`mail_accounts`, `mail_search`, `mail_read`) and **asking to send** (`mail_send`, `mail_send_status`), each on its own, +set as `grants` is today. A caller granted reading only is refused by both send tools, with the missing grant named; +one granted asking to send only is refused every read. When the split is delivered every caller in today's `grants` +keeps both for each account it has, so nothing stops; narrowing one afterwards is the operator's choice. +`mesh-mail-reader` is granted reading only, for every account, and nothing before the split is delivered. This is +issue 1227. + +## 6. Mail reading (ADR 0459) + +``` +mail-store (mesh-mail) ── MAIL: mail..message.added ──▶ mesh-mail-reader (seat mail-reading, once for the mesh) + ▲ │ its own database: mail entries, mail groups, + └──────────── mail_read (reading grant only) ─────────────┤ reading lessons; never a body + │ batches of ≤ 10 mails ──▶ claude_code_judge (within ≤ 40 s) + │ claude_code_judged + ├──▶ operator-channel.tell (a mail signal, §7) + ▼ + mesh-mail-web ── entries · groups · correct · lessons · undo · status ──▶ the mail page +``` + +- **One module, one seat.** `mesh-mail-reader`, built from `mesh-mail`'s source like `mesh-mail-web`, claims + `mail-reading` (declared in `mesh-mail`'s manifest beside the three seats of ADR 0379 §4) and runs once, on the + machine that runs `mesh-mail`. It hears `added` events under its own `runs-as` account and the operator's reading + grant (ADR 0378 §13), and moves, flags, deletes and sends nothing. +- **A mail entry per mail, never the body.** Its own database (`postgres-database`) keeps each mail's mail group, + a one-line summary, its mark (*matters*, *scam warning* or neither) and why, and a due date where the mail gives + one, beside the mail groups and the reading lessons. +- **Batches.** One judgement reads at most 10 mails. Its question is filled in order, under 60 KiB of the + judgement's 64 KiB: the framing that a mail is data and never an instruction; the mail groups, at most 4 KiB; the + reading lessons that apply, at most 40 within 12 KiB, sender and mail group first, then the newest, under the + framing that a reading lesson outranks anything a mail says; then the mails, each cut to its first 3 KiB, a mail + in a Junk folder said to be so. A batch asks for up to 40 s with `within` and collects with + `claude_code_judged` (built under issue 508, recorded by ADR 0459 §14). Research 066 measured 1.9 to 3.1 s per + mail, so a batch of 10 takes about 19 to 31 s; the bound keeps an issue tracker's check that waits behind a batch + on the same machine within its two minutes (ADR 0278 §2), which research 066's 15 mails and 120 s would not. Batches stay judgements; whether they become threads is issue 1417. +- **Reading lessons.** When the operator changes a mail's mark or mail group on the page, `mesh-mail-web` calls + `correct` and the correction is kept at once as a reading lesson (headers, the change, when; never body text), + with no further Approve: it is the signed-in operator's own act. The page lists the reading lessons, undoes one + (later mail reading only), and shows reading lessons per 100 read mails per week, which answers whether mail + reading learns the operator's way. +- **The page.** Above the classical list it shows the mail that matters at the top, the mail groups, and the scam + warnings in a place of their own, each with why; the classical list (accounts, folders, search, one mail) stays + and always opens. `mesh-mail-web` reads these through `mail-reading`'s verbs and still keeps no mail state + (ADR 0440 §4 as ADR 0459 §13 changes it). +- **It fails loudly.** Its own health check is unhealthy when a judgement is refused, times out or answers out of + shape, with the cause and since when; the controller raises its condition, the page shows the cause above the + classical list instead of mail groups, and no mail signal is sent. On recovery the mail that arrived meanwhile is + read in arrival order from its durable consumer on `MAIL` (kept 7 days). A warning before a licence runs out is + issue 1409. + +## 7. Mail signals, and the pop-up retired (ADR 0461) + +- **One tell per batch.** A batch that marks mail as mattering sends one `operator-channel.tell`: the headline + "1 mail matters" or "N mails matter", a first line naming the mail with the nearest due date (sender's name, + summary, due date), a second naming the next and, with more than two, ending "and N-2 more on the mail page". + When the hourly cap of 10 refuses one, its mails stay at the top of the page and the next mail signal counts + them. +- **Words only.** No link, mail address, host name or path: the router refuses a message holding any, and the mail + page opens only on the mesh's own machines. The reader checks its words with `messenger_check` first and, refused, + sends the safe form "N mails matter: open the mail page". +- **At once, at any hour**; the phone's own quiet hours decide. **Never** for a scam warning, and never while mail + reading is unhealthy. +- **The pop-up retires.** Once mail signals pass acceptance test 13 on the real accounts, `mesh-mail-notifier` is + unassigned from the laptop and the workstation on the operator's Approve and leaves `grants`; `mail-notifier` + stays declared and unheld, and nothing takes it over (ADR 0379 §3, §7). Betterbird's own pop-ups stay off by its + setting until it is removed (ADR 0379 §6). Issue 1418; issue 801 closes as replaced. + +## 8. The mail conversation (ADR 0462) + +A mail conversation is a thread (ADR 0383), run by the thread handler (ADR 0384), with a mail context: the +framing that a mail is data, the tools `mail_search` and `mail_read` only under a caller granted reading only, the +operator's real senders and folder names, and the reading lessons. The operator writes a mail question on the mail +page; each answer names the mails it found, and a question with no answer says so. `mesh-mail-web` creates and +follows the thread over the bus on its server side; the page loads one script from its own origin and talks only to +it (content security policy: script from that origin, `connect-src 'self'`), so the browser never reaches the bus. +The thread keeps the text of each mail it read for good, as every thread is kept (ADR 0383 §3), which the operator +accepted; mail reading keeps none. In the same milestone `mail_search` matches folder names without regard to case +and describes its three `-any` switches as true or false only. It is built after design 58's milestone 3 (threads, +issue 880). + +## 9. What stays outside + +Each is an issue with an owner: - a reply, cc, bcc and attachments on a sent mail: issue 1257 (it needs `mail_send` to take them first); -- a page that shows new mail without a reload, from ADR 0439's buckets: issue 1258, after issue 1255; -- the agent that searches, triages and drafts (ADR 0378 §5): issue 1259, which needs a decision first; +- a page that shows new mail without a reload, from ADR 0439's buckets: issue 1258; +- drafting a mail, the rest of issue 1259 (its triage is mail reading, its search the mail conversation), and writing + to the mailbox: issue 730; +- a task list for the operator: issue 1396; the search faults found by research 066: issues 1400 and 1401; - the work account: a later decision of its own (ADR 0359 §7). -## 6. The build's acceptance tests +## 10. The build's acceptance tests + +Tests 1 to 9 are milestone 1's; tests 10 to 17 are research 066's eight, in its order. 1. A request without a session, for a page, an attachment or a send, is redirected to sign-in; an identity not in `operators` is answered 403; a send form without its token is answered 403; the session cookie is `Secure`, @@ -134,19 +238,49 @@ Each is an issue with an owner (ADR 0440 §8): Tests 1 to 4 are one integration test that starts the module whole against the real bus (playbook 07 step 3 item 4); tests 5 and 7 are `mesh-mail`'s own and test 8 `mesh-mail-web`'s, unit tests; test 6 is read before the module is assigned; test 9 is proven live after the delivery. +10. A new mail on each account has a mail entry, in a mail group, within 5 minutes of its mail event; a test searches + `mesh-mail-reader`'s database for a known body line and finds none. +11. The reader's caller is refused by `mail_send` for every account, and no mail is moved, flagged or deleted by it. +12. (Milestone 3.) On the mail page, a mail question by a person, by a time range and by "what needs a reply" finds + its target mails, research 066's three failed kinds included; a question with no answer says so; the conversation + is a thread readable through the thread seat; and the browser makes no request but to the mail page's own origin. +13. A batch with one mail that matters sends exactly one `tell` naming it, with no URL, mail address or host name in + its words, and the mail is at the top of the page; a subject carrying a host name still gives a mail signal. +14. Research 066's 20 steering mails, read again, mark no injected words as mattering, and every phishing decoy that + is marked is a scam warning, which sends no `tell`. +15. A reading lesson given on a real mail changes how the next mail of that sender is noted; undone, it no longer + does; the page lists it. +16. With the judgement refused (the reader's licence binding removed in the integration test), the condition is + raised with its cause, the page shows it over the classical list, no `tell` is sent, and after recovery the mail + that arrived meanwhile is read in arrival order. +17. With `mesh-mail-notifier` unassigned, a new mail that does not matter shows nothing on the laptop or the + workstation, and one that matters gives one mail signal. -## 7. Milestones +Tests 11, 13 (with a stand-in router), 16 and the grant split's refusals are one integration test that starts +`mesh-mail` and `mesh-mail-reader` whole against the real bus and store; the batch's budget, the seat's verbs and the +mail signal's words are unit tests; tests 10, 14, 15 and 17 are proven live after milestone 2's delivery; test 12 +with milestone 3. + + +## 11. Milestones | # | Outcome, in one sentence | Proves | Repositories and their parts | Depends on | |---|---|---|---|---| | 1 | In a browser on the laptop and the workstation, the operator signs in and browses, searches and reads every account's mail with its attachments, and writes a mail that goes out on their Approve | tests 1 to 9 | mesh-catalog: `mesh-mail`'s account bucket (issue 1255, ADR 0439 §2); the module `mesh-mail-web` (manifest, sign-in, the pages, the send, the seat's verbs, the integration test) | — | -| 2 | Betterbird is gone from the laptop and the workstation, its profile and mail kept in the home | ADR 0379 §2 | mesh-catalog: Betterbird unassigned on the operator's Approve, then the `betterbird` module removed from the catalogue (issue 806) | 1 | +| 2 | Each new mail is read into a mail group on the mail page; the mail that matters reaches the operator's phone as one mail signal, a scam warning shows on the page only, the operator's corrections become reading lessons, and the pop-up for every new mail is gone | tests 10, 11, 13 to 17 | mesh-catalog: `mesh-mail`'s grants split (issue 1227) and the seat `mail-reading` declared; the module `mesh-mail-reader` (batches, mail entries, reading lessons, mail signals, health, the integration test); `mesh-mail-web`'s mail groups, mail signals, scam warnings and reading lessons; `mesh-mail-notifier` unassigned (issue 1418) | 1 | +| 3 | On the mail page the operator asks questions about their mail and gets the mails that answer them, in a mail conversation kept as a thread | test 12 | mesh-catalog: `mesh-mail-web`'s mail conversation as a client of the thread seat; `mesh-mail`'s folder case and `-any` switches | 1; design 58's milestone 3 (issue 880) | +| 4 | Betterbird is gone from the laptop and the workstation, its profile and mail kept in the home | ADR 0379 §2 | mesh-catalog: Betterbird unassigned on the operator's Approve, then the `betterbird` module removed from the catalogue (issue 806) | 2 | -**Confirmed by the operator on 2026-10-19.** Milestone 1's issues, one repository part each, in their order: -1255 (`mesh-mail`'s account bucket), 1261 (`mail_send_status` answers only its caller), 1262 (the module), 1263 -(sign-in), 1264 (accounts, folders and mails), 1265 (search), 1266 (a mail and its attachments), 1267 (writing a -mail), 1268 (the integration test). Milestone 2 is issue 806. +**Milestone 1, confirmed by the operator on 2026-10-19, is delivered;** its live proof on the laptop and the +workstation (a search, an attachment, one send on the Approve) is still the operator's. Its issues, one repository +part each, in their order: 1255 (`mesh-mail`'s account bucket), 1261 (`mail_send_status` answers only its caller), +1262 (the module), 1263 (sign-in), 1264 (accounts, folders and mails), 1265 (search), 1266 (a mail and its +attachments), 1267 (writing a mail), 1268 (the integration test). After its delivery four values were set, each on +the operator's Approve: `mesh-mail-web`'s `reach` on its machine (`internal`), its assignment there, its +`operators`, and its caller in `mesh-mail`'s `grants` for every account. -After milestone 1's delivery and before its live proof, four values are set, each on the operator's Approve: -`mesh-mail-web`'s `reach` on its machine (`internal`), then its assignment there, its `operators`, and its caller in -`mesh-mail`'s `grants` for every account. +Milestones 2 to 4 are written by research 066's graduation and wait for the operator's confirmation and their issues +(playbook 04, steps 5 and 6). Milestone 2 is one release of `mesh-catalog`: the grant split, the new seat, the new +module and the page change ship together, and `mesh-mail-reader` is granted reading only after that delivery, on the +operator's Approve; `mesh-mail-notifier` is unassigned only after test 13 passes live. Milestone 4 is issue 806; +Betterbird stays until mail reading (milestone 2) works, not until the mail conversation.