mesh-self-check reads the placement, seat holders, presence and health reports through the seats' types and clients; RootContract is the node-engine client's (B5g; ADR 0634 §1, §4; issue 1966)
Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
@@ -9,12 +9,13 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
assignerclient "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
||||
placing "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient"
|
||||
"git.novox.be/novox/mesh-sdk/go/views"
|
||||
@@ -76,10 +77,10 @@ func NewChecker() *Checker {
|
||||
// self-check's own (first-view-unanswered), written into its state findings.
|
||||
func newViews() *views.Reader {
|
||||
r := &views.Reader{Module: Module, Logf: logf, Findings: meshconditionsclient.FindingWriter{State: stdio.State("findings")}, Acts: meshhandactsclient.Recorder{Ask: stdio.Ask}}
|
||||
r.Follow(delivery.AssignerModule, delivery.PlacementState, PlacementTool)
|
||||
r.Follow(delivery.AssignerModule, delivery.SeatHoldersState, SeatHoldersTool)
|
||||
r.Follow(delivery.IndexerModule, "modules", ModulesTool)
|
||||
r.Follow(delivery.IndexerModule, "manifests", ManifestsTool)
|
||||
r.Follow(assignerclient.Module, placing.StatePlacement, PlacementTool)
|
||||
r.Follow(assignerclient.Module, placing.StateSeatHolders, SeatHoldersTool)
|
||||
r.Follow(indexerModule, "modules", ModulesTool)
|
||||
r.Follow(indexerModule, "manifests", ManifestsTool)
|
||||
return r
|
||||
}
|
||||
|
||||
|
||||
@@ -22,15 +22,14 @@ import (
|
||||
"git.novox.be/novox/mesh-catalog/modules/disk-load/diskreadings"
|
||||
"git.novox.be/novox/mesh-catalog/modules/restic/backupgood"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// The states D13 reads (declared under `reads`).
|
||||
const (
|
||||
ReadingsRead = diskreadings.ReadingsRead
|
||||
GoodRead = backupgood.GoodRead
|
||||
ModulesRead = delivery.IndexerModule + ".modules"
|
||||
ManifestsRead = delivery.IndexerModule + ".manifests"
|
||||
ModulesRead = indexerModule + ".modules"
|
||||
ManifestsRead = indexerModule + ".manifests"
|
||||
)
|
||||
|
||||
// The kinds D13 raises.
|
||||
|
||||
@@ -10,6 +10,9 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
watchdogs "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshwatchdogs"
|
||||
assignerclient "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
||||
placing "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
||||
"net"
|
||||
"regexp"
|
||||
"slices"
|
||||
@@ -18,22 +21,21 @@ import (
|
||||
"sync"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// The states the machine probes read (declared under `reads`), and the verb D8 asks each node.
|
||||
const (
|
||||
PlacementRead = delivery.AssignerModule + "." + "placement"
|
||||
SeatHoldersRead = delivery.SeatHoldersRead
|
||||
PresenceRead = delivery.WatchmanModule + ".presence"
|
||||
PlacementRead = assignerclient.Module + "." + "placement"
|
||||
SeatHoldersRead = assignerclient.SeatHoldersRead
|
||||
PresenceRead = watchmanModule + ".presence"
|
||||
BannedSeat = "node-intrusion-prevention"
|
||||
BannedVerb = "seat:" + BannedSeat + ".banned"
|
||||
ScopeMachine = meshconditionsclient.ScopeMachine
|
||||
)
|
||||
|
||||
// placements is every node's placement, by node, from the state's keys in either form: a node key and its module keys,
|
||||
// or the form before, `placement.<node>` (delivery.PlacementsOf; research 072 row 37).
|
||||
func (c *Checker) placements() (map[string]delivery.Placement, error) {
|
||||
// or the form before, `placement.<node>` (assignerclient.PlacementsOf; research 072 row 37).
|
||||
func (c *Checker) placements() (map[string]placing.Placement, error) {
|
||||
keys, err := c.Store.Keys(PlacementRead)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the placement cannot be read: %w", err)
|
||||
@@ -48,7 +50,7 @@ func (c *Checker) placements() (map[string]delivery.Placement, error) {
|
||||
values[k] = raw
|
||||
}
|
||||
}
|
||||
out, err := delivery.PlacementsOf(values)
|
||||
out, err := assignerclient.PlacementsOf(values)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the placement is not readable: %w", err)
|
||||
}
|
||||
@@ -63,15 +65,15 @@ func (c *Checker) heard() (map[string]bool, error) {
|
||||
}
|
||||
out := map[string]bool{}
|
||||
for _, k := range keys {
|
||||
node, _, _, err := delivery.ParsePresenceKey(k)
|
||||
if err != nil || node == "" {
|
||||
node, ok := watchdogs.ParsePresenceByNodeKey(k)
|
||||
if !ok || node == "" {
|
||||
continue
|
||||
}
|
||||
raw, ok, err := c.Store.Get(PresenceRead, k)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the presence of %s cannot be read: %w", node, err)
|
||||
}
|
||||
var p delivery.Presence
|
||||
var p watchdogs.PresenceByNodeValue
|
||||
if ok && json.Unmarshal(raw, &p) == nil && p.Online {
|
||||
out[node] = true
|
||||
}
|
||||
@@ -94,7 +96,7 @@ func (c *Checker) holdersOf(seat string) ([]string, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the holders of %s cannot be read: %w", k, err)
|
||||
}
|
||||
var sh delivery.SeatHolders
|
||||
var sh placing.SeatHolders
|
||||
if !ok || json.Unmarshal(raw, &sh) != nil || sh.Seat != seat || sh.Node == "" || len(sh.Holders) == 0 {
|
||||
continue
|
||||
}
|
||||
@@ -214,3 +216,10 @@ func probeBans(ctx context.Context, c *Checker) ([]Observation, error) {
|
||||
}
|
||||
return sorted(out), nil
|
||||
}
|
||||
|
||||
// The holders whose states the probes read, one constant each (a reader names its holder once; ADR 0634 §4).
|
||||
const (
|
||||
watchmanModule = "mesh-delivery-watchman"
|
||||
indexerModule = "mesh-delivery-indexer"
|
||||
nodeEngineModule = "mesh-host"
|
||||
)
|
||||
|
||||
@@ -9,18 +9,19 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
engineclient "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
nodeseat "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
placing "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// The engine's words and bounds the judgement reads (mesh-host rootsearch).
|
||||
const (
|
||||
// HealthRead is the node-engines' health reports.
|
||||
HealthRead = delivery.NodeHealthRead
|
||||
HealthRead = (nodeEngineModule + "." + nodeseat.StateHealth)
|
||||
// rootPending starts a verdict's reason while the engine's setuid search runs and no complete one judges.
|
||||
rootPending = "not judged yet (search running)"
|
||||
// rootQuiet is how long nothing is raised while an agent account waits for that search, counted from when this
|
||||
@@ -32,17 +33,17 @@ const (
|
||||
)
|
||||
|
||||
// report is a node's health report, and whether it has one.
|
||||
func (c *Checker) report(node string) (delivery.NodeHealth, bool, error) {
|
||||
func (c *Checker) report(node string) (nodeseat.HealthByNodeValue, bool, error) {
|
||||
raw, ok, err := c.Store.Get(HealthRead, node)
|
||||
if err != nil {
|
||||
return delivery.NodeHealth{}, false, fmt.Errorf("the health report of %s cannot be read: %w", node, err)
|
||||
return nodeseat.HealthByNodeValue{}, false, fmt.Errorf("the health report of %s cannot be read: %w", node, err)
|
||||
}
|
||||
if !ok {
|
||||
return delivery.NodeHealth{}, false, nil
|
||||
return nodeseat.HealthByNodeValue{}, false, nil
|
||||
}
|
||||
h, err := delivery.DecodeNodeHealth(raw)
|
||||
h, err := engineclient.DecodeHealth(raw)
|
||||
if err != nil {
|
||||
return delivery.NodeHealth{}, false, fmt.Errorf("the health report of %s: %w", node, err)
|
||||
return nodeseat.HealthByNodeValue{}, false, fmt.Errorf("the health report of %s: %w", node, err)
|
||||
}
|
||||
return h, true, nil
|
||||
}
|
||||
@@ -83,7 +84,7 @@ const (
|
||||
|
||||
// judgedConfined is the controller's judgement over one machine's report: confined only when the machine is heard
|
||||
// from, its engine judges root, and every verdict on the agent account is healthy. why is said either way.
|
||||
func judgedConfined(agent string, h delivery.NodeHealth, had, heard bool) (bool, string) {
|
||||
func judgedConfined(agent string, h nodeseat.HealthByNodeValue, had, heard bool) (bool, string) {
|
||||
switch {
|
||||
case !had:
|
||||
return false, fmt.Sprintf("the agent account %s is not judged: the machine's node-engine has stated "+
|
||||
@@ -91,10 +92,10 @@ func judgedConfined(agent string, h delivery.NodeHealth, had, heard bool) (bool,
|
||||
case !heard:
|
||||
return false, fmt.Sprintf("the agent account %s is not judged: the machine is not heard from, and a verdict "+
|
||||
"from before is not a verdict on now", agent)
|
||||
case h.Contract < delivery.RootContract:
|
||||
case h.Contract < engineclient.RootContract:
|
||||
return false, fmt.Sprintf("the agent account %s is not judged: the machine's node-engine is older than "+
|
||||
"the judging of an account's root (its statement's contract is %d, the judging is %d)",
|
||||
agent, h.Contract, delivery.RootContract)
|
||||
agent, h.Contract, engineclient.RootContract)
|
||||
}
|
||||
verdicts := verdictsOn(agent, h)
|
||||
if len(verdicts) == 0 {
|
||||
@@ -118,8 +119,8 @@ func judgedConfined(agent string, h delivery.NodeHealth, had, heard bool) (bool,
|
||||
}
|
||||
|
||||
// verdictsOn is every verdict on the account, sorted by module and resource.
|
||||
func verdictsOn(agent string, h delivery.NodeHealth) []delivery.RootVerdict {
|
||||
var out []delivery.RootVerdict
|
||||
func verdictsOn(agent string, h nodeseat.HealthByNodeValue) []nodeseat.RootAccountVerdict {
|
||||
var out []nodeseat.RootAccountVerdict
|
||||
for _, v := range h.Root {
|
||||
if v.Account == agent {
|
||||
out = append(out, v)
|
||||
@@ -131,8 +132,8 @@ func verdictsOn(agent string, h delivery.NodeHealth) []delivery.RootVerdict {
|
||||
|
||||
// rootVerdictKind reads a report for the agent account: pending when every verdict on it is healthy or waits for the
|
||||
// setuid search, at least one of them that; complete when every verdict is healthy or one found a way to root.
|
||||
func rootVerdictKind(agent string, h delivery.NodeHealth, had, heard bool) int {
|
||||
if !had || !heard || h.Contract < delivery.RootContract {
|
||||
func rootVerdictKind(agent string, h nodeseat.HealthByNodeValue, had, heard bool) int {
|
||||
if !had || !heard || h.Contract < engineclient.RootContract {
|
||||
return verdictOther
|
||||
}
|
||||
pending, any := false, false
|
||||
@@ -159,7 +160,7 @@ func rootVerdictKind(agent string, h delivery.NodeHealth, had, heard bool) int {
|
||||
|
||||
// agentQuiet says the one thing keeping a machine's agent account unjudged is the engine's setuid search, first seen
|
||||
// waiting less than rootQuiet ago.
|
||||
func (c *Checker) agentQuiet(node, agent string, h delivery.NodeHealth, had, heard bool) bool {
|
||||
func (c *Checker) agentQuiet(node, agent string, h nodeseat.HealthByNodeValue, had, heard bool) bool {
|
||||
switch rootVerdictKind(agent, h, had, heard) {
|
||||
case verdictComplete:
|
||||
c.pending.judged(node)
|
||||
@@ -186,7 +187,7 @@ type confinement struct {
|
||||
}
|
||||
|
||||
// confined judges node's agent account from its placement, its report and its presence.
|
||||
func (c *Checker) confined(node string, place delivery.Placement, heard bool) (confinement, error) {
|
||||
func (c *Checker) confined(node string, place placing.Placement, heard bool) (confinement, error) {
|
||||
agent := place.Decided.AgentAccount
|
||||
if agent == "" {
|
||||
return confinement{Why: fmt.Sprintf("%s names no agent account: agents run as the operator account (%s)",
|
||||
|
||||
@@ -4,6 +4,13 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
engineclient "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
nodeseat "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
watchdogs "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshwatchdogs"
|
||||
assignerclient "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
||||
placing "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
||||
catalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -13,7 +20,6 @@ import (
|
||||
"git.novox.be/novox/mesh-catalog/modules/restic/backupgood"
|
||||
"git.novox.be/novox/mesh-sdk/go/buscompose"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// bus is the bus module's answers as a probe asks them: every mesh stream and consumer as defined, unless changed.
|
||||
@@ -257,42 +263,42 @@ func machines() *memStore {
|
||||
node, address, endpoint string
|
||||
online bool
|
||||
}{{"novox", "10.10.0.1/16", "203.0.113.5:51820", true}, {"ace", "10.10.0.2/16", "", true}, {"g14", "10.10.0.3/16", "", false}} {
|
||||
pl := delivery.Placement{V: 1, Node: p.node,
|
||||
Decided: delivery.NodeDecided{Address: p.address, Endpoint: p.endpoint, Hub: p.endpoint != ""}}
|
||||
pl := placing.Placement{V: 1, Node: p.node,
|
||||
Decided: placing.NodeDecided{Address: p.address, Endpoint: p.endpoint, Hub: p.endpoint != ""}}
|
||||
if p.node == "ace" {
|
||||
pl.Modules = []delivery.PlacedModule{{Module: "sshd"}} // a module key beside the node key
|
||||
pl.Modules = []placing.PlacedModule{{Module: "sshd"}} // a module key beside the node key
|
||||
}
|
||||
place(s, pl)
|
||||
_ = s.Put(PresenceRead, p.node, delivery.Presence{V: 1, Online: p.online})
|
||||
key, _ := delivery.SeatHoldersKey(BannedSeat, p.node)
|
||||
_ = s.Put(SeatHoldersRead, key, delivery.SeatHolders{V: 1, Seat: BannedSeat, Scope: "node", Node: p.node,
|
||||
Holders: []delivery.SeatHolder{{Module: "fail2ban", Node: p.node}}})
|
||||
_ = s.Put(PresenceRead, p.node, watchdogs.PresenceByNodeValue{V: 1, Online: p.online})
|
||||
key, _ := assignerclient.SeatHoldersKey(BannedSeat, p.node)
|
||||
_ = s.Put(SeatHoldersRead, key, placing.SeatHolders{V: 1, Seat: BannedSeat, Scope: "node", Node: p.node,
|
||||
Holders: []placing.SeatHoldersHoldersItem{{Module: "fail2ban", Node: p.node}}})
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// place writes a node's placement as the seat does, its node key and a key per module (research 072 row 37); g14's in
|
||||
// the form before, `placement.g14`, which the probes still read while the change is delivered.
|
||||
func place(s *memStore, p delivery.Placement) {
|
||||
func place(s *memStore, p placing.Placement) {
|
||||
if p.Node == "g14" {
|
||||
raw, _ := delivery.EncodePlacement(p)
|
||||
raw, _ := assignerclient.EncodePlacement(p)
|
||||
_ = s.Put(PlacementRead, "placement.g14", json.RawMessage(raw))
|
||||
return
|
||||
}
|
||||
values, _ := delivery.PlacementValues(p)
|
||||
values, _ := assignerclient.PlacementValues(p)
|
||||
for k, raw := range values {
|
||||
_ = s.Put(PlacementRead, k, json.RawMessage(raw))
|
||||
}
|
||||
}
|
||||
|
||||
// placed is every node's placement the store holds.
|
||||
func placed(s *memStore) map[string]delivery.Placement {
|
||||
func placed(s *memStore) map[string]placing.Placement {
|
||||
keys, _ := s.Keys(PlacementRead)
|
||||
values := map[string][]byte{}
|
||||
for _, k := range keys {
|
||||
values[k], _, _ = s.Get(PlacementRead, k)
|
||||
}
|
||||
out, _ := delivery.PlacementsOf(values)
|
||||
out, _ := assignerclient.PlacementsOf(values)
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -314,20 +320,20 @@ func agents(verdicts map[string]string) *memStore {
|
||||
p := placed(s)[node]
|
||||
p.Decided.AgentAccount = "agent"
|
||||
place(s, p)
|
||||
h := delivery.NodeHealth{V: 1, Node: node, Contract: 13}
|
||||
h := nodeseat.HealthByNodeValue{V: 1, Node: node, Contract: 13}
|
||||
switch v {
|
||||
case "":
|
||||
case "old":
|
||||
h.Contract = 2
|
||||
h.Root = []delivery.RootVerdict{{Account: "agent", Module: "claude-code", Resource: "claude-code.agent", State: stateHealthy}}
|
||||
h.Root = []nodeseat.RootAccountVerdict{{Account: "agent", Module: "claude-code", Resource: "claude-code.agent", State: stateHealthy}}
|
||||
case "pending":
|
||||
h.Root = []delivery.RootVerdict{{Account: "agent", Module: "claude-code", Resource: "claude-code.agent",
|
||||
h.Root = []nodeseat.RootAccountVerdict{{Account: "agent", Module: "claude-code", Resource: "claude-code.agent",
|
||||
State: stateUnknown, Reason: rootPending + ": 3 of 9 trees"}}
|
||||
default:
|
||||
h.Root = []delivery.RootVerdict{{Account: "agent", Module: "claude-code", Resource: "claude-code.agent", State: v,
|
||||
h.Root = []nodeseat.RootAccountVerdict{{Account: "agent", Module: "claude-code", Resource: "claude-code.agent", State: v,
|
||||
Reason: "can become root without a person: a sudo rule"}}
|
||||
}
|
||||
raw, _ := delivery.EncodeNodeHealth(h)
|
||||
raw, _ := engineclient.EncodeHealth(h)
|
||||
_ = s.Put(HealthRead, node, json.RawMessage(raw))
|
||||
}
|
||||
return s
|
||||
@@ -429,10 +435,10 @@ func (r *dataRig) firstSeen(node, module, item string, ago time.Duration) *dataR
|
||||
}
|
||||
|
||||
func (r *dataRig) holder(node string) {
|
||||
key, _ := delivery.SeatHoldersKey("node-backup", node)
|
||||
_ = r.s.Put(SeatHoldersRead, key, delivery.SeatHolders{V: 1, Seat: "node-backup", Scope: "node", Node: node,
|
||||
Holders: []delivery.SeatHolder{{Module: "restic", Node: node}}})
|
||||
_ = r.s.Put(PresenceRead, node, delivery.Presence{V: 1, Online: true})
|
||||
key, _ := assignerclient.SeatHoldersKey("node-backup", node)
|
||||
_ = r.s.Put(SeatHoldersRead, key, placing.SeatHolders{V: 1, Seat: "node-backup", Scope: "node", Node: node,
|
||||
Holders: []placing.SeatHoldersHoldersItem{{Module: "restic", Node: node}}})
|
||||
_ = r.s.Put(PresenceRead, node, watchdogs.PresenceByNodeValue{V: 1, Online: true})
|
||||
}
|
||||
|
||||
// dataMesh builds the mesh: pg declares data (backed up within 48 h, written all the time within 24 h) and dumps; plex
|
||||
@@ -450,9 +456,9 @@ func dataMesh(change func(*dataRig)) *memStore {
|
||||
man.Data.Own = append(man.Data.Own, it)
|
||||
}
|
||||
raw, _ := json.Marshal(man)
|
||||
digest := delivery.ManifestDigest(raw)
|
||||
_ = r.s.Put(ManifestsRead, digest, delivery.Manifest{V: 1, Module: module, JSON: string(raw)})
|
||||
_ = r.s.Put(ModulesRead, module, delivery.Module{V: 1, Manifest: digest})
|
||||
digest := catalogclient.ManifestDigest(raw)
|
||||
_ = r.s.Put(ManifestsRead, digest, catalog.ManifestsByDigestValue{V: 1, Module: module, Manifest: string(raw)})
|
||||
_ = r.s.Put(ModulesRead, module, catalog.ModulesByModuleValue{V: 1, Manifest: digest})
|
||||
}
|
||||
declare("pg", map[string]catalogue.DataItem{
|
||||
"data": {Class: catalogue.ClassValuable, Within: "48h", Active: "24h"},
|
||||
|
||||
@@ -3,6 +3,13 @@ module mesh-self-check
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshwatchdogs v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/go/seats/nodeengine v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104174359-05b3e2e1b94f
|
||||
|
||||
Reference in New Issue
Block a user