Every check the mesh had ran after a merge, on a machine: a manifest the node-engine refused (236), an identity a real machine's name made too long (263). merge-gate raises the mesh as the facts snapshot says it is and the mesh with the change, each in a throwaway store through the controller's own records, composes every machine twice and validates it with the node-engine's validator, and fails what the change breaks, naming the machine's roles and the module - plus a manifest the judging controller cannot read, a consumer left out of its grant, a module removed while a machine runs it, a new module the node-engine would refuse; it warns on a wide rebuild. The forge's new head of a pull request becomes a check the controller asks of the build seat: the head and, beside it, the controller the mesh runs, the catalogue, the host and the lab; a throwaway store and bus of the versions the mesh runs; the repository's merge-check.sh in the mesh's Go toolchain with no container runtime socket; then mesh-lab's replays. The verdict is said as checked, an error never a pass, and nothing is recorded or registered.
179 lines
8.6 KiB
Go
179 lines
8.6 KiB
Go
package broker
|
|
|
|
import (
|
|
"fmt"
|
|
"slices"
|
|
"strings"
|
|
)
|
|
|
|
// The writers table (novox/hq to-be 45 §1, ADR 0227 rule 1), compiled in.
|
|
//
|
|
// **Every kind of state the core keeps has one writer; anyone else asks it.** The table is the design's,
|
|
// row for row, with what each row writes on the bus where it writes there. Two things read it: the
|
|
// composition of every principal's grants (PermissionsFor), which **refuses a grant that lets a
|
|
// principal publish on a subject another writes** — so a second writer cannot be granted by accident,
|
|
// and the composition says which state and whose — and the test beside it, which walks the rows
|
|
// against the design's list and the composition of a whole mesh. Changing a writer is a change to
|
|
// this table, through a decision.
|
|
|
|
// WriterRow is one row of the writers table.
|
|
type WriterRow struct {
|
|
State string
|
|
Writer string
|
|
KeptIn string
|
|
Others string
|
|
// Subjects are the bus subjects a write of this state is a publish to; none for state kept off the
|
|
// bus (the controller's store, a module's own) or not built yet.
|
|
Subjects []string
|
|
// Writes says a principal granted a publish pattern overlapping Subjects is this state's writer —
|
|
// given the pattern, because a machine writes its own report and no other's.
|
|
Writes func(p Principal, pattern string) bool
|
|
// Shared says why more than one principal may publish here; empty for one writer.
|
|
Shared string
|
|
}
|
|
|
|
// isController, and the other writers' tests, are who a row's writer is as a principal.
|
|
func isController(p Principal, _ string) bool { return p.Kind == KindController }
|
|
|
|
// ownMachine is a node writing a subject whose third token is its own name, and no wildcard there.
|
|
func ownMachine(p Principal, pattern string) bool {
|
|
tokens := strings.Split(pattern, ".")
|
|
return p.Kind == KindNode && len(tokens) > 2 && tokens[2] == p.Node
|
|
}
|
|
|
|
// holdsSeatOf is a principal holding the seat a `mesh.seat.<seat>.…` pattern names: its module's own
|
|
// principal, or the node tools carrying a module that holds it (ADR 0175, the runtime is its modules).
|
|
func holdsSeatOf(p Principal, pattern string) bool {
|
|
tokens := strings.Split(pattern, ".")
|
|
if len(tokens) < 3 {
|
|
return false
|
|
}
|
|
seat := tokens[2]
|
|
holds := func(seats []Seat) bool {
|
|
return slices.ContainsFunc(seats, func(s Seat) bool { return s.Name == seat })
|
|
}
|
|
switch p.Kind {
|
|
case KindModule:
|
|
return holds(p.Holds)
|
|
case KindNodeTools:
|
|
return slices.ContainsFunc(p.Carries, func(d Declared) bool { return holds(d.Holds) })
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ownModule is a module publishing under its own name, or the node tools carrying it.
|
|
func ownModule(p Principal, pattern string) bool {
|
|
tokens := strings.Split(pattern, ".")
|
|
if len(tokens) < 3 {
|
|
return false
|
|
}
|
|
module := tokens[2]
|
|
switch p.Kind {
|
|
case KindModule:
|
|
return p.Module == module
|
|
case KindNodeTools:
|
|
return slices.ContainsFunc(p.Carries, func(d Declared) bool { return d.Module == module })
|
|
}
|
|
return false
|
|
}
|
|
|
|
// kvOf is a bucket's write subjects.
|
|
func kvOf(bucket string) []string { return []string{"$KV." + bucket + ".>"} }
|
|
|
|
// WritersTable is to-be 45 §1, in its order.
|
|
var WritersTable = []WriterRow{
|
|
{State: "a machine's declaration", Writer: "controller (lease holder)", KeptIn: "the bus, last per subject",
|
|
Others: "read", Subjects: []string{"mesh.node.*.declare"}, Writes: isController},
|
|
{State: "a machine's applied state and its report", Writer: "the node-engine's apply queue",
|
|
KeptIn: "the machine; the report on the bus", Others: "the reconcile and a delivery enqueue, never apply",
|
|
Subjects: []string{"mesh.control.*.report"}, Writes: ownMachine},
|
|
{State: "the controller lease", Writer: "the controller instance holding it", KeptIn: "key-value " + LeaseBucket,
|
|
Others: "a candidate waits", Subjects: kvOf(LeaseBucket), Writes: isController},
|
|
{State: "plans and their tiers", Writer: "controller (lease holder), compare-and-set on the plan's revision",
|
|
KeptIn: "the controller's store", Others: "read through plans"},
|
|
{State: "conditions", Writer: "controller", KeptIn: "key-value " + ConditionsBucket + " (and its history, " +
|
|
ConditionHistoryBucket + ")", Others: "raise or clear only through observations the controller reads",
|
|
Subjects: append(kvOf(ConditionsBucket), kvOf(ConditionHistoryBucket)...), Writes: isController},
|
|
{State: "calls and their outcomes", Writer: "controller", KeptIn: "key-value " + CallsBucket,
|
|
Others: "read by id", Subjects: kvOf(CallsBucket), Writes: isController},
|
|
{State: "the hand-act log", Writer: "controller, through the verbs that act", KeptIn: "key-value " + HandActsBucket,
|
|
Others: "—", Subjects: kvOf(HandActsBucket), Writes: isController},
|
|
// What the healers did (novox/hq to-be 45 §7, Phase 3): the controller's alone, in its store — the
|
|
// budgets and the mesh-wide brake are counted from it, so a controller restarting cannot reset them.
|
|
{State: "the healers' acts and their brake", Writer: "controller (lease holder), each act begun before it is made",
|
|
KeptIn: "the controller's store", Others: "read through healers; each act said as healer-acted and in its condition's tried"},
|
|
{State: "stream definitions and bus permissions", Writer: "controller", KeptIn: "the bus", Others: "—",
|
|
// A stream's definition, and a durable consumer's by the API that names it so. Not every
|
|
// consumer create: a module watching its own bucket makes and deletes an ordered consumer on the
|
|
// bucket's stream (ADR 0201), which defines nothing the mesh keeps.
|
|
Subjects: []string{"$JS.API.STREAM.CREATE.>", "$JS.API.STREAM.UPDATE.>", "$JS.API.STREAM.DELETE.>",
|
|
"$JS.API.CONSUMER.DURABLE.CREATE.>"},
|
|
Writes: isController},
|
|
{State: "builds and their outcomes", Writer: "the build seat's holder", KeptIn: "its own state",
|
|
Others: "the controller asks",
|
|
Subjects: []string{"mesh.seat.node-build-agent.event.built", "mesh.seat.mesh-build-machine.event.built"},
|
|
Writes: holdsSeatOf,
|
|
Shared: "every machine holding the build seat answers the asks it took; each outcome names its ask"},
|
|
{State: "a merge announced", Writer: "one announcer per forge (the hook, or the poll when the hook is absent — never both)",
|
|
KeptIn: "the bus", Others: "—", Subjects: []string{"mesh.mod.*.event.pull.merged"}, Writes: ownModule},
|
|
// A pull request's head, before it merges (novox/hq to-be 45 §9): the same one announcer.
|
|
{State: "a pull request's head announced", Writer: "the forge's announcer, the one that announces its merges",
|
|
KeptIn: "the bus", Others: "the controller asks the build seat to check it", Subjects: []string{"mesh.mod.*.event.pull.updated"},
|
|
Writes: ownModule},
|
|
{State: "a pull request's merge check", Writer: "the build seat's holder that ran it, said as the controller's `checked`",
|
|
KeptIn: "the bus", Others: "the forge's holder sets it as the pull request's status"},
|
|
{State: "a provider's standing", Writer: "the provider", KeptIn: "the provider's events",
|
|
Others: "the controller keeps the newest word as a condition",
|
|
Subjects: []string{"mesh.mod.*.event.provisioner.failing", "mesh.mod.*.event.provisioner.recovered",
|
|
"mesh.mod.*.event.provisioner.retirement"},
|
|
Writes: ownModule},
|
|
{State: "the operator-channel's open messages", Writer: "the seat's holder", KeptIn: "its own key-value state",
|
|
Others: "—"},
|
|
{State: "the facts snapshot", Writer: "controller", KeptIn: "the artifact store, facts/latest",
|
|
Others: "the build seat reads"},
|
|
}
|
|
|
|
// CheckWriters refuses a grant that lets a principal publish on a subject the writers table gives
|
|
// another writer (to-be 45 §1): which state, whose, and the pattern that would make a second writer.
|
|
func CheckWriters(p Principal, publish []string) error {
|
|
var problems []string
|
|
for _, pattern := range publish {
|
|
for _, row := range WritersTable {
|
|
if row.Writes == nil {
|
|
continue
|
|
}
|
|
for _, subject := range row.Subjects {
|
|
if !SubjectsOverlap(pattern, subject) || row.Writes(p, pattern) {
|
|
continue
|
|
}
|
|
problems = append(problems, fmt.Sprintf("%s may publish %s, which writes %s (%s), whose writer is %s",
|
|
p.Username(), pattern, row.State, subject, row.Writer))
|
|
}
|
|
}
|
|
}
|
|
if len(problems) == 0 {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("a second writer would be granted (novox/hq to-be 45 §1, one writer per piece of state):\n %s",
|
|
strings.Join(problems, "\n "))
|
|
}
|
|
|
|
// SubjectsOverlap says some subject matches both patterns: `*` is one token, `>` one or more to the end.
|
|
func SubjectsOverlap(a, b string) bool {
|
|
x, y := strings.Split(a, "."), strings.Split(b, ".")
|
|
for i := 0; ; i++ {
|
|
switch {
|
|
case i == len(x) && i == len(y):
|
|
return true
|
|
case i == len(x) || i == len(y):
|
|
return false
|
|
case x[i] == ">" || y[i] == ">":
|
|
return true
|
|
case x[i] == "*" || y[i] == "*" || x[i] == y[i]:
|
|
continue
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
}
|