Design the mail web client: mesh-mail-web on the private network behind Keycloak, reading and sending through mesh-mail (ADR 0440); cite ADR 0439 in the as-is of a module's state
This commit is contained in:
@@ -5,6 +5,7 @@ code: [mesh-controller internal/catalogue/state.go, mesh-controller internal/bro
|
||||
updated: 2026-10-04
|
||||
decisions:
|
||||
- "ADR 0201"
|
||||
- "ADR 0439"
|
||||
---
|
||||
|
||||
# A module's state, as it runs
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: designed
|
||||
code: [mesh-catalog]
|
||||
updated: 2026-10-19
|
||||
decisions:
|
||||
- "ADR 0440"
|
||||
- "ADR 0378"
|
||||
- "ADR 0379"
|
||||
- "ADR 0359"
|
||||
- "ADR 0361"
|
||||
- "ADR 0138"
|
||||
- "ADR 0167"
|
||||
- "ADR 0439"
|
||||
- "ADR 0201"
|
||||
- "ADR 0436"
|
||||
---
|
||||
|
||||
# 59 — The mail web client
|
||||
|
||||
**The operator's mail, every account of it, in a browser on the mesh's own machines. A module of its own,
|
||||
`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.**
|
||||
(ADR 0440, building on ADR 0378 §5 and ADR 0379 §2.)
|
||||
|
||||
**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.
|
||||
|
||||
## 1. The parts
|
||||
|
||||
```
|
||||
browser (laptop, workstation) ── https://mail.<node>.internal ──▶ route proxy (internal reach only, the mesh's own certificate)
|
||||
│
|
||||
▼
|
||||
mesh-mail-web (seat mail-webclient, once for the mesh)
|
||||
│ sign-in: oidc-client ──▶ Keycloak
|
||||
│ pages rendered on the server, a mail shown as text
|
||||
▼ over the bus, as caller <machine>.mesh-mail-web
|
||||
mail-store (mesh-mail): mail_accounts · mail_search · mail_read
|
||||
mail_send ──▶ the operator's Approve (Telegram)
|
||||
mail_send_status
|
||||
mesh-mail's account bucket (ADR 0439 §2): folders and their counts
|
||||
```
|
||||
|
||||
- **`mesh-mail-web`** is built from `mesh-mail`'s source, as `mesh-mail-notifier` is, and is assigned to the machine
|
||||
that runs `mesh-mail` (the control node today). It keeps no mail: no database, nothing on disk (ADR 0440 §1, §4).
|
||||
- **`mesh-mail`** gains only its account bucket (ADR 0439 §2, issue 1255), released with `mesh-mail-web`. It
|
||||
answers `mesh-mail-web` only for the accounts its trusted setting `grants` gives `mesh-mail-web` (ADR 0359 §4,
|
||||
ADR 0378 §13). `mail_send_status` becomes part of `mail-store`'s protocol (ADR 0440 §7).
|
||||
- **The browser never reaches the bus.** The bus has no WebSocket listener or sign-in for a person yet (research 036,
|
||||
gap G1), so every page is HTML from `mesh-mail-web` (ADR 0440 §5).
|
||||
|
||||
## 2. Reachable from the private network only (ADR 0440 §2)
|
||||
|
||||
The operator's rule of 2026-10-15: a web UI for mail is reached only over the mesh's private network, never from
|
||||
outside. Two things hold it:
|
||||
|
||||
1. **Its route's reach is `internal`** (ADR 0138 §7). Reach is a setting of the machine a module runs on, keyed by
|
||||
port, and with none the route proxy composes a public name as well as the internal one. So `mesh-mail-web`'s
|
||||
`reach` is set to `internal` on its machine before it is assigned there. The route proxy then composes only
|
||||
`mail.<node>.internal`, serves it only to the mesh's own machines (ADR 0167 §6), and certifies it with the mesh's
|
||||
own authority.
|
||||
2. **The module answers only its internal name.** A request whose host is not `mail.<node>.internal` is answered
|
||||
404, so a public name composed by mistake shows nothing.
|
||||
|
||||
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).
|
||||
|
||||
## 3. Signing in (ADR 0440 §3)
|
||||
|
||||
`mesh-mail-web` requires `oidc-client` from Keycloak, as grafana does, and uses the authorisation-code flow. It answers
|
||||
no page, attachment or send to a browser that has not signed in, and lets in only the identities its trusted setting
|
||||
`operators` names (nobody by default). The session cookie is `Secure`, `HttpOnly` and `SameSite=Strict` and ends
|
||||
after 12 hours. Every form that changes something (today only the send) carries a token bound to the session.
|
||||
|
||||
The operator chose this on 2026-10-19: without it, any process on any mesh machine, agents included, could read every
|
||||
account through the page and get around `grants`.
|
||||
|
||||
## 4. The pages
|
||||
|
||||
| page | what it shows | how |
|
||||
|---|---|---|
|
||||
| accounts | every granted account, its state, its folders and their counts | `mail_accounts`; the folders from `mesh-mail`'s account bucket, only of an account `mail_accounts` answered (the bucket's read is wider than the grant, ADR 0440 §4) |
|
||||
| folder | one folder's mails, newest first, in pages of 50 | `mail_search` with the folder; the next page asks for mails before the last one shown |
|
||||
| search | mails across every granted account by words, sender, recipient, date, attachment | `mail_search`'s filters (issues 1228-1232) |
|
||||
| mail | one mail: its headers, its body as escaped text, its attachments | `mail_read` |
|
||||
| attachment | one attachment, as a download | `mail_read` with the attachment's key |
|
||||
| write | from (a granted account), to, subject, body; after sending, the Approve's state | `mail_send`, then `mail_send_status` |
|
||||
|
||||
- **A mail is shown as text** (ADR 0440 §5): `mail_read` turns HTML into text, the page escapes it, and nothing
|
||||
remote is loaded. An attachment answers `Content-Disposition: attachment` and `X-Content-Type-Options: nosniff`.
|
||||
Every page carries a content security policy that allows no script or style from elsewhere.
|
||||
- **A send is an ask** (ADR 0361 §2): the page says the ask is raised, then follows `mail_send_status` (ADR 0436
|
||||
§12's states, now part of `mail-store`'s protocol, ADR 0440 §7) until a final one: sent, failed, refused, not
|
||||
proven, declined, expired or unknown. The operator's Approve comes on Telegram, as for every mail the mesh sends.
|
||||
- **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
|
||||
|
||||
Each is an issue with an owner (ADR 0440 §8):
|
||||
|
||||
- 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;
|
||||
- the work account: a later decision of its own (ADR 0359 §7).
|
||||
|
||||
## 6. The build's acceptance tests
|
||||
|
||||
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`,
|
||||
`HttpOnly` and `SameSite=Strict` and ends 12 hours after sign-in.
|
||||
2. A request whose host is not the internal name is answered 404.
|
||||
3. Against a stand-in `mail-store` and a filled account bucket on the real bus, the pages call only
|
||||
`mail_accounts`, `mail_search`, `mail_read`, `mail_send` and `mail_send_status`, list folders from the bucket,
|
||||
page by date, and a send shows its state; the manifest declares no database and no writable directory.
|
||||
4. A mail whose body holds `<script>` and a remote image is shown as text; an attachment is a download with
|
||||
`nosniff`; every page carries a content security policy allowing only its own origin.
|
||||
5. After a mail is indexed, `mesh-mail`'s account bucket changes only the keys that describe it, within ADR 0439's
|
||||
bounds (issue 1255's own tests).
|
||||
6. The declaration for its machine composes `mesh-mail-web`'s route with its internal name only (`mesh-controller.plan`).
|
||||
7. The acceptance sentence at the top, live: in a browser on the laptop and on the workstation, for every account.
|
||||
|
||||
Tests 1 to 4 are one integration test that starts the module whole against the real bus (playbook 07 step 3 item 4);
|
||||
test 5 is `mesh-mail`'s own; test 6 is read before the module is assigned; test 7 is proven live after the delivery.
|
||||
|
||||
## 7. 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 7 | 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 |
|
||||
|
||||
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. Milestone 1's prepare step (playbook 07 step 0) opens its issues and writes
|
||||
their numbers here.
|
||||
@@ -58,6 +58,7 @@ document is written and this one's status becomes `implemented`.
|
||||
| [`56-the-mail-client-set-up-by-the-mesh.md`](56-the-mail-client-set-up-by-the-mesh.md) | **Designed.** On every node with a display server, the `betterbird` module installs Betterbird from a pinned upstream release mirrored into the artifact store, ships the Owl add-on pinned and installed by Betterbird's enterprise policy, and declares the operator's mail accounts in a trusted setting without credentials; each account signs in once at a person's first use, and one the profile already holds is adopted by its address | ADR 0316, ADR 0317 |
|
||||
| [`57-building-config-and-provisioning.md`](57-building-config-and-provisioning.md) | **Designed.** The work of ADR 0348 and ADR 0349 as 37 packages, each an issue with its proof: foundations, template files, config in its own module, the provisioner and its client, secrets in the vault, bindings from providers, collections, a module's runtime readers, and removing the old forms; at most five worked at once | ADR 0348, ADR 0349 |
|
||||
| [`58-agent-work-as-the-meshs-own.md`](58-agent-work-as-the-meshs-own.md) | **Designed.** Agent work as the mesh's own: a thread seat keeps every thread whole on the bus; a thread handler runs the harness headless in a sandbox per session, the workspace on the thread's own forge branch; a workspace toolkit and named tools, no general command runner; one identity seat for persons, agents and modules, roles of grants, every tool an effect, signed bus logins, and the switch to them one identity at a time | ADR 0383, ADR 0384, ADR 0385, ADR 0386, ADR 0387, ADR 0388, ADR 0389, ADR 0390 |
|
||||
| [`59-the-mail-web-client.md`](59-the-mail-web-client.md) | **Designed.** The operator's mail, every account, in a browser on the mesh's own machines: a module of its own, `mesh-mail-web`, holds `mail-webclient`, renders pages on the server and reads and sends only through `mail-store`'s verbs under a caller granted per account; its route reaches the private network only, the operator signs in with Keycloak, and a mail goes out only on the operator's Approve; then Betterbird is removed | ADR 0440, ADR 0378, ADR 0379 |
|
||||
|
||||
## Not yet written
|
||||
|
||||
|
||||
Reference in New Issue
Block a user