mesh/delivery-group group feat/r4d rejected: a member's own check failed
mesh/repo-check out of date: judged beside mesh-catalog#434 at c948f3a61c74, it is now at 7451e7edf4e5; judged again on a recheck or when it is merged
mesh/merge-gate group feat/r4d: composed again for the group's new heads
mesh/delivery superseded: a newer head of the same pull request
mesh/check failed: unit: i3/cmd/i3-tools.TestTheMainFileAndEveryModulesContributionLoadTogether: manifest_test.go:247: expected the launcher, clipboar…
A bundle is built only from its module's directory (ADR 0480 clause 4), so the proxy leaves examples/ and the controller's internal/broker; the broker keeps no second copy of what the proxy shares with mesh-sdk. The image and its Dockerfile go, and a test holds that only build-agent declares one (clause 11).
179 lines
6.6 KiB
Go
179 lines
6.6 KiB
Go
package broker
|
|
|
|
import (
|
|
"slices"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// What the mesh issues an assignment to serve and to reach (novox/hq ADR 0160).
|
|
//
|
|
// A module's code names its tools and its events; **where they land is the mesh's to decide**, and
|
|
// it decided it twice — once in the runtime, once here, by one rule compiled into both. Now the
|
|
// controller composes a membership for every module on every machine and publishes it to a subject
|
|
// only that assignment reads; the runtime serves exactly what the membership says, and the account's
|
|
// grant is the same composition read the other way. The shape issued today is the shape the mesh
|
|
// already had, so nothing moves when a membership first arrives; only who decides it moves.
|
|
|
|
// Placements is where every module runs, for deciding which instance answers for the module.
|
|
type Placements struct {
|
|
// Nodes is each module's machines.
|
|
Nodes map[string][]string
|
|
// Interchangeable is each module whose definition says its instances are the same anywhere,
|
|
// so the module's plain subject is issued to all of them in one queue.
|
|
Interchangeable map[string]bool
|
|
// Kinds is every kind held of a kinded bench, by whom and with what capabilities (ADR 0259 §5).
|
|
Kinds []KindHeld
|
|
}
|
|
|
|
// AnswersForTheModule says whether an instance of a module on one machine is issued the module's
|
|
// plain subject: when it is the only instance, or when the definition says instances are
|
|
// interchangeable. A stateful module on two machines gets only its machines' subjects, so a call
|
|
// that names none reaches nothing rather than the wrong store.
|
|
func (p Placements) AnswersForTheModule(module string) bool {
|
|
return len(p.Nodes[module]) <= 1 || p.Interchangeable[module]
|
|
}
|
|
|
|
// MembershipFor composes one assignment's membership from what it declared and where everything
|
|
// runs. The subjects are the ones PermissionsFor grants, derived here once more only until the
|
|
// grant itself is read from the membership — which is the next step, not this one.
|
|
func MembershipFor(node string, d Declared, where Placements) Membership {
|
|
own := "mesh.mod." + d.Module
|
|
m := Membership{
|
|
Node: node, Module: d.Module,
|
|
Emits: own + ".event.{event}",
|
|
Tools: own + ".tool.tools",
|
|
}
|
|
// This machine's address always; the module's when this instance answers for the module.
|
|
m.Serves = append(m.Serves, Served{Subject: own + ".tool.{tool}." + node})
|
|
if where.AnswersForTheModule(d.Module) {
|
|
m.Serves = append(m.Serves, Served{Subject: own + ".tool.{tool}", Queue: "serve." + d.Module})
|
|
}
|
|
for _, s := range d.Holds {
|
|
if servedOnlyByTheController(s) {
|
|
continue // answered by the serving controller alone, never through a membership
|
|
}
|
|
for _, verb := range s.Serves {
|
|
m.Seats = append(m.Seats, SeatServed{Seat: s.Name, Verb: verb, Subject: seatToolSubject(s, verb, node)})
|
|
}
|
|
}
|
|
m.State = stateIssuedFor(d, node)
|
|
m.Logs = logsIssuedFor(d)
|
|
m.Streams = streamsIssuedFor(d)
|
|
m.Hears = HeardIssuedFor(node, d)
|
|
m.Waits = waitsIssuedFor(d)
|
|
t := SeatTrafficOf(d.Module, d.Holds, d.Uses, d.Watches)
|
|
for _, s := range append(append([]Seat{}, d.Uses...), d.Watches...) {
|
|
if !s.Kinded {
|
|
continue
|
|
}
|
|
for _, k := range where.Kinds {
|
|
if k.Seat == s.Name && !kindListed(t.Kinds, k) {
|
|
t.Kinds = append(t.Kinds, k)
|
|
}
|
|
}
|
|
}
|
|
if len(t.Publish)+len(t.Subscribe)+len(t.Answers)+len(t.Workers)+len(t.Records)+len(t.Kinds) > 0 {
|
|
m.SeatTraffic = &t
|
|
}
|
|
if len(d.Invokes) > 0 {
|
|
m.Reaches = map[string][]string{}
|
|
for _, t := range d.Invokes {
|
|
if t == "*" || strings.HasPrefix(t, "seat:") {
|
|
continue // every tool, or a role's: addressed by name, not resolved per instance
|
|
}
|
|
module, tool, ok := strings.Cut(t, ".")
|
|
if !ok {
|
|
continue
|
|
}
|
|
var reach []string
|
|
if where.AnswersForTheModule(module) {
|
|
reach = append(reach, "mesh.mod."+module+".tool."+tool)
|
|
}
|
|
nodes := append([]string{}, where.Nodes[module]...)
|
|
sort.Strings(nodes)
|
|
for _, n := range nodes {
|
|
reach = append(reach, "mesh.mod."+module+".tool."+tool+"."+n)
|
|
}
|
|
m.Reaches[t] = reach
|
|
}
|
|
}
|
|
return m
|
|
}
|
|
|
|
// PlacementsOf reads where everything runs from the records the bus's accounts are composed from.
|
|
func PlacementsOf(r Records, interchangeable map[string]bool) Placements {
|
|
p := Placements{Nodes: map[string][]string{}, Interchangeable: interchangeable}
|
|
for node, declared := range r.Assigned {
|
|
for _, d := range declared {
|
|
p.Nodes[d.Module] = append(p.Nodes[d.Module], node)
|
|
}
|
|
}
|
|
for _, nodes := range p.Nodes {
|
|
sort.Strings(nodes)
|
|
}
|
|
// Where the router runs: a verified sender is believed only while its machine is root-free too. A router
|
|
// placed nowhere, or on more than one machine, frees nothing.
|
|
var routerNodes []string
|
|
for node, declared := range r.Assigned {
|
|
for _, d := range declared {
|
|
for _, s := range d.Holds {
|
|
if s.Name == routerSeat && !slices.Contains(routerNodes, node) {
|
|
routerNodes = append(routerNodes, node)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
routerFree := len(routerNodes) == 1 && r.RootFree[routerNodes[0]]
|
|
for node, declared := range r.Assigned {
|
|
for _, d := range declared {
|
|
for _, s := range d.Holds {
|
|
if s.Kinded && s.Kind != "" {
|
|
p.Kinds = append(p.Kinds, KindHeld{Seat: s.Name, Kind: s.Kind, Module: d.Module, Node: node,
|
|
Capabilities: placedCapabilities(s.Capabilities, d.RunsAs, routerFree && r.RootFree[node])})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
sort.Slice(p.Kinds, func(i, j int) bool {
|
|
a, b := p.Kinds[i], p.Kinds[j]
|
|
if a.Seat != b.Seat {
|
|
return a.Seat < b.Seat
|
|
}
|
|
if a.Kind != b.Kind {
|
|
return a.Kind < b.Kind
|
|
}
|
|
return a.Node < b.Node
|
|
})
|
|
return p
|
|
}
|
|
|
|
// routerSeat is the seat the router of asks holds (novox/hq ADR 0259 §3).
|
|
const routerSeat = "operator-channel"
|
|
|
|
// placedCapabilities is what a kind's claim promises, as far as its placement lets the router believe it
|
|
// (novox/hq ADR 0259 §8): `verified-sender` only from a holder that runs as an account of its own, on a bus
|
|
// account of its own — never one the machine's runtime carries as the operator's account — and only while
|
|
// its machine and the router's were root-free when composed (rootFree; the review of 2026-10-09, H3). The
|
|
// membership carrying it is composed at a push, so it can outlive a pass that later fails: the router asks
|
|
// the controller's root-free verb again before it honours an approval, and that is the check that holds.
|
|
func placedCapabilities(declared []string, runsAs string, rootFree bool) []string {
|
|
var out []string
|
|
for _, c := range declared {
|
|
if c == "verified-sender" && (runsAs == "" || !rootFree) {
|
|
continue
|
|
}
|
|
out = append(out, c)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func kindListed(list []KindHeld, k KindHeld) bool {
|
|
for _, x := range list {
|
|
if x.Seat == k.Seat && x.Kind == k.Kind && x.Module == k.Module && x.Node == k.Node {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|