From c192576f6b2fb0f2170447e26a1067452c3e3c9d Mon Sep 17 00:00:00 2001 From: jochen Date: Tue, 13 Oct 2026 02:03:13 +0200 Subject: [PATCH] to-be 56: the betterbird module owns Betterbird's whole configuration, shown without a credential (issue 586, ADR 0329) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The design said the tools answer no address and declared only the accounts. It now names ADR 0329 and ADR 0326, the five new settings and prefs as one entry per preference (§8), what the tools show (§6), and how each part is checked. --- .../56-the-mail-client-set-up-by-the-mesh.md | 129 +++++++++++++++--- 1 file changed, 111 insertions(+), 18 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 3f9e8256..6c676709 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 @@ -2,10 +2,12 @@ layer: to-be status: designed code: [] -updated: 2026-10-12 +updated: 2026-10-13 decisions: - "ADR 0316" - "ADR 0317" + - "ADR 0326" + - "ADR 0329" --- # 56 — The mail client, set up by the mesh @@ -14,12 +16,17 @@ decisions: 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.) +(ADR 0316, from issue 345.) **The module owns Betterbird's whole configuration as settings, mesh-wide with a +node's layer over each: the preferences, the accounts, the identities with their signatures, the CalDAV +calendars, the CardDAV address books, the filters and the tags; its tools show the operator's own values, never +a credential.** (ADR 0329, from issue 586.) **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 same profile, with its mail and logins, after the switch. Once the operator has approved the derived layers, +every desktop node shows the same signatures, calendars, address books, filters and tags, and `betterbird_drift` +on each says no difference. 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. @@ -31,11 +38,13 @@ is issue 345's and is not repeated here. This document adds what sets Betterbird | 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 defaults file naming the system's spelling dictionaries, and one naming the preferences kept at Betterbird's own default (no value of the operator's) | 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 settings as the node-engine merges them, one mergeable file per group, each naming the keys it takes (ADR 0326) | the module's directory in the home | owned, whole | +| the user.js, composed from the settings | the profile, placed by the tools | the module's, or never touched | +| the lists in the profile's `prefs.js`: accounts, outgoing servers, an adopted account's identities, the calendars' order | the profile | written into: the module's own keys only, only while Betterbird is closed | +| each declared account's filter file | the account's directory in the profile | owned, whole, only while Betterbird is closed | +| the profile: mail, calendar events, contacts, 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' @@ -159,14 +168,26 @@ the provider's own sign-in can issue. With no credential to carry, the rule that ## 6. What the tools say -`betterbird_status` and `betterbird_check` add, never with an address, a host or a name: +**The tools show the operator's own configuration, never a credential** (ADR 0329 §1–§3, changing ADR 0316 §16 +and ADR 0317 §3). An answer may hold an account's name, address, user name and its servers' hosts and ports; an +identity's name, address, reply-to, organisation and signature; a calendar's or an address book's name, address, +colour and options; a filter's conditions and actions; a tag's name and colour. These values appear only in a +tool's answer to its caller and, for a proposed value, in the controller's proposal record; never in an event, a +log line, a condition, a metric, the module's record per node, an issue, the knowledge base or this repository. +The operator channel's content rule does not apply to these answers, because it withholds the addresses they +show; in its place every answer passes the module's own credential check, which withholds a field whose name says +password, token, secret or cookie, and the password part of any address. The login store is read only to count, +by server, the sign-ins kept. The values reach whoever may call the module's tools, which is every login and +agent session on the operator's nodes; the ask itself still shows a proposed value in its shape. -- 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. +| tool | answers | +|---|---| +| `betterbird_status`, `betterbird_check` | the installed version against the pin and the newest release; the policy placed; Owl's version and whether it is active; the declared accounts in the list and how many have not signed in yet; and, per kind, the declared entries that drift or are refused and the changes waiting for Betterbird to close | +| `betterbird_accounts` | the accounts with their values and the declared account each is, Owl's marked by name and address; with `propose`, `accounts` derived and proposed, the value shown | +| `betterbird_identities`, `betterbird_calendars`, `betterbird_address_books`, `betterbird_filters`, `betterbird_tags` | the profile's entries beside the declared ones, how each resolved, an ICS calendar by its name only | +| `betterbird_derive` | the five kinds derived per layer, the fields another node holds differently, what was left out; with `propose`, the next layer proposed | +| `betterbird_drift` | per kind and declared entry: holds, differs (each field with both values), lacks, or refused; and the undeclared entries | +| `betterbird_config` | each managed preference through the locks, the setting `prefs` and Betterbird's defaults, with its drift | ## 7. What changes for the builder @@ -178,12 +199,79 @@ Not decided here: a package repository of the mesh's own, which would also answe node runs from outside the official repositories. ADR 0316 weighed it, and rejected it as larger than this module needs. +## 8. The rest of the configuration (ADR 0329) + +**The preferences are one entry each.** The setting `prefs` maps a preference's name to its value, so a node's +layer overrides one preference, or takes one out with null, and the operator's setup lives in the mesh-wide +layer, not in the catalogue. The module ships only its policy's locks and the names it keeps at Betterbird's own +default (issue 586). `prefs` refuses a credential's preference, an account's (§4), and the four prefixes the +settings below own: `calendar.registry.`, `calendar.list.`, `ldap_2.servers.` and `mailnews.tags.`, so each value +has one owner. + +**Five more settings**, set mesh-wide so every desktop node has the same setup, with a node's layer holding only a +difference that node is meant to have: + +| setting | an entry | matched in the profile by | +|---|---|---| +| `identities` | an identity of a declared account: name, address, reply-to, organisation, the signature's text, whether it is HTML, whether it goes above the quote; the account's first identity takes its name and address from `accounts` | the account's default, or an identity of that account with the address | +| `calendars` | a CalDAV calendar: name, address, colour, read-only, reminders, user name, the identity that answers invitations | its address | +| `address-books` | a CardDAV address book: name, address, user name | its address | +| `filters` | per declared account, the ordered list of filters: name, on or off, when it runs, all or any condition, conditions, actions, a folder named by its path within the account | owned whole | +| `tags` | per tag key: name, colour, place | its key | + +An identity, a calendar or an address book may carry, in a node's own layer only, the optional `key` of the +profile's entry to adopt there, as `accounts` does. The first four are **trusted**, because each names where a +password, a reply or forwarded mail goes; `tags` names no server and sends no mail, and is set through the +`settings` verb as well. A setting holds no line break, so a signature is a list of its lines; no setting holds a +password, a token or a cookie, and an address whose user part carries a password is refused. + +**Adopted as accounts are.** At every apply each declared entry is matched as §4 matches an account: recorded, +the module's own key, a node's `key`, else one match adopted, none created under the module's key, several +refused with the remedy. Its declared fields go into the user.js under that key. An entry leaving the setting is +released if adopted; if created, its preferences are removed, never a calendar's events, an address book's +contacts or a cache. An entry no setting declares stays the person's. Owl's accounts and calendars, a calendar +subscribed by an ICS address (such an address is often itself the secret), local calendars and local address +books are never declared. + +**Filters are owned whole per declared account.** For an account named in `filters`, the module writes its filter +file from the declared list, turning each folder's path into that folder's address on the node; a filter added in +Betterbird to that account is gone at the next apply, and drift shows it first. Before its first write on a node +the module keeps the file it replaces in its own directory in the home. When an account leaves `filters`, the +module stops writing its file, which stays the person's as last written, and says where the kept copy is. An +account never named keeps its file. + +**What Betterbird rewrites is written only while it is closed:** the lists in `prefs.js` and each filter file. +An apply while it runs writes neither, and the module's check counts, per kind, the changes that wait; the next +start from the launcher writes them. The user.js is placed as before, since Betterbird reads it only at its start. + +**Derived from the profiles, proposed one layer at a time.** `betterbird_derive` asks every desktop node that +holds Betterbird for what its profile holds. An entry found on every such node goes into the mesh-wide layer +with the value of the node the derive was called on, and the answer lists every field another node holds +differently, which then shows as drift on that node; an entry found on some nodes only goes into those nodes' +own layers. With `propose` it proposes the next layer for the operator's Approve: first the mesh-wide one, then +each node's that needs one, each only after the one before it was set or declined, against the layer as it then +stands and carrying that layer whole (ADR 0277 keeps one open ask per module and machine). It proposes nothing +while a desktop node does not answer, because a layer derived without it would put on every node what only some +hold. The first mesh-wide proposal also carries the operator's setup of `prefs`, which the catalogue held before. + ## 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. + `updates_disabled`, and sets `DisableAppUpdate`; `prefs` refuses the account prefixes and the four of §8; + `accounts` refuses a password-like field; a declared account's user.js holds only the module's own keys; every + mergeable file names the keys it takes, `tags` alone untrusted; the module's defaults hold no value of the + operator's. +- **The controller's tests:** a key of a trusted file that names its keys is refused through the `settings` verb, + and a key of a file nothing trusts, `tags`, is set through it. +- **The configuration's tests, on profile fixtures:** each kind adopted, created, or refused when ambiguous, a + `key` settling it; leaving releases or removes and never touches events; a declared filter list, forwarding + included, written with this node's folder addresses, the replaced file kept, a file never named left byte for + byte, one taken out left as last written; with Betterbird running nothing of `prefs.js` or a filter file is + written and the waiting changes are counted; derived from two nodes, an entry both hold mesh-wide with the + calling node's value and the other's differing fields listed, one on one node in its own layer, Owl's, ICS + calendars and a passworded address left out, and one layer proposed at a time; drift names each differing field; + no answer holds a credential. - **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. @@ -196,9 +284,14 @@ module needs. 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. +- **Live, after the operator's Approve of the derived layers:** on the node derived on, `betterbird_drift` shows no + drift for the mesh-wide entries; on the other desktop node it first shows each field where it differs, and, + after Betterbird is closed there and started again, none; `betterbird_check` on both says no drift and no + waiting change. ## References -- ADR 0316, ADR 0317, ADR 0180, ADR 0182, ADR 0205, ADR 0208, ADR 0277. -- Issue 345 (the reminder window, extended on 2026-10-12), issue 342, issue 339. +- ADR 0316, ADR 0317, ADR 0326, ADR 0329, ADR 0180, ADR 0182, ADR 0205, ADR 0208, ADR 0277. +- Issue 345 (the reminder window, extended on 2026-10-12), issue 586 (the whole configuration), issue 342, + issue 339. - Thunderbird's enterprise policy templates (ESR 153): `ExtensionSettings`, `DisableAppUpdate`, `Preferences`.