mesh/delivery-group group feat/sdk1 rejected: composed together: the checker's verdict at the members' heads (428d048dd50a): unit: git.novox.be/novox/mesh-sdk/…
mesh/delivery closed: a newer head of the same pull request
mesh/check failed: unit: git.novox.be/novox/mesh-sdk/go/process.TestStatePutWritesAKeyWhole: a Put through a short blackout: 0, mesh-mail's state send…
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
145 lines
5.6 KiB
Go
145 lines
5.6 KiB
Go
package catalogue
|
||
|
||
import (
|
||
"math"
|
||
"regexp"
|
||
"sort"
|
||
)
|
||
|
||
// Shape is what a kind received as data looks like.
|
||
type Shape struct {
|
||
// Places are the fields a holder narrows its placeholders by, each with every value it takes.
|
||
Places []PlaceField
|
||
// Order is the whole-number field that orders the pieces within one placeholder (lowest first,
|
||
// then module order); absent in a piece means OrderDefault.
|
||
Order string
|
||
// Check is what else is wrong with one piece's data, beyond its placing fields and its order.
|
||
Check func(data map[string]any) []string `json:"-"`
|
||
// Examples are pieces every holder's template must render, one for each variant the shape has,
|
||
// so a holder that cannot render one is refused at registration and not found on a machine.
|
||
Examples []map[string]any
|
||
// Reads is the state on the bus a piece shows, each a state and the one key of it, or nothing
|
||
// (novox/hq ADR 0260): a contributor offers only state it keeps itself, and the holder on the same
|
||
// machine is granted to read that key, so the holder's manifest names no contributor.
|
||
Reads func(data map[string]any) []StateRead `json:"-"`
|
||
// Text says every piece of this kind is, rendered, text that a collector may place as a collection of items
|
||
// (design 57 L3): a collector then names the kind as `${collected:contribution:<seat>:<kind>:<field>=<value>,…}`.
|
||
Text bool
|
||
}
|
||
|
||
// PlaceField is one field a holder places by, and the values it takes.
|
||
type PlaceField struct {
|
||
Field string
|
||
Values []string
|
||
}
|
||
|
||
// OrderDefault is a piece's order when it gives none: the middle of 0–99.
|
||
const OrderDefault = 50
|
||
|
||
// KnownCapabilities is every capability the node-engine detects (mesh-host internal/profile), the
|
||
// names an `if-capability` may give (novox/hq ADR 0255). A name nothing detects would leave its
|
||
// contribution out on every machine, silently, so it is refused. A detector added there is added here.
|
||
var KnownCapabilities = []string{
|
||
"battery", "container-groups", "container-runtime", "file-references", "firewall", "graphical-session", "overlay",
|
||
"package-manager",
|
||
"power-meter", "privileged", "seat", "service-manager", "uplink-dhcpcd", "uplink-networkmanager",
|
||
"uplink-systemd-networkd", "user-namespaces", "virtualisation",
|
||
}
|
||
|
||
// StateRead is one key of another module's state that a holder reads: Read is `<module>.<name>`,
|
||
// and Key the one key, empty for the holder's own machine until ReadsGranted names it.
|
||
type StateRead struct {
|
||
Read, Key string
|
||
}
|
||
|
||
// stateKeyName is a key a piece may name: a machine's name is one.
|
||
var stateKeyName = regexp.MustCompile(`^[a-z0-9][a-z0-9-]*$`)
|
||
|
||
// ReadsGranted is the state a holder reads because modules on its machine offer it in pieces of a
|
||
// kind its seat receives as data (novox/hq ADR 0260), each with the one key the piece shows — the
|
||
// machine's own when it names none — sorted and once each. A module's items of such a kind (design 57 L3) grant
|
||
// what they show too, always at the machine's own key; an item whose capability the machine lacks grants a key
|
||
// that is never written, which is no wider than its own machine's. The bus grants the holder these keys and
|
||
// no others: the holder names no contributor, and reads only what is offered to it where it runs.
|
||
func ReadsGranted(holder Manifest, onMachine []Manifest, machine string) []StateRead {
|
||
seen := map[StateRead]bool{}
|
||
for _, claim := range holder.Claims {
|
||
s, known := SeatNamed(claim.Name)
|
||
if !known {
|
||
continue
|
||
}
|
||
for _, r := range s.Receives {
|
||
if r.Shape == nil || r.Shape.Reads == nil {
|
||
continue
|
||
}
|
||
for _, m := range onMachine {
|
||
for _, c := range m.allContributions() {
|
||
if cs, ok := SeatNamed(c.Seat); !ok || cs.Name != s.Name || c.Kind != r.Kind {
|
||
continue
|
||
}
|
||
for _, sr := range r.Shape.Reads(c.Data) {
|
||
if sr.Key == "" {
|
||
sr.Key = machine
|
||
}
|
||
if module, _, err := ReadState(sr.Read); err == nil && module == m.Module && module != holder.Module &&
|
||
stateKeyName.MatchString(sr.Key) {
|
||
seen[sr] = true
|
||
}
|
||
}
|
||
}
|
||
// A moved contributor's items of the kind (design 57 L3): what each shows, read from its attributes as a
|
||
// contribution's data, and always this machine's own key (ADR 0260), never another's (issue 774:
|
||
// power's draw block became an item and i3status-rust was issued nothing).
|
||
for _, it := range m.Items {
|
||
if it.Collection != "contribution:"+s.Name+":"+r.Kind {
|
||
continue
|
||
}
|
||
for _, sr := range r.Shape.Reads(itemData(it)) {
|
||
sr.Key = machine
|
||
if module, _, err := ReadState(sr.Read); err == nil && module == m.Module && module != holder.Module &&
|
||
stateKeyName.MatchString(sr.Key) {
|
||
seen[sr] = true
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
out := make([]StateRead, 0, len(seen))
|
||
for sr := range seen {
|
||
out = append(out, sr)
|
||
}
|
||
sort.Slice(out, func(i, j int) bool {
|
||
if out[i].Read != out[j].Read {
|
||
return out[i].Read < out[j].Read
|
||
}
|
||
return out[i].Key < out[j].Key
|
||
})
|
||
return out
|
||
}
|
||
|
||
// itemData is an item's attributes in the shape of a contribution's data: each at the top and among the options, where
|
||
// a shape's Reads looks for what a piece shows (barBlockReads: shows, options.state).
|
||
func itemData(it Item) map[string]any {
|
||
data, options := map[string]any{}, map[string]any{}
|
||
for k, v := range it.Attributes {
|
||
data[k], options[k] = v, v
|
||
}
|
||
data["options"] = options
|
||
return data
|
||
}
|
||
|
||
// wholeIn is v as a whole number within [lo, hi]; JSON gives every number as a float.
|
||
func wholeIn(v any, lo, hi int) (int, bool) {
|
||
f, ok := v.(float64)
|
||
if !ok {
|
||
if i, isInt := v.(int); isInt {
|
||
f, ok = float64(i), true
|
||
}
|
||
}
|
||
if !ok || f != math.Trunc(f) || f < float64(lo) || f > float64(hi) {
|
||
return 0, false
|
||
}
|
||
return int(f), true
|
||
}
|