Merge pull request 'Grant the bus's own module the snapshot API and nothing else (hq ADR 0235)' (#90) from feat/bus-snapshot into main
mesh/delivery delivered

This commit was merged in pull request #90.
This commit is contained in:
2026-10-06 16:24:53 +00:00
11 changed files with 463 additions and 2 deletions
+67
View File
@@ -0,0 +1,67 @@
package catalogue
import (
"os"
"strings"
"testing"
)
// The module holding mesh-broker is the bus, and its account is granted the bus's snapshot API and
// nothing else (novox/hq ADR 0235). Anything it declared to say or hear on the bus would be granted
// nothing, so it is refused at the parser rather than silently dropped.
func TestTheBussAccountSaysNothingOnTheBus(t *testing.T) {
raw := []byte(`{"module":"bus","version":"1","provides":[{"name":"mesh-bus","scope":"mesh"}],
"claims":[{"name":"mesh-broker","scope":"mesh"}],"own-secrets":{"broker":"/run/broker"},
"emits":["something.happened"]}`)
_, err := ParseManifest(raw)
if err == nil || !strings.Contains(err.Error(), "granted the bus's snapshot API and nothing else") {
t.Fatalf("a bus module declaring what it emits was not refused, or not for the reason: %v", err)
}
quiet := []byte(`{"module":"bus","version":"1","provides":[{"name":"mesh-bus","scope":"mesh"}],
"claims":[{"name":"mesh-broker","scope":"mesh"}],"own-secrets":{"broker":"/run/broker"},
"tools":["bus_streams"]}`)
m, err := ParseManifest(quiet)
if err != nil {
t.Fatalf("a bus module with an account and tools was refused: %v", err)
}
if !m.ClaimsSeat(BrokerSeat) {
t.Fatal("it does not read as holding the broker seat")
}
}
// The catalogue's bus protects its streams by the snapshot, not as live files: its streams' item is a
// dump into its snapshots, it has the account the dump runs as, and the dump runs the snapshot
// program in the bus's own container.
func TestTheCataloguesBusIsBackedUpBySnapshot(t *testing.T) {
raw, err := os.ReadFile("../../../mesh-catalog/modules/nats/module.json")
if err != nil {
t.Skip("the catalogue is not checked out beside this repository")
}
m, err := ParseManifest(raw)
if err != nil {
t.Fatal(err)
}
if _, account := m.OwnSecrets["broker"]; !account {
t.Fatal("the bus declares no account, so its snapshot could not reach it")
}
var found bool
if m.Data == nil {
t.Fatal("the bus declares no data")
}
for _, it := range m.Data.Own {
if it.ID != "jetstream" {
continue
}
found = true
if !it.Backup.IsDump() || it.Backup.Into != "snapshots" {
t.Fatalf("the bus's streams are protected by %+v, not a snapshot into its snapshots", it.Backup)
}
if !strings.Contains(it.Backup.Dump, "mesh-nats-snapshot snapshot") {
t.Fatalf("the dump does not run the snapshot program: %s", it.Backup.Dump)
}
}
if !found {
t.Fatal("the bus declares no item for its streams")
}
}
+23
View File
@@ -1402,6 +1402,29 @@ func ParseManifest(raw []byte) (Manifest, error) {
m.Module, offer.Name))
}
}
// **The bus's own account copies the bus and says nothing on it** (novox/hq ADR 0235). The module
// holding mesh-broker is granted the snapshot API and nothing else, so anything it declared to say
// or hear on the bus would be granted nothing — refused here rather than silently dropped.
if _, account := m.OwnSecrets["broker"]; account && m.ClaimsSeat(BrokerSeat) {
var said []string
if len(m.EmitsAll()) > 0 {
said = append(said, "emits")
}
for _, f := range []struct {
name string
n int
}{{"consumes", len(m.Consumes)}, {"uses", len(m.Uses)}, {"invokes", len(m.Invokes)},
{"state", len(m.State)}, {"reads", len(m.Reads)}} {
if f.n > 0 {
said = append(said, f.name)
}
}
if len(said) > 0 {
problems = append(problems, fmt.Sprintf(
"%s holds %s and declares a bus account, which is granted the bus's snapshot API and nothing "+
"else (novox/hq ADR 0235); its %s would be granted nothing", m.Module, BrokerSeat, strings.Join(said, ", ")))
}
}
for _, r := range m.Requires {
if r == "amqp" {
problems = append(problems, fmt.Sprintf(
+5 -1
View File
@@ -106,7 +106,7 @@ var defaultSeats = append([]Seat{
// the mesh's own transport. ADR 0128 then made that connection something a module requires
// rather than receives ambiently — 23 of the catalogue's modules never speak, and an ambient
// connection would mint a credential for each.
{Name: "mesh-broker", Scope: ScopeMesh, Delivers: "mesh-bus", Decision: "novox/hq ADR 0079"},
{Name: BrokerSeat, Scope: ScopeMesh, Delivers: "mesh-bus", Decision: "novox/hq ADR 0079"},
// The vault: the controller seals every minted credential with what it provides, which is the
// test for a seat of the mesh's own (novox/hq ADR 0161) — a second provider of `secret` is a
// second claimant, refused by name, rather than a candidate for a pin.
@@ -635,3 +635,7 @@ func buildAgentVerbs() []Verb {
Input: schema(map[string]string{}, nil)},
}
}
// BrokerSeat is the mesh's bus. Its holder is the bus, and its account — when it declares one — may
// snapshot the bus's streams and do nothing else (novox/hq ADR 0235).
const BrokerSeat = "mesh-broker"