From 05ff6065d0301a4f0606f4da0be1f228ec43c895 Mon Sep 17 00:00:00 2001 From: jochen Date: Sun, 27 Sep 2026 14:43:16 +0200 Subject: [PATCH] Event names are checked now, per manifest and across the catalogue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue 127 stood because nothing compared the two halves. Every manifest was well-formed on its own and every derivation correct on its own, and no cross-module subscription in the mesh matched anything — a subscription that matches nothing is not an error, it is silence. Two checks, because the mistake is possible at two scales. Per manifest: an event is a local name, and `module.` is refused with the name to write instead. A module emitting under what reads as another module's name is refused too, pointing at the seat, where a name outlives whoever holds it. Across the catalogue: where a consumed event's emitter is present, it must emit that event. It cannot demand a live emitter for everything — a module lives in its own repository and may be installed long before the one whose events it wants — so the rule is narrower and still catches this. It found two real dangling subscriptions the moment it ran. Wildcards were undecided and two manifests needed them: `*` is one name and `**` is the rest, spelled the mesh's way and derived to `>` here and `#` on the old bus. A manifest naming either would stop being true when the wire changed, which is the whole reason names are local. And the field documentation taught the old form, examples included — which is why the drift was uniform across 37 manifests rather than scattered. Nobody was guessing; everybody followed the comment. --- internal/broker/agreement.go | 125 +++++++++++++++ internal/broker/agreement_catalogue_test.go | 108 +++++++++++++ internal/broker/derived.go | 8 +- internal/broker/nats.go | 56 ++++++- internal/catalogue/events.go | 161 ++++++++++++++++++++ internal/catalogue/manifest.go | 32 +++- internal/catalogue/no_subjects_test.go | 48 ++++++ internal/inventory/busrecords.go | 4 +- 8 files changed, 526 insertions(+), 16 deletions(-) create mode 100644 internal/broker/agreement.go create mode 100644 internal/broker/agreement_catalogue_test.go create mode 100644 internal/catalogue/events.go diff --git a/internal/broker/agreement.go b/internal/broker/agreement.go new file mode 100644 index 0000000..3f4fc6b --- /dev/null +++ b/internal/broker/agreement.go @@ -0,0 +1,125 @@ +package broker + +import ( + "fmt" + "sort" + "strings" +) + +// Do the emitters and the consumers of a catalogue agree? +// +// **The check that was missing** (novox/hq 04-ISSUES/127). Every manifest was individually +// well-formed and every derivation individually correct, and no cross-module subscription in the +// mesh matched anything: a consumer's declaration derived into a namespace nobody publishes to. +// Nothing failed, because a subscription that matches nothing is not an error — it is silence. +// +// The comparison has to be over the whole catalogue, because the two halves live in different +// manifests, and it cannot simply demand that every consumed event have a live emitter: a module +// may be installed long before the one whose events it wants. So the rule is narrower and still +// catches this: **where the emitter is present, it must emit what the consumer asked for.** + +// AConsumer is one module's interest in another's events, as this check needs it. +type AConsumer struct { + Module string + Consumes []string +} + +// AnEmitter is one module's events. +type AnEmitter struct { + Module string + Emits []string +} + +// Disagreements are the consumed events whose emitter is in the catalogue and does not emit them. +// +// Returned as sentences rather than as structs: every one of them is read by a person deciding +// whether a manifest or a catalogue is wrong, and a pair of names without the reason is a puzzle. +func Disagreements(emitters []AnEmitter, consumers []AConsumer, seats []DeclaredSeat) []string { + emits := map[string]map[string]bool{} + for _, e := range emitters { + if emits[e.Module] == nil { + emits[e.Module] = map[string]bool{} + } + for _, name := range e.Emits { + emits[e.Module][name] = true + } + } + // A seat's events are published by its holder under the seat's name, so a consumer naming the + // seat is naming something real even though no module declares it as its own. + for _, s := range seats { + if len(s.Emits) == 0 { + continue + } + if emits[s.Name] == nil { + emits[s.Name] = map[string]bool{} + } + for _, name := range s.Emits { + emits[s.Name][name] = true + } + } + + var out []string + for _, c := range consumers { + for _, pattern := range c.Consumes { + emitter, event, named := strings.Cut(pattern, ".") + // Every event from everyone, or every event from one module: both are deliberate and + // neither names a particular event to check. + if !named || emitter == "*" || emitter == catalogueTheRest || event == catalogueTheRest { + continue + } + known, present := emits[emitter] + if !present { + // Not installed here, which is ordinary: a module lives in its own repository and + // may be registered later. Nothing to compare, so nothing to say. + continue + } + if matchesAny(event, known) { + continue + } + out = append(out, fmt.Sprintf( + "%s consumes %q and %s emits %s — so that subscription would match nothing, and "+ + "nothing would report it", + c.Module, pattern, emitter, listOf(known))) + } + } + sort.Strings(out) + return out +} + +// matchesAny says whether one of an emitter's event names satisfies a consumer's pattern. +func matchesAny(pattern string, emitted map[string]bool) bool { + want := strings.Split(pattern, ".") + for name := range emitted { + if matches(want, strings.Split(name, ".")) { + return true + } + } + return false +} + +func matches(pattern, name []string) bool { + for i, part := range pattern { + if part == catalogueTheRest { + return i < len(name) + } + if i >= len(name) { + return false + } + if part != "*" && part != name[i] { + return false + } + } + return len(pattern) == len(name) +} + +func listOf(names map[string]bool) string { + if len(names) == 0 { + return "nothing" + } + out := make([]string, 0, len(names)) + for n := range names { + out = append(out, n) + } + sort.Strings(out) + return strings.Join(out, ", ") +} diff --git a/internal/broker/agreement_catalogue_test.go b/internal/broker/agreement_catalogue_test.go new file mode 100644 index 0000000..9c4a348 --- /dev/null +++ b/internal/broker/agreement_catalogue_test.go @@ -0,0 +1,108 @@ +package broker + +import ( + "encoding/json" + "os" + "path/filepath" + "strings" + "testing" +) + +// **Do the catalogue's emitters and consumers agree?** +// +// This is the check whose absence let issue 127 stand: every manifest was individually well-formed, +// every derivation individually correct, and no cross-module subscription in the mesh matched +// anything. A subscription that matches nothing is not an error — it is silence — so nothing +// anywhere reported it. +// +// It compares what one manifest asks to hear against what another says it emits. It cannot demand +// that every consumed event have a live emitter, because a module lives in its own repository and +// may be registered long before the one whose events it wants. Where the emitter *is* here, it must +// emit what the consumer asked for. +func TestTheCataloguesEmittersAndConsumersAgree(t *testing.T) { + emitters, consumers, seats := theCataloguesEvents(t) + + if bad := Disagreements(emitters, consumers, seats); len(bad) > 0 { + t.Fatalf("%d subscription(s) in the catalogue would match nothing:\n %s", + len(bad), strings.Join(bad, "\n ")) + } +} + +// And the check itself catches the thing it exists for, so it cannot pass by doing nothing. +func TestTheAgreementCheckCatchesASubscriptionThatMatchesNothing(t *testing.T) { + bad := Disagreements( + []AnEmitter{{Module: "builder", Emits: []string{"built"}}}, + []AConsumer{{Module: "mesh-catalog", Consumes: []string{"builder.finished"}}}, + nil) + if len(bad) != 1 { + t.Fatalf("a consumer asking for an event its emitter does not emit was not caught: %v", bad) + } + if !strings.Contains(bad[0], "builder.finished") || !strings.Contains(bad[0], "built") { + t.Fatalf("the report names neither what was asked for nor what is emitted: %s", bad[0]) + } + + // A module that is not here is not a disagreement: it may be registered later. + if bad := Disagreements(nil, + []AConsumer{{Module: "plex", Consumes: []string{"sonarr.download.completed"}}}, nil); len(bad) != 0 { + t.Fatalf("a consumer whose emitter is not installed was reported: %v", bad) + } + + // A wildcard over emitters is deliberate and names no particular event to check. + if bad := Disagreements([]AnEmitter{{Module: "sonarr", Emits: []string{"download.completed"}}}, + []AConsumer{{Module: "plex", Consumes: []string{"*.download.completed"}}}, nil); len(bad) != 0 { + t.Fatalf("a wildcard over emitters was reported: %v", bad) + } + + // An event published under a seat's name is real even though no module declares it as its own. + if bad := Disagreements(nil, + []AConsumer{{Module: "watcher", Consumes: []string{"mesh-artifact-store.image.pushed"}}}, + []DeclaredSeat{{Name: "mesh-artifact-store", Emits: []string{"image.pushed"}}}); len(bad) != 0 { + t.Fatalf("an event a seat emits was reported as matching nothing: %v", bad) + } +} + +func theCataloguesEvents(t *testing.T) ([]AnEmitter, []AConsumer, []DeclaredSeat) { + t.Helper() + root := filepath.Join("..", "..", "..", "mesh-catalog", "modules") + entries, err := os.ReadDir(root) + if err != nil { + t.Skipf("catalogue sibling not present: %v", err) + } + var emitters []AnEmitter + var consumers []AConsumer + var seats []DeclaredSeat + for _, e := range entries { + if !e.IsDir() { + continue + } + raw, err := os.ReadFile(filepath.Join(root, e.Name(), "module.json")) + if err != nil { + continue + } + var m struct { + Module string `json:"module"` + Emits []string `json:"emits"` + Consumes []string `json:"consumes"` + Seats []struct { + Name string `json:"name"` + Emits []string `json:"emits"` + } `json:"seats"` + } + if err := json.Unmarshal(raw, &m); err != nil { + t.Fatalf("%s: %v", e.Name(), err) + } + if len(m.Emits) > 0 { + emitters = append(emitters, AnEmitter{Module: m.Module, Emits: m.Emits}) + } + if len(m.Consumes) > 0 { + consumers = append(consumers, AConsumer{Module: m.Module, Consumes: m.Consumes}) + } + for _, s := range m.Seats { + seats = append(seats, DeclaredSeat{Name: s.Name, Emits: s.Emits}) + } + } + if len(emitters) == 0 { + t.Skip("no manifests found beside this checkout") + } + return emitters, consumers, seats +} diff --git a/internal/broker/derived.go b/internal/broker/derived.go index 97d3174..b5e15c0 100644 --- a/internal/broker/derived.go +++ b/internal/broker/derived.go @@ -85,8 +85,12 @@ func SeatStreams(seats []DeclaredSeat) []Stream { // package stays free of the catalogue's own types — the same reason the host mirrors the // contracts instead of importing the sdk. type DeclaredSeat struct { - Name string - Accepts []string + Name string + Accepts []string + // Emits are the verbs the seat's holder publishes under the seat's own name. An event about a + // role belongs here rather than in the holder's namespace, because the name then outlives + // whoever fills it (novox/hq 04-ISSUES/127). + Emits []string RetainSeconds int } diff --git a/internal/broker/nats.go b/internal/broker/nats.go index 579079e..4e0a7aa 100644 --- a/internal/broker/nats.go +++ b/internal/broker/nats.go @@ -241,12 +241,11 @@ func PermissionsFor(p Principal) (Permissions, error) { // 2. What it consumes, by the emitter's own subject — an event is addressed to its // emitter, because the emitter's identity is the meaning (ADR 0118). for _, c := range p.Consumes { - emitter, event, ok := strings.Cut(c, ".") - if !ok { - return Permissions{}, fmt.Errorf( - "%q does not name an emitter and an event: a consumed event is .", c) + subject, err := consumedSubject(c) + if err != nil { + return Permissions{}, err } - sub = append(sub, "mesh.mod."+emitter+".event."+event) + sub = append(sub, subject) } // 3. Seats it holds: full participation. @@ -331,6 +330,53 @@ func seatSubject(s Seat, kind, verb string) string { // // They are derived here, beside the permission that must match them, because two places deriving // the same name is how a module ends up unable to ack its own deliveries. +// consumedSubject is where a consumed event lands, from the local pattern a module declared. +// +// **The mesh's wildcards become this transport's** (design 29 §1): `*` is one name on both, and `**` +// — the rest — is `>` here. A module writes neither transport's spelling, so a manifest stays correct +// when the wire changes, which is the whole reason names are local. +// +// `**` on its own is every event from every module: the emitter is any, the event is anything. An +// audit logger wants exactly that and says so in one token. +func consumedSubject(pattern string) (string, error) { + if pattern == catalogueTheRest { + return "mesh.mod.*.event.>", nil + } + emitter, event, named := strings.Cut(pattern, ".") + if !named || emitter == "" || event == "" { + return "", fmt.Errorf( + "%q does not name an emitter and an event: a consumed event is ., or "+ + "%q for every event", pattern, catalogueTheRest) + } + if emitter == catalogueTheRest { + return "", fmt.Errorf("%q stands for the rest of a name, so it cannot name the emitter", catalogueTheRest) + } + // Each name is checked before it becomes a subject: a name carrying a dot would add a token and + // silently widen the permission, which is the whole reason safeSubject exists. + var out []string + for _, part := range strings.Split(event, ".") { + switch part { + case catalogueTheRest: + out = append(out, ">") + case "*": + out = append(out, "*") + default: + if !safeSubject.MatchString(part) { + return "", fmt.Errorf("%q cannot be part of a subject: it would widen the permission", part) + } + out = append(out, part) + } + } + if emitter != "*" && !safeSubject.MatchString(emitter) { + return "", fmt.Errorf("%q cannot name an emitter: it would widen the permission", emitter) + } + return "mesh.mod." + emitter + ".event." + strings.Join(out, "."), nil +} + +// catalogueTheRest is the mesh's wildcard for "the rest of a name", duplicated from the catalogue +// package for the one direction of dependency the build queue's name is duplicated for. +const catalogueTheRest = "**" + func consumerStream(p Principal) string { switch p.Kind { case KindModule: diff --git a/internal/catalogue/events.go b/internal/catalogue/events.go new file mode 100644 index 0000000..90dda63 --- /dev/null +++ b/internal/catalogue/events.go @@ -0,0 +1,161 @@ +package catalogue + +import ( + "fmt" + "regexp" + "strings" +) + +// What a module may call an event, and what a consumer may ask for. +// +// A module names an event **locally**: `order.placed`, not a subject and not a routing key +// (design 29 §1). A consumer names the emitter and the event: `billing.order.placed`. The mesh +// derives the subject from those, so reorganising the subject space leaves every manifest correct. +// +// **Nothing checked this until every manifest in the catalogue was wrong the same way** +// (novox/hq 04-ISSUES/127). All thirty-seven kept the old bus's routing key — +// `module..` — which the derivation read as "a module called `module`", so every +// cross-module subscription in the mesh pointed at a namespace nobody publishes to. Nothing failed: +// the services started and none of them reacted. The documentation on these fields taught the old +// form too, which is why the drift was uniform rather than scattered. + +// eventName is one name in a local event: lower-case, and no wildcard. +var eventName = regexp.MustCompile(`^[a-z0-9][a-z0-9-]*$`) + +// The wildcards a consumer may use, spelled the mesh's way and derived to whatever the transport +// spells them as. +// +// **A manifest holds no transport token**, which is the whole point of naming locally: the bus the +// mesh runs on today spells these `*` and `#`, and the one being built spells them `*` and `>`. A +// manifest that said either would be a manifest that stopped being true when the wire changed. +const ( + // OneName stands for exactly one name. + OneName = "*" + // TheRest stands for one or more names, and may only come last. + TheRest = "**" +) + +// EventProblems is what is wrong with a manifest's events. +// +// Refused at registration, because the alternative is a module that installs, starts, connects and +// reacts to nothing — and every log line says it is fine. +func EventProblems(m Manifest) []string { + var problems []string + + for _, e := range m.Emits { + if was, stale := staleEventForm(e, m.Module); stale { + problems = append(problems, fmt.Sprintf( + "%s emits %q, which is the old bus's routing key. An event is named locally now, so "+ + "write %q — the mesh derives the subject (novox/hq design 29 §1)", + m.Module, was, strings.TrimPrefix(was, "module."+m.Module+"."))) + continue + } + if strings.HasPrefix(e, "module.") { + problems = append(problems, fmt.Sprintf( + "%s emits %q: `module.` is reserved, because it is how the old bus spelled a "+ + "routing key and an event named that way derives into a namespace nobody owns", + m.Module, e)) + continue + } + if err := localName(e); err != nil { + problems = append(problems, fmt.Sprintf("%s emits %q: %v", m.Module, e, err)) + continue + } + // **Its own name, never another's.** The bus enforces that a namespace belongs to the module + // it is named for, so an event named for somebody else cannot be published at all. If the + // event is about a role rather than about this module, it belongs on the seat: a name that + // is stable across whoever fills it (04-ISSUES/127). + if first, _, split := strings.Cut(e, "."); split && isAModuleNameOtherThan(first, m.Module) { + problems = append(problems, fmt.Sprintf( + "%s emits %q, which reads as another module's event. A module publishes under its "+ + "own name only. If this is about a role rather than about %s, declare it on that "+ + "seat, where the name survives the holder changing", + m.Module, e, m.Module)) + } + } + + for _, c := range m.Consumes { + if strings.HasPrefix(c, "module.") { + problems = append(problems, fmt.Sprintf( + "%s consumes %q, which is the old bus's pattern. A consumed event names its emitter "+ + "and the event: write %q", m.Module, c, strings.TrimPrefix(c, "module."))) + continue + } + if c == "#" { + problems = append(problems, fmt.Sprintf( + "%s consumes %q, which is the old bus's wildcard for everything. Write %q", + m.Module, c, TheRest)) + continue + } + if err := consumePattern(c); err != nil { + problems = append(problems, fmt.Sprintf("%s consumes %q: %v", m.Module, c, err)) + } + } + return problems +} + +// staleEventForm says an emitted name is this module's own old routing key, and what it was. +func staleEventForm(event, module string) (string, bool) { + return event, module != "" && strings.HasPrefix(event, "module."+module+".") +} + +// isAModuleNameOtherThan says a first token names some module of this mesh that is not this one. +// +// Only the mesh's own seats and the catalogue could answer this properly, and neither is reachable +// from a parser given one manifest. So this catches the case that actually happened — a name that +// is a *provision* the mesh defines, which is where "another module's event" comes from in practice +// — and the whole-catalogue check catches the rest. +func isAModuleNameOtherThan(first, module string) bool { + if first == module || first == "" { + return false + } + if _, isASeat := SeatNamed(first); isASeat { + return true + } + if _, isASeat := SeatDelivering(first); isASeat { + return true + } + return false +} + +// localName checks one event name: dot-separated names, no wildcards, nothing else. +func localName(event string) error { + if event == "" { + return fmt.Errorf("an event needs a name") + } + for _, part := range strings.Split(event, ".") { + if part == OneName || part == TheRest { + return fmt.Errorf("an emitted event names one event, so it carries no wildcard") + } + if !eventName.MatchString(part) { + return fmt.Errorf("%q is not a usable name: lower-case letters, digits and dashes", part) + } + } + return nil +} + +// consumePattern checks a consumed pattern: the emitter, then the event, with wildcards. +func consumePattern(pattern string) error { + if pattern == "" { + return fmt.Errorf("a consumed event needs an emitter and an event") + } + parts := strings.Split(pattern, ".") + for i, part := range parts { + switch { + case part == TheRest: + if i != len(parts)-1 { + return fmt.Errorf("%q stands for the rest of a name, so nothing may follow it", TheRest) + } + case part == OneName: + case !eventName.MatchString(part): + return fmt.Errorf("%q is not a usable name: lower-case letters, digits and dashes", part) + } + } + // `**` alone is every event from every module, which the audit logger wants and says plainly. + if len(parts) == 1 && parts[0] != TheRest { + return fmt.Errorf( + "%q names an emitter and no event. Write ., or %q for every event", + pattern, TheRest) + } + return nil +} diff --git a/internal/catalogue/manifest.go b/internal/catalogue/manifest.go index b874536..ed3b37f 100644 --- a/internal/catalogue/manifest.go +++ b/internal/catalogue/manifest.go @@ -176,15 +176,29 @@ type Manifest struct { // Requires are names that must be provided by something assigned to the same node. Requires []string `json:"requires,omitempty"` - // Emits are the event types this module publishes onto the broker — dotted topic keys, e.g. - // "module.umami.site.created". Declared so the mesh knows the event graph; events are - // provisioning's lighter sibling — 1:many and broadcast, no credential (novox/hq ADR 0041). + // Emits are the events this module publishes, named **locally**: `order.placed`, not a subject + // and not a routing key. The mesh derives where it lands (design 29 §1), so reorganising the + // subject space leaves this manifest correct. Events are provisioning's lighter sibling — 1:many + // and broadcast, no credential (novox/hq ADR 0041). + // + // A module publishes under its own name only. If the event is about a *role* rather than about + // this module, it belongs on that seat, where the name outlives whoever holds it. + // + // **This said "dotted topic keys, e.g. module.umami.site.created" until 04-ISSUES/127**, which + // is the old bus's routing key, and is why every manifest in the catalogue had the same mistake: + // nobody was guessing, everybody followed this comment. Emits []string `json:"emits,omitempty"` - // Consumes are the event patterns this module subscribes to — topic patterns over module, - // mesh and node events alike, e.g. "node.*.joined" or "#" (the audit logger). The runtime - // wires the subscription; the module ships the handler. A Consumes for an event nothing on - // the mesh Emits is a dangling edge. + // Consumes are the events this module reacts to, each naming its emitter and the event: + // `billing.order.placed`. `*` stands for one name and `**` for the rest, so `*.download.completed` + // is that event from any module and `**` is every event in the mesh. + // + // Spelled the mesh's way rather than the wire's, for the reason Emits is: the bus the mesh runs + // on today spells these `*` and `#`, the one being built spells them `*` and `>`, and a manifest + // naming either would stop being true when the wire changed. + // + // The runtime wires the subscription; the module ships the handler. A Consumes for an event + // nothing on the mesh Emits is a dangling edge. Consumes []string `json:"consumes,omitempty"` // Claims are singular resources. Two modules claiming one thing within a scope cannot both @@ -999,6 +1013,10 @@ func ParseManifest(raw []byte) (Manifest, error) { problems = append(problems, fmt.Sprintf("%s requires itself", m.Module)) } } + // What it may call an event, and what it may ask to hear (events.go). Checked here because a + // module whose event names are wrong installs, starts, connects and reacts to nothing, with + // every log line saying it is fine (novox/hq 04-ISSUES/127). + problems = append(problems, EventProblems(m)...) wellFormed := true for _, c := range m.Claims { if !name.MatchString(c.Name) { diff --git a/internal/catalogue/no_subjects_test.go b/internal/catalogue/no_subjects_test.go index f86a8d2..cb4b301 100644 --- a/internal/catalogue/no_subjects_test.go +++ b/internal/catalogue/no_subjects_test.go @@ -71,3 +71,51 @@ func TestNoManifestContainsASubject(t *testing.T) { } t.Logf("%d manifests hold no subject", checked) } + +// **Every module's event names are what design 29 says, across the whole catalogue.** +// +// The rule above holds by construction and turned out to be weaker than it reads: a manifest holds +// no subject, and every manifest in the catalogue still held the old bus's routing key, which +// derives into a namespace nobody owns (novox/hq 04-ISSUES/127). Nothing failed — the services +// started and none of them reacted. This is the check that was missing. +func TestEveryManifestsEventNamesAreLocal(t *testing.T) { + manifests := theCatalogue(t) + + var problems []string + for _, m := range manifests { + problems = append(problems, EventProblems(m)...) + } + if len(problems) > 0 { + t.Fatalf("the catalogue holds %d event name(s) the mesh would derive wrongly:\n %s", + len(problems), strings.Join(problems, "\n ")) + } +} + +// theCatalogue is every manifest beside this checkout, parsed the way registration parses one. +func theCatalogue(t *testing.T) []Manifest { + t.Helper() + root := filepath.Join("..", "..", "..", "mesh-catalog", "modules") + entries, err := os.ReadDir(root) + if err != nil { + t.Skipf("catalogue sibling not present: %v", err) + } + var out []Manifest + for _, e := range entries { + if !e.IsDir() { + continue + } + raw, err := os.ReadFile(filepath.Join(root, e.Name(), "module.json")) + if err != nil { + continue + } + var m Manifest + if err := json.Unmarshal(raw, &m); err != nil { + t.Fatalf("%s: %v", e.Name(), err) + } + out = append(out, m) + } + if len(out) == 0 { + t.Skip("no manifests found beside this checkout") + } + return out +} diff --git a/internal/inventory/busrecords.go b/internal/inventory/busrecords.go index 61051f2..a9a6ae1 100644 --- a/internal/inventory/busrecords.go +++ b/internal/inventory/busrecords.go @@ -88,8 +88,8 @@ func declaredFor(m catalogue.Manifest, seats map[string]catalogue.SeatDeclaratio Serves: m.Tools, } for _, c := range m.Claims { - // A seat the mesh defines for itself declares no protocol, so holding one grants nothing - // here — which is right: those seats say who does a job, not who may say what. + // A seat the mesh defines for itself carries no protocol, so holding one grants nothing here: + // those seats say who does a job, not who may say what. if s, declaredSomewhere := seats[c.Name]; declaredSomewhere { d.Holds = append(d.Holds, asSeat(s)) }