Merge pull request 'Revert #277: no showmissed lock in betterbird's policy (issue 640)' (#281) from fix/640-revert-the-showmissed-lock into main

This commit was merged in pull request #281.
This commit is contained in:
2026-10-13 15:06:09 +00:00
6 changed files with 10 additions and 172 deletions
+4 -18
View File
@@ -97,7 +97,7 @@ start; `prefs.js` wins over a default.
value true, false, a whole number or a string. The node-engine merges the layers key by key, so a node's layer
overrides one preference, or takes one out with `null`. The operator's setup approved on 2026-10-12 (compact
density, 14-point text, the vertical view with multi-line cards and threads, no new-mail pop-up, sound or tray
icon, the calendar's day from 09:00 to 20:00, reminders missed while asleep shown (since locked off: issue 640), chat off, remote content
icon, the calendar's day from 09:00 to 20:00, reminders missed while asleep shown, chat off, remote content
blocked, Do Not Track; 21 preferences) moved from the catalogue's defaults file into the mesh-wide layer of
`prefs`, set on the operator's Approve.
@@ -109,24 +109,10 @@ desktop node writes the map's text into the old build's user.js, and `betterbird
refused until the push.
**Locked** (the policy): Betterbird's updater off (`DisableAppUpdate`); telemetry off (`DisableTelemetry`);
add-ons change only through the mesh's pins (`ExtensionUpdate` false); and four preferences locked off:
add-ons change only through the mesh's pins (`ExtensionUpdate` false); and three preferences locked off:
`mail.shell.checkDefaultClient` (the ask to be the default client), `mail.inappnotifications.enabled` (the
vendor's in-app notices), `mailnews.start_page.enabled` (the start page, which otherwise loads from the
vendor at every start), and `calendar.alarms.showmissed` (the reminders of meetings that have passed, below).
**A passed meeting's reminder is not shown again** (novox/hq issue 640). With `calendar.alarms.showmissed` on,
Betterbird's alarm service fires, at every load of a calendar (each start, each refresh, and each time Owl
registers its calendar after signing in), every reminder of the past month whose item carries no later
acknowledgement (`X-MOZ-LASTACK`). Off, Betterbird's own default, a past reminder fires only while its meeting
has not ended. A dismissal is that acknowledgement, and Owl never sends it to Exchange (its changes carry the
reminder's offset alone): it lives only in Betterbird's local calendar cache, which Owl empties when it
resynchronises a calendar from scratch. With the pass on, Exchange meetings of the past month came back about
every ten minutes on g14 from the start that placed the setting's `true`, Dismiss or not. So the policy locks the
pass off, whatever the setting `prefs` or a profile says; a setting's value for it has no effect (`betterbird_config`
shows the lock). What it costs: a reminder that fell due while the laptop slept is not shown once the meeting
is over; one for a meeting still running is. `betterbird_check` says when the running Betterbird still has the
pass on beside an Owl calendar, with the remedy (the module's push, or `betterbird_restart` once the policy is
placed).
vendor's in-app notices), and `mailnews.start_page.enabled` (the start page, which otherwise loads from the
vendor at every start).
**Kept at Betterbird's default** (`// keeps Betterbird's default: <name>` in `mesh-defaults.js`): the theme,
system colours, always-underlined links, manual spam marking, and the composer's attachment pane. A value the
@@ -981,9 +981,6 @@ func (m *Machine) Check() (CheckAnswer, error) {
"take them out of install/defaults-pref/mesh-defaults.js in a reviewed catalogue change: an account is declared in the setting accounts")
}
}
if what, do := m.missedReminders(profilePath); what != "" {
add(what, do)
}
if why := m.defaultClientAsk(profilePath); why != "" && in.From != "not installed" {
add("Betterbird asks at its start to be the default mail client: "+why,
"push the module to this node: its policy locks "+defaultClientPref+" off (novox/hq issue 345)")
@@ -249,8 +249,8 @@ func TestTheConfigShowsTheLayersAndTheDrift(t *testing.T) {
f.withDefaults()
f.betterbirdsOwn()
links(t, nil)
f.declare(`user_pref("mail.uifontsize", 16); user_pref("mail.biff.show_alert", false); user_pref("calendar.alarms.playsound", false);`)
f.write(profileDir+"/user.js", mark+")\n"+`user_pref("mail.uifontsize", 16);`+"\n"+`user_pref("calendar.alarms.playsound", false);`+"\n")
f.declare(`user_pref("mail.uifontsize", 16); user_pref("mail.biff.show_alert", false); user_pref("calendar.alarms.showmissed", true);`)
f.write(profileDir+"/user.js", mark+")\n"+`user_pref("mail.uifontsize", 16);`+"\n"+`user_pref("calendar.alarms.showmissed", true);`+"\n")
f.write(profileDir+"/prefs.js", "// Mozilla User Preferences\n"+
`user_pref("extensions.activeThemeID", "thunderbird-compact-dark@mozilla.org");`+"\n"+
`user_pref("mail.uifontsize", 12);`+"\n"+
@@ -266,7 +266,7 @@ func TestTheConfigShowsTheLayersAndTheDrift(t *testing.T) {
for _, p := range c.Prefs {
by[p.Name] = p
}
if len(c.Prefs) != len(lockedOff)+3+len(shippedKeepsWant) || !strings.HasPrefix(c.Defaults, "placed") {
if len(c.Prefs) != 3+3+len(shippedKeepsWant) || !strings.HasPrefix(c.Defaults, "placed") {
t.Fatalf("every lock, declared preference and kept default: %d, %s", len(c.Prefs), c.Defaults)
}
if !reflect.DeepEqual(c.Drifting, []string{"extensions.activeThemeID", "mail.spam.manualMark", "mail.biff.show_alert"}) {
@@ -293,7 +293,7 @@ func TestTheConfigShowsTheLayersAndTheDrift(t *testing.T) {
if p := by["mailnews.start_page.enabled"]; p.Layer != "locked" || p.Effective != "false" || p.Drift != "" {
t.Errorf("a lock wins over the profile: %+v", p)
}
if p := by["calendar.alarms.playsound"]; p.Effective != "false" || p.Layer != "declared" {
if p := by["calendar.alarms.showmissed"]; p.Effective != "true" || p.Layer != "declared" {
t.Errorf("%+v", p)
}
// T5: the check says the drift, with both remedies.
@@ -18,7 +18,6 @@ import (
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"time"
@@ -173,8 +172,8 @@ func (m *Machine) policyState() string {
return "placed, and " + why
}
return "placed: Betterbird's updater, its telemetry and add-on updates off, Owl installed into every profile from " +
"the mesh's xpi with its updates off, and the ask to be the default mail client, the vendor's in-app notices, " +
"the start page and the reminders of meetings that have passed locked off"
"the mesh's xpi with its updates off, and the ask to be the default mail client, the vendor's in-app notices " +
"and the start page locked off"
}
// lockedOff are the preferences the module's policy locks to false (novox/hq issue 345), each with what it is.
@@ -182,70 +181,6 @@ var lockedOff = []struct{ Name, What string }{
{defaultClientPref, "the ask to be the default mail client"},
{"mail.inappnotifications.enabled", "the vendor's in-app notices"},
{"mailnews.start_page.enabled", "the start page, loaded from the vendor at every start"},
{missedPref, "the reminders of meetings that have passed (novox/hq issue 640)"},
}
// missedPref is Betterbird's pass over missed reminders. With it on, the calendar's alarm service fires, at
// every load of a calendar (each start, each refresh, each time Owl registers its calendar after signing in),
// every reminder of the past month whose item carries no later acknowledgement (X-MOZ-LASTACK); with it off
// (Betterbird's own default), a past reminder fires only while its meeting has not ended. Owl never sends a
// dismissal to Exchange (its changes carry the reminder's offset alone) and keeps it only in Betterbird's local
// cache, which a resynchronisation from scratch empties: a passed meeting's reminder then comes back at every
// reload, Dismiss or not (novox/hq issue 640). The policy locks it off, so no setting and no profile can turn the
// pass back on; betterbird_check says when the running Betterbird still has it on beside an Owl calendar.
const missedPref = "calendar.alarms.showmissed"
// missedReminders says why the running Betterbird shows a passed meeting's reminder again at each reload of an
// Owl calendar, with the remedy, or two empty strings: Betterbird's pass over missed reminders on (the profile's
// user.js, which wins at the start, else its prefs.js; Betterbird's default is off) while the profile holds an
// Owl calendar and the placed policy did not lock it off before Betterbird started.
func (m *Machine) missedReminders(profile string) (string, string) {
if profile == "" {
return "", ""
}
prefs := mergedPrefs(profile)
owl := 0
for _, c := range profileCalendars(prefs) {
if c.Kind == "owl" {
owl++
}
}
if owl == 0 {
return "", ""
}
value, from := "false", "Betterbird's default"
for _, f := range []string{"user.js", "prefs.js"} {
b, err := readBounded(filepath.Join(profile, f))
if err != nil {
continue
}
if v, ok := statementsOf(b)[missedPref]; ok {
value, from = v, "the profile's "+f
break
}
}
locked := m.locks()[missedPref] == "false"
ps := m.mainProcs()
if locked {
if len(ps) == 0 {
return "", ""
}
st, err := os.Stat(m.path(policyFile))
t, terr := time.Parse(time.RFC3339, ps[0].Since)
if err != nil || terr != nil || !st.ModTime().After(t) {
return "", ""
}
}
if value != "true" || len(ps) == 0 {
return "", ""
}
what := fmt.Sprintf("Betterbird shows the reminders of meetings that have passed again at every reload of its %d Owl "+
"calendar(s): %s is true (%s), and Owl keeps a dismissal only in Betterbird's local cache, never on the Exchange "+
"server (novox/hq issue 640)", owl, missedPref, from)
if locked {
return what, "betterbird_restart: the module's policy locks " + missedPref + " off, and the running Betterbird started before it was placed"
}
return what, "push the module to this node: its policy locks " + missedPref + " off; then betterbird_restart"
}
// policyProblem is everything the policy fails to say of what ADR 0316 and issue 345 ask, or "".
@@ -229,79 +229,3 @@ func mustRead(t *testing.T, f *fake, p string) []byte {
}
return raw
}
// owlCalendar is an Owl calendar in the profile's prefs.js, as Owl registers its account's calendar.
func (f *fake) owlCalendar(extra string) {
f.write(profileDir+"/prefs.js", "// Mozilla User Preferences\n"+
`user_pref("calendar.registry.45b5.type", "owl");`+"\n"+
`user_pref("calendar.registry.45b5.uri", "owl://someone%40example%2Eorg@outlook.example.org/");`+"\n"+
`user_pref("calendar.registry.45b5.name", "someone@example.org");`+"\n"+extra)
}
// A passed meeting's reminder shows at most once: the policy locks Betterbird's pass over missed reminders off,
// whatever the setting prefs or the profile say, because Owl keeps a dismissal only in Betterbird's local cache
// and the pass fires every unacknowledged reminder of the past month again at each reload (novox/hq issue 640).
func TestAPassedMeetingsReminderIsNotShownAgainAtEachReloadOfAnOwlCalendar(t *testing.T) {
raw, err := os.ReadFile(filepath.Join("..", "..", "install", "distribution", "policies.json"))
if err != nil {
t.Fatal(err)
}
if p := policyPrefsOf(raw)[missedPref]; p.Value != "false" || !p.Locked {
t.Fatalf("the module's policy locks %s off: %+v", missedPref, p)
}
before := strings.Replace(string(raw), `"calendar.alarms.showmissed"`, `"calendar.alarms.unrelated"`, 1)
if why := policyProblem([]byte(before)); !strings.Contains(why, missedPref) {
t.Fatalf("a policy that leaves the missed reminders on is said: %q", why)
}
f := newFake(t)
f.betterbird()
f.placedByTheMesh()
links(t, nil)
nag := func() Finding {
t.Helper()
c, err := f.Check()
if err != nil {
t.Fatal(err)
}
for _, x := range c.Findings {
if strings.Contains(x.What, "Owl calendar") {
return x
}
}
return Finding{}
}
// g14 on 2026-10-13: the module's user.js turns the pass on, an Owl calendar is in the profile, and the
// placed policy does not lock it.
f.write(policyFile, before)
f.owlCalendar("")
f.write(profileDir+"/user.js", mark+")\n"+`user_pref("calendar.alarms.showmissed", true);`+"\n")
if x := nag(); !strings.Contains(x.What, "1 Owl calendar") || !strings.Contains(x.What, "user.js") ||
!strings.Contains(x.Do, "push the module") {
t.Fatalf("the nag is a finding with its remedy: %+v", x)
}
// The policy placed after the running Betterbird started: in effect from its next start.
f.write(policyFile, string(raw))
later := time.Unix(1_900_000_000, 0)
os.Chtimes(filepath.Join(f.Root, policyFile), later, later)
if x := nag(); !strings.Contains(x.Do, "betterbird_restart") || strings.Contains(x.Do, "push") {
t.Fatalf("a lock placed after the start waits for a restart: %+v", x)
}
// The lock in effect: nothing the profile says turns the pass back on.
f.write(policyFile, string(raw))
f.owlCalendar(`user_pref("calendar.alarms.showmissed", true);` + "\n")
if x := nag(); x.What != "" {
t.Fatalf("the lock holds whatever the profile says: %+v", x)
}
// Without the lock, the pass off is no nag, and neither is the pass on without an Owl calendar.
f.write(policyFile, before)
f.write(profileDir+"/user.js", mark+")\n"+`user_pref("calendar.alarms.showmissed", false);`+"\n")
if x := nag(); x.What != "" {
t.Fatalf("the pass off: %+v", x)
}
f.write(profileDir+"/user.js", mark+")\n"+`user_pref("calendar.alarms.showmissed", true);`+"\n")
f.write(profileDir+"/prefs.js", `user_pref("calendar.registry.c1.type", "caldav");`+"\n"+`user_pref("calendar.registry.c1.uri", "https://dav.example.org/c1/");`+"\n")
if x := nag(); x.What != "" {
t.Fatalf("a CalDAV calendar keeps its dismissals on its server: %+v", x)
}
}
@@ -22,10 +22,6 @@
"mailnews.start_page.enabled": {
"Value": false,
"Status": "locked"
},
"calendar.alarms.showmissed": {
"Value": false,
"Status": "locked"
}
}
}