From b6ef01d6c6a7db4f45b2eaa44365132c42466993 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 29 Oct 2026 21:57:50 +0100 Subject: [PATCH] Let the watchman hear the bus module's slow consumers and closed connections and read the provisioner reports, and never the bus's API audit, so S9 and S21 have their facts without the vault's hashes (issue 1952 S5) --- go/buscompose/buscompose_test.go | 10 +++++++++- go/buscompose/health_grants.go | 12 +++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) 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 + ">", } }