v0.256.0: the box sends its own sentence in the household's language (R-558 Part B)
gates / gates (push) Successful in 24s

MinAgent: 0.131.0 (unchanged). Needs hub v0.118.0+, which shipped first and
tolerates a box that sends none of this - every box in the fleet is that box
until this release reaches it.

The hub writes a household's e-mails in their language now, but about a third
of those mails carry a sentence the BOX composed, naming a drive, an app or a
number. The hub cannot translate one. So the box sends it twice.

- message_customer on POST /api/v1/event, omitempty. A HUNGARIAN household
  sends nothing extra at all, so its payload stays byte-for-byte what every box
  sends today and the hub's fallback path keeps being the one production
  exercises rather than a branch nobody takes.
- 19 producers render both sentences from ONE bundle key. `message` stays
  Hungarian always: it is what the operator is mailed and what the hub logs.
- customer.language bootstraps a new box - stored choice, then config, then
  Hungarian. The config value is NEVER written into settings.json: that would
  record a choice the household never made.

The Hungarian did not move, measured twice: the wire golden from the slice-2
base commit, and the Go parity gate over all 19 new keys.

Three guards had to learn the change and one caught me: the test seam now
carries the new field; the R-329 severity register reported two dynamic sites
as no longer existing the moment they moved off PushEvent (the walk now checks
36 severity literals, up from 20); and TestConfigLanguageIsWiredInMain reads
main.go, because cmd/ is gitignored and ripgrep does not.

A mistake, named: the first pass dropped displayName from three producers,
which would have mailed customers "Alkalmazás telepítve: %!s(MISSING)". Caught
reading the diff; now pinned by a test that refuses %!/MISSING/%s/%d in either
language.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-09-18 16:54:20 +02:00
parent 8fb2f9ef9d
commit bef39598d0
16 changed files with 1601 additions and 1068 deletions
@@ -49,6 +49,14 @@ var hubSeverityVocabulary = map[string]bool{
var severityArg = map[string]int{
"emit": 1, // (eventType, severity, message, details)
"PushEvent": 1, // (eventType, severity, message, details)
// v0.256.0 (R-558): the key-based producers. Registered here in the SAME commit that introduced
// them — the walk finds severities by FUNCTION NAME, so a new push helper that is not listed is
// a set of call sites this contract silently stops checking. It caught its own omission: the
// conversion moved two dynamic sites off PushEvent and the register immediately reported them as
// no longer existing, which is the "a stale register entry is also a failure" half doing its job.
"pushEventMsg": 1, // (eventType, severity, key, details, args...)
"pushEventMsgSuffix": 1, // (eventType, severity, key, suffix, details, args...)
"pushEventBoth": 1, // (eventType, severity, message, messageCustomer, details)
}
// knownDynamicSeveritySites are the call sites that pass a VARIABLE rather than a literal, so this
@@ -57,7 +65,13 @@ var severityArg = map[string]int{
// honest limit of an AST check is that it cannot follow a variable, and an unlisted limit is not a
// limit, it is a hole.
var knownDynamicSeveritySites = map[string]string{
"internal/notify/notifier.go:emit": `pass-through of its own severity parameter to PushEvent — the value is checked at emit's CALLERS, above`,
"internal/notify/notifier.go:emit": `pass-through of its own severity parameter to PushEvent — the value is checked at emit's CALLERS, above`,
// v0.256.0 (R-558): three more pass-throughs, the same shape as emit. Each forwards its OWN
// severity parameter to pushEventBoth without inspecting it, so the value is checked at their
// callers — which is where the walk's 36 literals are found.
"internal/notify/notifier.go:PushEvent": `pass-through of its own severity parameter to pushEventBoth — checked at PushEvent's callers`,
"internal/notify/notifier.go:pushEventMsg": `pass-through of its own severity parameter to pushEventBoth — checked at pushEventMsg's callers`,
"internal/notify/notifier.go:pushEventMsgSuffix": `pass-through of its own severity parameter to pushEventBoth — checked at pushEventMsgSuffix's callers`,
"internal/notify/notifier.go:NotifyControllerUpdated": `local var: "info", or "error" when the update failed`,
"internal/notify/notifier.go:NotifyDRCompleted": `local var: "info", or "warning" when failCount > 0`,
"internal/notify/notifier.go:NotifyAgentChannelDown": `internal/channelhealth's classifier — every severity literal in checker.go is "warning" or "error"`,