package catalogue import ( "fmt" "sort" "strings" ) // The seats a mesh can have (novox/hq ADR 0110). // // **A closed set, defined here rather than by whoever claims one.** Until this, a well-formed name // became a seat by being claimed, so nothing could say which seats a mesh has or who fills them: // `the-showcase` and `the-build-machine` were each invented by the module claiming it. The set is // what a person reads to learn what a mesh can have, so an entry nobody argued for is an entry // nobody can explain — the same reason every shape in the host's vocabulary names its decision. // // A seat is held by a module assignment. What the mesh knows about a holder is what it knows about // that assignment; nothing about holders is kept here or anywhere else. // Seat is one role the mesh defines. type Seat struct { // Name is what a manifest claims. Name string // Scope is where there may be only one holder. Scope string // Delivers is the provision the seat's holder answers for, or empty. A seat that delivers a // provision may only be held by a module providing it at the seat's scope, and its holder is // what a requirement for that provision resolves to when several modules provide it. Delivers string // Accepts, Emits and Serves are the protocol of the role, as local verbs — the same three a // module declares for a seat of its own (novox/hq ADR 0118), and empty for most of these: a seat // is usually about who does a job and not about what may be said to them. // // **Named here so the mesh has no role it cannot describe** (ADR 0121). Without them a build // machine had three audiences for one outcome and nothing derived a grant for any of them, and an // event about a role had nowhere to live but the namespace of whichever module held that role // today — which the bus refuses, because a namespace belongs to who it is named for. Accepts []string Emits []string Serves []string // Decision is the record that made it a seat. Decision string } // seats is the whole set, in the order a person reads it: the mesh's own, then a node's. var seats = []Seat{ {Name: "mesh-controller", Scope: ScopeMesh, Decision: "novox/hq ADR 0079"}, {Name: "mesh-store", Scope: ScopeMesh, Delivers: "postgres-database", Decision: "novox/hq ADR 0079"}, // What holding this delivers is the mesh's own bus (novox/hq ADR 0120): a module that speaks // to the mesh requires `mesh-bus` and receives an address, a sealed credential and the trust // to verify the server. A module that requires nothing gets no account at all — 23 of the // catalogue's 72 never speak, and an ambient connection would mint a credential for each. // // Corrected twice in one day, which is worth the comment. It read `amqp`, which was the old // broker's interface and not this seat's; ADR 0117 emptied it, reasoning that a bus cannot be // provisioned; and it is neither. The bus's accounts are composed by the controller rather // than created by a provisioner, so nothing waits on a bus account in order to make one — // which is a fact about the *mechanism*, not a reason the connection cannot be required. // // `mesh-bus` is the mesh's own; `nats` is a private NATS server a module may provide as a // backing service, the way `amqp` is provided (ADR 0119). Never the same name. {Name: "mesh-broker", Scope: ScopeMesh, Delivers: "mesh-bus", Decision: "novox/hq ADR 0079"}, {Name: "mesh-artifact-store", Scope: ScopeMesh, Delivers: "artifact-store", Decision: "novox/hq ADR 0075"}, {Name: "mesh-catalog", Scope: ScopeMesh, Decision: "novox/hq ADR 0110"}, {Name: "mesh-npm-package-registry", Scope: ScopeMesh, Delivers: "npm-package-registry", Decision: "novox/hq ADR 0109"}, {Name: "mesh-git", Scope: ScopeMesh, Delivers: "git", Decision: "novox/hq ADR 0111"}, // A build is work submitted to this role, and its outcome is the role's own event (ADR 0121). // One publish reaches whoever asked, the controller that records it, and the catalogue that // places it in the module graph — which is what the old bus's shared exchange did for free, and // what a dedicated build branch was doing a second way. {Name: "mesh-build-machine", Scope: ScopeNode, Accepts: []string{"build"}, Emits: []string{"built"}, Decision: "novox/hq ADR 0110"}, {Name: "mesh-dns-port", Scope: ScopeNode, Decision: "novox/hq ADR 0110"}, {Name: "mesh-intrusion-prevention", Scope: ScopeNode, Decision: "novox/hq ADR 0110"}, {Name: "mesh-packet-filter", Scope: ScopeNode, Decision: "novox/hq ADR 0110"}, {Name: "mesh-private-network", Scope: ScopeNode, Decision: "novox/hq ADR 0110"}, {Name: "mesh-resolver-configuration", Scope: ScopeNode, Decision: "novox/hq ADR 0110"}, {Name: "mesh-showcase", Scope: ScopeNode, Decision: "novox/hq ADR 0110"}, } // Seats is every seat the mesh defines, in reading order. func Seats() []Seat { return append([]Seat(nil), seats...) } // SeatNamed is the seat a claim names, if the mesh defines one. func SeatNamed(name string) (Seat, bool) { for _, s := range seats { if s.Name == name { return s, true } } return Seat{}, false } // SeatDelivering is the seat whose holder answers for a provision, if there is one. func SeatDelivering(provision string) (Seat, bool) { if provision == "" { return Seat{}, false } for _, s := range seats { if s.Delivers == provision { return s, true } } return Seat{}, false } // claimProblems is what is wrong with a manifest's claims against the set. // // Three refusals, each naming the seat: a seat the mesh does not define, a seat claimed at another // scope, and a seat that delivers a provision claimed by a module that does not provide it — which // would make the module the mesh's answer for something it cannot answer. func claimProblems(m Manifest) []string { var problems []string for _, c := range m.Claims { if now, was := renamedSeats[c.Name]; was { // Named rather than refused as unknown: whoever wrote it knew what they meant, and // the mesh knows what it is called now — the same courtesy the `needs`/`own-secrets` // rename gets. Without this the refusal would be "not a seat", which sends somebody // reading code for a name that is one character different. problems = append(problems, fmt.Sprintf( "%s claims %q, which is now called %q (novox/hq ADR 0118: the mesh's own seats "+ "are named mesh-*, and the prefix is what reserves them)", m.Module, c.Name, now)) continue } seat, known := SeatNamed(c.Name) if !known { // Not one of the mesh's own, which no longer means it is not a seat: a module may // declare its own (novox/hq ADR 0118), and whether anybody declared *this* one is a // fact about the catalogue rather than about this manifest. Deferred to // CatalogueProblems, which refuses it at registration — the same guarantee ADR 0110 // wanted, at the same moment, from a set nobody maintains by hand. continue } if c.At() != seat.Scope { problems = append(problems, fmt.Sprintf( "%s claims %s at scope %q, and %s is a %s seat", m.Module, c.Name, c.At(), c.Name, seat.Scope)) } if seat.Delivers != "" && !providesAt(m, seat.Delivers, seat.Scope) { problems = append(problems, fmt.Sprintf( "%s claims %s, whose holder answers for %q, and %s does not provide %q at %s scope", m.Module, c.Name, seat.Delivers, m.Module, seat.Delivers, seat.Scope)) } } return problems } func providesAt(m Manifest, provision, scope string) bool { for _, o := range m.Provides { if o.Name == provision && o.At() == scope { return true } } return false } func seatNames() string { names := make([]string, 0, len(seats)) for _, s := range seats { names = append(names, s.Name) } sort.Strings(names) return strings.Join(names, ", ") } // HolderAmong is which of several providers of a provision holds the seat that delivers it. // // Found by the (node, module) pair, because a provider is identified by both (novox/hq to-be 23): // two modules on one node could both provide a provision, and only the one holding the seat // answers for it. Nothing when no seat delivers the provision, when nobody holds // it, or when the holder is not among the providers offered. func HolderAmong(provision string, providers []Provider, held []Held) (Provider, bool) { seat, delivered := SeatDelivering(provision) if !delivered { return Provider{}, false } for _, h := range held { if h.Claim != seat.Name || h.Scope != seat.Scope { continue } for _, p := range providers { if p.Node == h.Node && p.Module == h.Module { return p, true } } } return Provider{}, false } // renamedSeats is what the mesh's own seats used to be called (novox/hq ADR 0118). // // **A rename here is not a data migration**, which ADR 0118 assumed it was and a progressive // insight there corrects: a seat's holding is *derived* at resolution from the claims in // manifests (`resolve.go`), never stored, so there are no recorded old names to rewrite. What // exists is source — manifests in the catalogue — and this list is how one written against the // old name is told what it became rather than refused as unknown. // // It is kept, not retired after the catalogue is updated: a module lives in its own repository // ([ADR 0069]) and may be registered from anywhere, so an old name can arrive long after the // catalogue beside this checkout stopped using one. var renamedSeats = map[string]string{ "the-artifact-store": "mesh-artifact-store", "the-catalogue": "mesh-catalog", "npm-package-registry": "mesh-npm-package-registry", "git": "mesh-git", "the-build-machine": "mesh-build-machine", "the-dns-port": "mesh-dns-port", "the-intrusion-prevention": "mesh-intrusion-prevention", "the-packet-filter": "mesh-packet-filter", "the-private-network": "mesh-private-network", "the-resolver-configuration": "mesh-resolver-configuration", "the-showcase": "mesh-showcase", } // SeatsWithAProtocol are the mesh's own seats that say something about what may be said to them or by // them, which is the set the bus derives streams, consumers and permissions from. // // Most of the set is not here, and that is the ordinary case: a seat saying only who does a job grants // nothing on the bus and needs no queue. func SeatsWithAProtocol() []Seat { var out []Seat for _, s := range seats { if len(s.Accepts) > 0 || len(s.Emits) > 0 || len(s.Serves) > 0 { out = append(out, s) } } return out }