controller v0.264.0: the household is told when an update is undone or held, in its language
gates / gates (push) Successful in 25s

app_update_undone / app_update_held events (09 decision 15), on by
default and seeded once on existing boxes; R-606 update sentences as
key+args rendered per reader; R-646 startup applied-meta backfill for
apps current with the catalog; R-620 a disabled notifier WARNs once per
event type. Needs hub v0.120.0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-09-23 13:51:21 +02:00
parent c3a2aba0d2
commit bc278944a3
27 changed files with 1086 additions and 86 deletions
@@ -0,0 +1,23 @@
package main
import "testing"
// v0.264.0. The update-event sink and the R-646 backfill are both CALLED from main.go (an AST walk —
// a comment naming them would not count). A sink built and never wired would fail silently: no event,
// no mail, no error — this project's commonest defect, four recorded instances.
//
// COMPANION RED-PROOF (REPORT.md): comment out the SetUpdateEventSink call in main.go — this fails.
func TestUpdateEventSinkIsWiredAtStartup(t *testing.T) {
lines, _, _ := slice4CallLines(t)
if len(lines["SetUpdateEventSink"]) == 0 {
t.Fatal("SetUpdateEventSink is never called — an undone or held update would tell nobody")
}
for _, callee := range []string{"NotifyAppUpdateUndone", "NotifyAppUpdateHeld", "RestoreHoldForLang"} {
if len(lines[callee]) == 0 {
t.Errorf("main.go must call %s inside the sink", callee)
}
}
if len(lines["BackfillAppliedMeta"]) == 0 || len(lines["AdoptPins"]) == 0 || lines["BackfillAppliedMeta"][0] < lines["AdoptPins"][0] {
t.Error("BackfillAppliedMeta (R-646) must be called, after AdoptPins")
}
}