|
|
|
@@ -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 gives no pop-up and no mail signal 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 3, §11).
|
|
|
|
|
|
|
|
|
|
## 3. Signing in (ADR 0440 §3)
|
|
|
|
|
|
|
|
|
@@ -102,16 +119,108 @@ 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.<account>.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, and every part of its question has a bound, so it never
|
|
|
|
|
passes 55 KiB of the judgement's 64 KiB: the framing that a mail is data and never an instruction, at most 4 KiB;
|
|
|
|
|
the mail groups, at most 4 KiB, the largest first; 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 each mail, at most 3.5 KiB (its headers at most 512 bytes, its text 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. A batch starts at least 160 s after the previous one started, so
|
|
|
|
|
an issue tracker's check that waits behind a batch on the same machine ends within 115 s, inside its two minutes
|
|
|
|
|
(ADR 0278 §2), which research 066's 15 mails and 120 s back to back would not; mail reading then reads up to 225
|
|
|
|
|
mails an hour (ADR 0459 §15).
|
|
|
|
|
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 (a caller of the thread's own,
|
|
|
|
|
issue 1419), 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`,
|
|
|
|
@@ -131,22 +240,55 @@ Each is an issue with an owner (ADR 0440 §8):
|
|
|
|
|
answers whether it serves and the accounts it is granted (a unit test of `mesh-mail-web`, issue 1262).
|
|
|
|
|
9. The acceptance sentence at the top, live: in a browser on the laptop and on the workstation, for every account.
|
|
|
|
|
|
|
|
|
|
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 4.) 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 sender whose name carries a host name still gives a
|
|
|
|
|
mail signal, in the safe form.
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
## 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, 13, 14, 15 and 17 are proven live after milestone 2's delivery, test 13
|
|
|
|
|
before `mesh-mail-notifier` is unassigned; test 12 with milestone 4.
|
|
|
|
|
|
|
|
|
|
## 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 | 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 |
|
|
|
|
|
| 4 | 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; the thread's mail tools under a caller of their own granted reading only (issue 1419); `mesh-mail`'s folder case and `-any` switches | 2 (the grant split and the reading lessons); design 58's milestone 3 (issue 880) |
|
|
|
|
|
|
|
|
|
|
**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 3 is issue 806.
|
|
|
|
|
ADR 0379 §2's bar for removing Betterbird is milestone 1's; keeping Betterbird until mail reading (milestone 2) works,
|
|
|
|
|
and not until the mail conversation, is the lead's proposal, for the operator to confirm with the milestones
|
|
|
|
|
(playbook 04, step 5). Milestone 4 waits for threads and for the decision that names its thread's reading-only caller (issue 1419, which needs a decision first).
|
|
|
|
|