From 2059175b89cb171b33c0092bb4201e6bc248c0f6 Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 12 Oct 2026 12:22:51 +0200 Subject: [PATCH 1/2] Design the mail client set up by the mesh: Betterbird pinned, Owl by policy, accounts without credentials (issue 345, ADR 0316) --- .../56-the-mail-client-set-up-by-the-mesh.md | 165 ++++++++++++++++++ 03-DESIGN/01-to-be/README.md | 1 + 2 files changed, 166 insertions(+) create mode 100644 03-DESIGN/01-to-be/56-the-mail-client-set-up-by-the-mesh.md 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..068769f9 --- /dev/null +++ b/03-DESIGN/01-to-be/56-the-mail-client-set-up-by-the-mesh.md @@ -0,0 +1,165 @@ +--- +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 and keeps it in the artifact store, which reaches only the operator's own nodes. It is + never committed to a repository. +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 From db92ca73ff4ef506e7e877e112852df50c9f82fe Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 12 Oct 2026 12:29:07 +0200 Subject: [PATCH 2/2] Record the operator's answer: Owl's pinned copy stays in the private artifact store (issue 345, ADR 0316) --- 03-DESIGN/01-to-be/56-the-mail-client-set-up-by-the-mesh.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 index 068769f9..802da923 100644 --- 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 @@ -67,8 +67,10 @@ Owl is an outside vendor's add-on under a proprietary licence: it may be loaded 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 and keeps it in the artifact store, which reaches only the operator's own nodes. It is - never committed to a repository. +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