The mesh issues an assignment's subjects: a membership per module on a machine (hq ADR 0160)
For every module on every machine the controller composes what that instance serves — its machine's address always, the module's plain address in a queue when it is alone or its definition says its instances are interchangeable — the verbs of the seats it holds at the seats' subjects, where its events land, and what it may reach, resolved the same way for the modules it invokes. Published beside the node's declaration on `mesh.assignment.<node>.<module>`, last per subject in a stream that allows direct reads, and the account may read exactly its own. Composed from the same records the bus's accounts are, so what a runtime serves and what its account may are one composition. `instances: interchangeable` is the one fact a definition states for it. The shape issued is the shape the mesh already had, so nothing moves when the membership arrives; the runtime that reads it instead of deriving it is the next piece.
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
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, `<module>.<tool>`, 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
|
||||
}
|
||||
Reference in New Issue
Block a user