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` (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 }