The controller ended by its unassignment (ADR 0590): its user, its lease's cut and reads, the operator's hand path to its compositions and its writers' rows go; an enrolling node enrols through mesh-enrolment alone; the desk's prompt stays the vault's alone (DeskOnly). The golden lists are now this composer's own, taken first from the controller's with its user removed. The bus module's tools may remove the controller's leftovers by name, consumers, CONTROL, ASSIGNMENTS, its 17 buckets and each node's declaration files' bucket, under the word controller-leftovers (clause 6). The provisioner client reads placement in place of the assignments mirror. Issue 1126 B3a, B5. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
149 lines
6.1 KiB
Go
149 lines
6.1 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
)
|
|
|
|
// ProcessBusField is the key a process resource declares its bus on. Controller-side only: never composed
|
|
// onto the node-engine's process resource (novox/hq ADR 0436 §1) — the wire declaration is mesh-host's own,
|
|
// vendored, with a strict decoder that refuses an unknown key.
|
|
const ProcessBusField = "bus"
|
|
|
|
// ProcessBus is what one process of a module does on the bus (novox/hq ADR 0436 §1, §2, §3, §5): its own bus
|
|
// account, granted exactly this and nothing of the module's other grants or the tool runner's — never a seat
|
|
// to hold or serve, never a tool call or seat verb answered, never a restricted stream heard, never a
|
|
// warrant published (§3, §5).
|
|
type ProcessBus struct {
|
|
// Emits are the events this process emits, named locally, as the module's own Emits names them
|
|
// (EmitsAll): a process emits only what its module may emit as a whole.
|
|
Emits []string `json:"emits,omitempty"`
|
|
// Queues are the module's own work-queue streams (ADR 0436 §6) this process consumes.
|
|
Queues []string `json:"queues,omitempty"`
|
|
// State is the module's own state, by local name, this process reads and writes.
|
|
State []string `json:"state,omitempty"`
|
|
// Asks says whether this process reads the router's records of this module's own asks.
|
|
Asks bool `json:"asks,omitempty"`
|
|
// Invokes are the tools and seat verbs this process calls, each <module>.<tool> or a role's
|
|
// seat:<seat>.<verb> (as Manifest.Invokes), or * for every tool on the mesh.
|
|
Invokes []string `json:"invokes,omitempty"`
|
|
// Removes are what a tools bundle's account may remove from the bus, from a closed set (BusRemovals; novox/hq ADR
|
|
// 0564 Limits 4): declared alone, by the module holding mesh-broker, never by a process.
|
|
Removes []string `json:"removes,omitempty"`
|
|
}
|
|
|
|
// What a bus account may remove (novox/hq ADR 0564 Limits 4), each its own grant: keys of the node-engine's acts
|
|
// bucket, and the ended controller's leftovers on the bus (ADR 0590 clause 6): its consumers, its CONTROL and ASSIGNMENTS
|
|
// streams, its buckets and the declaration files' object buckets, each by its name. A further consumer, stream or bucket
|
|
// is a change of this set, through a decision.
|
|
const (
|
|
RemovesEngineActs = "mesh-host_acts"
|
|
// RemovesControllerLeftovers is the closed set of the ended controller's leftovers (buscompose.ControllerLeftovers).
|
|
// The word does not name the controller's module, so the check that refuses any manifest naming it (ADR 0590 clause
|
|
// 5) holds over the bus module's manifest too.
|
|
RemovesControllerLeftovers = "controller-leftovers"
|
|
)
|
|
|
|
// BusAccount is one account of a module's own beside its `broker`: a process's (ADR 0436) or a tools bundle's (ADR 0564
|
|
// Limits 4), by its id (the process resource's, or the bundle's binary), with what its bus says.
|
|
type BusAccount struct {
|
|
ID string
|
|
Bus ProcessBus
|
|
// Tools says it is a tools bundle's, run by the node's tool runtime, not a process the node runs.
|
|
Tools bool
|
|
}
|
|
|
|
// BusAccounts is every own bus account of the module, processes first in resource order, then tools bundles in artifact
|
|
// order: what the grant composition composes a principal for, each delivered in its own secret broker-<id>. A tools
|
|
// bundle's is read from the repository's build, or from the resolved manifest's bundles.
|
|
func (m Manifest) BusAccounts() []BusAccount {
|
|
var out []BusAccount
|
|
buses := m.ProcessBuses()
|
|
for _, r := range m.Resources {
|
|
id := fmt.Sprint(r["id"])
|
|
if b, ok := buses[id]; ok && fmt.Sprint(r["type"]) == "process" {
|
|
out = append(out, BusAccount{ID: id, Bus: b})
|
|
}
|
|
}
|
|
seen := map[string]bool{}
|
|
if m.Build != nil {
|
|
for _, a := range m.Build.Artifacts {
|
|
if a.Bus != nil && a.Kind == ArtifactBundle && BinaryOf(a) != "" && !seen[BinaryOf(a)] {
|
|
seen[BinaryOf(a)] = true
|
|
out = append(out, BusAccount{ID: BinaryOf(a), Bus: *a.Bus, Tools: true})
|
|
}
|
|
}
|
|
}
|
|
for _, b := range m.Bundles {
|
|
if b.Bus != nil && b.Binary != "" && !seen[b.Binary] {
|
|
seen[b.Binary] = true
|
|
out = append(out, BusAccount{ID: b.Binary, Bus: *b.Bus, Tools: true})
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// ToolsBusSecret says name is the own secret a tools bundle's bus account is held in: `broker-<binary>` for a bundle
|
|
// whose bus the manifest declares (ADR 0564 Limits 4). Its file belongs to the runtime's account, which runs the bundle.
|
|
func ToolsBusSecret(m Manifest, name string) bool {
|
|
for _, a := range m.BusAccounts() {
|
|
if a.Tools && name == "broker-"+a.ID {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// UnmarshalJSON reads a process's bus strictly: an unknown key — serves, a seat to hold or claim, or
|
|
// anything else that would answer a tool call or a seat's verb — is refused here, by name (novox/hq ADR
|
|
// 0436 §3).
|
|
func (b *ProcessBus) UnmarshalJSON(raw []byte) error {
|
|
type plain ProcessBus
|
|
var p plain
|
|
dec := json.NewDecoder(bytes.NewReader(raw))
|
|
dec.DisallowUnknownFields()
|
|
if err := dec.Decode(&p); err != nil {
|
|
return fmt.Errorf("a process's bus is {emits, queues, state, asks, invokes}: %w", typedUnknown(err))
|
|
}
|
|
*b = ProcessBus(p)
|
|
return nil
|
|
}
|
|
|
|
// processBusOf is the bus a process resource declares, and whether it declared one at all.
|
|
func processBusOf(r map[string]any) (bus ProcessBus, present bool, err error) {
|
|
raw, present := r["bus"]
|
|
if !present {
|
|
return ProcessBus{}, false, nil
|
|
}
|
|
body, err := json.Marshal(raw)
|
|
if err != nil {
|
|
return ProcessBus{}, true, err
|
|
}
|
|
if err := json.Unmarshal(body, &bus); err != nil {
|
|
return ProcessBus{}, true, err
|
|
}
|
|
return bus, true, nil
|
|
}
|
|
|
|
// ProcessBuses is every process of this module that declares bus, by its id (the manifest's own resource
|
|
// id, novox/hq ADR 0436, settled by the lead) — what the grant composition reads to grant a process
|
|
// account exactly what it emits, consumes and calls (novox/hq ADR 0436 §2).
|
|
func (m Manifest) ProcessBuses() map[string]ProcessBus {
|
|
var out map[string]ProcessBus
|
|
for _, r := range m.Resources {
|
|
if fmt.Sprint(r["type"]) != "process" {
|
|
continue
|
|
}
|
|
b, present, err := processBusOf(r)
|
|
if !present || err != nil {
|
|
continue
|
|
}
|
|
if out == nil {
|
|
out = map[string]ProcessBus{}
|
|
}
|
|
out[fmt.Sprint(r["id"])] = b
|
|
}
|
|
return out
|
|
}
|