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..…` 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}, {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"}, 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 } } }