diff --git a/03-DESIGN/01-to-be/56-the-mail-client-set-up-by-the-mesh.md b/03-DESIGN/01-to-be/56-the-mail-client-set-up-by-the-mesh.md new file mode 100644 index 00000000..802da923 --- /dev/null +++ b/03-DESIGN/01-to-be/56-the-mail-client-set-up-by-the-mesh.md @@ -0,0 +1,167 @@ +--- +layer: to-be +status: designed +code: [] +updated: 2026-10-12 +decisions: + - "ADR 0316" +--- + +# 56 — The mail client, set up by the mesh + +**On every node that holds a display server, the `betterbird` module installs Betterbird from a pinned +upstream release, ships the Owl add-on with it through Betterbird's enterprise policy, and declares the +operator's mail accounts without their credentials. A password or token never passes through the mesh: each +account signs in once, at a person's first use, and Betterbird keeps the result in its own login store.** +(ADR 0316, from issue 345.) + +**Acceptance.** On a desktop node set up from nothing, the operator starts Betterbird from the launcher and +finds Owl installed and the declared accounts listed; each asks for its sign-in once, and the work account is +created in Owl's own setup. On a desktop node that ran the community package before, the same Betterbird opens +the same profile, with its mail and logins, after the switch. + +The module's look-only part (status, preferences, a declared user.js, its log, a restart and a reminder test) +is issue 345's and is not repeated here. This document adds what sets Betterbird up. + +## 1. What the module places + +| what | where | class | +|---|---|---| +| Betterbird, the upstream release archive, unpacked | the installation directory the community package uses | owned, whole | +| the enterprise policy, `policies.json` | the installation's `distribution` directory | owned, whole | +| the Owl add-on, the vendor's xpi | the installation directory | owned, whole | +| a defaults file naming the system's spelling dictionaries | the installation's `defaults/pref` | owned, whole | +| a launcher entry and a start script | under the system's local prefix | owned, whole | +| the user.js, from `prefs` and `accounts` | the module's directory in the home, copied into the profile by its tools | owned, whole | +| the account list in the profile's `prefs.js` | the profile | written into: the module's own keys only | +| the profile: mail, address books, calendars, logins | the profile | found | + +The module creates no link: where the community package linked the command, the icons and the dictionaries, +the module places a start script, a launcher entry that names the installation directly, and the dictionaries' +path as a default preference. + +## 2. Betterbird: a pinned upstream release + +The manifest names the release by its URL, its version and its sha256. The builder fetches it, refuses it when +the digest differs and names both, and keeps it in the artifact store as the module's `archive` artifact; a node +receives it by digest like any other artifact. For a release pinned this way, the pin replaces ADR 0205 §1's +copy in the module's source directory, because a release is about 80 MB; ADR 0205 §3 (unpacked into a +directory the module owns) and §4 (an upgrade is a reviewed change) still apply. + +**The installation path does not move.** Betterbird chooses the profile it opens by the installation's path. A +new path would make it pick another profile, or create an empty one, at its first start, which looks to the +person like their configuration lost. So the archive is unpacked where the community package put it. + +**The switch from the community package** is once per node. The module declares the community package absent +(ADR 0180), and its archive is placed only once the package is gone. The module is marked for a person's +approval, so the switch waits on each node for the operator, who quits Betterbird first. The profile is not +touched. + +**Upgrades** are a change to the pin, reviewed and delivered like any other. Betterbird's own updater is turned +off by the policy (`DisableAppUpdate`). The module's status says the installed version beside the newest +upstream release, so a release left behind is visible. + +## 3. Owl: pinned, mirrored, installed by policy + +Owl is an outside vendor's add-on under a proprietary licence: it may be loaded on the licensee's own devices, +and may not be copied to anyone else. The vendor serves only its newest version, at one address. + +1. The manifest pins Owl by version and the sha256 of the vendor's xpi. +2. The builder fetches it once and keeps it in the mesh's private artifact store, used only on the operator's own + machines. It is never committed to a repository, and a node never fetches it from the vendor. Whether such a + private copy is within the licence's ban on copying to third parties was the operator's call, made on + 2026-10-12. +3. The node-engine places it in the installation directory. +4. The module's `policies.json` installs it through `ExtensionSettings`, as `force_installed`, from the placed + file (a `file://` URL), with `updates_disabled`. Betterbird installs it into every profile, and the person + cannot remove it. + +Owl declares the range of Betterbird versions it supports. The catalogue's check refuses a manifest whose +Betterbird pin lies outside the range of its Owl pin, so a Betterbird upgrade that Owl does not support yet +waits for Owl. + +**Owl's licence is the person's.** The vendor licenses it per mail address, after a month's trial. The mesh holds +no Owl licence, key or payment, and says nothing about one. + +## 4. Accounts: declared, without credentials + +The module gains the setting `accounts`. Per account it names: + +- a name, and the address; +- the incoming server: kind (IMAP or POP3), host, port, connection security, sign-in method, user name; +- the outgoing server: host, port, connection security, sign-in method, user name. + +It never holds a password or a token, and the setting refuses a field that could. It is **trusted**: it says +which server receives the person's password, so it is changed at the controller's terminal, or proposed through +a verb and set on the operator's warrant (ADR 0277). An account without connection security is therefore the +operator's act, like a preference that relaxes a certificate check (issue 339). + +**How a declared account reaches Betterbird.** Betterbird's accounts are preferences: a list of account keys, +and per key the account, its identity, its incoming server and its outgoing server. + +1. The module writes each declared account's preferences under keys of its own, ending `mesh_`, into the + user.js it places, so they win at every start. The `prefs` setting refuses those prefixes, so an account has + one owner. +2. The account list, and the list of outgoing servers, are **written into**, never owned. Owl adds its own + account to the same list, and a list declared in user.js would drop that account at every start. The module + adds its keys to the profile's `prefs.js` only while Betterbird is not running, keeps every other key and + their order, and removes only its own keys when an account leaves the setting. The mail already fetched + stays in the profile. + +**The work account is not declared.** It goes through Owl: the person creates it in Owl's own setup, which +signs in through the provider's own login with its second factor, and binds Owl's licence to the address. + +## 5. Sign-in, and why no password comes from the vault + +A declared account signs in at its first use: through OAuth in Betterbird's own sign-in window where the server +offers it, otherwise by the person typing the password once. Betterbird keeps the result in its own login +store. The mesh writes nothing into that store. + +Writing a password from the vault into it was weighed and rejected. The store is encrypted with a key the +profile holds, which a primary password may protect, and Betterbird rewrites it while it runs. Writing it means +running the store's cryptography against the profile with Betterbird stopped. It fails behind a primary +password. And it would keep, in the vault, a copy of a password the mail provider rotates, or of a token only +the provider's own sign-in can issue. With no credential to carry, the rule that a password is never a setting +(issue 342) holds without any mechanism. + +## 6. What the tools say + +`betterbird_status` and `betterbird_check` add, never with an address, a host or a name: + +- the installed version against the pin and the newest upstream release, and whether it came from the mesh or + from the community package; +- whether the policy file is placed; +- Owl's version against its pin, and whether it is active; +- how many declared accounts are in the profile's list, beside how many other accounts exist, and how many have + not signed in yet. + +## 7. What changes for the builder + +The builder gains one source for an artifact: an upstream file by URL and sha256, fetched, checked and kept in +the artifact store. Betterbird's release and Owl's xpi are its first users. A download that no longer matches +its pin fails the build and names both digests. + +Not decided here: a package repository of the mesh's own, which would also answer the other packages a desktop +node runs from outside the official repositories. ADR 0316 weighed it, and rejected it as larger than this +module needs. + +## How it is checked + +- **The catalogue's tests:** both pins carry a URL, a version and a sha256; a Betterbird pin outside Owl's range + is refused; the generated policy file names Owl as `force_installed` from the placed file with + `updates_disabled`, and sets `DisableAppUpdate`; `prefs` refuses the account prefixes; `accounts` refuses a + password-like field; a declared account's user.js holds only the module's own keys. +- **The module's tests:** writing into `prefs.js` adds and removes only the module's keys, keeps the others and + their order, and is refused while Betterbird runs; the count of declared accounts without a kept sign-in is + taken from the login store's server names, which are kept in the clear, without decrypting anything. +- **The builder's tests:** an upstream file whose download does not match its sha256 fails the build. +- **Live, on each desktop node after its switch:** `betterbird_check` says installed at the pinned version from + the mesh, the policy placed, Owl at its pin and active, the profile in use the same as before the switch, and + the declared accounts in the list with how many have not signed in yet; the package manager's list of foreign packages no longer names the + community package. + +## References + +- ADR 0316, ADR 0180, ADR 0182, ADR 0205, ADR 0208, ADR 0277. +- Issue 345 (the reminder window, extended on 2026-10-12), issue 342, issue 339. +- Thunderbird's enterprise policy templates (ESR 153): `ExtensionSettings`, `DisableAppUpdate`, `Preferences`. diff --git a/03-DESIGN/01-to-be/README.md b/03-DESIGN/01-to-be/README.md index 163fea68..70eebdec 100644 --- a/03-DESIGN/01-to-be/README.md +++ b/03-DESIGN/01-to-be/README.md @@ -55,6 +55,7 @@ document is written and this one's status becomes `implemented`. | [`53-the-issue-tracker.md`](53-the-issue-tracker.md) | **Designed.** The mesh keeps its issues in one module, `issues`, on the control-node: every number from one counter advanced by compare-and-set, the cycle's issue rules refused at the write, an event per change; an issue lives whole in the tracker, hq keeping no issue content (ADR 0291) | ADR 0275, ADR 0291 | | [`54-the-controllers-records-on-the-bus.md`](54-the-controllers-records-on-the-bus.md) | **Designed.** The controller keeps five record mirrors on the bus (nodes, assignments, walks, builds, settings): each a bucket written from its store only when the record changed, with one event per write, and audited against the store every self-check run, a difference raised as `mirror-drifted` before the healer H6 repairs it | ADR 0289 | | [`55-the-knowledge-base.md`](55-the-knowledge-base.md) | **Designed.** The mesh keeps its decision records and research efforts whole in one module, `mesh-knowledge`, on the control-node: number, status, topic, typed links both ways, each decision's clauses with their own status, and the reasoning, with every version; a record is proposed, checked at the write, reviewed by the knowledge base's own judgement and accepted there, with the operator's Approve when it needs it; agents ask what holds in one bounded call | ADR 0290 | +| [`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 | ADR 0316 | ## Not yet written