package broker import ( "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. // Membership is one assignment's subjects: what this instance of a module on this machine serves, // and what it may reach. type Membership struct { Node string `json:"node"` Module string `json:"module"` // Serves is every address a tool of this instance answers on. `{tool}` stands for the tool's // own name, which the module knows and the mesh does not need to: the mesh issues the address, // the runtime fills the name. An address with a queue is shared with the module's other // instances, and the bus hands each call to one of them; an address without is this instance's. Serves []Served `json:"serves"` // Seats is every verb of a seat this instance holds, at the subject the seat's callers use. Seats []SeatServed `json:"seats,omitempty"` // Emits is where an event of this module lands; `{event}` stands for the event's name. Emits string `json:"emits"` // Reaches is each tool this module may call, `.`, to the subjects that reach it: // the first is whichever instance answers, when the mesh issued one; the rest name a machine. Reaches map[string][]string `json:"reaches,omitempty"` // Tools is where this instance answers what it serves — the runtime's one verb of its own. Tools string `json:"tools"` } // Served is one address a tool is answered on. type Served struct { Subject string `json:"subject"` Queue string `json:"queue,omitempty"` } // SeatServed is one verb of a held seat, where its callers ask. type SeatServed struct { Seat string `json:"seat"` Verb string `json:"verb"` Subject string `json:"subject"` } // MembershipSubject is the one address a runtime derives for itself: where its own membership is // published, from the two names its credential carries. Everything else is in the membership. func MembershipSubject(node, module string) string { return "mesh.assignment." + node + "." + module } // 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 } // 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 { for _, verb := range s.Serves { m.Seats = append(m.Seats, SeatServed{Seat: s.Name, Verb: verb, Subject: seatToolSubject(s, verb, node)}) } } 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) } return p }