Design the mail client set up by the mesh: Betterbird pinned, Owl by policy, accounts without credentials (issue 345, ADR 0316)
This commit is contained in:
@@ -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_<name>`, 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`.
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user