Fixes issue 640 (Betterbird on g14 re-shows ten month-old Exchange meeting reminders about every ten minutes, and Dismiss does not hold).
Cause (read in Betterbird 153.4 and Owl 1.5.6.1 as installed in /opt/betterbird)
CalAlarmService.addAlarmsForItem: for a past reminder on a writable calendar, with calendar.alarms.showmissed true it fires every reminder whose item has no alarmLastAck (X-MOZ-LASTACK) at or after the reminder. With it false (Betterbird's default) a past reminder fires only while the meeting has not ended.
initAlarms looks back one month (MAX_SNOOZE_MONTHS) and runs at every start, every calendar onLoad (each refresh of a cached calendar), each onCalendarRegistered (Owl re-registers its calendar after each sign-in, StartupAfterLogin -> reloadCalendars), and when suppressAlarms/disabled/notifications.times change. 15 to 21 September is inside the month before 13 October.
Owl never sends a dismissal to Exchange: its modifyItem compares JSON that carries only the reminder's offset (and for an invitation reduces it to the reminder alone), so X-MOZ-LASTACK lives only in Betterbird's local cache (cache.sqlite). Owl's sync keeps it while it updates an item from the cache, and loses it when it resynchronises from scratch (replayChangesOn retry, resetLog).
The nag started at the 13:24 start that placed the setting's showmissed: true (the operator's earlier approval); before that the pass was off.
Nothing in the module writes alarm state, the calendar cache or a calendar's registry keys it did not create (grep of the tools: removals only for calendars the module created; betterbird_config_apply clears only managed preferences).
Fix
install/distribution/policies.json locks calendar.alarms.showmissed to false (Status: locked; Betterbird's Preferences policy accepts the calendar. prefix). Whatever the setting prefs or the profile says, a passed meeting's reminder is not shown again. Cost: a reminder that fell due while the laptop slept is not shown after the meeting ended; one for a meeting still running still is.
lockedOff names the lock, so betterbird_status/betterbird_check say a policy without it is not the module's.
betterbird_check gains a finding: the running Betterbird has the pass on (user.js, else prefs.js) while the profile holds an Owl calendar, and the lock is not in effect. Remedy: push the module, or betterbird_restart when the policy was placed after the start. A CalDAV-only profile is not flagged (CalDAV keeps X-MOZ-LASTACK on its server).
README: the locked list and why.
Tests
New TestAPassedMeetingsReminderIsNotShownAgainAtEachReloadOfAnOwlCalendar: the repo's policy locks the pass off; a policy without the lock is named; g14's state (user.js true, Owl calendar, no lock) is a finding with the push remedy; lock placed after the start -> restart remedy; lock in effect -> no finding even with prefs.js true; pass off, or CalDAV only -> no finding.
On main it fails (the module's policy locks calendar.alarms.showmissed off: {Value: Locked:false}; run with the constant written out, since missedPref is new).
TestTheConfigShowsTheLayersAndTheDrift used showmissed as its example of a declared preference; it uses calendar.alarms.playsound now, and counts the locks by lockedOff.
MESH_GATE=<controller built from mesh-controller main 6f1a053> MESH_CHECK_MODULES=betterbird sh merge-check.sh passes.
Acceptance
A passed meeting's reminder shows at most once (with the lock: not at all once the meeting is over), and Dismiss makes it stay away across Owl syncs and restarts: after the push and a Betterbird restart, betterbird_prefs names calendar.alarms.showmissed false and betterbird_check has no Owl-reminder finding, and no reminder of a past meeting appears over the next hour.
Rollout
The module's upgrade policy is record: the operator quits Betterbird and pushes it to g14 and shanks; Betterbird starts again from the launcher (or betterbird_restart). The setting's showmissed false (proposal s09bf8c99da7ac8b9, issue 641) is no longer needed for this, and is harmless beside the lock.
Fixes issue 640 (Betterbird on g14 re-shows ten month-old Exchange meeting reminders about every ten minutes, and Dismiss does not hold).
## Cause (read in Betterbird 153.4 and Owl 1.5.6.1 as installed in /opt/betterbird)
- `CalAlarmService.addAlarmsForItem`: for a past reminder on a writable calendar, with `calendar.alarms.showmissed` true it fires every reminder whose item has no `alarmLastAck` (X-MOZ-LASTACK) at or after the reminder. With it false (Betterbird's default) a past reminder fires only while the meeting has not ended.
- `initAlarms` looks back one month (`MAX_SNOOZE_MONTHS`) and runs at every start, every calendar `onLoad` (each refresh of a cached calendar), each `onCalendarRegistered` (Owl re-registers its calendar after each sign-in, `StartupAfterLogin` -> `reloadCalendars`), and when `suppressAlarms`/`disabled`/`notifications.times` change. 15 to 21 September is inside the month before 13 October.
- Owl never sends a dismissal to Exchange: its `modifyItem` compares JSON that carries only the reminder's offset (and for an invitation reduces it to the reminder alone), so X-MOZ-LASTACK lives only in Betterbird's local cache (cache.sqlite). Owl's sync keeps it while it updates an item from the cache, and loses it when it resynchronises from scratch (`replayChangesOn` retry, `resetLog`).
- The nag started at the 13:24 start that placed the setting's `showmissed: true` (the operator's earlier approval); before that the pass was off.
- Nothing in the module writes alarm state, the calendar cache or a calendar's registry keys it did not create (grep of the tools: removals only for calendars the module created; `betterbird_config_apply` clears only managed preferences).
## Fix
- `install/distribution/policies.json` locks `calendar.alarms.showmissed` to false (`Status: locked`; Betterbird's `Preferences` policy accepts the `calendar.` prefix). Whatever the setting `prefs` or the profile says, a passed meeting's reminder is not shown again. Cost: a reminder that fell due while the laptop slept is not shown after the meeting ended; one for a meeting still running still is.
- `lockedOff` names the lock, so `betterbird_status`/`betterbird_check` say a policy without it is not the module's.
- `betterbird_check` gains a finding: the running Betterbird has the pass on (user.js, else prefs.js) while the profile holds an Owl calendar, and the lock is not in effect. Remedy: push the module, or `betterbird_restart` when the policy was placed after the start. A CalDAV-only profile is not flagged (CalDAV keeps X-MOZ-LASTACK on its server).
- README: the locked list and why.
## Tests
- New `TestAPassedMeetingsReminderIsNotShownAgainAtEachReloadOfAnOwlCalendar`: the repo's policy locks the pass off; a policy without the lock is named; g14's state (user.js true, Owl calendar, no lock) is a finding with the push remedy; lock placed after the start -> restart remedy; lock in effect -> no finding even with prefs.js true; pass off, or CalDAV only -> no finding.
- On main it fails (`the module's policy locks calendar.alarms.showmissed off: {Value: Locked:false}`; run with the constant written out, since `missedPref` is new).
- `TestTheConfigShowsTheLayersAndTheDrift` used showmissed as its example of a declared preference; it uses `calendar.alarms.playsound` now, and counts the locks by `lockedOff`.
- `MESH_GATE=<controller built from mesh-controller main 6f1a053> MESH_CHECK_MODULES=betterbird sh merge-check.sh` passes.
## Acceptance
A passed meeting's reminder shows at most once (with the lock: not at all once the meeting is over), and Dismiss makes it stay away across Owl syncs and restarts: after the push and a Betterbird restart, `betterbird_prefs` names `calendar.alarms.showmissed` false and `betterbird_check` has no Owl-reminder finding, and no reminder of a past meeting appears over the next hour.
## Rollout
The module's upgrade policy is `record`: the operator quits Betterbird and pushes it to g14 and shanks; Betterbird starts again from the launcher (or `betterbird_restart`). The setting's showmissed false (proposal s09bf8c99da7ac8b9, issue 641) is no longer needed for this, and is harmless beside the lock.
mesh/merge-gate head 73404a3c3f21 merged into trunk 5f491e0c097d — pass: builds betterbird, sent nowhere; no bus step; 2 wait(s) for a person; every machin…
mesh/repo-check head 73404a3c3f21 merged into trunk 5f491e0c097d — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
With calendar.alarms.showmissed on, Betterbird's alarm service fires every
reminder of the past month that carries no later acknowledgement at each
load of a calendar. Owl never sends a dismissal to Exchange and keeps it only
in Betterbird's local cache, so on g14 September's Exchange meetings came
back about every ten minutes, Dismiss or not. The policy now locks the pass
off whatever the setting or profile says, and betterbird_check says when the
running Betterbird still has it on beside an Owl calendar.
Deliverynovox/mesh-catalog@73404a3c3f21 — delivered since 2026-10-13T14:49:28Z
Delivery plan — builds betterbird, sent nowhere; no bus step; 2 wait(s) for a person
tier 0: betterbird
g14: waits for a person: betterbird
shanks: waits for a person: betterbird
betterbird waits for a person: its policy records (person)
Transitions
2026-10-13 14:32 (new) → proposed (announced): novox/mesh-catalog#277's head announced
2026-10-13 14:47 proposed → checked (checked): a verdict names this commit and the trunk's head as its trunk commit
2026-10-13 14:47 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail
2026-10-13 14:48 ready → published (merged): on the trunk its modules follow: merged there, and its walk opened — or nothing for a walk to move
2026-10-13 14:48 published → delivering (go): its walk started: let go by this owner in its group's order, by a person, or on the controller's own path — not a failed walk that carried a later commit for it, after which it waits for a walk of its own commit
2026-10-13 14:49 delivering → delivered (done): its walk is done, carrying its own commit: every machine of its deploy plan passed, or was left as its policy says
The commit's note under refs/notes/mesh-plan keeps every transition: git log --notes=mesh-plan.
<!-- mesh-delivery:view -->
**Delivery** `novox/mesh-catalog@73404a3c3f21` — **delivered** since 2026-10-13T14:49:28Z
**Delivery plan** — builds betterbird, sent nowhere; no bus step; 2 wait(s) for a person
- tier 0: betterbird
- g14: waits for a person: betterbird
- shanks: waits for a person: betterbird
- betterbird waits for a person: its policy records (person)
**Transitions**
- 2026-10-13 14:32 (new) → proposed (announced): novox/mesh-catalog#277's head announced
- 2026-10-13 14:47 proposed → checked (checked): a verdict names this commit and the trunk's head as its trunk commit
- 2026-10-13 14:47 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail
- 2026-10-13 14:48 ready → published (merged): on the trunk its modules follow: merged there, and its walk opened — or nothing for a walk to move
- 2026-10-13 14:48 published → delivering (go): its walk started: let go by this owner in its group's order, by a person, or on the controller's own path — not a failed walk that carried a later commit for it, after which it waits for a walk of its own commit
- 2026-10-13 14:49 delivering → delivered (done): its walk is done, carrying its own commit: every machine of its deploy plan passed, or was left as its policy says
The commit's note under `refs/notes/mesh-plan` keeps every transition: `git log --notes=mesh-plan`.
Merge check at 73404a3c
Judged: head 73404a3c3f21 merged into trunk 305c2ba24733; read beside it: mesh-controller 6f1a05366c1d, mesh-controller-main 6f1a05366c1d, mesh-host 0e92ef3d1b21, mesh-tools 954e1a53eef9
mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: betterbird)
mesh/repo-check: PASS — its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
Delivery plan — builds betterbird, sent nowhere; no bus step; 2 wait(s) for a person
tier 0: betterbird
g14: waits for a person: betterbird
shanks: waits for a person: betterbird
betterbird waits for a person: its policy records (person)
read by no module's build: modules/betterbird/README.md, modules/betterbird/cmd/betterbird-tools/config_test.go, modules/betterbird/cmd/betterbird-tools/install_test.go
Run by the build seat on novox as build-1791901976000711509 (builds --log build-1791901976000711509).
--- the gate: betterbird
betterbird: ok, 17 tool(s), invokes seat:mesh-controller.settings, seat:mesh-controller.nodes, seat:mesh-controller.node, betterbird.betterbird_derive
long-running resources without health: 0
files asking for a setting without saying whether it is trusted: 0
1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here
judging 0 module(s) changed against 4 machine(s), as the snapshot of 2026-10-13T14:32:41Z says they run
composing every machine as the mesh is
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
composing every machine with the change
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose)
judged by controller development build against the facts of 2026-10-13T14:32:41Z
machines:
- a machine (f35): composes
- holds mesh-dns-resolver (idt): composes
- holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes
- a machine (yvbpsm): composes
a merge would rebuild 1 module(s) in 1 tier(s): betterbird
notes:
- modules/betterbird/README.md, modules/betterbird/cmd/betterbird-tools/config_test.go and modules/betterbird/cmd/betterbird-tools/install_test.go read by no module's build, and rebuild nothing (issue 280)
--- its merge-check.sh (go toolchain)
ok git.novox.be/novox/mesh-catalog/checks/words 0.010s
words: 12 retired words and 70 daemons' lines, none in the 46593 strings of 717 files
ok git.novox.be/novox/mesh-catalog/checks/agenthome 0.013s
agent-home: no archive and no file written into under the agent's home
ok git.novox.be/novox/mesh-catalog/checks/secrets 0.013s
secrets: no setting can replace a vault secret in a mergeable file (1 known, nested)
ok git.novox.be/novox/mesh-catalog/checks/paths 0.007s
paths: no module names another's state on its machine (3 known, listed to go)
long-running resources without health: 66, as kept
ok git.novox.be/novox/mesh-catalog/checks/deps 0.232s
testing modules/betterbird
ok betterbird/cmd/betterbird-tools 4.540s
--- its merge-check-typescript.sh (typescript toolchain)
Out of date when it landed: out of date: judged against trunk 305c2ba247, the trunk is now 5f491e0c097d; judged again on a recheck or when it is merged
**Merge check** at `73404a3c`
Judged: head `73404a3c3f21` merged into trunk `305c2ba24733`; read beside it: mesh-controller `6f1a05366c1d`, mesh-controller-main `6f1a05366c1d`, mesh-host `0e92ef3d1b21`, mesh-tools `954e1a53eef9`
- `mesh/merge-gate`: **PASS** — every machine composes with the change as it did without (4 of 4 compose) (modules: betterbird)
- `mesh/repo-check`: **PASS** — its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
**Delivery plan** — builds betterbird, sent nowhere; no bus step; 2 wait(s) for a person
- tier 0: betterbird
- g14: waits for a person: betterbird
- shanks: waits for a person: betterbird
- betterbird waits for a person: its policy records (person)
- read by no module's build: modules/betterbird/README.md, modules/betterbird/cmd/betterbird-tools/config_test.go, modules/betterbird/cmd/betterbird-tools/install_test.go
Run by the build seat on novox as `build-1791901976000711509` (`builds --log build-1791901976000711509`).
```
--- the gate: betterbird
betterbird: ok, 17 tool(s), invokes seat:mesh-controller.settings, seat:mesh-controller.nodes, seat:mesh-controller.node, betterbird.betterbird_derive
long-running resources without health: 0
files asking for a setting without saying whether it is trusted: 0
1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here
judging 0 module(s) changed against 4 machine(s), as the snapshot of 2026-10-13T14:32:41Z says they run
composing every machine as the mesh is
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
composing every machine with the change
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose)
judged by controller development build against the facts of 2026-10-13T14:32:41Z
machines:
- a machine (f35): composes
- holds mesh-dns-resolver (idt): composes
- holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes
- a machine (yvbpsm): composes
a merge would rebuild 1 module(s) in 1 tier(s): betterbird
notes:
- modules/betterbird/README.md, modules/betterbird/cmd/betterbird-tools/config_test.go and modules/betterbird/cmd/betterbird-tools/install_test.go read by no module's build, and rebuild nothing (issue 280)
--- its merge-check.sh (go toolchain)
ok git.novox.be/novox/mesh-catalog/checks/words 0.010s
words: 12 retired words and 70 daemons' lines, none in the 46593 strings of 717 files
ok git.novox.be/novox/mesh-catalog/checks/agenthome 0.013s
agent-home: no archive and no file written into under the agent's home
ok git.novox.be/novox/mesh-catalog/checks/secrets 0.013s
secrets: no setting can replace a vault secret in a mergeable file (1 known, nested)
ok git.novox.be/novox/mesh-catalog/checks/paths 0.007s
paths: no module names another's state on its machine (3 known, listed to go)
long-running resources without health: 66, as kept
ok git.novox.be/novox/mesh-catalog/checks/deps 0.232s
testing modules/betterbird
ok betterbird/cmd/betterbird-tools 4.540s
--- its merge-check-typescript.sh (typescript toolchain)
```
**Out of date when it landed**: out of date: judged against trunk 305c2ba24733, the trunk is now 5f491e0c097d; judged again on a recheck or when it is merged
Structural fix and cost. Locking calendar.alarms.showmissed off in the policy (install/distribution/policies.json:26) is the right structural fix: Owl keeps a dismissal (X-MOZ-LASTACK) only in the local cache, so no profile-side fix can make Dismiss stick across a resync, and the lock makes any setting or profile value inert. The cost (a reminder that fell due while the laptop slept is not shown once its meeting has ended; one for a meeting still running is) is acceptable and is stated in README.md:117-129. No contradiction remains: the catalogue sets showmissed nowhere, and the mesh-wide prefs layer now says false (proposal s09bf8c99da7ac8b9, approved 2026-10-13 16:13). The key is now inert; it can stay in the layer as the operator's recorded choice. Nit: README.md:100 still reads "reminders missed while asleep shown (since locked off: issue 640)", correct but clumsy; and the fixture of the approved setup in cmd/betterbird-tools/config_test.go:37 still has showmissed true. Neither blocks.
betterbird_check finding (install.go:200-249, betterbird.go:984). Correct: it reads user.js before prefs.js as Betterbird applies them, reports only while Betterbird runs, gives "push the module" when the placed policy lacks the lock and "betterbird_restart" when the lock was placed after the running process started. It is quiet with no Owl calendar (install.go:208-210), quiet with the pass off, and quiet once the lock is in effect; the new test covers each case, including CalDAV. Minor: the start time is RFC3339 to the second while the policy mtime has nanoseconds (install.go:233-236), so a policy placed within the same second as the start would read as placed after it. The same pattern already exists at betterbird.go:633, and a push requires Betterbird to be quit, so it is negligible.
Policy completeness (install.go:185 adds the lock to lockedOff, read by policyProblem at install.go:288). The tools bundle (/usr/local/lib/mesh-betterbird) and the distribution archive (/opt/betterbird/distribution) are resources of the same module under upgrade policy record, so they arrive in the same push. Before the push the old binary with the old list runs: nothing changes in what any node reports. After the push the new policy is placed with the lock and policyState says "placed: ...". No node shows "the enterprise policy is placed, and it does not lock ..." unless its distribution were older than its tools, which one module push does not produce.
Tests: a fresh clone at this head, nice -n 19 go test -race -count=1 ./... in modules/betterbird: ok (4.2s); the new test and TestTheConfigShowsTheLayersAndTheDrift pass verbosely; go vet clean.
Checks: mesh/merge-gate and mesh/repo-check both PASS against trunk 305c2ba247 (4 of 4 compose; merge-check.sh passed), but that verdict is out of date (trunk is now 5f491e0c09) and the fresh run asked by the merge tool is pending in the build queue. Merge only on the new verdict.
Verdict: approve at 73404a3c3f2152bf16099501fe0c7a956c49cf4a
Independent review for issue 640.
1. Structural fix and cost. Locking calendar.alarms.showmissed off in the policy (install/distribution/policies.json:26) is the right structural fix: Owl keeps a dismissal (X-MOZ-LASTACK) only in the local cache, so no profile-side fix can make Dismiss stick across a resync, and the lock makes any setting or profile value inert. The cost (a reminder that fell due while the laptop slept is not shown once its meeting has ended; one for a meeting still running is) is acceptable and is stated in README.md:117-129. No contradiction remains: the catalogue sets showmissed nowhere, and the mesh-wide prefs layer now says false (proposal s09bf8c99da7ac8b9, approved 2026-10-13 16:13). The key is now inert; it can stay in the layer as the operator's recorded choice. Nit: README.md:100 still reads "reminders missed while asleep shown (since locked off: issue 640)", correct but clumsy; and the fixture of the approved setup in cmd/betterbird-tools/config_test.go:37 still has showmissed true. Neither blocks.
2. betterbird_check finding (install.go:200-249, betterbird.go:984). Correct: it reads user.js before prefs.js as Betterbird applies them, reports only while Betterbird runs, gives "push the module" when the placed policy lacks the lock and "betterbird_restart" when the lock was placed after the running process started. It is quiet with no Owl calendar (install.go:208-210), quiet with the pass off, and quiet once the lock is in effect; the new test covers each case, including CalDAV. Minor: the start time is RFC3339 to the second while the policy mtime has nanoseconds (install.go:233-236), so a policy placed within the same second as the start would read as placed after it. The same pattern already exists at betterbird.go:633, and a push requires Betterbird to be quit, so it is negligible.
3. Policy completeness (install.go:185 adds the lock to lockedOff, read by policyProblem at install.go:288). The tools bundle (/usr/local/lib/mesh-betterbird) and the distribution archive (/opt/betterbird/distribution) are resources of the same module under upgrade policy record, so they arrive in the same push. Before the push the old binary with the old list runs: nothing changes in what any node reports. After the push the new policy is placed with the lock and policyState says "placed: ...". No node shows "the enterprise policy is placed, and it does not lock ..." unless its distribution were older than its tools, which one module push does not produce.
4. Tests: a fresh clone at this head, `nice -n 19 go test -race -count=1 ./...` in modules/betterbird: ok (4.2s); the new test and TestTheConfigShowsTheLayersAndTheDrift pass verbosely; go vet clean.
5. Checks: mesh/merge-gate and mesh/repo-check both PASS against trunk 305c2ba24733 (4 of 4 compose; merge-check.sh passed), but that verdict is out of date (trunk is now 5f491e0c097d) and the fresh run asked by the merge tool is pending in the build queue. Merge only on the new verdict.
Merge check at 73404a3c
Judged: head 73404a3c3f21 merged into trunk 5f491e0c097d; read beside it: mesh-controller 6f1a05366c1d, mesh-controller-main 6f1a05366c1d, mesh-host 0e92ef3d1b21, mesh-tools 954e1a53eef9
mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: betterbird)
mesh/repo-check: PASS — its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
Delivery plan — builds betterbird, sent nowhere; no bus step; 2 wait(s) for a person
tier 0: betterbird
g14: waits for a person: betterbird
shanks: waits for a person: betterbird
betterbird waits for a person: its policy records (person)
read by no module's build: modules/betterbird/README.md, modules/betterbird/cmd/betterbird-tools/config_test.go, modules/betterbird/cmd/betterbird-tools/install_test.go
Run by the build seat on novox as build-1791902543309358659 (builds --log build-1791902543309358659).
--- the gate: betterbird
betterbird: ok, 17 tool(s), invokes seat:mesh-controller.settings, seat:mesh-controller.nodes, seat:mesh-controller.node, betterbird.betterbird_derive
long-running resources without health: 0
files asking for a setting without saying whether it is trusted: 0
1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here
judging 0 module(s) changed against 4 machine(s), as the snapshot of 2026-10-13T14:42:41Z says they run
composing every machine as the mesh is
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
composing every machine with the change
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose)
judged by controller development build against the facts of 2026-10-13T14:42:41Z
machines:
- a machine (f35): composes
- holds mesh-dns-resolver (idt): composes
- holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes
- a machine (yvbpsm): composes
a merge would rebuild 1 module(s) in 1 tier(s): betterbird
notes:
- modules/betterbird/README.md, modules/betterbird/cmd/betterbird-tools/config_test.go and modules/betterbird/cmd/betterbird-tools/install_test.go read by no module's build, and rebuild nothing (issue 280)
--- its merge-check.sh (go toolchain)
ok git.novox.be/novox/mesh-catalog/checks/words 0.007s
words: 12 retired words and 70 daemons' lines, none in the 46614 strings of 717 files
ok git.novox.be/novox/mesh-catalog/checks/agenthome 0.014s
agent-home: no archive and no file written into under the agent's home
ok git.novox.be/novox/mesh-catalog/checks/secrets 0.013s
secrets: no setting can replace a vault secret in a mergeable file (1 known, nested)
ok git.novox.be/novox/mesh-catalog/checks/paths 0.006s
paths: no module names another's state on its machine (3 known, listed to go)
long-running resources without health: 66, as kept
ok git.novox.be/novox/mesh-catalog/checks/deps 0.286s
testing modules/betterbird
ok betterbird/cmd/betterbird-tools 5.156s
--- its merge-check-typescript.sh (typescript toolchain)
**Merge check** at `73404a3c`
Judged: head `73404a3c3f21` merged into trunk `5f491e0c097d`; read beside it: mesh-controller `6f1a05366c1d`, mesh-controller-main `6f1a05366c1d`, mesh-host `0e92ef3d1b21`, mesh-tools `954e1a53eef9`
- `mesh/merge-gate`: **PASS** — every machine composes with the change as it did without (4 of 4 compose) (modules: betterbird)
- `mesh/repo-check`: **PASS** — its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
**Delivery plan** — builds betterbird, sent nowhere; no bus step; 2 wait(s) for a person
- tier 0: betterbird
- g14: waits for a person: betterbird
- shanks: waits for a person: betterbird
- betterbird waits for a person: its policy records (person)
- read by no module's build: modules/betterbird/README.md, modules/betterbird/cmd/betterbird-tools/config_test.go, modules/betterbird/cmd/betterbird-tools/install_test.go
Run by the build seat on novox as `build-1791902543309358659` (`builds --log build-1791902543309358659`).
```
--- the gate: betterbird
betterbird: ok, 17 tool(s), invokes seat:mesh-controller.settings, seat:mesh-controller.nodes, seat:mesh-controller.node, betterbird.betterbird_derive
long-running resources without health: 0
files asking for a setting without saying whether it is trusted: 0
1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here
judging 0 module(s) changed against 4 machine(s), as the snapshot of 2026-10-13T14:42:41Z says they run
composing every machine as the mesh is
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
composing every machine with the change
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose)
judged by controller development build against the facts of 2026-10-13T14:42:41Z
machines:
- a machine (f35): composes
- holds mesh-dns-resolver (idt): composes
- holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes
- a machine (yvbpsm): composes
a merge would rebuild 1 module(s) in 1 tier(s): betterbird
notes:
- modules/betterbird/README.md, modules/betterbird/cmd/betterbird-tools/config_test.go and modules/betterbird/cmd/betterbird-tools/install_test.go read by no module's build, and rebuild nothing (issue 280)
--- its merge-check.sh (go toolchain)
ok git.novox.be/novox/mesh-catalog/checks/words 0.007s
words: 12 retired words and 70 daemons' lines, none in the 46614 strings of 717 files
ok git.novox.be/novox/mesh-catalog/checks/agenthome 0.014s
agent-home: no archive and no file written into under the agent's home
ok git.novox.be/novox/mesh-catalog/checks/secrets 0.013s
secrets: no setting can replace a vault secret in a mergeable file (1 known, nested)
ok git.novox.be/novox/mesh-catalog/checks/paths 0.006s
paths: no module names another's state on its machine (3 known, listed to go)
long-running resources without health: 66, as kept
ok git.novox.be/novox/mesh-catalog/checks/deps 0.286s
testing modules/betterbird
ok betterbird/cmd/betterbird-tools 5.156s
--- its merge-check-typescript.sh (typescript toolchain)
```
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Fixes issue 640 (Betterbird on g14 re-shows ten month-old Exchange meeting reminders about every ten minutes, and Dismiss does not hold).
Cause (read in Betterbird 153.4 and Owl 1.5.6.1 as installed in /opt/betterbird)
CalAlarmService.addAlarmsForItem: for a past reminder on a writable calendar, withcalendar.alarms.showmissedtrue it fires every reminder whose item has noalarmLastAck(X-MOZ-LASTACK) at or after the reminder. With it false (Betterbird's default) a past reminder fires only while the meeting has not ended.initAlarmslooks back one month (MAX_SNOOZE_MONTHS) and runs at every start, every calendaronLoad(each refresh of a cached calendar), eachonCalendarRegistered(Owl re-registers its calendar after each sign-in,StartupAfterLogin->reloadCalendars), and whensuppressAlarms/disabled/notifications.timeschange. 15 to 21 September is inside the month before 13 October.modifyItemcompares JSON that carries only the reminder's offset (and for an invitation reduces it to the reminder alone), so X-MOZ-LASTACK lives only in Betterbird's local cache (cache.sqlite). Owl's sync keeps it while it updates an item from the cache, and loses it when it resynchronises from scratch (replayChangesOnretry,resetLog).showmissed: true(the operator's earlier approval); before that the pass was off.betterbird_config_applyclears only managed preferences).Fix
install/distribution/policies.jsonlockscalendar.alarms.showmissedto false (Status: locked; Betterbird'sPreferencespolicy accepts thecalendar.prefix). Whatever the settingprefsor the profile says, a passed meeting's reminder is not shown again. Cost: a reminder that fell due while the laptop slept is not shown after the meeting ended; one for a meeting still running still is.lockedOffnames the lock, sobetterbird_status/betterbird_checksay a policy without it is not the module's.betterbird_checkgains a finding: the running Betterbird has the pass on (user.js, else prefs.js) while the profile holds an Owl calendar, and the lock is not in effect. Remedy: push the module, orbetterbird_restartwhen the policy was placed after the start. A CalDAV-only profile is not flagged (CalDAV keeps X-MOZ-LASTACK on its server).Tests
TestAPassedMeetingsReminderIsNotShownAgainAtEachReloadOfAnOwlCalendar: the repo's policy locks the pass off; a policy without the lock is named; g14's state (user.js true, Owl calendar, no lock) is a finding with the push remedy; lock placed after the start -> restart remedy; lock in effect -> no finding even with prefs.js true; pass off, or CalDAV only -> no finding.the module's policy locks calendar.alarms.showmissed off: {Value: Locked:false}; run with the constant written out, sincemissedPrefis new).TestTheConfigShowsTheLayersAndTheDriftused showmissed as its example of a declared preference; it usescalendar.alarms.playsoundnow, and counts the locks bylockedOff.MESH_GATE=<controller built from mesh-controller main 6f1a053> MESH_CHECK_MODULES=betterbird sh merge-check.shpasses.Acceptance
A passed meeting's reminder shows at most once (with the lock: not at all once the meeting is over), and Dismiss makes it stay away across Owl syncs and restarts: after the push and a Betterbird restart,
betterbird_prefsnamescalendar.alarms.showmissedfalse andbetterbird_checkhas no Owl-reminder finding, and no reminder of a past meeting appears over the next hour.Rollout
The module's upgrade policy is
record: the operator quits Betterbird and pushes it to g14 and shanks; Betterbird starts again from the launcher (orbetterbird_restart). The setting's showmissed false (proposal s09bf8c99da7ac8b9, issue 641) is no longer needed for this, and is harmless beside the lock.Delivery
novox/mesh-catalog@73404a3c3f21— delivered since 2026-10-13T14:49:28ZDelivery plan — builds betterbird, sent nowhere; no bus step; 2 wait(s) for a person
Transitions
The commit's note under
refs/notes/mesh-plankeeps every transition:git log --notes=mesh-plan.Merge check at
73404a3cJudged: head
73404a3c3f21merged into trunk305c2ba24733; read beside it: mesh-controller6f1a05366c1d, mesh-controller-main6f1a05366c1d, mesh-host0e92ef3d1b21, mesh-tools954e1a53eef9mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: betterbird)mesh/repo-check: PASS — its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)Delivery plan — builds betterbird, sent nowhere; no bus step; 2 wait(s) for a person
Run by the build seat on novox as
build-1791901976000711509(builds --log build-1791901976000711509).Out of date when it landed: out of date: judged against trunk
305c2ba247, the trunk is now 5f491e0c097d; judged again on a recheck or when it is mergedVerdict: approve at
73404a3c3fIndependent review for issue 640.
Structural fix and cost. Locking calendar.alarms.showmissed off in the policy (install/distribution/policies.json:26) is the right structural fix: Owl keeps a dismissal (X-MOZ-LASTACK) only in the local cache, so no profile-side fix can make Dismiss stick across a resync, and the lock makes any setting or profile value inert. The cost (a reminder that fell due while the laptop slept is not shown once its meeting has ended; one for a meeting still running is) is acceptable and is stated in README.md:117-129. No contradiction remains: the catalogue sets showmissed nowhere, and the mesh-wide prefs layer now says false (proposal s09bf8c99da7ac8b9, approved 2026-10-13 16:13). The key is now inert; it can stay in the layer as the operator's recorded choice. Nit: README.md:100 still reads "reminders missed while asleep shown (since locked off: issue 640)", correct but clumsy; and the fixture of the approved setup in cmd/betterbird-tools/config_test.go:37 still has showmissed true. Neither blocks.
betterbird_check finding (install.go:200-249, betterbird.go:984). Correct: it reads user.js before prefs.js as Betterbird applies them, reports only while Betterbird runs, gives "push the module" when the placed policy lacks the lock and "betterbird_restart" when the lock was placed after the running process started. It is quiet with no Owl calendar (install.go:208-210), quiet with the pass off, and quiet once the lock is in effect; the new test covers each case, including CalDAV. Minor: the start time is RFC3339 to the second while the policy mtime has nanoseconds (install.go:233-236), so a policy placed within the same second as the start would read as placed after it. The same pattern already exists at betterbird.go:633, and a push requires Betterbird to be quit, so it is negligible.
Policy completeness (install.go:185 adds the lock to lockedOff, read by policyProblem at install.go:288). The tools bundle (/usr/local/lib/mesh-betterbird) and the distribution archive (/opt/betterbird/distribution) are resources of the same module under upgrade policy record, so they arrive in the same push. Before the push the old binary with the old list runs: nothing changes in what any node reports. After the push the new policy is placed with the lock and policyState says "placed: ...". No node shows "the enterprise policy is placed, and it does not lock ..." unless its distribution were older than its tools, which one module push does not produce.
Tests: a fresh clone at this head,
nice -n 19 go test -race -count=1 ./...in modules/betterbird: ok (4.2s); the new test and TestTheConfigShowsTheLayersAndTheDrift pass verbosely; go vet clean.Checks: mesh/merge-gate and mesh/repo-check both PASS against trunk
305c2ba247(4 of 4 compose; merge-check.sh passed), but that verdict is out of date (trunk is now5f491e0c09) and the fresh run asked by the merge tool is pending in the build queue. Merge only on the new verdict.Merge check at
73404a3cJudged: head
73404a3c3f21merged into trunk5f491e0c097d; read beside it: mesh-controller6f1a05366c1d, mesh-controller-main6f1a05366c1d, mesh-host0e92ef3d1b21, mesh-tools954e1a53eef9mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: betterbird)mesh/repo-check: PASS — its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)Delivery plan — builds betterbird, sent nowhere; no bus step; 2 wait(s) for a person
Run by the build seat on novox as
build-1791902543309358659(builds --log build-1791902543309358659).