mesh/delivery merged: source.main moved to novox/mesh-host@875b4ee5ce38
mesh/delivery-group group fix/1966-seat-reads-bootstrap merged: every member is merged
mesh/check failed (2): mesh-delivery-checker: no published of the tagged version of go-sdk of mesh-delivery…; and 1 more | 9ab85ef92e6c on 875b4ee5ce38
manifestReads froze the statement at its first call, before the engine gave catalogue even its bootstrapped seats (ADR 0635 empties mesh-sdk's table until the list is read), so ace's engine stated no seat and its own next build, naming mesh-catalog, was held on it. The words and forms stay the build's, computed once; the seats are those of the list held now. The reader of the seat list restates after each change: the node record carries the new statement at once and the node seat's event manifest-reads-changed says it (ADR 0618 §4); an unchanged statement writes and says nothing; the verb manifest-reads answers when it was made. Claude-Session: https://claude.ai/code/session_01NcbgfFfQ4Z9UE5jK32PmE5
148 lines
5.3 KiB
Go
148 lines
5.3 KiB
Go
package link
|
|
|
|
import (
|
|
"encoding/json"
|
|
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
|
sdkkeys "git.novox.be/novox/mesh-sdk/go/keys"
|
|
"slices"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
)
|
|
|
|
// The node's record (novox/hq ADR 0555 §3, issue 1126): what the machine observes of itself, written in its own have key
|
|
// `<node>.node` (nodeengine.HaveByNodeNodeValue.Record) with its facts, so every node and the parts that compose read it from the bus
|
|
// and hear its change as the key's. Each value is the one the controller took from this node's enrolment and reports
|
|
// until now (its profile's present capabilities and networks, its outward links, its health statement's contract, and
|
|
// the public halves of the keys it enrolled with), read here from the same sources, never from the controller.
|
|
|
|
// RecordKeys are the public halves the node holds and the certificate the mesh issued for its internal name, as read
|
|
// from its state directory at the time of a pass: the certificate arrives after enrolment, a rekey changes the tunnel key.
|
|
type RecordKeys struct {
|
|
TunnelKey string
|
|
SealingKey string
|
|
ServingKey string
|
|
Certificate string
|
|
// Authority is the mesh's own certificate as this machine last applied it (public).
|
|
Authority string
|
|
// Trusts is what this machine trusts a mesh signature from now (delivery.Trusts, ADR 0550 §2).
|
|
Trusts []string
|
|
// Stated is the capabilities its tool runner states of the calls it answers (CapabilitiesKey), which the record says
|
|
// beside the machine's own: a module whose manifest asks for one is held off a machine whose runner does not state it.
|
|
Stated []string
|
|
}
|
|
|
|
// buildReads are the words and forms this engine reads of a manifest (novox/hq ADR 0614 §4): the reader capabilities of
|
|
// the mesh-sdk it is built with, its seats left out. Computed once: they are the build's, and never change while it runs.
|
|
var buildReads = sync.OnceValue(func() []string {
|
|
return slices.DeleteFunc(catalogue.ManifestReads(), func(r string) bool { return strings.HasPrefix(r, catalogue.ReadSeat) })
|
|
})
|
|
|
|
// readsNow is what this engine reads of a manifest now: its build's words and forms, and the seats of the seat list it
|
|
// holds now (catalogue.Seats, which the engine's reader of the list gives catalogue: bootstrapped or read; ADR 0636).
|
|
func readsNow() []string {
|
|
set := map[string]bool{}
|
|
for _, r := range buildReads() {
|
|
set[r] = true
|
|
}
|
|
for _, seat := range catalogue.Seats() {
|
|
set[catalogue.ReadSeat+seat.Name] = true
|
|
}
|
|
out := make([]string, 0, len(set))
|
|
for r := range set {
|
|
out = append(out, r)
|
|
}
|
|
sort.Strings(out)
|
|
return out
|
|
}
|
|
|
|
// readsStatement is what this engine states it reads and since when (the verb manifest-reads, ADR 0618 §3): made at its
|
|
// first reading, and made again by Restate whenever the seat list it holds changes what it reads (ADR 0618 §4). The seat
|
|
// list is the process's, so the statement is too.
|
|
type readsStatement struct {
|
|
mu sync.Mutex
|
|
reads []string
|
|
at time.Time
|
|
}
|
|
|
|
var stated readsStatement
|
|
|
|
// now is the statement, made at the first reading.
|
|
func (s *readsStatement) now() ([]string, time.Time) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
if s.reads == nil {
|
|
s.reads, s.at = readsNow(), time.Now().UTC()
|
|
}
|
|
return slices.Clone(s.reads), s.at
|
|
}
|
|
|
|
// restate makes the statement again from what the engine reads now, and says whether it changed.
|
|
func (s *readsStatement) restate() ([]string, bool) {
|
|
cur := readsNow()
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
if s.reads != nil && slices.Equal(cur, s.reads) {
|
|
return slices.Clone(cur), false
|
|
}
|
|
s.reads, s.at = cur, time.Now().UTC()
|
|
return slices.Clone(cur), true
|
|
}
|
|
|
|
// manifestReads is what this engine states it reads of a manifest now, which its record carries.
|
|
func manifestReads() []string {
|
|
reads, _ := stated.now()
|
|
return reads
|
|
}
|
|
|
|
// recordOf is the node's record from one facts report and its keys; nil for a report that carries no profile (one that
|
|
// says nothing of the machine, such as a rekey), so the record last written stands.
|
|
func recordOf(node string, r Report, keys RecordKeys) *nodeengine.NodeRecord {
|
|
if r.Profile == nil {
|
|
return nil
|
|
}
|
|
var p struct {
|
|
Capabilities []struct {
|
|
Name string `json:"name"`
|
|
Present bool `json:"present"`
|
|
} `json:"capabilities"`
|
|
Machine *struct {
|
|
Networks []catalogue.AttachedNetwork `json:"networks"`
|
|
} `json:"machine"`
|
|
}
|
|
raw, err := json.Marshal(r.Profile)
|
|
if err != nil || json.Unmarshal(raw, &p) != nil {
|
|
return nil
|
|
}
|
|
rec := &nodeengine.NodeRecord{V: sdkkeys.Version, Node: node, TunnelKey: keys.TunnelKey, SealingKey: keys.SealingKey,
|
|
ServingKey: keys.ServingKey, Certificate: keys.Certificate, Authority: keys.Authority, Trusts: keys.Trusts,
|
|
ManifestReads: manifestReads()}
|
|
for _, c := range p.Capabilities {
|
|
if c.Present && c.Name != "" {
|
|
rec.Capabilities = append(rec.Capabilities, c.Name)
|
|
}
|
|
}
|
|
for _, c := range keys.Stated {
|
|
if c != "" && !slices.Contains(rec.Capabilities, c) {
|
|
rec.Capabilities = append(rec.Capabilities, c)
|
|
}
|
|
}
|
|
sort.Strings(rec.Capabilities)
|
|
if p.Machine != nil {
|
|
rec.NetworksSaid = true
|
|
for _, n := range p.Machine.Networks {
|
|
rec.Networks = append(rec.Networks, nodeengine.AttachedNetwork{Link: n.Link, Network: n.Network})
|
|
}
|
|
}
|
|
if len(r.Outward) > 0 {
|
|
rec.OutwardLinks = append([]string(nil), r.Outward...)
|
|
}
|
|
if r.Health != nil {
|
|
rec.Contract = int64(r.Health.Contract)
|
|
}
|
|
return rec
|
|
}
|