catalogue's working set starts empty and a host gives it the seats it read (UseSeats over SeatsFromDefinitions); the node-engine's verbs and events take the seat's name from the caller. Tests take their seats from seatlisttest. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
218 lines
11 KiB
Go
218 lines
11 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// 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 — unless the seat is Replicated.
|
|
Scope string
|
|
// Replicated says a mesh seat may be held on several machines at once, each holder answering the
|
|
// same thing (novox/hq ADR 0223): the mesh's resolver, held on the anchor and the home server so a
|
|
// machine's resolver file lists two that give one answer. Each holder is on record, added by an
|
|
// act (`seat <name> --add <node>/<module>`), never by being assigned: two claimants with nothing on
|
|
// record are refused exactly as for any mesh seat. One per machine still — two modules on one
|
|
// node claiming it are refused. Compiled, never stored, like Receives: it is the mesh's definition of
|
|
// the role, and the store's rows carry no column for it.
|
|
Replicated bool
|
|
// 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
|
|
// PulledByKind says each accepted kind is a worker of its own on the seat's stream, which its holders pull in the
|
|
// order Accepts lists them (novox/hq ADR 0525 §2, ADR 0536 §4): the build seats'. Compiled, never stored, like
|
|
// Receives.
|
|
PulledByKind bool
|
|
// Yields says the seat's holder gives way to everything else on its node (novox/hq ADR 0553): its runtime runs at
|
|
// idle priority in the node's one yielding slice, under the node's one processor share (buildcpu.ShareSetting) that
|
|
// every yielding holder of the node shares. The build seats': a build must never slow the operator's desktop.
|
|
// Compiled, never stored, like Receives.
|
|
Yields bool
|
|
// Serves carries each verb in full — name, description, schema — because a role's tools are the
|
|
// mesh's to define and an agent's to call (novox/hq ADR 0132, design 33 §2).
|
|
Serves []Verb
|
|
// Receives is what other modules may contribute to the seat's holder, by kind (novox/hq ADR
|
|
// 0212): each kind is text in the tool's own grammar, placed by the holder with
|
|
// ${contribution:<seat>:<kind>}. Compiled, never stored: like the protocol, it is the mesh's
|
|
// definition of the role, and the store's rows carry no column for it.
|
|
Receives []Receivable
|
|
// Paths are the paths on its machine the seat's holder owns (novox/hq ADR 0287): a holder declares
|
|
// every one, and a module claiming no seat that names a path may not declare it. Only a node seat
|
|
// names paths. Compiled, never stored, like Receives: adding or taking away a path is a decision,
|
|
// recorded, and the store's rows carry no column for it.
|
|
Paths []SeatPath
|
|
// HeldByTheEngine says the seat is held on every node by that node's engine, which serves its verbs on its own bus
|
|
// account (novox/hq ADR 0563 §1): no module claims it and no assignment holds it. Compiled, never stored, like
|
|
// Receives.
|
|
HeldByTheEngine bool
|
|
// Decision is the record that made it a seat.
|
|
Decision string
|
|
}
|
|
|
|
// Receivable is one kind of contribution a seat receives (novox/hq ADR 0212 §2): its name, and the
|
|
// comment prefix of the tool's grammar, with which the controller names each contributing module.
|
|
type Receivable struct {
|
|
Kind string
|
|
Comment string
|
|
// Dirs says a contribution of this kind may name its contributor's own directories as
|
|
// `${dir:<id>}`, filled with where they are on the machine before the holder places it (novox/hq
|
|
// to-be 43): a module saying which of its data to back up names a directory the mesh placed, and
|
|
// only the mesh knows where. Off for every kind written in a tool's grammar that has its own
|
|
// `${…}` — a shell's — where the mesh filling one would change what the tool reads.
|
|
Dirs bool
|
|
// Shape, when set, makes the kind data rather than text (novox/hq ADR 0255): a contributor gives
|
|
// `data` in this shape, and the holder renders each piece with the template its claim gives.
|
|
Shape *Shape
|
|
// Offered says a contribution of this kind does not depend on the seat (novox/hq ADR 0255): where
|
|
// nobody holds it nothing is placed, and the contributor runs there all the same. A battery's bar
|
|
// block is offered — the power module is on every machine, and a server has no bar.
|
|
Offered bool
|
|
}
|
|
|
|
// seats is the working set the lookups read, empty until a host gives it the seat list (UseSeats): mesh-sdk compiles no
|
|
// seat list and imports no contracts module (novox/hq ADR 0635 §1). A host reads the list from the bus
|
|
// (mesh-sdk go/seatlist, which holds the foundation's bootstrapped definitions until the indexer is heard) and gives
|
|
// its seats here at start and at each change.
|
|
var seats []Seat
|
|
|
|
// UseSeats replaces the working set with the seat list a host read (seatlist.Reader.Seats, via SeatsFromDefinitions).
|
|
//
|
|
// **Empty is ignored on purpose.** A list that was not read yet — or one that could not be — must leave the set in force
|
|
// rather than emptying it: an empty set would refuse every claim and could stop a composition altogether, which is a
|
|
// far worse failure than running on the set the host last had.
|
|
func UseSeats(s []Seat) {
|
|
if len(s) == 0 {
|
|
return
|
|
}
|
|
seats = append([]Seat(nil), s...)
|
|
}
|
|
|
|
// aliases maps a seat's former names to its current canonical name (novox/hq ADR 0122). Loaded from
|
|
// the store alongside the set, so a reference to a name a seat used to have — a manifest's claim, a
|
|
// held record — still resolves to it after a rename, and nothing downstream has to change.
|
|
var aliases = map[string]string{}
|
|
|
|
// Seats is every seat the mesh defines, in reading order.
|
|
func Seats() []Seat {
|
|
return append([]Seat(nil), seats...)
|
|
}
|
|
|
|
// SeatNamed is the seat a name refers to, whether that is its current name or one it used to have
|
|
// (novox/hq ADR 0122). A former name resolves to the seat's canonical row, so a rename breaks no
|
|
// reference to the old name.
|
|
func SeatNamed(name string) (Seat, bool) {
|
|
for _, s := range seats {
|
|
if s.Name == name {
|
|
return s, true
|
|
}
|
|
}
|
|
if canonical, aliased := aliases[name]; aliased {
|
|
for _, s := range seats {
|
|
if s.Name == canonical {
|
|
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
|
|
}
|
|
|
|
// CanHold is why a module could not hold a seat, or nothing: its definition must claim the seat at
|
|
// the seat's scope, and provide what the seat delivers, if it delivers anything. The seat is the
|
|
// store's row, so this is judged only where the store's set is loaded — at registration and in the
|
|
// handover command (novox/hq ADR 0131), never in the parser.
|
|
func CanHold(m Manifest, seat Seat) error {
|
|
var claimed *Claim
|
|
for i := range m.Claims {
|
|
if hs, ok := SeatNamed(m.Claims[i].Name); ok && hs.Name == seat.Name {
|
|
claimed = &m.Claims[i]
|
|
}
|
|
}
|
|
if claimed == nil {
|
|
return fmt.Errorf("%s does not claim %s", m.Module, seat.Name)
|
|
}
|
|
if claimed.At() != seat.Scope {
|
|
return fmt.Errorf("%s claims %s at scope %q, and %s is a %s seat",
|
|
m.Module, seat.Name, claimed.At(), seat.Name, seat.Scope)
|
|
}
|
|
if seat.Delivers != "" && !providesAt(m, seat.Delivers, seat.Scope) {
|
|
return fmt.Errorf("%s claims %s, whose holder answers for %q, and %s does not provide %q at %s scope",
|
|
m.Module, seat.Name, seat.Delivers, m.Module, seat.Delivers, seat.Scope)
|
|
}
|
|
// **A holder owns what the role owns** (novox/hq ADR 0287): every path the seat names, declared as
|
|
// a resource or as a file the mesh renders for it, so which module owns /etc/hosts on a machine is
|
|
// answered by the seat's definition and not by reading every manifest.
|
|
if missing := missingPaths(m, seat.Paths); len(missing) > 0 {
|
|
return fmt.Errorf("%s claims %s but does not declare %s, which that seat's holder owns (novox/hq "+
|
|
"ADR 0287) — a holder declares every path its seat names, as a resource or as a file the mesh "+
|
|
"renders for it", m.Module, seat.Name, strings.Join(missing, ", "))
|
|
}
|
|
// **Serving the seat's tools is a condition of holding it** (novox/hq ADR 0132). A holder that
|
|
// does not answer what the role promises is every caller's timeout, found at registration and
|
|
// at handover instead, naming the verbs rather than the fact that something is missing.
|
|
if missing := unservedVerbs(claimed.ServesFor(m), seat.Serves); len(missing) > 0 {
|
|
return fmt.Errorf("%s claims %s but does not serve %s, which that seat's protocol promises "+
|
|
"(novox/hq ADR 0132) — a holder names every verb its seat declares, under the claim's "+
|
|
"serves or among its own tools",
|
|
m.Module, seat.Name, strings.Join(missing, ", "))
|
|
}
|
|
// And nothing the seat does not promise: a verb named here that the protocol lacks is served
|
|
// to nobody, which is a typo the holder would otherwise discover as a caller's timeout.
|
|
if extra := unpromised(claimed.Serves, seat.Serves); len(extra) > 0 {
|
|
return fmt.Errorf("%s claims %s and says it serves %s, which that seat's protocol does not "+
|
|
"promise — a claim's serves names the seat's verbs and nothing else",
|
|
m.Module, seat.Name, strings.Join(extra, ", "))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func providesAt(m Manifest, provision, scope string) bool {
|
|
for _, o := range m.Provides {
|
|
if o.Name == provision && o.At() == scope {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// 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
|
|
}
|