Revert the showmissed lock of #277: the operator wants no special handling in betterbird's config (hq issue 640)
mesh/merge-gate head f1f866050eee merged into trunk e209d46faf3d — pass: builds betterbird, sent nowhere; no bus step; 2 wait(s) for a person; every machin…
mesh/repo-check head f1f866050eee merged into trunk e209d46faf3d — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery delivered

The fix is the approved settings layer (proposal s09bf8c99da7ac8b9:
calendar.alarms.showmissed false), pushed to g14 and shanks.
This commit is contained in:
jochen
2026-10-13 16:54:51 +02:00
parent 4d15a155e6
commit f1f866050e
6 changed files with 10 additions and 172 deletions
@@ -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)
}
}