diff --git a/go/buscompose/buscompose_test.go b/go/buscompose/buscompose_test.go index e4f8212..968a7c8 100644 --- a/go/buscompose/buscompose_test.go +++ b/go/buscompose/buscompose_test.go @@ -656,11 +656,19 @@ func healthChecks(t *testing.T) { if name == WatchmanModule { // It hears its watchdogs' words on core subjects (Limits 3, 11). for _, subject := range []string{"mesh.mod.power.event.sleeping", "mesh.control.g14.tools-alive", - "mesh.seat.mesh-self-check.event.checked", "mesh.mod.keycloak.event.provisioner.failing"} { + "mesh.seat.mesh-self-check.event.checked", "mesh.mod.keycloak.event.provisioner.failing", + "mesh.mod.nats.event.bus.slow-consumer", "prov.applied.g14.keycloak"} { if !MaySubscribe(perms, subject) { t.Errorf("the watchman may not hear %s", subject) } } + // Never the bus's API audit, which carries every API answer in the account (the lead, 2026-10-29). + if MaySubscribe(perms, "$JS.EVENT.ADVISORY.API") || MaySubscribe(perms, "$JS.EVENT.ADVISORY.API.x") { + t.Error("the watchman hears the bus's API audit") + } + if !MayPublish(perms, "$JS.API.DIRECT.GET.PROV_APPLIED.prov.applied.g14.keycloak") { + t.Error("the watchman may not read the newest provisioner report") + } } subjects := append(append([]string(nil), may[name]...), may[name+" sources"]...) denied := append(append([]string(nil), mayNot[name]...), mayNot[name+" sources"]...) diff --git a/go/buscompose/health_grants.go b/go/buscompose/health_grants.go index e795a85..e31a935 100644 --- a/go/buscompose/health_grants.go +++ b/go/buscompose/health_grants.go @@ -89,6 +89,10 @@ func WatchmanDeadLetterGrants(sources []string) []string { "$JS.API.STREAM.INFO." + DeadLettersStream, "$JS.API.STREAM.MSG.GET." + DeadLettersStream, deadLetterPrefix + ">", + // And the provisioner clients' reports, the newest per subject read at its start (S21, ADR 0362 ยง3). + "$JS.API.STREAM.INFO." + ProvAppliedStream, + "$JS.API.STREAM.MSG.GET." + ProvAppliedStream, + "$JS.API.DIRECT.GET." + ProvAppliedStream + ".>", } for _, s := range DeadLetterSources(sources) { out = append(out, "$JS.API.STREAM.INFO."+s, "$JS.API.STREAM.MSG.GET."+s) @@ -98,7 +102,10 @@ func WatchmanDeadLetterGrants(sources []string) []string { // WatchmanHears are the core subjects the watchman subscribes, with no durable consumer (ADR 0564 clauses 4, 5 and // Limits 3, 11): the power seat's words (S1, ADR 0211), the providers' failing and recovered words (S8), each tool -// runner's beat (S11), the self-check's checked (S10), a consumer deleted (S9), and the dead letters kept. +// runner's beat (S11), the self-check's checked (S10), a consumer deleted and the bus module's slow consumers and closed +// connections (S9), the provisioner clients' reports (S21), and the dead letters kept. Never the bus's API audit +// (`$JS.EVENT.ADVISORY.API`): it carries every API call's request and answer in the account, the vault's hashes among +// them (the lead, 2026-10-29). func WatchmanHears() []string { return []string{ "mesh.mod.power.event.*", @@ -107,6 +114,9 @@ func WatchmanHears() []string { "mesh.control.*.tools-alive", "mesh.seat.mesh-self-check.event." + health.EventChecked, "$JS.EVENT.ADVISORY.CONSUMER.DELETED.>", + "mesh.mod.nats.event.bus.slow-consumer", + "mesh.mod.nats.event.bus.connection-closed", + ProvAppliedSubjects, deadLetterPrefix + ">", } }