Merge pull request 'Modules without the controller: vault judges each sensitive caller, nox records its hand acts, provisioner binds ahead of the want (issue 1126 group B)' (#594) from feat/1126-removal into main
This commit was merged in pull request #594.
This commit is contained in:
@@ -1,39 +1,138 @@
|
|||||||
// Package controllerdeps is the catalogue's side of the lease write's manifest test (novox/hq ADR 0571 §13, ADR 0582
|
// Package controllerdeps is the catalogue's dependence check on the controller (novox/hq ADR 0590 §5, issue 1126): the
|
||||||
// clause 2, issue 774): no module of this tree reads, invokes, consumes, waits on or uses the controller beyond what the
|
// controller is not placed, so no module of this tree reads, requires, invokes, consumes, waits on, uses or otherwise
|
||||||
// named list of mesh-sdk's catalogue.KeepList keeps to the write itself, and the lease key, which names the lease's holder
|
// names it. Nothing is kept: not the lease, not the entries once kept to its write (ADR 0571 §13, superseded with ADR
|
||||||
// whoever that is. It decodes every manifest under root/modules and judges them with mesh-sdk's ManifestRefusals, the
|
// 0582). A later decision that places a controller again must first lift this check.
|
||||||
// judge the grants part's grants_lease_check and the other two repositories' tests run, so the rule is said once.
|
//
|
||||||
|
// It refuses, over the tree at root:
|
||||||
|
//
|
||||||
|
// - a directory modules/mesh-controller: the controller is no module of the catalogue;
|
||||||
|
// - a manifest string naming the controller's seat, a state, verb or event of it, or one of its buckets
|
||||||
|
// (`mesh-controller.<name>`, `seat:mesh-controller.<verb>`, `mesh-controller_<bucket>`), anywhere in the manifest;
|
||||||
|
// - a manifest string that is the bare module name `mesh-controller` outside the module's own `config`, where a value
|
||||||
|
// is the module's data (an issue owner naming the controller's repository is no dependency), not an address;
|
||||||
|
// - a line of any module's file naming one of the controller's buckets (`mesh-controller_`): its buckets are removed
|
||||||
|
// after the controller's end (ADR 0590 §6), so a reader of one reads nothing. A test (`_test.go`, `.test.ts`) reads
|
||||||
|
// no live bucket, its names are its own data, and a `testdata` directory is a copy of text from elsewhere (a design,
|
||||||
|
// a backlog): both are skipped.
|
||||||
|
//
|
||||||
|
// The rule is said once, here, for the catalogue: decoding stays the manifests check's (a manifest that is no JSON is
|
||||||
|
// an error here, never judged).
|
||||||
package controllerdeps
|
package controllerdeps
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Refusals is each reason the lease write's check gives over the manifests under root/modules in one phase
|
// Module is the controller's module name, and Bucket the prefix every bucket of its carries.
|
||||||
// (catalogue.PhaseBefore or catalogue.PhaseWrite), none when it passes. A module.json that does not decode is an error:
|
const (
|
||||||
// the manifests check says which, and nothing here is judged on a tree it cannot read.
|
Module = "mesh-controller"
|
||||||
func Refusals(root, phase string) ([]string, error) {
|
Bucket = Module + "_"
|
||||||
|
)
|
||||||
|
|
||||||
|
// address is a string naming the controller's seat, one of its states, verbs or events, or one of its buckets.
|
||||||
|
var address = regexp.MustCompile(`(^|[^A-Za-z0-9_-])(seat:)?mesh-controller([._][A-Za-z0-9])`)
|
||||||
|
|
||||||
|
// Refusals is each reason the check gives over the tree at root, sorted; none when it passes.
|
||||||
|
func Refusals(root string) ([]string, error) {
|
||||||
|
var out []string
|
||||||
|
if _, err := os.Lstat(filepath.Join(root, "modules", Module)); err == nil {
|
||||||
|
out = append(out, Module+": the controller is no module of the catalogue: it is not placed and nothing may depend on it (novox/hq ADR 0590 §5)")
|
||||||
|
}
|
||||||
files, err := filepath.Glob(filepath.Join(root, "modules", "*", "module.json"))
|
files, err := filepath.Glob(filepath.Join(root, "modules", "*", "module.json"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
sort.Strings(files)
|
|
||||||
manifests := map[string]catalogue.Manifest{}
|
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
raw, err := os.ReadFile(f)
|
raw, err := os.ReadFile(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
m, err := catalogue.Decode(raw)
|
var doc any
|
||||||
if err != nil {
|
if err := json.Unmarshal(raw, &doc); err != nil {
|
||||||
return nil, fmt.Errorf("%s: %w", f, err)
|
return nil, fmt.Errorf("%s: %w", f, err)
|
||||||
}
|
}
|
||||||
manifests[m.Module] = m
|
module := filepath.Base(filepath.Dir(f))
|
||||||
|
for _, s := range names(doc, false, nil) {
|
||||||
|
out = append(out, fmt.Sprintf("%s: names %q: the controller is not placed, so no manifest reads, requires, invokes, "+
|
||||||
|
"consumes, waits on or uses it; name the seat that holds the job now (novox/hq ADR 0590 §5)", module, s))
|
||||||
}
|
}
|
||||||
return catalogue.ManifestRefusals(manifests, phase), nil
|
}
|
||||||
|
buckets, err := bucketLines(root)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
out = append(out, buckets...)
|
||||||
|
sort.Strings(out)
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// names collects the strings of a decoded manifest that name the controller; inConfig is under the module's `config`.
|
||||||
|
func names(v any, inConfig bool, into []string) []string {
|
||||||
|
switch v := v.(type) {
|
||||||
|
case string:
|
||||||
|
if address.MatchString(v) || (!inConfig && strings.TrimSpace(v) == Module) {
|
||||||
|
into = append(into, v)
|
||||||
|
}
|
||||||
|
case []any:
|
||||||
|
for _, e := range v {
|
||||||
|
into = names(e, inConfig, into)
|
||||||
|
}
|
||||||
|
case map[string]any:
|
||||||
|
keys := make([]string, 0, len(v))
|
||||||
|
for k := range v {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
for _, k := range keys {
|
||||||
|
into = names(v[k], inConfig || k == "config", into)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return into
|
||||||
|
}
|
||||||
|
|
||||||
|
// bucketLines is every line of a module's file, outside tests and testdata, that names one of the controller's buckets.
|
||||||
|
func bucketLines(root string) ([]string, error) {
|
||||||
|
var out []string
|
||||||
|
modules := filepath.Join(root, "modules")
|
||||||
|
err := filepath.WalkDir(modules, func(path string, d fs.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) && path == modules {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if d.IsDir() {
|
||||||
|
if n := d.Name(); n == "testdata" || n == "node_modules" || n == ".git" {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !d.Type().IsRegular() || strings.HasSuffix(d.Name(), "_test.go") || strings.HasSuffix(d.Name(), ".test.ts") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
sc := bufio.NewScanner(f)
|
||||||
|
sc.Buffer(make([]byte, 0, 64*1024), 16*1024*1024)
|
||||||
|
rel, _ := filepath.Rel(root, path)
|
||||||
|
for n := 1; sc.Scan(); n++ {
|
||||||
|
if strings.Contains(sc.Text(), Bucket) {
|
||||||
|
out = append(out, fmt.Sprintf("%s:%d: names the controller's bucket (%s…): its buckets are removed after its "+
|
||||||
|
"end (novox/hq ADR 0590 §6); read the seat that holds the job now", filepath.ToSlash(rel), n, Bucket))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sc.Err()
|
||||||
|
})
|
||||||
|
return out, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,46 +5,51 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// 774/S13/AC1: every manifest of the catalogue at this head, at the phase of the lease write's own delivery, reads,
|
// ADR 0590 §5 (issue 1126 B10): no manifest of the catalogue at this head reads, invokes, consumes, waits on, uses or
|
||||||
// invokes and consumes nothing of the controller beyond the lease and the entries kept to the write.
|
// otherwise names the controller's seat, states or buckets, and no module's code names a bucket of the controller's.
|
||||||
func TestNoManifestDependsOnTheControllerBeyondTheLeaseAndTheEntriesKeptToTheWrite(t *testing.T) {
|
func TestNothingInTheCatalogueNamesTheController(t *testing.T) {
|
||||||
got, err := Refusals("../..", catalogue.PhaseWrite)
|
got, err := Refusals("../..")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if len(got) > 0 {
|
for _, r := range got {
|
||||||
t.Errorf("the catalogue still declares on the controller (novox/hq ADR 0571 §13):\n%s", strings.Join(got, "\n"))
|
t.Error(r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The check refuses what it must and lets the lease and the kept entries through, on a tree of its own.
|
// The check refuses every name of the controller, the lease and the entries once kept to its write among them, on a
|
||||||
func TestRefusalsJudgeATreeByPhase(t *testing.T) {
|
// tree of its own; a repository named mesh-controller (an issue's owner) and a word in prose are no dependency.
|
||||||
|
func TestRefusalsJudgeATree(t *testing.T) {
|
||||||
for _, row := range []struct {
|
for _, row := range []struct {
|
||||||
name, phase string
|
name string
|
||||||
manifests map[string]string
|
files map[string]string
|
||||||
want string // empty: passes
|
want string // empty: passes
|
||||||
}{
|
}{
|
||||||
{"the lease is no dependency", catalogue.PhaseWrite, map[string]string{"a": `{"module":"a","reads":["mesh-controller.lease"]}`}, ""},
|
{"the lease is refused like any read", map[string]string{"modules/a/module.json": `{"module":"a","reads":["mesh-controller.lease"]}`}, `a: names "mesh-controller.lease"`},
|
||||||
{"a read of the controller's states", catalogue.PhaseWrite, map[string]string{"a": `{"module":"a","reads":["mesh-controller.conditions"]}`}, "a declares read mesh-controller.conditions"},
|
{"a verb once kept to the write", map[string]string{"modules/b/module.json": `{"module":"b","invokes":["seat:mesh-controller.collect"]}`}, `b: names "seat:mesh-controller.collect"`},
|
||||||
{"a kept entry passes before the delivery", catalogue.PhaseBefore, map[string]string{"artifact-store-tools": `{"module":"artifact-store-tools","invokes":["seat:mesh-controller.collect"]}`}, ""},
|
{"an event consumed", map[string]string{"modules/c/module.json": `{"module":"c","consumes":[{"event":"mesh-controller.artifacts-gone"}]}`}, "mesh-controller.artifacts-gone"},
|
||||||
{"a kept-to-the-write entry passes at the write", catalogue.PhaseWrite, map[string]string{"artifact-store-tools": `{"module":"artifact-store-tools","invokes":["seat:mesh-controller.collect"]}`}, ""},
|
{"a requirement", map[string]string{"modules/d/module.json": `{"module":"d","requires":[{"name":"x","from":"mesh-controller"}]}`}, `d: names "mesh-controller"`},
|
||||||
|
{"the controller's own module", map[string]string{"modules/mesh-controller/module.json": `{"module":"mesh-controller"}`}, "mesh-controller: the controller is no module of the catalogue"},
|
||||||
|
{"a bucket in code", map[string]string{"modules/e/cmd/e/main.go": "package main\nconst b = \"mesh-controller_assignments\"\n"}, "modules/e/cmd/e/main.go:2: names the controller's bucket"},
|
||||||
|
{"a bucket in a test is its data", map[string]string{"modules/e/cmd/e/main_test.go": "package main\n// mesh-controller_lease\n"}, ""},
|
||||||
|
{"a fixture copied from elsewhere", map[string]string{"modules/e/cmd/e/testdata/x.md": "mesh-controller_rollouts\n"}, ""},
|
||||||
|
{"an owner and prose pass", map[string]string{"modules/f/module.json": `{"module":"f","config":{"owners":{"default":["mesh-controller","mesh-controller modules/route-proxy"]}},"why":"the mesh-controller module is gone"}`}, ""},
|
||||||
|
{"the seat's successors pass", map[string]string{"modules/g/module.json": `{"module":"g","reads":["mesh-placement.placement"],"invokes":["seat:mesh-hand-acts.hand-act"]}`}, ""},
|
||||||
} {
|
} {
|
||||||
t.Run(row.name, func(t *testing.T) {
|
t.Run(row.name, func(t *testing.T) {
|
||||||
root := t.TempDir()
|
root := t.TempDir()
|
||||||
for module, text := range row.manifests {
|
for path, text := range row.files {
|
||||||
dir := filepath.Join(root, "modules", module)
|
p := filepath.Join(root, path)
|
||||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := os.WriteFile(filepath.Join(dir, "module.json"), []byte(text), 0o644); err != nil {
|
if err := os.WriteFile(p, []byte(text), 0o644); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
got, err := Refusals(root, row.phase)
|
got, err := Refusals(root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,3 @@
|
|||||||
module checks/controllerdeps
|
module checks/controllerdeps
|
||||||
|
|
||||||
go 1.26.0
|
go 1.22
|
||||||
|
|
||||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101235037-c21d8fd9692c
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/klauspost/compress v1.20.0 // indirect
|
|
||||||
github.com/nats-io/nats.go v1.54.0 // indirect
|
|
||||||
github.com/nats-io/nkeys v0.4.16 // indirect
|
|
||||||
github.com/nats-io/nuid v1.0.1 // indirect
|
|
||||||
golang.org/x/crypto v0.57.0 // indirect
|
|
||||||
golang.org/x/sys v0.48.0 // indirect
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915 h1:x8k7IeMirMTxUo8BH+xUO+MDnHYgU+636jl1NIw1wvs=
|
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101235037-c21d8fd9692c h1:/YCxh+NYfqry3PxZYY1nQXaccupobxpsFt8c9BYuGBg=
|
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101235037-c21d8fd9692c/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
|
||||||
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
|
||||||
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
|
||||||
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
|
||||||
github.com/nats-io/nats.go v1.54.0/go.mod h1:y+DZoD1oBOYfZTU681eTUiUjI0vbqYGixNVFHcjHJ0k=
|
|
||||||
github.com/nats-io/nkeys v0.4.16 h1:rd5oAuLOb8mnAycB0xleuEBNS1pVVnN0fv/FF34Eypg=
|
|
||||||
github.com/nats-io/nkeys v0.4.16/go.mod h1:llLgWoI0o4z/Q57q2R1kHfmocyhGV6VG/U18Glg1Afs=
|
|
||||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
|
||||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
|
||||||
golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M=
|
|
||||||
golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA=
|
|
||||||
golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
|
|
||||||
golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
# What these tests read outside their package, as paths from the repository's root (issue 1936).
|
# What these tests read outside their package, as paths from the repository's root (issue 1936).
|
||||||
# every module's directory (its manifest), decoded and judged for what it still declares on the controller.
|
# every module's directory: its manifest, for a name of the controller, and its files, for one of the controller's buckets.
|
||||||
modules
|
modules
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ module checks/credentialform
|
|||||||
|
|
||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915
|
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915 h1:x8k7IeMirMTxUo8BH+xUO+MDnHYgU+636jl1NIw1wvs=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791 h1:7SYIDPMxbPBr8dMB6k9fYsgVt4ZyWVL41q4LMFsfAvs=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
# The calls of the controller's hand-act log a manifest of this catalogue may still invoke, each `<module> <verb> <why>`:
|
|
||||||
# the fallback of a recorder (mesh-sdk handacts.Recorder) while the seat mesh-hand-acts has no holder that answers, until
|
|
||||||
# the controller's lease is written to a holder that is no controller. The check of this directory (handacts) refuses any
|
|
||||||
# other, and an entry whose module no longer invokes it: the list only shrinks, and it is empty before the lease write
|
|
||||||
# (novox/hq ADR 0565 §11; issue 1953, issue 774). It is empty: no manifest invokes the controller's log (issue 1953, 1955).
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
module git.novox.be/novox/mesh-catalog/checks/handacts
|
|
||||||
|
|
||||||
go 1.22
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
// Command handacts refuses a manifest that records into the controller's hand-act log (novox/hq ADR 0565 §11): every
|
|
||||||
// recorder records through the seat mesh-hand-acts from its delivery, so no manifest invokes
|
|
||||||
// `seat:mesh-controller.hand-act`, `.warranted` or `.drill`. The recorders' fallbacks to the controller's log while the
|
|
||||||
// seat has no holder are named in checks/handacts/fallbacks, each module and verb with why; that list only shrinks, and
|
|
||||||
// an entry whose module no longer invokes the verb is refused too.
|
|
||||||
//
|
|
||||||
// go run . <catalog root>
|
|
||||||
//
|
|
||||||
// Exits 1 on a finding.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Verbs are the controller's hand-act verbs, as a manifest invokes them, and Allowed the list of named fallbacks.
|
|
||||||
var Verbs = []string{"hand-act", "warranted", "drill"}
|
|
||||||
|
|
||||||
const (
|
|
||||||
invokePrefix = "seat:mesh-controller."
|
|
||||||
Allowed = "checks/handacts/fallbacks"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Findings is every refusal in the catalog at root, sorted.
|
|
||||||
func Findings(root string) ([]string, error) {
|
|
||||||
allowed := map[string]bool{}
|
|
||||||
raw, err := os.ReadFile(filepath.Join(root, Allowed))
|
|
||||||
if err != nil && !os.IsNotExist(err) {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, line := range strings.Split(string(raw), "\n") {
|
|
||||||
if f := strings.Fields(line); len(f) >= 3 && !strings.HasPrefix(f[0], "#") {
|
|
||||||
allowed[f[0]+" "+f[1]] = true
|
|
||||||
} else if len(f) > 0 && !strings.HasPrefix(f[0], "#") {
|
|
||||||
return nil, fmt.Errorf("%s: %q is not `<module> <verb> <why>`", Allowed, line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
manifests, err := filepath.Glob(filepath.Join(root, "modules", "*", "module.json"))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var out []string
|
|
||||||
invoked := map[string]bool{}
|
|
||||||
for _, path := range manifests {
|
|
||||||
raw, err := os.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var m struct {
|
|
||||||
Module string `json:"module"`
|
|
||||||
Invokes []string `json:"invokes"`
|
|
||||||
}
|
|
||||||
if json.Unmarshal(raw, &m) != nil || m.Module == "mesh-controller" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, inv := range m.Invokes {
|
|
||||||
verb, ok := strings.CutPrefix(strings.TrimSpace(inv), invokePrefix)
|
|
||||||
if !ok || !contains(Verbs, verb) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
invoked[m.Module+" "+verb] = true
|
|
||||||
if !allowed[m.Module+" "+verb] {
|
|
||||||
out = append(out, fmt.Sprintf("%s invokes %s%s: record through seat:mesh-hand-acts.%s (novox/hq ADR 0565 §11), "+
|
|
||||||
"or name the fallback in %s", m.Module, invokePrefix, verb, verb, Allowed))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for entry := range allowed {
|
|
||||||
if !invoked[entry] {
|
|
||||||
out = append(out, fmt.Sprintf("%s names %q, which is no longer invoked: take it off the list, which only shrinks",
|
|
||||||
Allowed, entry))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Strings(out)
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func contains(list []string, s string) bool {
|
|
||||||
for _, x := range list {
|
|
||||||
if x == s {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if len(os.Args) != 2 {
|
|
||||||
fmt.Fprintln(os.Stderr, "usage: handacts <catalog root>")
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
found, err := Findings(os.Args[1])
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
for _, f := range found {
|
|
||||||
fmt.Println(f)
|
|
||||||
}
|
|
||||||
if len(found) > 0 {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
fmt.Println("no manifest records into the controller's hand-act log but the fallbacks named")
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// No manifest invokes the controller's hand-act verbs but a named fallback; a named fallback no longer invoked is
|
|
||||||
// refused (novox/hq ADR 0565 §11's catalog test).
|
|
||||||
func TestFindings(t *testing.T) {
|
|
||||||
for _, row := range []struct {
|
|
||||||
name string
|
|
||||||
files map[string]string
|
|
||||||
want string // empty: clean
|
|
||||||
}{
|
|
||||||
{"clean", map[string]string{"modules/a/module.json": `{"module":"a","invokes":["seat:mesh-hand-acts.hand-act"]}`}, ""},
|
|
||||||
{"an unnamed call", map[string]string{"modules/a/module.json": `{"module":"a","invokes":["seat:mesh-controller.warranted"]}`},
|
|
||||||
"a invokes seat:mesh-controller.warranted"},
|
|
||||||
{"a named fallback, and the controller's own", map[string]string{
|
|
||||||
"modules/a/module.json": `{"module":"a","invokes":["seat:mesh-controller.drill"]}`,
|
|
||||||
"modules/mesh-controller/module.json": `{"module":"mesh-controller","invokes":["seat:mesh-controller.hand-act"]}`,
|
|
||||||
Allowed: "# why\na drill fallback until the lease write\n"}, ""},
|
|
||||||
{"a fallback no longer invoked", map[string]string{Allowed: "gone hand-act fallback\n"}, `names "gone hand-act"`},
|
|
||||||
{"another verb of the controller", map[string]string{"modules/a/module.json": `{"module":"a","invokes":["seat:mesh-controller.conditions"]}`}, ""},
|
|
||||||
} {
|
|
||||||
t.Run(row.name, func(t *testing.T) {
|
|
||||||
root := t.TempDir()
|
|
||||||
for path, text := range row.files {
|
|
||||||
p := filepath.Join(root, path)
|
|
||||||
if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := os.WriteFile(p, []byte(text), 0o644); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
got, err := Findings(root)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if all := strings.Join(got, "\n"); (row.want == "") != (all == "") || !strings.Contains(all, row.want) {
|
|
||||||
t.Fatalf("findings = %q, want one containing %q", all, row.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The run of this check over the real tree.
|
|
||||||
func TestTheCatalogRecordsOnlyThroughTheSeat(t *testing.T) {
|
|
||||||
got, err := Findings(filepath.Join("..", ".."))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
for _, f := range got {
|
|
||||||
t.Error(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# What these tests read outside their package, as paths from the repository's root (issue 1936).
|
|
||||||
# every manifest, for what it invokes.
|
|
||||||
modules/*/module.json
|
|
||||||
@@ -2,4 +2,4 @@ module checks/health
|
|||||||
|
|
||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915
|
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915 h1:x8k7IeMirMTxUo8BH+xUO+MDnHYgU+636jl1NIw1wvs=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791 h1:7SYIDPMxbPBr8dMB6k9fYsgVt4ZyWVL41q4LMFsfAvs=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ module checks/manifests
|
|||||||
|
|
||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915
|
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915 h1:x8k7IeMirMTxUo8BH+xUO+MDnHYgU+636jl1NIw1wvs=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791 h1:7SYIDPMxbPBr8dMB6k9fYsgVt4ZyWVL41q4LMFsfAvs=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
# The modules of this catalogue that still read the controller's assignments (mesh-controller.assignments), each with
|
|
||||||
# why. The check of this directory (noplacement) refuses any other module that reads them, and an entry here whose module no longer
|
|
||||||
# does: the list only shrinks, and it is empty before the controller's lease is written to a holder that is no
|
|
||||||
# controller (novox/hq ADR 0549 §3, §6; issue 774).
|
|
||||||
@@ -1,7 +1,3 @@
|
|||||||
module git.novox.be/novox/mesh-catalog/checks/noplacement
|
module git.novox.be/novox/mesh-catalog/checks/noplacement
|
||||||
|
|
||||||
go 1.22
|
go 1.22
|
||||||
|
|
||||||
require git.novox.be/novox/mesh-catalog/checks/trunk v0.0.0
|
|
||||||
|
|
||||||
replace git.novox.be/novox/mesh-catalog/checks/trunk => ../trunk
|
|
||||||
|
|||||||
@@ -10,11 +10,8 @@
|
|||||||
// 3. a manifest naming a state, stream, event or bucket of the indexer's placement (`mesh-delivery-indexer.placement…`
|
// 3. a manifest naming a state, stream, event or bucket of the indexer's placement (`mesh-delivery-indexer.placement…`
|
||||||
// or `mesh-delivery-indexer_placement…`) in any string value, such as `reads`;
|
// or `mesh-delivery-indexer_placement…`) in any string value, such as `reads`;
|
||||||
//
|
//
|
||||||
// 4. a manifest, other than mesh-controller's, that reads the controller's assignments (`mesh-controller.assignments`):
|
// The controller's assignments, which item 4 once listed readers of, are refused with every other name of the
|
||||||
// every reader takes placement from the seat mesh-placement (novox/hq ADR 0549 §3, ADR 0567), and the lease write is
|
// controller by checks/controllerdeps (novox/hq ADR 0590 §5).
|
||||||
// refused while one still reads them (ADR 0549 §6; issue 774). The modules that still read them for what the
|
|
||||||
// placement does not carry are named in checks/noplacement/assignments-readers, each with why; that list only
|
|
||||||
// shrinks: an entry whose module no longer reads them is refused too.
|
|
||||||
//
|
//
|
||||||
// go run . <catalogue root>
|
// go run . <catalogue root>
|
||||||
//
|
//
|
||||||
@@ -61,74 +58,10 @@ func Findings(root string) ([]string, error) {
|
|||||||
out = append(out, fmt.Sprintf("%s: names %q, which the indexer no longer keeps; placement is the seat mesh-placement's (novox/hq ADR 0567 §12)", module, s))
|
out = append(out, fmt.Sprintf("%s: names %q, which the indexer no longer keeps; placement is the seat mesh-placement's (novox/hq ADR 0567 §12)", module, s))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
readers, err := assignmentsReaders(root)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
out = append(out, readers...)
|
|
||||||
sort.Strings(out)
|
sort.Strings(out)
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AssignmentsRead is the controller's assignments as a manifest reads them, and Allowed the file, relative to the
|
|
||||||
// catalogue's root, that names the modules still reading them: `<module> <why>` per line, `#` a comment.
|
|
||||||
const (
|
|
||||||
AssignmentsRead = "mesh-controller.assignments"
|
|
||||||
Allowed = "checks/noplacement/assignments-readers"
|
|
||||||
)
|
|
||||||
|
|
||||||
// assignmentsReaders is every finding on the reads of the controller's assignments: a module other than the controller
|
|
||||||
// reading them that Allowed does not name, and a module Allowed names that no longer reads them (issue 774).
|
|
||||||
func assignmentsReaders(root string) ([]string, error) {
|
|
||||||
allowed := map[string]bool{}
|
|
||||||
raw, err := os.ReadFile(filepath.Join(root, Allowed))
|
|
||||||
if err != nil && !os.IsNotExist(err) {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, line := range strings.Split(string(raw), "\n") {
|
|
||||||
if f := strings.Fields(line); len(f) > 0 && !strings.HasPrefix(f[0], "#") {
|
|
||||||
allowed[f[0]] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
manifests, err := filepath.Glob(filepath.Join(root, "modules", "*", "module.json"))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var found []string
|
|
||||||
reads := map[string]bool{}
|
|
||||||
for _, path := range manifests {
|
|
||||||
raw, err := os.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var m struct {
|
|
||||||
Module string `json:"module"`
|
|
||||||
Reads []string `json:"reads"`
|
|
||||||
}
|
|
||||||
if json.Unmarshal(raw, &m) != nil || m.Module == "mesh-controller" {
|
|
||||||
continue // a manifest that does not read is the manifest checks' to refuse
|
|
||||||
}
|
|
||||||
for _, r := range m.Reads {
|
|
||||||
if strings.TrimSpace(r) != AssignmentsRead {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
reads[m.Module] = true
|
|
||||||
if !allowed[m.Module] {
|
|
||||||
found = append(found, fmt.Sprintf("%s reads %s: read where modules run from mesh-delivery-assigner.placement, "+
|
|
||||||
"the seat mesh-placement's (novox/hq ADR 0549 §3, ADR 0567); the lease write is refused while a module reads "+
|
|
||||||
"the assignments (ADR 0549 §6)", m.Module, AssignmentsRead))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for module := range allowed {
|
|
||||||
if !reads[module] {
|
|
||||||
found = append(found, fmt.Sprintf("%s names %s, which no longer reads %s: take it off the list, which only shrinks",
|
|
||||||
Allowed, module, AssignmentsRead))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return found, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// named collects the strings of a decoded manifest that start with one of the Prefixes.
|
// named collects the strings of a decoded manifest that start with one of the Prefixes.
|
||||||
func named(v any, into []string) []string {
|
func named(v any, into []string) []string {
|
||||||
switch v := v.(type) {
|
switch v := v.(type) {
|
||||||
@@ -166,5 +99,5 @@ func main() {
|
|||||||
if len(found) > 0 {
|
if len(found) > 0 {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
fmt.Println("no placement directory, no manifest reads the indexer's placement, and only the modules listed read the controller's assignments")
|
fmt.Println("no placement directory, and no manifest reads the indexer's placement")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.novox.be/novox/mesh-catalog/checks/trunk"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFindingsRefuseWhatThePlacementPathLeft(t *testing.T) {
|
func TestFindingsRefuseWhatThePlacementPathLeft(t *testing.T) {
|
||||||
@@ -21,12 +19,6 @@ func TestFindingsRefuseWhatThePlacementPathLeft(t *testing.T) {
|
|||||||
{"a read of the indexer's placement", map[string]string{"modules/a/module.json": `{"module":"a","reads":["mesh-delivery-indexer.placement"]}`}, `a: names "mesh-delivery-indexer.placement"`},
|
{"a read of the indexer's placement", map[string]string{"modules/a/module.json": `{"module":"a","reads":["mesh-delivery-indexer.placement"]}`}, `a: names "mesh-delivery-indexer.placement"`},
|
||||||
{"a read of its candidates", map[string]string{"modules/b/module.json": `{"module":"b","reads":["mesh-delivery-indexer.placement-candidates"]}`}, "placement-candidates"},
|
{"a read of its candidates", map[string]string{"modules/b/module.json": `{"module":"b","reads":["mesh-delivery-indexer.placement-candidates"]}`}, "placement-candidates"},
|
||||||
{"its event", map[string]string{"modules/c/module.json": `{"module":"c","on":[{"event":"mesh-delivery-indexer.placement-changed"}]}`}, "placement-changed"},
|
{"its event", map[string]string{"modules/c/module.json": `{"module":"c","on":[{"event":"mesh-delivery-indexer.placement-changed"}]}`}, "placement-changed"},
|
||||||
// Issue 774 (ADR 0549 §3, §6): a reader of the controller's assignments is refused unless the list names it, the
|
|
||||||
// controller's own manifest is not judged, and a name on the list whose module no longer reads them is refused.
|
|
||||||
{"an unlisted reader of the assignments", map[string]string{"modules/r/module.json": `{"module":"r","reads":["mesh-controller.assignments"]}`}, "r reads mesh-controller.assignments"},
|
|
||||||
{"a listed reader and the controller", map[string]string{"modules/l/module.json": `{"module":"l","reads":["mesh-controller.assignments"]}`,
|
|
||||||
"modules/mesh-controller/module.json": `{"module":"mesh-controller","reads":["mesh-controller.assignments"]}`, Allowed: "# why\nl its plan\n"}, ""},
|
|
||||||
{"a listed module that reads them no more", map[string]string{Allowed: "gone no longer reads\n"}, "names gone, which no longer reads"},
|
|
||||||
{"the seat's own state is allowed", map[string]string{"modules/d/module.json": `{"module":"d","reads":["mesh-placement.placement"]}`}, ""},
|
{"the seat's own state is allowed", map[string]string{"modules/d/module.json": `{"module":"d","reads":["mesh-placement.placement"]}`}, ""},
|
||||||
} {
|
} {
|
||||||
t.Run(row.name, func(t *testing.T) {
|
t.Run(row.name, func(t *testing.T) {
|
||||||
@@ -60,29 +52,4 @@ func TestTheCatalogueHoldsNoPlacementOfTheRepositoryPath(t *testing.T) {
|
|||||||
for _, f := range got {
|
for _, f := range got {
|
||||||
t.Error(f)
|
t.Error(f)
|
||||||
}
|
}
|
||||||
// Issue 774 (ADR 0549 §3, §6; the comparison of novox/hq ADR 0529 clause 2): the list of modules still reading the
|
|
||||||
// controller's assignments gains none the trunk's copy at the merge base does not hold. Its first copy gains nothing.
|
|
||||||
path, ok := trunk.Copy(t, Allowed)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
names := func(path string) map[string]bool {
|
|
||||||
raw, err := os.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
out := map[string]bool{}
|
|
||||||
for _, line := range strings.Split(string(raw), "\n") {
|
|
||||||
if f := strings.Fields(line); len(f) > 0 && !strings.HasPrefix(f[0], "#") {
|
|
||||||
out[f[0]] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
base := names(path)
|
|
||||||
for module := range names(filepath.Join("..", "..", Allowed)) {
|
|
||||||
if !base[module] {
|
|
||||||
t.Errorf("%s gains %s, which the trunk's copy does not hold: the list only shrinks", Allowed, module)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ module checks/readable
|
|||||||
|
|
||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915
|
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915 h1:x8k7IeMirMTxUo8BH+xUO+MDnHYgU+636jl1NIw1wvs=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791 h1:7SYIDPMxbPBr8dMB6k9fYsgVt4ZyWVL41q4LMFsfAvs=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
|
|||||||
+37
-1
@@ -4,7 +4,9 @@
|
|||||||
// own code that is not a test (a tool's description, the notes and errors it answers with) and every
|
// own code that is not a test (a tool's description, the notes and errors it answers with) and every
|
||||||
// description in its manifest. Comments are not read: an agent never sees them. And no daemon's line
|
// description in its manifest. Comments are not read: an agent never sees them. And no daemon's line
|
||||||
// (novox/hq ADR 0272, lines.go): the copy in retired-lines, the lines a person would otherwise be told to
|
// (novox/hq ADR 0272, lines.go): the copy in retired-lines, the lines a person would otherwise be told to
|
||||||
// type of the node-engine's or the controller's binary, where the operator's command is `nox`.
|
// type of the node-engine's or the controller's binary, where the operator's command is `nox`. And no address of the
|
||||||
|
// controller (novox/hq ADR 0590 §5): it is not placed, so a text naming one of its verbs, states or buckets sends its
|
||||||
|
// reader nowhere.
|
||||||
//
|
//
|
||||||
// A word is matched whole and in any case, a space in it matching any run of white space, and its plural
|
// A word is matched whole and in any case, a space in it matching any run of white space, and its plural
|
||||||
// (`s` or `es` on its last part) is matched as the word. A vendor word is
|
// (`s` or `es` on its last part) is matched as the word. A vendor word is
|
||||||
@@ -89,11 +91,45 @@ func run(root string) int {
|
|||||||
len(findings))
|
len(findings))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
// And no address of the controller (novox/hq ADR 0590 §5): it is not placed, so a text that sends an agent or the
|
||||||
|
// operator to one of its verbs, states or buckets sends them nowhere.
|
||||||
|
if findings := checkAddresses(texts); len(findings) > 0 {
|
||||||
|
for _, f := range findings {
|
||||||
|
fmt.Println(f)
|
||||||
|
}
|
||||||
|
fmt.Printf("words: %d address(es) of the controller in what a module shows — name the seat that holds the job now (novox/hq ADR 0590)\n",
|
||||||
|
len(findings))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
fmt.Printf("words: %d retired words and %d daemons' lines, none in the %d strings of %d files\n", len(words), len(lines),
|
fmt.Printf("words: %d retired words and %d daemons' lines, none in the %d strings of %d files\n", len(words), len(lines),
|
||||||
len(texts), files)
|
len(texts), files)
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// controllerAddress is an address of the controller: its seat's verb or state (`mesh-controller.<name>`, with or without
|
||||||
|
// `seat:`) or one of its buckets (`mesh-controller_<bucket>`). The module's name in prose is no address.
|
||||||
|
var controllerAddress = regexp.MustCompile(`(^|[^A-Za-z0-9_-])(seat:)?mesh-controller[._][A-Za-z0-9<]`)
|
||||||
|
|
||||||
|
// checkAddresses is every text that names an address of the controller (novox/hq ADR 0590 §5).
|
||||||
|
func checkAddresses(texts []text) []string {
|
||||||
|
var out []string
|
||||||
|
for _, t := range texts {
|
||||||
|
if !strings.Contains(t.s, "mesh-controller") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if m := controllerAddress.FindStringIndex(t.s); m != nil {
|
||||||
|
line := t.line
|
||||||
|
if line > 0 {
|
||||||
|
line += strings.Count(t.s[:m[0]], "\n")
|
||||||
|
}
|
||||||
|
out = append(out, fmt.Sprintf("%s:%d: names an address of the controller, which is not placed — in %q", t.file, line,
|
||||||
|
excerpt(t.s, m[0], m[1])))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Strings(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// collect reads every text a module can show under root: the strings of its own code that is not a test, and every string of
|
// collect reads every text a module can show under root: the strings of its own code that is not a test, and every string of
|
||||||
// its manifests.
|
// its manifests.
|
||||||
func collect(root string) ([]text, int, error) {
|
func collect(root string) ([]text, int, error) {
|
||||||
|
|||||||
@@ -95,3 +95,25 @@ func TestTheCatalogueHoldsNoRetiredWordAndNoDaemonsLine(t *testing.T) {
|
|||||||
t.Fatalf("the check of the real catalogue exits %d (see what it printed above)", code)
|
t.Fatalf("the check of the real catalogue exits %d (see what it printed above)", code)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ADR 0590 §5 (issue 1126 B10): no text a module shows names an address of the controller, which is not placed; prose
|
||||||
|
// naming it passes.
|
||||||
|
func TestCheckAddressesRefusesTheControllersAddresses(t *testing.T) {
|
||||||
|
for _, c := range []struct {
|
||||||
|
s string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{"push this machine (mesh-controller.push)", true},
|
||||||
|
{"ask seat:mesh-controller.nodes", true},
|
||||||
|
{"the bucket mesh-controller_assignments", true},
|
||||||
|
{"`mesh-controller.<verb>`", true},
|
||||||
|
{"the mesh-controller module is gone.", false},
|
||||||
|
{"located in mesh-controller#246", false},
|
||||||
|
{"ask mesh-placement.placement", false},
|
||||||
|
} {
|
||||||
|
got := checkAddresses([]text{{file: "m/a.go", line: 1, s: c.s}})
|
||||||
|
if (len(got) == 1) != c.want {
|
||||||
|
t.Errorf("checkAddresses(%q) = %q, want a finding: %v", c.s, got, c.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ module adwaita
|
|||||||
|
|
||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915
|
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/klauspost/compress v1.20.0 // indirect
|
github.com/klauspost/compress v1.20.0 // indirect
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915 h1:x8k7IeMirMTxUo8BH+xUO+MDnHYgU+636jl1NIw1wvs=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791 h1:7SYIDPMxbPBr8dMB6k9fYsgVt4ZyWVL41q4LMFsfAvs=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
||||||
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
||||||
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ func TestOlderNamesNotReadAreSaidFirst(t *testing.T) {
|
|||||||
func references(t *testing.T, a *asked) func(map[string]any) (any, error) {
|
func references(t *testing.T, a *asked) func(map[string]any) (any, error) {
|
||||||
f := world()
|
f := world()
|
||||||
s := &Store{Container: "mesh-registry", Run: f.run(t)}
|
s := &Store{Container: "mesh-registry", Run: f.run(t)}
|
||||||
for _, tl := range Tools(s, Controller{Ask: a.ask}, &Catalogue{Ask: a.ask}) {
|
for _, tl := range Tools(s, recordsAsked{a}, &Catalogue{Ask: a.ask}) {
|
||||||
if tl.Name == "artifact_store_references" {
|
if tl.Name == "artifact_store_references" {
|
||||||
return tl.Run
|
return tl.Run
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,22 +2,16 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// What the records say, asked of the controller and never copied (novox/hq ADR 0251 §2). The
|
// What the records say, in the shape the seat's holder answers them (Holder; novox/hq ADR 0251 §2, ADR 0571 clause 2):
|
||||||
// controller decides what the mesh keeps; this bundle only sets that beside what the store holds.
|
// the holder decides what the mesh keeps; the tools only set that beside what the store holds.
|
||||||
|
|
||||||
// Asking is how a tool on the mesh is asked: stdio.Ask, or a test's.
|
// Asking is how a tool on the mesh is asked: stdio.Ask, or a test's.
|
||||||
type Asking func(key string, body any) (json.RawMessage, error)
|
type Asking func(key string, body any) (json.RawMessage, error)
|
||||||
|
|
||||||
// ControllerSeat is the seat whose verbs the controller serves.
|
// Record is one reference the mesh recorded making, as the seat's `artifacts` answers it.
|
||||||
const ControllerSeat = "mesh-controller"
|
|
||||||
|
|
||||||
// Record is one reference the mesh recorded making, as the controller's `artifacts` answers it.
|
|
||||||
type Record struct {
|
type Record struct {
|
||||||
Reference string `json:"reference"`
|
Reference string `json:"reference"`
|
||||||
Repository string `json:"repository,omitempty"`
|
Repository string `json:"repository,omitempty"`
|
||||||
@@ -27,7 +21,7 @@ type Record struct {
|
|||||||
Why []string `json:"why,omitempty"`
|
Why []string `json:"why,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Records is the controller's `artifacts` answer.
|
// Records is the seat's `artifacts` answer.
|
||||||
type Records struct {
|
type Records struct {
|
||||||
Store string `json:"store"`
|
Store string `json:"store"`
|
||||||
KeptBuilds int `json:"kept_builds"`
|
KeptBuilds int `json:"kept_builds"`
|
||||||
@@ -39,7 +33,7 @@ type Records struct {
|
|||||||
Note string `json:"-"`
|
Note string `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CollectAnswer is the controller's `collect` answer.
|
// CollectAnswer is the seat's `collect` answer.
|
||||||
type CollectAnswer struct {
|
type CollectAnswer struct {
|
||||||
DryRun bool `json:"dry_run"`
|
DryRun bool `json:"dry_run"`
|
||||||
Store string `json:"store"`
|
Store string `json:"store"`
|
||||||
@@ -53,15 +47,12 @@ type CollectAnswer struct {
|
|||||||
Skipped int `json:"skipped"`
|
Skipped int `json:"skipped"`
|
||||||
Left int `json:"left"`
|
Left int `json:"left"`
|
||||||
Stopped string `json:"stopped"`
|
Stopped string `json:"stopped"`
|
||||||
// Rest is whatever else the controller said, passed on as it said it.
|
// Rest is whatever else the sweep said, passed on as it said it.
|
||||||
Rest map[string]json.RawMessage `json:"-"`
|
Rest map[string]json.RawMessage `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Controller asks the controller's seat through the runtime.
|
// answerOf reads a verb's answer whatever wraps it: {output, ok, answer}, a text the runtime handed over, or the
|
||||||
type Controller struct{ Ask Asking }
|
// protocol's content list.
|
||||||
|
|
||||||
// answerOf reads a verb's answer whatever wraps it: the controller's {output, ok, answer}, a text the
|
|
||||||
// runtime handed over, or the protocol's content list.
|
|
||||||
func answerOf(raw json.RawMessage) (json.RawMessage, string, bool) {
|
func answerOf(raw json.RawMessage) (json.RawMessage, string, bool) {
|
||||||
var s string
|
var s string
|
||||||
if json.Unmarshal(raw, &s) == nil {
|
if json.Unmarshal(raw, &s) == nil {
|
||||||
@@ -95,89 +86,11 @@ func answerOf(raw json.RawMessage) (json.RawMessage, string, bool) {
|
|||||||
return raw, string(raw), true
|
return raw, string(raw), true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Controller) verb(name string, args map[string]any) (json.RawMessage, error) {
|
|
||||||
if c.Ask == nil {
|
|
||||||
return nil, errors.New("this bundle has no way to ask the controller")
|
|
||||||
}
|
|
||||||
raw, err := c.Ask("seat:"+ControllerSeat+"."+name, args)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("the controller's %s could not be asked: %w", name, err)
|
|
||||||
}
|
|
||||||
answer, output, ok := answerOf(raw)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("the controller refused %s: %s", name, lastLine(output))
|
|
||||||
}
|
|
||||||
if len(answer) == 0 || string(answer) == "null" {
|
|
||||||
return nil, fmt.Errorf("the controller's %s answered no data: %s", name, lastLine(output))
|
|
||||||
}
|
|
||||||
return answer, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func lastLine(s string) string {
|
func lastLine(s string) string {
|
||||||
lines := strings.Split(strings.TrimSpace(s), "\n")
|
lines := strings.Split(strings.TrimSpace(s), "\n")
|
||||||
return strings.TrimSpace(lines[len(lines)-1])
|
return strings.TrimSpace(lines[len(lines)-1])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Artifacts asks what the records say, with the references already let go of when the answer can carry
|
|
||||||
// them; when it cannot, without, and says so.
|
|
||||||
func (c Controller) Artifacts(repository string) (*Records, error) {
|
|
||||||
args := map[string]any{"collected": "true"}
|
|
||||||
if repository != "" {
|
|
||||||
args["repository"] = repository
|
|
||||||
}
|
|
||||||
raw, err := c.verb("artifacts", args)
|
|
||||||
collected := true
|
|
||||||
note := ""
|
|
||||||
if err != nil {
|
|
||||||
delete(args, "collected")
|
|
||||||
var again error
|
|
||||||
raw, again = c.verb("artifacts", args)
|
|
||||||
if again != nil {
|
|
||||||
return nil, again
|
|
||||||
}
|
|
||||||
collected = false
|
|
||||||
note = "the references the mesh already let go of could not be asked for (" + err.Error() +
|
|
||||||
"), so a manifest the mesh let go of that the store still holds is counted as unrecorded"
|
|
||||||
}
|
|
||||||
var r Records
|
|
||||||
if err := json.Unmarshal(raw, &r); err != nil {
|
|
||||||
return nil, fmt.Errorf("the controller's artifacts answer is not readable: %v", err)
|
|
||||||
}
|
|
||||||
for i := range r.References {
|
|
||||||
fill(&r.References[i])
|
|
||||||
}
|
|
||||||
r.CollectedAsked, r.Note = collected, note
|
|
||||||
return &r, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect asks the controller's sweep: a dry run unless confirm, which needs why.
|
|
||||||
func (c Controller) Collect(why string, confirm bool, most int) (*CollectAnswer, error) {
|
|
||||||
args := map[string]any{}
|
|
||||||
if confirm {
|
|
||||||
if strings.TrimSpace(why) == "" {
|
|
||||||
return nil, errors.New("a real collection needs why: nothing was asked")
|
|
||||||
}
|
|
||||||
args["why"] = why
|
|
||||||
args["confirm"] = "true"
|
|
||||||
}
|
|
||||||
if most > 0 {
|
|
||||||
args["most"] = strconv.Itoa(most)
|
|
||||||
}
|
|
||||||
raw, err := c.verb("collect", args)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var a CollectAnswer
|
|
||||||
if err := json.Unmarshal(raw, &a); err != nil {
|
|
||||||
return nil, fmt.Errorf("the controller's collect answer is not readable: %v", err)
|
|
||||||
}
|
|
||||||
_ = json.Unmarshal(raw, &a.Rest)
|
|
||||||
if !confirm && !a.DryRun && len(a.LetGo) > 0 {
|
|
||||||
return nil, fmt.Errorf("the controller let go of %d artifacts when only a dry run was asked", len(a.LetGo))
|
|
||||||
}
|
|
||||||
return &a, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// fill reads the repository, digest and kind out of a reference when the answer left them out.
|
// fill reads the repository, digest and kind out of a reference when the answer left them out.
|
||||||
func fill(r *Record) {
|
func fill(r *Record) {
|
||||||
repo, digest, kind := splitReference(r.Reference)
|
repo, digest, kind := splitReference(r.Reference)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -280,7 +281,7 @@ func TestOnlyTheirBytesLeaveOutWhatAnotherStateMarks(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// asked records every ask, and answers as the controller would.
|
// asked records every ask, and answers as the seat and the catalogue would.
|
||||||
type asked struct {
|
type asked struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
calls []string
|
calls []string
|
||||||
@@ -293,6 +294,51 @@ type asked struct {
|
|||||||
noCatalogue bool
|
noCatalogue bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// recordsAsked is the tools' Recorded in a test: each call asked of asked, as `artifacts` or `collect`, with the
|
||||||
|
// arguments a real run gives the seat's sweep.
|
||||||
|
type recordsAsked struct{ a *asked }
|
||||||
|
|
||||||
|
func (r recordsAsked) Artifacts(repository string) (*Records, error) {
|
||||||
|
args := map[string]any{"collected": "true"}
|
||||||
|
if repository != "" {
|
||||||
|
args["repository"] = repository
|
||||||
|
}
|
||||||
|
raw, err := r.a.ask("artifacts", args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var recs Records
|
||||||
|
answer, _, _ := answerOf(raw)
|
||||||
|
if err := json.Unmarshal(answer, &recs); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for i := range recs.References {
|
||||||
|
fill(&recs.References[i])
|
||||||
|
}
|
||||||
|
recs.CollectedAsked = true
|
||||||
|
return &recs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r recordsAsked) Collect(why string, confirm bool, most int) (*CollectAnswer, error) {
|
||||||
|
args := map[string]any{}
|
||||||
|
if confirm {
|
||||||
|
if strings.TrimSpace(why) == "" {
|
||||||
|
return nil, fmt.Errorf("a real collection needs why: nothing was asked")
|
||||||
|
}
|
||||||
|
args["why"], args["confirm"] = why, "true"
|
||||||
|
}
|
||||||
|
if most > 0 {
|
||||||
|
args["most"] = strconv.Itoa(most)
|
||||||
|
}
|
||||||
|
raw, err := r.a.ask("collect", args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var c CollectAnswer
|
||||||
|
answer, _, _ := answerOf(raw)
|
||||||
|
return &c, json.Unmarshal(answer, &c)
|
||||||
|
}
|
||||||
|
|
||||||
func (a *asked) ask(key string, body any) (json.RawMessage, error) {
|
func (a *asked) ask(key string, body any) (json.RawMessage, error) {
|
||||||
a.mu.Lock()
|
a.mu.Lock()
|
||||||
defer a.mu.Unlock()
|
defer a.mu.Unlock()
|
||||||
@@ -304,7 +350,7 @@ func (a *asked) ask(key string, body any) (json.RawMessage, error) {
|
|||||||
}
|
}
|
||||||
var answer any
|
var answer any
|
||||||
switch key {
|
switch key {
|
||||||
case "seat:mesh-controller.artifacts":
|
case "artifacts":
|
||||||
answer = records()
|
answer = records()
|
||||||
case CatalogueModules:
|
case CatalogueModules:
|
||||||
if a.noCatalogue {
|
if a.noCatalogue {
|
||||||
@@ -331,7 +377,7 @@ func (a *asked) ask(key string, body any) (json.RawMessage, error) {
|
|||||||
// The catalogue's tool answers through the runtime as the protocol's content list.
|
// The catalogue's tool answers through the runtime as the protocol's content list.
|
||||||
raw, _ := json.Marshal(map[string]any{"module": map[string]any{"module": name, "manifest": json.RawMessage(manifest)}})
|
raw, _ := json.Marshal(map[string]any{"module": map[string]any{"module": name, "manifest": json.RawMessage(manifest)}})
|
||||||
return json.Marshal(map[string]any{"content": []map[string]any{{"type": "text", "text": string(raw)}}})
|
return json.Marshal(map[string]any{"content": []map[string]any{{"type": "text", "text": string(raw)}}})
|
||||||
case "seat:mesh-controller.collect":
|
case "collect":
|
||||||
if args["confirm"] == "true" {
|
if args["confirm"] == "true" {
|
||||||
answer = CollectAnswer{LetGo: []string{"artifact-store://app/server@" + d("m-old")}}
|
answer = CollectAnswer{LetGo: []string{"artifact-store://app/server@" + d("m-old")}}
|
||||||
} else {
|
} else {
|
||||||
@@ -347,7 +393,7 @@ func (a *asked) ask(key string, body any) (json.RawMessage, error) {
|
|||||||
func tool(t *testing.T, a *asked, name string) func(map[string]any) (any, error) {
|
func tool(t *testing.T, a *asked, name string) func(map[string]any) (any, error) {
|
||||||
f := world()
|
f := world()
|
||||||
s := &Store{Container: "mesh-registry", Run: f.run(t)}
|
s := &Store{Container: "mesh-registry", Run: f.run(t)}
|
||||||
for _, tl := range Tools(s, Controller{Ask: a.ask}, &Catalogue{Ask: a.ask}) {
|
for _, tl := range Tools(s, recordsAsked{a}, &Catalogue{Ask: a.ask}) {
|
||||||
if tl.Name == name {
|
if tl.Name == name {
|
||||||
return tl.Run
|
return tl.Run
|
||||||
}
|
}
|
||||||
@@ -390,21 +436,6 @@ func TestARealRunWithoutWhyIsRefusedAndAsksNothing(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReferencesFallBackWhenCollectedCannotBeCarried(t *testing.T) {
|
|
||||||
a := &asked{fail: map[string]bool{"collected": true}}
|
|
||||||
out, err := tool(t, a, "artifact_store_references")(map[string]any{"repository": "app/server"})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
m := out.(map[string]any)
|
|
||||||
if m["note"] == nil || m["manifests"] == nil {
|
|
||||||
t.Errorf("answer %v", m)
|
|
||||||
}
|
|
||||||
if _, err := tool(t, a, "artifact_store_references")(map[string]any{"repository": "nope"}); err == nil {
|
|
||||||
t.Error("an unknown repository was answered")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTheToolsServedAreTheToolsTheManifestNames(t *testing.T) {
|
func TestTheToolsServedAreTheToolsTheManifestNames(t *testing.T) {
|
||||||
raw, err := os.ReadFile("../../module.json")
|
raw, err := os.ReadFile("../../module.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -423,7 +454,7 @@ func TestTheToolsServedAreTheToolsTheManifestNames(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
var served []string
|
var served []string
|
||||||
for _, tl := range Tools(&Store{}, Controller{}, &Catalogue{}) {
|
for _, tl := range Tools(&Store{}, recordsAsked{&asked{}}, &Catalogue{}) {
|
||||||
if !strings.HasPrefix(tl.Name, "artifact_store_") || tl.Description == "" || tl.Run == nil {
|
if !strings.HasPrefix(tl.Name, "artifact_store_") || tl.Description == "" || tl.Run == nil {
|
||||||
t.Errorf("tool %q", tl.Name)
|
t.Errorf("tool %q", tl.Name)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Recorded is what says which references the mesh keeps and lets go of them: the seat's holder (Holder, novox/hq ADR
|
// Recorded is what says which references the mesh keeps and lets go of them: the seat's holder (Holder, novox/hq ADR
|
||||||
// 0571 clause 2), or before it the controller (Controller), each in the controller's answer's shape.
|
// 0571 clause 2), or a test's stand-in.
|
||||||
type Recorded interface {
|
type Recorded interface {
|
||||||
Artifacts(repository string) (*Records, error)
|
Artifacts(repository string) (*Records, error)
|
||||||
Collect(why string, confirm bool, most int) (*CollectAnswer, error)
|
Collect(why string, confirm bool, most int) (*CollectAnswer, error)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ module artifactstoretools
|
|||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791
|
||||||
github.com/nats-io/nats.go v1.54.0
|
github.com/nats-io/nats.go v1.54.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915 h1:x8k7IeMirMTxUo8BH+xUO+MDnHYgU+636jl1NIw1wvs=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791 h1:7SYIDPMxbPBr8dMB6k9fYsgVt4ZyWVL41q4LMFsfAvs=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
||||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
||||||
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ module asuszephyrusg14
|
|||||||
|
|
||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915
|
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/klauspost/compress v1.20.0 // indirect
|
github.com/klauspost/compress v1.20.0 // indirect
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915 h1:x8k7IeMirMTxUo8BH+xUO+MDnHYgU+636jl1NIw1wvs=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791 h1:7SYIDPMxbPBr8dMB6k9fYsgVt4ZyWVL41q4LMFsfAvs=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
||||||
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
||||||
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ module avahi
|
|||||||
|
|
||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915
|
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/klauspost/compress v1.20.0 // indirect
|
github.com/klauspost/compress v1.20.0 // indirect
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915 h1:x8k7IeMirMTxUo8BH+xUO+MDnHYgU+636jl1NIw1wvs=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791 h1:7SYIDPMxbPBr8dMB6k9fYsgVt4ZyWVL41q4LMFsfAvs=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
||||||
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
||||||
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ module blueman
|
|||||||
|
|
||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915
|
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/klauspost/compress v1.20.0 // indirect
|
github.com/klauspost/compress v1.20.0 // indirect
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915 h1:x8k7IeMirMTxUo8BH+xUO+MDnHYgU+636jl1NIw1wvs=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791 h1:7SYIDPMxbPBr8dMB6k9fYsgVt4ZyWVL41q4LMFsfAvs=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
||||||
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
||||||
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ module bluetooth
|
|||||||
|
|
||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915
|
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/klauspost/compress v1.20.0 // indirect
|
github.com/klauspost/compress v1.20.0 // indirect
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915 h1:x8k7IeMirMTxUo8BH+xUO+MDnHYgU+636jl1NIw1wvs=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791 h1:7SYIDPMxbPBr8dMB6k9fYsgVt4ZyWVL41q4LMFsfAvs=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
||||||
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
||||||
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ module brother
|
|||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791
|
||||||
github.com/nats-io/nats-server/v2 v2.11.17
|
github.com/nats-io/nats-server/v2 v2.11.17
|
||||||
github.com/nats-io/nats.go v1.54.0
|
github.com/nats-io/nats.go v1.54.0
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915 h1:x8k7IeMirMTxUo8BH+xUO+MDnHYgU+636jl1NIw1wvs=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791 h1:7SYIDPMxbPBr8dMB6k9fYsgVt4ZyWVL41q4LMFsfAvs=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
||||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
||||||
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||||
|
|||||||
@@ -120,8 +120,10 @@ nothing else of the home. A project is one entry of the projects directory and a
|
|||||||
The agent kept reaching for `ssh <machine> journalctl` while the service manager's `journal` verb existed. So:
|
The agent kept reaching for `ssh <machine> journalctl` while the service manager's `journal` verb existed. So:
|
||||||
|
|
||||||
- **The "instead of" table.** Every seat verb and module tool may say which shell commands it replaces
|
- **The "instead of" table.** Every seat verb and module tool may say which shell commands it replaces
|
||||||
(`replaces`, in the seat's definition or the module's manifest). The module asks the controller — `tools`,
|
(`replaces`, in the seat's definition or the module's manifest). The module reads the bus — the seats' verbs from the
|
||||||
`modules`, `nodes`, `node` — at start, every ten minutes and on `claude_code_render`, keeps the answer in its
|
seats' contract, the placed modules' manifests from `mesh-delivery-indexer.modules` and `.manifests`, the
|
||||||
|
machines and where each module runs from `mesh-delivery-assigner.placement` (issue 1126: the controller that
|
||||||
|
answered this is not placed) — at start, every ten minutes and on `claude_code_render`, keeps it in its
|
||||||
state (`mesh-tools.json`), and renders it into `CLAUDE.md` as one row per seat or module: `<node>/<seat>.` and
|
state (`mesh-tools.json`), and renders it into `CLAUDE.md` as one row per seat or module: `<node>/<seat>.` and
|
||||||
each verb with the commands it replaces. Generated, never written by hand: a verb that gains `replaces` is in
|
each verb with the commands it replaces. Generated, never written by hand: a verb that gains `replaces` is in
|
||||||
the next render. Ordered by what the guard here refused most, then the machines' seats, the modules, the
|
the next render. Ordered by what the guard here refused most, then the machines' seats, the modules, the
|
||||||
|
|||||||
@@ -8,22 +8,26 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||||
)
|
)
|
||||||
|
|
||||||
// meshForTheGuard is a mesh of two machines and the verbs that replace what an agent runs over ssh, as the
|
// meshForTheGuard is a mesh of two machines and the verbs that replace what an agent runs over ssh, as the
|
||||||
// controller's records say them.
|
// seats' contract and the modules' manifests say them.
|
||||||
func meshForTheGuard() GuardData {
|
func meshForTheGuard() GuardData {
|
||||||
tools := json.RawMessage(`{"seats":[
|
seats := []catalogue.Seat{
|
||||||
{"seat":"mesh-controller","scope":"mesh","tools":[{"name":"node","description":"What one machine reported it can do.","replaces":["hostnamectl","uptime"]}]},
|
{Name: "mesh-placement", Scope: catalogue.ScopeMesh, Serves: []catalogue.Verb{
|
||||||
{"seat":"node-service-manager","scope":"node","tools":[
|
{Name: "placement", Description: "Read placement: one node's key.", Replaces: []string{"hostnamectl", "uptime"}}}},
|
||||||
{"name":"status","description":"One unit as the service manager sees it now: its states.","replaces":["systemctl status","systemctl is-active"]},
|
{Name: "node-service-manager", Scope: catalogue.ScopeNode, Serves: []catalogue.Verb{
|
||||||
{"name":"restart","description":"Restart one unit.","replaces":["systemctl restart"]},
|
{Name: "status", Description: "One unit as the service manager sees it now: its states.", Replaces: []string{"systemctl status", "systemctl is-active"}},
|
||||||
{"name":"journal","description":"The last lines of one unit's journal.","replaces":["journalctl"]}]},
|
{Name: "restart", Description: "Restart one unit.", Replaces: []string{"systemctl restart"}},
|
||||||
{"seat":"node-hostname","scope":"node","tools":[
|
{Name: "journal", Description: "The last lines of one unit's journal.", Replaces: []string{"journalctl"}}}},
|
||||||
{"name":"add","description":"Add one address and its names to the operator's lines of /etc/hosts.","replaces":["edit /etc/hosts","HOSTALIASES"]}]}]}`)
|
{Name: "node-hostname", Scope: catalogue.ScopeNode, Serves: []catalogue.Verb{
|
||||||
modules := json.RawMessage(`[{"module":"docker","on":["anchor"],"replaces":{"docker_logs":["docker logs"],"docker_list":["docker ps"]}},
|
{Name: "add", Description: "Add one address and its names to the operator's lines of /etc/hosts.", Replaces: []string{"edit /etc/hosts", "HOSTALIASES"}}}},
|
||||||
{"module":"systemd","on":["anchor","laptop"]}]`)
|
}
|
||||||
return GuardData{Node: "laptop", Replacements: ReplacementsOf(tools, modules), Machines: []Machine{
|
modules := []ModuleTools{{Module: "docker", On: []string{"anchor"}, Replaces: map[string][]string{"docker_logs": {"docker logs"}, "docker_list": {"docker ps"}}},
|
||||||
|
{Module: "systemd", On: []string{"anchor", "laptop"}}}
|
||||||
|
return GuardData{Node: "laptop", Replacements: ReplacementsOf(seats, modules), Machines: []Machine{
|
||||||
{Name: "anchor", Domains: []string{"anchor" + InternalSuffix, "anchor.example"}, Addresses: []string{"192.0.2.10", "10.10.0.1"}},
|
{Name: "anchor", Domains: []string{"anchor" + InternalSuffix, "anchor.example"}, Addresses: []string{"192.0.2.10", "10.10.0.1"}},
|
||||||
{Name: "laptop", Domains: []string{"laptop" + InternalSuffix}, Addresses: []string{"10.10.0.4"}},
|
{Name: "laptop", Domains: []string{"laptop" + InternalSuffix}, Addresses: []string{"10.10.0.4"}},
|
||||||
}}
|
}}
|
||||||
@@ -69,21 +73,21 @@ func bash(command string) HookInput {
|
|||||||
func TestSSHToAMeshMachineIsRefusedNamingTheTool(t *testing.T) {
|
func TestSSHToAMeshMachineIsRefusedNamingTheTool(t *testing.T) {
|
||||||
g := guard()
|
g := guard()
|
||||||
for command, want := range map[string]string{
|
for command, want := range map[string]string{
|
||||||
"ssh anchor journalctl -u mesh-controller -n 50": "anchor/node-service-manager.journal",
|
"ssh anchor journalctl -u mesh-config -n 50": "anchor/node-service-manager.journal",
|
||||||
"ssh anchor 'sudo journalctl -fu sshd'": "anchor/node-service-manager.journal",
|
"ssh anchor 'sudo journalctl -fu sshd'": "anchor/node-service-manager.journal",
|
||||||
"ssh -p 22 root@anchor.example systemctl status nats": "anchor/node-service-manager.status",
|
"ssh -p 22 root@anchor.example systemctl status nats": "anchor/node-service-manager.status",
|
||||||
"ssh anchor.internal systemctl restart nats": "anchor/node-service-manager.restart",
|
"ssh anchor.internal systemctl restart nats": "anchor/node-service-manager.restart",
|
||||||
"ssh -o StrictHostKeyChecking=no 10.10.0.1 docker ps -a": "anchor/docker.docker_list",
|
"ssh -o StrictHostKeyChecking=no 10.10.0.1 docker ps -a": "anchor/docker.docker_list",
|
||||||
"ssh anchor docker logs --tail 100 nats": "anchor/docker.docker_logs",
|
"ssh anchor docker logs --tail 100 nats": "anchor/docker.docker_logs",
|
||||||
"ssh laptop docker logs x": "anchor/docker.docker_logs",
|
"ssh laptop docker logs x": "anchor/docker.docker_logs",
|
||||||
"ssh a uptime": "mesh-controller.node",
|
"ssh a uptime": "mesh-placement.placement",
|
||||||
"cd /tmp && ssh anchor journalctl": "anchor/node-service-manager.journal",
|
"cd /tmp && ssh anchor journalctl": "anchor/node-service-manager.journal",
|
||||||
"sudo -u root ssh anchor journalctl": "anchor/node-service-manager.journal",
|
"sudo -u root ssh anchor journalctl": "anchor/node-service-manager.journal",
|
||||||
"timeout 10 ssh anchor journalctl": "anchor/node-service-manager.journal",
|
"timeout 10 ssh anchor journalctl": "anchor/node-service-manager.journal",
|
||||||
"env LANG=C ssh anchor journalctl": "anchor/node-service-manager.journal",
|
"env LANG=C ssh anchor journalctl": "anchor/node-service-manager.journal",
|
||||||
"bash -c 'ssh anchor journalctl -u x'": "anchor/node-service-manager.journal",
|
"bash -c 'ssh anchor journalctl -u x'": "anchor/node-service-manager.journal",
|
||||||
`echo "$(ssh anchor journalctl -n 5)" | tail`: "anchor/node-service-manager.journal",
|
`echo "$(ssh anchor journalctl -n 5)" | tail`: "anchor/node-service-manager.journal",
|
||||||
"for n in anchor laptop; do ssh $n uptime; done; ssh anchor uptime": "mesh-controller.node",
|
"for n in anchor laptop; do ssh $n uptime; done; ssh anchor uptime": "mesh-placement.placement",
|
||||||
"ssh -J anchor 203.0.113.9 journalctl": "anchor/node-service-manager.journal",
|
"ssh -J anchor 203.0.113.9 journalctl": "anchor/node-service-manager.journal",
|
||||||
"ssh via-anchor journalctl": "anchor/node-service-manager.journal",
|
"ssh via-anchor journalctl": "anchor/node-service-manager.journal",
|
||||||
"ssh nas.lan journalctl": "anchor/node-service-manager.journal",
|
"ssh nas.lan journalctl": "anchor/node-service-manager.journal",
|
||||||
|
|||||||
@@ -1,43 +1,82 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||||
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||||
)
|
)
|
||||||
|
|
||||||
// askingAController answers the controller's verbs as the seat does: `tools` in-process, the rest as the
|
// aBus is the states claude-code reads, as a test holds them: by read, key and value.
|
||||||
// command's output and its JSON.
|
type aBus map[string]map[string][]byte
|
||||||
func askingAController(t *testing.T) Ask {
|
|
||||||
|
func (b aBus) Keys(read string) ([]string, error) {
|
||||||
|
if b[read] == nil {
|
||||||
|
return nil, errors.New("no such state")
|
||||||
|
}
|
||||||
|
var keys []string
|
||||||
|
for k := range b[read] {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
return keys, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b aBus) Get(read, key string) ([]byte, bool, error) {
|
||||||
|
v, ok := b[read][key]
|
||||||
|
return v, ok, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// someSeats are two seats as the seats' contract says them: one held once for the mesh, one on every machine.
|
||||||
|
func someSeats() []catalogue.Seat {
|
||||||
|
return []catalogue.Seat{
|
||||||
|
{Name: "mesh-placement", Scope: catalogue.ScopeMesh, Serves: []catalogue.Verb{
|
||||||
|
{Name: "placement", Description: "Read placement: one node's key.", Replaces: []string{"hostnamectl", "uptime"}},
|
||||||
|
{Name: "propose", Description: "Propose a change."}}},
|
||||||
|
{Name: "node-service-manager", Scope: catalogue.ScopeNode, Serves: []catalogue.Verb{
|
||||||
|
{Name: "status", Description: "One unit as the service manager sees it now: its states, whether it starts at boot.", Replaces: []string{"systemctl status", "systemctl is-active"}},
|
||||||
|
{Name: "journal", Description: "The last lines of one unit's journal.", Replaces: []string{"journalctl"}}}},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// aMesh is two machines, anchor (with a public domain) and laptop, docker and zsh placed on anchor, docker's manifest
|
||||||
|
// saying what its tool replaces, as the seat mesh-placement and the indexer keep them on the bus.
|
||||||
|
func aMesh(t *testing.T) aBus {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
wrap := func(output string, answer any) json.RawMessage {
|
b := aBus{PlacementRead: {}, ModulesRead: {}, ManifestsRead: {}}
|
||||||
raw, _ := json.Marshal(map[string]any{"ok": true, "output": output, "answer": answer})
|
for _, p := range []delivery.Placement{
|
||||||
return raw
|
{V: 1, Node: "anchor", Decided: delivery.NodeDecided{PublicDomain: "Anchor.Example"},
|
||||||
|
Modules: []delivery.PlacedModule{{Module: "docker"}, {Module: "zsh"}}},
|
||||||
|
{V: 1, Node: "laptop"},
|
||||||
|
} {
|
||||||
|
values, err := delivery.PlacementValues(p)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
return func(address string, args any) (json.RawMessage, error) {
|
for k, v := range values {
|
||||||
switch address {
|
b[PlacementRead][k] = v
|
||||||
case "seat:mesh-controller.tools":
|
|
||||||
return json.RawMessage(`{"seats":[
|
|
||||||
{"seat":"mesh-controller","scope":"mesh","tools":[{"name":"node","description":"What one machine reported it can do, what it is assigned, and why.","replaces":["hostnamectl","uptime"]},{"name":"nodes","description":"Every machine."}]},
|
|
||||||
{"seat":"node-service-manager","scope":"node","tools":[
|
|
||||||
{"name":"status","description":"One unit as the service manager sees it now: its states, whether it starts at boot.","replaces":["systemctl status","systemctl is-active"]},
|
|
||||||
{"name":"journal","description":"The last lines of one unit's journal.","replaces":["journalctl"]}]}]}`), nil
|
|
||||||
case "seat:mesh-controller.modules":
|
|
||||||
return wrap("[...]", []any{map[string]any{"module": "docker", "on": []string{"anchor"},
|
|
||||||
"replaces": map[string][]string{"docker_logs": {"docker logs"}}}, map[string]any{"module": "zsh", "on": []string{"anchor"}}}), nil
|
|
||||||
case "seat:mesh-controller.nodes":
|
|
||||||
return wrap("", []any{map[string]any{"name": "anchor"}, map[string]any{"name": "laptop"}}), nil
|
|
||||||
case "seat:mesh-controller.node":
|
|
||||||
node, _ := args.(map[string]any)["node"].(string)
|
|
||||||
if node == "anchor" {
|
|
||||||
return wrap("anchor\n mode converged\n public domain Anchor.Example\n\n what it runs\n", nil), nil
|
|
||||||
}
|
}
|
||||||
return wrap("laptop\n mode converged\n", nil), nil
|
|
||||||
}
|
}
|
||||||
return nil, errors.New("no such verb")
|
for module, manifest := range map[string]string{
|
||||||
|
"docker": `{"module":"docker","replaces":{"docker_logs":["docker logs"]}}`,
|
||||||
|
"zsh": `{"module":"zsh"}`,
|
||||||
|
} {
|
||||||
|
digest := fmt.Sprintf("%x", sha256.Sum256([]byte(manifest)))
|
||||||
|
mkey, _ := delivery.ManifestsKey(digest)
|
||||||
|
raw, err := json.Marshal(delivery.Manifest{V: 1, Module: module, JSON: manifest})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
b[ManifestsRead][mkey] = raw
|
||||||
|
key, _ := delivery.ModulesKey(module)
|
||||||
|
b[ModulesRead][key], _ = json.Marshal(delivery.Module{V: 1, None: "nothing is built of it", Manifest: digest, Repository: "novox/mesh-catalog", Commit: "c"})
|
||||||
|
}
|
||||||
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolving(name string) []string {
|
func resolving(name string) []string {
|
||||||
@@ -45,10 +84,11 @@ func resolving(name string) []string {
|
|||||||
"laptop": {"127.0.0.1", "10.10.0.4"}}[name]
|
"laptop": {"127.0.0.1", "10.10.0.4"}}[name]
|
||||||
}
|
}
|
||||||
|
|
||||||
// What the controller says of its tools and machines is read whole: the seats' verbs, the modules' own tools,
|
// Issue 1126 B13: what the mesh says of its tools and machines is read from the bus whole, never asked of the
|
||||||
// every machine with its domains and addresses — a loopback address never one of them.
|
// controller: the seats' verbs, the placed modules' own tools, every machine with its domains and addresses — a
|
||||||
func TestTheMeshsToolsAreAskedOfTheController(t *testing.T) {
|
// loopback address never one of them.
|
||||||
m, err := AskMeshTools(askingAController(t), resolving)
|
func TestTheMeshsToolsAreReadFromTheBus(t *testing.T) {
|
||||||
|
m, err := ReadMeshToolsFrom(aMesh(t), someSeats(), resolving)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -56,7 +96,7 @@ func TestTheMeshsToolsAreAskedOfTheController(t *testing.T) {
|
|||||||
for _, r := range m.Replacements {
|
for _, r := range m.Replacements {
|
||||||
addresses = append(addresses, r.Address)
|
addresses = append(addresses, r.Address)
|
||||||
}
|
}
|
||||||
if got := strings.Join(addresses, ","); got != "mesh-controller.node,node-service-manager.status,node-service-manager.journal,docker.docker_logs" {
|
if got := strings.Join(addresses, ","); got != "mesh-placement.placement,node-service-manager.status,node-service-manager.journal,docker.docker_logs" {
|
||||||
t.Errorf("replacements: %s", got)
|
t.Errorf("replacements: %s", got)
|
||||||
}
|
}
|
||||||
if len(m.Machines) != 2 || strings.Join(m.Machines[0].Domains, ",") != "anchor.internal,anchor.example" ||
|
if len(m.Machines) != 2 || strings.Join(m.Machines[0].Domains, ",") != "anchor.internal,anchor.example" ||
|
||||||
@@ -71,12 +111,12 @@ func TestTheMeshsToolsAreAskedOfTheController(t *testing.T) {
|
|||||||
// **The table is generated, not written** (novox/hq ADR 0245): what the records say a verb replaces is a row,
|
// **The table is generated, not written** (novox/hq ADR 0245): what the records say a verb replaces is a row,
|
||||||
// the machines' seats first, and nothing is rendered where the records say nothing.
|
// the machines' seats first, and nothing is rendered where the records say nothing.
|
||||||
func TestTheInsteadOfTableIsGeneratedFromTheRecords(t *testing.T) {
|
func TestTheInsteadOfTableIsGeneratedFromTheRecords(t *testing.T) {
|
||||||
m, _ := AskMeshTools(askingAController(t), resolving)
|
m, _ := ReadMeshToolsFrom(aMesh(t), someSeats(), resolving)
|
||||||
table := InsteadOf(&m)
|
table := InsteadOf(&m)
|
||||||
for _, want := range []string{
|
for _, want := range []string{
|
||||||
"| `<node>/node-service-manager.` | `status` — `systemctl status`, `systemctl is-active` · `journal` — `journalctl` |",
|
"| `<node>/node-service-manager.` | `status` — `systemctl status`, `systemctl is-active` · `journal` — `journalctl` |",
|
||||||
"| `<node>/docker.` | `docker_logs` — `docker logs` |",
|
"| `<node>/docker.` | `docker_logs` — `docker logs` |",
|
||||||
"| `mesh-controller.` | `node` — `hostnamectl`, `uptime` |",
|
"| `mesh-placement.` | `placement` — `hostnamectl`, `uptime` |",
|
||||||
"Never `ssh` to a mesh machine",
|
"Never `ssh` to a mesh machine",
|
||||||
} {
|
} {
|
||||||
if !strings.Contains(table, want) {
|
if !strings.Contains(table, want) {
|
||||||
@@ -84,14 +124,14 @@ func TestTheInsteadOfTableIsGeneratedFromTheRecords(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if strings.Index(table, "node-service-manager.") > strings.Index(table, "docker.") ||
|
if strings.Index(table, "node-service-manager.") > strings.Index(table, "docker.") ||
|
||||||
strings.Index(table, "docker.") > strings.Index(table, "`mesh-controller.`") {
|
strings.Index(table, "docker.") > strings.Index(table, "`mesh-placement.`") {
|
||||||
t.Errorf("not the machines' seats, then modules, then the mesh's:\n%s", table)
|
t.Errorf("not the machines' seats, then modules, then the mesh's:\n%s", table)
|
||||||
}
|
}
|
||||||
// What the guard here named most comes first: its row, and in its row its verb.
|
// What the guard here named most comes first: its row, and in its row its verb.
|
||||||
m.Refused = map[string]int{"node-service-manager.journal": 3, "mesh-controller.node": 1}
|
m.Refused = map[string]int{"node-service-manager.journal": 3, "mesh-placement.placement": 1}
|
||||||
ranked := InsteadOf(&m)
|
ranked := InsteadOf(&m)
|
||||||
if !strings.Contains(ranked, "| `<node>/node-service-manager.` | `journal` — `journalctl` · `status`") ||
|
if !strings.Contains(ranked, "| `<node>/node-service-manager.` | `journal` — `journalctl` · `status`") ||
|
||||||
strings.Index(ranked, "`mesh-controller.`") > strings.Index(ranked, "`<node>/docker.`") {
|
strings.Index(ranked, "`mesh-placement.`") > strings.Index(ranked, "`<node>/docker.`") {
|
||||||
t.Errorf("not ranked by the guard's record:\n%s", ranked)
|
t.Errorf("not ranked by the guard's record:\n%s", ranked)
|
||||||
}
|
}
|
||||||
m.Refused = nil
|
m.Refused = nil
|
||||||
@@ -121,7 +161,7 @@ func TestTheInsteadOfTableIsGeneratedFromTheRecords(t *testing.T) {
|
|||||||
// Asked again with the same answer, nothing is written and nothing renders.
|
// Asked again with the same answer, nothing is written and nothing renders.
|
||||||
func TestTheMeshsToolsAreKeptOnlyWhenTheyChange(t *testing.T) {
|
func TestTheMeshsToolsAreKeptOnlyWhenTheyChange(t *testing.T) {
|
||||||
p := Paths{State: t.TempDir()}
|
p := Paths{State: t.TempDir()}
|
||||||
m, _ := AskMeshTools(askingAController(t), resolving)
|
m, _ := ReadMeshToolsFrom(aMesh(t), someSeats(), resolving)
|
||||||
if !KeepMeshTools(p, m) {
|
if !KeepMeshTools(p, m) {
|
||||||
t.Fatal("the first answer was not kept")
|
t.Fatal("the first answer was not kept")
|
||||||
}
|
}
|
||||||
@@ -138,18 +178,32 @@ func TestTheMeshsToolsAreKeptOnlyWhenTheyChange(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Where claude-code runs is read from the controller's `modules` answer as it comes — JSON, from `module list
|
// Where a module runs is read from the seat mesh-placement's placement (issue 1126 B13), never the controller's answer.
|
||||||
// --json` — and from the printed list too.
|
func TestTheMachinesRunningAModuleAreReadFromPlacement(t *testing.T) {
|
||||||
func TestTheMachinesRunningTheModuleAreReadFromTheControllersAnswer(t *testing.T) {
|
b := aMesh(t)
|
||||||
asJSON := json.RawMessage(`{"ok":true,"output":"[...]","answer":[{"module":"zsh","on":["a"]},{"module":"claude-code","on":["a","b"]}]}`)
|
if got, err := NodesRunning(b, "zsh"); err != nil || strings.Join(got, ",") != "anchor" {
|
||||||
if got := strings.Join(NodesRunning(asJSON, "claude-code"), ","); got != "a,b" {
|
t.Errorf("zsh runs on %v (%v)", got, err)
|
||||||
t.Errorf("from JSON: %q", got)
|
|
||||||
}
|
}
|
||||||
printed := json.RawMessage(`{"ok":true,"output":"zsh 1 built x on a\nclaude-code 1 built y on a, b\n"}`)
|
if got, err := NodesRunning(b, "claude-code"); err != nil || got != nil {
|
||||||
if got := strings.Join(NodesRunning(printed, "claude-code"), ","); got != "a,b" {
|
t.Errorf("a module that runs nowhere: %v (%v)", got, err)
|
||||||
t.Errorf("from the printed list: %q", got)
|
|
||||||
}
|
}
|
||||||
if got := NodesRunning(json.RawMessage(`{"ok":true,"answer":[{"module":"zsh","on":["a"]}]}`), "claude-code"); got != nil {
|
if _, err := NodesRunning(aBus{}, "zsh"); err == nil {
|
||||||
t.Errorf("a module that runs nowhere: %v", got)
|
t.Error("an unreadable placement was read as no machine")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Issue 1126 B13 AC1 (ADR 0590): over the seats the mesh defines, the rendered instructions, their table among them,
|
||||||
|
// name no address of the controller, which is not placed: each job is named by the seat that owns it now.
|
||||||
|
func TestTheRenderedInstructionsNameNoControllerAddress(t *testing.T) {
|
||||||
|
m, err := ReadMeshToolsFrom(aMesh(t), catalogue.Seats(), resolving)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
md := RenderWithMesh(Facts{Node: "w", Console: "x"}, Settings{}, nil, "/h", nil, Config{}, &m)["CLAUDE.md"]
|
||||||
|
if !strings.Contains(md, "## Instead of a shell command") {
|
||||||
|
t.Fatalf("no table rendered:\n%s", md)
|
||||||
|
}
|
||||||
|
if strings.Contains(md, "mesh-controller") {
|
||||||
|
t.Errorf("the instructions name the controller:\n%s", md)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ package main
|
|||||||
|
|
||||||
// instructionsText is the managed instruction file, generated from the TypeScript renderer it replaced so
|
// instructionsText is the managed instruction file, generated from the TypeScript renderer it replaced so
|
||||||
// the file under the agent's managed directory did not change by a byte when the module moved to Go;
|
// the file under the agent's managed directory did not change by a byte when the module moved to Go;
|
||||||
// a test holds it to that renderer's own output (testdata/rendered-by-typescript.json).
|
// a test holds it to that renderer's output (testdata/rendered-by-typescript.json), changed since only
|
||||||
|
// where the controller's verbs were named (issue 1126 B13: the controller is not placed, ADR 0590).
|
||||||
func instructionsText(node, role string) string {
|
func instructionsText(node, role string) string {
|
||||||
return "# This machine is a node of a Novox mesh\n\nWritten by the mesh's `claude-code` module. Edit the module's settings or the catalog, never this file:\nit is rewritten whenever the module renders.\n\n## Who this node is\n\n- **Node:** `" +
|
return "# This machine is a node of a Novox mesh\n\nWritten by the mesh's `claude-code` module. Edit the module's settings or the catalog, never this file:\nit is rewritten whenever the module renders.\n\n## Who this node is\n\n- **Node:** `" +
|
||||||
node +
|
node +
|
||||||
"`\n- **Role:** " +
|
"`\n- **Role:** " +
|
||||||
role +
|
role +
|
||||||
"\n- The other nodes, their roles and what runs where: ask the controller (`mesh-controller.nodes`,\n `mesh-controller.node`). Nothing here lists them, because a copy drifts.\n\n## How a session on this mesh works\n\nThe console is the only way to the mesh: the MCP server named `mesh`. It offers five tools, and\neverything else is an address you find and call through them:\n\n- `mesh_search` — words in, matching addresses out. `mesh_describe` — one address's arguments.\n- `mesh_call` — call an address. A seat the mesh holds once is `<seat>.<verb>` (the mesh's own verbs\n are `mesh-controller.<verb>`: `status`, `plan`, `node`, `assign`, `push`, `settings`);\n a module on a machine is `<node>/<module>.<tool>`.\n- `mesh_overview` and `mesh_machine` — the mesh's seats and machines, and what one machine runs.\n\n- **Symptom first.** For an error, a failing service or anything unexpected, search the record with the\n literal text before forming a hypothesis: the records module's `records_search`, then\n `records_read`.\n- **Ask the mesh before changing it**, and change it through the controller's verbs or the catalog.\n- **A licence** through the `anthropic-licence-manager` seat's verbs. Never edit the agent's credentials\n file by hand, never print or ask for a token.\n\n## Hard rules\n\n- A file the mesh manages is changed through the verb or the catalog that owns it, never on disk. If\n unsure, `mesh-controller.plan` for the node says what the mesh writes there.\n- Never write to a store's database by hand; schema changes are numbered migrations.\n- Never push to a main branch: a branch, a pull request, and a human approval for every merge.\n- The mesh creates no symlinks, and nobody else does either.\n- A package is declared in a module, never installed by hand.\n\n## Conventions\n\n- Commit messages are concise, in the imperative, about why.\n- Test before pushing: nodes update unattended.\n- The playbooks in the record say how research, decisions, designs, issues and hand-offs are done.\n"
|
"\n- The other nodes, their roles and what runs where: ask the placement seat (`mesh-placement.placement`),\n or `mesh_overview` and `mesh_machine`. Nothing here lists them, because a copy drifts.\n\n## How a session on this mesh works\n\nThe console is the only way to the mesh: the MCP server named `mesh`. It offers five tools, and\neverything else is an address you find and call through them:\n\n- `mesh_search` — words in, matching addresses out. `mesh_describe` — one address's arguments.\n- `mesh_call` — call an address. A seat the mesh holds once is `<seat>.<verb>` (the mesh's own seats\n include `mesh-placement`, `mesh-delivery`, `mesh-config` and `mesh-conditions`);\n a module on a machine is `<node>/<module>.<tool>`.\n- `mesh_overview` and `mesh_machine` — the mesh's seats and machines, and what one machine runs.\n\n- **Symptom first.** For an error, a failing service or anything unexpected, search the record with the\n literal text before forming a hypothesis: the records module's `records_search`, then\n `records_read`.\n- **Ask the mesh before changing it**, and change it through the seats' verbs or the catalog.\n- **A licence** through the `anthropic-licence-manager` seat's verbs. Never edit the agent's credentials\n file by hand, never print or ask for a token.\n\n## Hard rules\n\n- A file the mesh manages is changed through the verb or the catalog that owns it, never on disk. If\n unsure, `<node>/node-engine.composition` for the module says what the mesh writes there.\n- Never write to a store's database by hand; schema changes are numbered migrations.\n- Never push to a main branch: a branch, a pull request, and a human approval for every merge.\n- The mesh creates no symlinks, and nobody else does either.\n- A package is declared in a module, never installed by hand.\n\n## Conventions\n\n- Commit messages are concise, in the imperative, about why.\n- Test before pushing: nodes update unattended.\n- The playbooks in the record say how research, decisions, designs, issues and hand-offs are done.\n"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import (
|
|||||||
|
|
||||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||||
"git.novox.be/novox/mesh-sdk/go/asks"
|
"git.novox.be/novox/mesh-sdk/go/asks"
|
||||||
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||||
)
|
)
|
||||||
|
|
||||||
func say(format string, args ...any) {
|
func say(format string, args ...any) {
|
||||||
@@ -204,54 +205,21 @@ func (s stateOf) Get(key string) (json.RawMessage, bool, error) {
|
|||||||
return e.Value, true, nil
|
return e.Value, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// nodesRunningMe is the nodes claude-code runs on, from the controller's list of modules — for the register
|
// nodesRunningMe is the nodes claude-code runs on, from the seat mesh-placement's placement — for the register
|
||||||
// tool's question.
|
// tool's question.
|
||||||
func nodesRunningMe() ([]string, error) {
|
func nodesRunningMe() ([]string, error) { return NodesRunning(busStates{}, "claude-code") }
|
||||||
raw, err := ask("seat:mesh-controller.modules", map[string]any{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return NodesRunning(raw, "claude-code"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NodesRunning reads where a module runs from the controller's `modules` answer. The verb runs `module list
|
// busStates reads the states the manifest declares in its reads through the runtime.
|
||||||
// --json`, so the answer is a JSON list of {module, on}: the lines of the printed list it was once read as
|
type busStates struct{}
|
||||||
// never came, and every "also on" hint and every `nodes: "all"` named no machine. The printed form, `<module>
|
|
||||||
// … on a, b`, is still read when that is what came.
|
func (busStates) Keys(read string) ([]string, error) { return stdio.State(read).Keys() }
|
||||||
func NodesRunning(raw json.RawMessage, module string) []string {
|
|
||||||
var listed []struct {
|
func (busStates) Get(read, key string) ([]byte, bool, error) {
|
||||||
Module string `json:"module"`
|
e, err := stdio.State(read).Get(key)
|
||||||
On []string `json:"on"`
|
if err != nil || e == nil {
|
||||||
|
return nil, false, err
|
||||||
}
|
}
|
||||||
if json.Unmarshal(verbAnswer(raw), &listed) == nil {
|
return e.Value, true, nil
|
||||||
for _, m := range listed {
|
|
||||||
if m.Module == module {
|
|
||||||
return m.On
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
text := outputOf(raw)
|
|
||||||
if text == "" {
|
|
||||||
text = string(raw)
|
|
||||||
}
|
|
||||||
for _, line := range strings.Split(text, "\n") {
|
|
||||||
if !strings.HasPrefix(line, module+" ") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
_, on, ok := strings.Cut(line, " on ")
|
|
||||||
if !ok || strings.TrimSpace(on) == "nothing" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var out []string
|
|
||||||
for _, n := range strings.Split(on, ",") {
|
|
||||||
if n = strings.TrimSpace(n); n != "" {
|
|
||||||
out = append(out, n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func fingerprintOfFile(path string) any {
|
func fingerprintOfFile(path string) any {
|
||||||
@@ -320,11 +288,11 @@ func tools(p Paths, servers ServerState, view *ServerView, config ConfigState, c
|
|||||||
Description: "Claude Code on this machine as the mesh configured it: the licence it holds and when its token expires, what it reports holding, the managed files, the MCP servers registered here. Fingerprints only, never a token.",
|
Description: "Claude Code on this machine as the mesh configured it: the licence it holds and when its token expires, what it reports holding, the managed files, the MCP servers registered here. Fingerprints only, never a token.",
|
||||||
Run: func(map[string]any) (any, error) { return status(p), nil }},
|
Run: func(map[string]any) (any, error) { return status(p), nil }},
|
||||||
{Name: "claude_code_render",
|
{Name: "claude_code_render",
|
||||||
Description: "Write Claude Code's managed directory now, from the mesh's facts, this module's settings and the servers registered here — after asking the controller again what each tool replaces and which machines the mesh has (the instructions' table and the shell's guard).",
|
Description: "Write Claude Code's managed directory now, from the mesh's facts, this module's settings and the servers registered here — after reading the bus again for what each tool replaces and which machines the mesh has (the instructions' table and the shell's guard).",
|
||||||
Run: func(map[string]any) (any, error) {
|
Run: func(map[string]any) (any, error) {
|
||||||
answer := map[string]any{}
|
answer := map[string]any{}
|
||||||
if m, err := AskMeshTools(ask, lookup); err != nil {
|
if m, err := ReadMeshToolsFrom(busStates{}, catalogue.Seats(), lookup); err != nil {
|
||||||
answer["asked"] = "the controller did not answer in full (" + err.Error() + "); what was kept is rendered"
|
answer["asked"] = "the bus could not be read in full (" + err.Error() + "); what was kept is rendered"
|
||||||
} else {
|
} else {
|
||||||
KeepMeshTools(p, m)
|
KeepMeshTools(p, m)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,21 +4,27 @@ package main
|
|||||||
// shell need it (novox/hq ADR 0245): every seat verb and module tool that says what shell command it
|
// shell need it (novox/hq ADR 0245): every seat verb and module tool that says what shell command it
|
||||||
// replaces, and the mesh's machines by every name and address a session could reach one by.
|
// replaces, and the mesh's machines by every name and address a session could reach one by.
|
||||||
//
|
//
|
||||||
// Asked of the controller — `tools` for the seats' verbs, `modules` for the modules' own tools, `nodes`
|
// Read from the bus at start and every few minutes (issue 1126 B13; the controller that answered this before is
|
||||||
// and `node` for the machines — at start and every few minutes, kept in the module's state so a render
|
// not placed, novox/hq ADR 0590): the seats' verbs from the seats' contract (mesh-sdk catalogue), the modules'
|
||||||
// never waits for the bus, and rendered whenever it changes. Nothing here is written by hand: a verb that
|
// own tools from the manifests the indexer keeps (`mesh-delivery-indexer.modules` and `.manifests`), and the
|
||||||
// gains a `replaces` in the records is in the next render's table and the guard's next refusal.
|
// machines and where each module runs from the seat mesh-placement's `placement`. Kept in the module's state so
|
||||||
|
// a render never waits for the bus, and rendered whenever it changes. Nothing here is written by hand: a verb
|
||||||
|
// that gains a `replaces` is in the next render's table and the guard's next refusal.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||||
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Replacement is one verb or tool and the shell commands it is the mesh's way to do.
|
// Replacement is one verb or tool and the shell commands it is the mesh's way to do.
|
||||||
@@ -41,11 +47,11 @@ type Machine struct {
|
|||||||
Addresses []string `json:"addresses,omitempty"`
|
Addresses []string `json:"addresses,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MeshTools is what the controller said, as kept in the module's state.
|
// MeshTools is what the bus said, as kept in the module's state.
|
||||||
type MeshTools struct {
|
type MeshTools struct {
|
||||||
Replacements []Replacement `json:"replacements"`
|
Replacements []Replacement `json:"replacements"`
|
||||||
Machines []Machine `json:"machines"`
|
Machines []Machine `json:"machines"`
|
||||||
// Asked is when the controller last answered.
|
// Asked is when the bus was last read whole.
|
||||||
Asked string `json:"asked,omitempty"`
|
Asked string `json:"asked,omitempty"`
|
||||||
// Refused is how often the guard here named each tool instead of a work-around, from its record: the
|
// Refused is how often the guard here named each tool instead of a work-around, from its record: the
|
||||||
// table's order. Never kept — read from the record at each render.
|
// table's order. Never kept — read from the record at each render.
|
||||||
@@ -78,42 +84,120 @@ func ReadMeshTools(p Paths) *MeshTools {
|
|||||||
return &m
|
return &m
|
||||||
}
|
}
|
||||||
|
|
||||||
// verbAnswer is a controller verb's answer: the command's printed output, and its JSON when it printed one.
|
// The states read, each declared in the manifest's reads.
|
||||||
func verbAnswer(raw json.RawMessage) json.RawMessage {
|
const (
|
||||||
var r struct {
|
PlacementRead = delivery.PlacementRead
|
||||||
Output string `json:"output"`
|
ModulesRead = delivery.IndexerModule + ".modules"
|
||||||
Answer json.RawMessage `json:"answer"`
|
ManifestsRead = delivery.IndexerModule + ".manifests"
|
||||||
}
|
)
|
||||||
if json.Unmarshal(raw, &r) != nil {
|
|
||||||
return raw
|
// States reads the module states the manifest declares in its reads: a state's keys, and one key's value.
|
||||||
}
|
type States interface {
|
||||||
if len(r.Answer) > 0 && string(r.Answer) != "null" {
|
Keys(read string) ([]string, error)
|
||||||
return r.Answer
|
Get(read, key string) ([]byte, bool, error)
|
||||||
}
|
|
||||||
if r.Output != "" {
|
|
||||||
t := strings.TrimSpace(r.Output)
|
|
||||||
for _, open := range []string{"[", "{"} {
|
|
||||||
if strings.HasPrefix(t, open) {
|
|
||||||
return json.RawMessage(t)
|
|
||||||
}
|
|
||||||
if i := strings.Index(t, "\n"+open); i >= 0 {
|
|
||||||
return json.RawMessage(strings.TrimSpace(t[i+1:]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return json.RawMessage(nil)
|
|
||||||
}
|
|
||||||
return raw
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func outputOf(raw json.RawMessage) string {
|
// ModuleTools is one module's own tools that replace a command, as its manifest says (`replaces`), and where it runs.
|
||||||
var r struct {
|
type ModuleTools struct {
|
||||||
Output string `json:"output"`
|
Module string
|
||||||
}
|
On []string
|
||||||
_ = json.Unmarshal(raw, &r)
|
Replaces map[string][]string
|
||||||
return r.Output
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var publicDomain = regexp.MustCompile(`(?m)^\s*public domain\s+(\S+)\s*$`)
|
// placements is every node's placement, from the seat mesh-placement's state.
|
||||||
|
func placements(st States) (map[string]delivery.Placement, error) {
|
||||||
|
keys, err := st.Keys(PlacementRead)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s: %w", PlacementRead, err)
|
||||||
|
}
|
||||||
|
read := delivery.NewPlacementKeys()
|
||||||
|
for _, k := range keys {
|
||||||
|
v, found, err := st.Get(PlacementRead, k)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s %s: %w", PlacementRead, k, err)
|
||||||
|
}
|
||||||
|
if found {
|
||||||
|
_, _ = read.Put(k, v, 0, false) // a key that does not read places nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return read.All(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// runsOn is, by module, the nodes placement puts it on, sorted.
|
||||||
|
func runsOn(placed map[string]delivery.Placement) map[string][]string {
|
||||||
|
out := map[string][]string{}
|
||||||
|
for node, p := range placed {
|
||||||
|
for _, m := range p.Modules {
|
||||||
|
out[m.Module] = append(out[m.Module], node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, nodes := range out {
|
||||||
|
sort.Strings(nodes)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodesRunning is the nodes placement puts module on.
|
||||||
|
func NodesRunning(st States, module string) ([]string, error) {
|
||||||
|
placed, err := placements(st)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return runsOn(placed)[module], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// modulesTools is every placed module's own tools that replace a command, by module, from the manifest the indexer
|
||||||
|
// keeps of it. A module whose manifest cannot be read is left out and said in the error.
|
||||||
|
func modulesTools(st States, on map[string][]string) ([]ModuleTools, error) {
|
||||||
|
var out []ModuleTools
|
||||||
|
var problems []error
|
||||||
|
modules := make([]string, 0, len(on))
|
||||||
|
for m := range on {
|
||||||
|
modules = append(modules, m)
|
||||||
|
}
|
||||||
|
sort.Strings(modules)
|
||||||
|
for _, module := range modules {
|
||||||
|
key, err := delivery.ModulesKey(module)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
raw, found, err := st.Get(ModulesRead, key)
|
||||||
|
if err != nil || !found {
|
||||||
|
if err != nil {
|
||||||
|
problems = append(problems, fmt.Errorf("%s %s: %w", ModulesRead, module, err))
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m, err := delivery.DecodeModule(raw)
|
||||||
|
if err != nil || m.Manifest == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mkey, err := delivery.ManifestsKey(m.Manifest)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
raw, found, err = st.Get(ManifestsRead, mkey)
|
||||||
|
if err != nil || !found {
|
||||||
|
if err != nil {
|
||||||
|
problems = append(problems, fmt.Errorf("%s of %s: %w", ManifestsRead, module, err))
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
kept, err := delivery.DecodeManifest(raw)
|
||||||
|
if err != nil {
|
||||||
|
problems = append(problems, fmt.Errorf("the manifest of %s: %w", module, err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var man struct {
|
||||||
|
Replaces map[string][]string `json:"replaces"`
|
||||||
|
}
|
||||||
|
if json.Unmarshal([]byte(kept.JSON), &man) != nil || len(man.Replaces) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out = append(out, ModuleTools{Module: module, On: on[module], Replaces: man.Replaces})
|
||||||
|
}
|
||||||
|
return out, errors.Join(problems...)
|
||||||
|
}
|
||||||
|
|
||||||
// firstSentence is what a verb does, short: its description to the first full stop, at most 70 characters.
|
// firstSentence is what a verb does, short: its description to the first full stop, at most 70 characters.
|
||||||
func firstSentence(s string) string {
|
func firstSentence(s string) string {
|
||||||
@@ -131,40 +215,22 @@ func firstSentence(s string) string {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReplacementsOf reads the controller's `tools` and `modules` answers: the seats' verbs first, in the
|
// ReplacementsOf is every seat verb and module tool that says it replaces a command: the seats' verbs first, in the
|
||||||
// records' order, then the modules' own tools by module and tool.
|
// seats' order, then the modules' own tools by module and tool.
|
||||||
func ReplacementsOf(tools, modules json.RawMessage) []Replacement {
|
func ReplacementsOf(seats []catalogue.Seat, modules []ModuleTools) []Replacement {
|
||||||
var out []Replacement
|
var out []Replacement
|
||||||
var seats struct {
|
for _, s := range seats {
|
||||||
Seats []struct {
|
for _, v := range s.Serves {
|
||||||
Seat string `json:"seat"`
|
if len(v.Replaces) == 0 {
|
||||||
Scope string `json:"scope"`
|
|
||||||
Tools []struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
Replaces []string `json:"replaces"`
|
|
||||||
} `json:"tools"`
|
|
||||||
} `json:"seats"`
|
|
||||||
}
|
|
||||||
if json.Unmarshal(tools, &seats) == nil {
|
|
||||||
for _, s := range seats.Seats {
|
|
||||||
for _, t := range s.Tools {
|
|
||||||
if len(t.Replaces) == 0 {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
out = append(out, Replacement{Address: s.Seat + "." + t.Name, NodeScoped: s.Scope == "node", Seat: true,
|
out = append(out, Replacement{Address: s.Name + "." + v.Name, NodeScoped: s.Scope == catalogue.ScopeNode, Seat: true,
|
||||||
Does: firstSentence(t.Description), Replaces: t.Replaces})
|
Does: firstSentence(v.Description), Replaces: v.Replaces})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
sorted := append([]ModuleTools(nil), modules...)
|
||||||
var listed []struct {
|
sort.SliceStable(sorted, func(i, j int) bool { return sorted[i].Module < sorted[j].Module })
|
||||||
Module string `json:"module"`
|
for _, m := range sorted {
|
||||||
On []string `json:"on"`
|
|
||||||
Replaces map[string][]string `json:"replaces"`
|
|
||||||
}
|
|
||||||
if json.Unmarshal(modules, &listed) == nil {
|
|
||||||
sort.SliceStable(listed, func(i, j int) bool { return listed[i].Module < listed[j].Module })
|
|
||||||
for _, m := range listed {
|
|
||||||
names := make([]string, 0, len(m.Replaces))
|
names := make([]string, 0, len(m.Replaces))
|
||||||
for t := range m.Replaces {
|
for t := range m.Replaces {
|
||||||
names = append(names, t)
|
names = append(names, t)
|
||||||
@@ -178,7 +244,6 @@ func ReplacementsOf(tools, modules json.RawMessage) []Replacement {
|
|||||||
Does: strings.ReplaceAll(strings.TrimPrefix(t, strings.ReplaceAll(m.Module, "-", "_")+"_"), "_", " ")})
|
Does: strings.ReplaceAll(strings.TrimPrefix(t, strings.ReplaceAll(m.Module, "-", "_")+"_"), "_", " ")})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,36 +255,24 @@ func lookup(name string) []string {
|
|||||||
return addrs
|
return addrs
|
||||||
}
|
}
|
||||||
|
|
||||||
// AskMeshTools asks the controller now. A part that does not answer is left out and said in the error;
|
// ReadMeshToolsFrom reads the bus now: the seats' verbs, the placed modules' own tools, and every node placement names
|
||||||
// what did answer is kept.
|
// with its domains and addresses. A part that cannot be read is left out and said in the error; what was read is kept.
|
||||||
func AskMeshTools(ask Ask, resolve func(string) []string) (MeshTools, error) {
|
func ReadMeshToolsFrom(st States, seats []catalogue.Seat, resolve func(string) []string) (MeshTools, error) {
|
||||||
var m MeshTools
|
var m MeshTools
|
||||||
tools, terr := ask("seat:mesh-controller.tools", map[string]any{})
|
placed, err := placements(st)
|
||||||
modules, merr := ask("seat:mesh-controller.modules", map[string]any{})
|
|
||||||
if terr != nil && merr != nil {
|
|
||||||
return m, terr
|
|
||||||
}
|
|
||||||
m.Replacements = ReplacementsOf(verbAnswer(tools), verbAnswer(modules))
|
|
||||||
nodesRaw, err := ask("seat:mesh-controller.nodes", map[string]any{})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
m.Replacements = ReplacementsOf(seats, nil)
|
||||||
return m, err
|
return m, err
|
||||||
}
|
}
|
||||||
var nodes []struct {
|
modules, merr := modulesTools(st, runsOn(placed))
|
||||||
Name string `json:"name"`
|
m.Replacements = ReplacementsOf(seats, modules)
|
||||||
}
|
for node, p := range placed {
|
||||||
_ = json.Unmarshal(verbAnswer(nodesRaw), &nodes)
|
machine := Machine{Name: node, Domains: []string{node + InternalSuffix}}
|
||||||
for _, n := range nodes {
|
if d := strings.ToLower(strings.TrimSpace(p.Decided.PublicDomain)); d != "" {
|
||||||
if n.Name == "" {
|
machine.Domains = append(machine.Domains, d)
|
||||||
continue
|
|
||||||
}
|
|
||||||
machine := Machine{Name: n.Name, Domains: []string{n.Name + InternalSuffix}}
|
|
||||||
if shown, err := ask("seat:mesh-controller.node", map[string]any{"node": n.Name}); err == nil {
|
|
||||||
if d := publicDomain.FindStringSubmatch(outputOf(shown)); d != nil {
|
|
||||||
machine.Domains = append(machine.Domains, strings.ToLower(d[1]))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
seen := map[string]bool{}
|
seen := map[string]bool{}
|
||||||
for _, name := range append([]string{n.Name}, machine.Domains...) {
|
for _, name := range append([]string{node}, machine.Domains...) {
|
||||||
for _, a := range resolve(name) {
|
for _, a := range resolve(name) {
|
||||||
if !seen[a] && !net.ParseIP(a).IsLoopback() {
|
if !seen[a] && !net.ParseIP(a).IsLoopback() {
|
||||||
seen[a] = true
|
seen[a] = true
|
||||||
@@ -231,7 +284,7 @@ func AskMeshTools(ask Ask, resolve func(string) []string) (MeshTools, error) {
|
|||||||
m.Machines = append(m.Machines, machine)
|
m.Machines = append(m.Machines, machine)
|
||||||
}
|
}
|
||||||
sort.Slice(m.Machines, func(i, j int) bool { return m.Machines[i].Name < m.Machines[j].Name })
|
sort.Slice(m.Machines, func(i, j int) bool { return m.Machines[i].Name < m.Machines[j].Name })
|
||||||
return m, nil
|
return m, merr
|
||||||
}
|
}
|
||||||
|
|
||||||
// KeepMeshTools writes what was asked to the module's state when it differs from what was kept, and
|
// KeepMeshTools writes what was asked to the module's state when it differs from what was kept, and
|
||||||
@@ -281,19 +334,19 @@ func guardFiles(p Paths, m *MeshTools) *GuardFiles {
|
|||||||
return &GuardFiles{Exe: exe, Data: GuardDataOf(p, m), WaitsDir: WaitsDirPath(p)}
|
return &GuardFiles{Exe: exe, Data: GuardDataOf(p, m), WaitsDir: WaitsDirPath(p)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MeshToolsEvery is how often the controller is asked again.
|
// MeshToolsEvery is how often the bus is read again.
|
||||||
const MeshToolsEvery = 10 * time.Minute
|
const MeshToolsEvery = 10 * time.Minute
|
||||||
|
|
||||||
// refreshMeshTools asks the controller at start and every few minutes, and renders when what it said changed.
|
// refreshMeshTools reads the bus at start and every few minutes, and renders when what it said changed.
|
||||||
func refreshMeshTools(p Paths) {
|
func refreshMeshTools(p Paths) {
|
||||||
for {
|
for {
|
||||||
m, err := AskMeshTools(ask, lookup)
|
m, err := ReadMeshToolsFrom(busStates{}, catalogue.Seats(), lookup)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
say("asking the controller what each tool replaces: %v", err)
|
say("reading what each tool replaces and which machines the mesh has: %v", err)
|
||||||
}
|
}
|
||||||
if (err == nil || len(m.Replacements) > 0) && KeepMeshTools(p, m) {
|
if (err == nil || len(m.Replacements) > 0) && KeepMeshTools(p, m) {
|
||||||
if _, err := RenderNow(p, writeManaged); err != nil {
|
if _, err := RenderNow(p, writeManaged); err != nil {
|
||||||
say("rendering what the controller said of its tools: %v", err)
|
say("rendering what the bus said of the tools: %v", err)
|
||||||
} else {
|
} else {
|
||||||
say("the mesh's tools changed: %d replace a command, %d machines; rendered", len(m.Replacements), len(m.Machines))
|
say("the mesh's tools changed: %d replace a command, %d machines; rendered", len(m.Replacements), len(m.Machines))
|
||||||
}
|
}
|
||||||
@@ -306,7 +359,7 @@ func refreshMeshTools(p Paths) {
|
|||||||
func GuardStatus(p Paths, last int) map[string]any {
|
func GuardStatus(p Paths, last int) map[string]any {
|
||||||
m := ReadMeshTools(p)
|
m := ReadMeshTools(p)
|
||||||
d := GuardDataOf(p, m)
|
d := GuardDataOf(p, m)
|
||||||
asked := "never: the controller has not answered yet"
|
asked := "never: the bus has not been read whole yet"
|
||||||
if m != nil {
|
if m != nil {
|
||||||
asked = m.Asked
|
asked = m.Asked
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,11 +32,11 @@
|
|||||||
"withKey": {
|
"withKey": {
|
||||||
"managed-mcp.json": "{\n \"mcpServers\": {\n \"anton\": {\n \"type\": \"stdio\",\n \"command\": \"node\",\n \"args\": [\n \"/a/b.js\"\n ],\n \"env\": {}\n },\n \"docs\": {\n \"type\": \"stdio\",\n \"command\": \"docs-mcp\",\n \"args\": [\n \"--x\"\n ]\n },\n \"mesh\": {\n \"type\": \"http\",\n \"url\": \"http://127.0.0.1:4270/mcp\"\n },\n \"search\": {\n \"type\": \"http\",\n \"url\": \"https://s.example/mcp\"\n }\n }\n}\n",
|
"managed-mcp.json": "{\n \"mcpServers\": {\n \"anton\": {\n \"type\": \"stdio\",\n \"command\": \"node\",\n \"args\": [\n \"/a/b.js\"\n ],\n \"env\": {}\n },\n \"docs\": {\n \"type\": \"stdio\",\n \"command\": \"docs-mcp\",\n \"args\": [\n \"--x\"\n ]\n },\n \"mesh\": {\n \"type\": \"http\",\n \"url\": \"http://127.0.0.1:4270/mcp\"\n },\n \"search\": {\n \"type\": \"http\",\n \"url\": \"https://s.example/mcp\"\n }\n }\n}\n",
|
||||||
"managed-settings.json": "{\n \"attribution\": {\n \"commit\": \"\",\n \"pr\": \"\"\n },\n \"allowAllClaudeAiMcps\": true,\n \"apiKeyHelper\": \"/state/api-key-helper\"\n}\n",
|
"managed-settings.json": "{\n \"attribution\": {\n \"commit\": \"\",\n \"pr\": \"\"\n },\n \"allowAllClaudeAiMcps\": true,\n \"apiKeyHelper\": \"/state/api-key-helper\"\n}\n",
|
||||||
"CLAUDE.md": "# This machine is a node of a Novox mesh\n\nWritten by the mesh's `claude-code` module. Edit the module's settings or the catalog, never this file:\nit is rewritten whenever the module renders.\n\n## Who this node is\n\n- **Node:** `workstation`\n- **Role:** the laptop\n- The other nodes, their roles and what runs where: ask the controller (`mesh-controller.nodes`,\n `mesh-controller.node`). Nothing here lists them, because a copy drifts.\n\n## How a session on this mesh works\n\nThe console is the only way to the mesh: the MCP server named `mesh`. It offers five tools, and\neverything else is an address you find and call through them:\n\n- `mesh_search` — words in, matching addresses out. `mesh_describe` — one address's arguments.\n- `mesh_call` — call an address. A seat the mesh holds once is `<seat>.<verb>` (the mesh's own verbs\n are `mesh-controller.<verb>`: `status`, `plan`, `node`, `assign`, `push`, `settings`);\n a module on a machine is `<node>/<module>.<tool>`.\n- `mesh_overview` and `mesh_machine` — the mesh's seats and machines, and what one machine runs.\n\n- **Symptom first.** For an error, a failing service or anything unexpected, search the record with the\n literal text before forming a hypothesis: the records module's `records_search`, then\n `records_read`.\n- **Ask the mesh before changing it**, and change it through the controller's verbs or the catalog.\n- **A licence** through the `anthropic-licence-manager` seat's verbs. Never edit the agent's credentials\n file by hand, never print or ask for a token.\n\n## Hard rules\n\n- A file the mesh manages is changed through the verb or the catalog that owns it, never on disk. If\n unsure, `mesh-controller.plan` for the node says what the mesh writes there.\n- Never write to a store's database by hand; schema changes are numbered migrations.\n- Never push to a main branch: a branch, a pull request, and a human approval for every merge.\n- The mesh creates no symlinks, and nobody else does either.\n- A package is declared in a module, never installed by hand.\n\n## Conventions\n\n- Commit messages are concise, in the imperative, about why.\n- Test before pushing: nodes update unattended.\n- The playbooks in the record say how research, decisions, designs, issues and hand-offs are done.\n"
|
"CLAUDE.md": "# This machine is a node of a Novox mesh\n\nWritten by the mesh's `claude-code` module. Edit the module's settings or the catalog, never this file:\nit is rewritten whenever the module renders.\n\n## Who this node is\n\n- **Node:** `workstation`\n- **Role:** the laptop\n- The other nodes, their roles and what runs where: ask the placement seat (`mesh-placement.placement`),\n or `mesh_overview` and `mesh_machine`. Nothing here lists them, because a copy drifts.\n\n## How a session on this mesh works\n\nThe console is the only way to the mesh: the MCP server named `mesh`. It offers five tools, and\neverything else is an address you find and call through them:\n\n- `mesh_search` — words in, matching addresses out. `mesh_describe` — one address's arguments.\n- `mesh_call` — call an address. A seat the mesh holds once is `<seat>.<verb>` (the mesh's own seats\n include `mesh-placement`, `mesh-delivery`, `mesh-config` and `mesh-conditions`);\n a module on a machine is `<node>/<module>.<tool>`.\n- `mesh_overview` and `mesh_machine` — the mesh's seats and machines, and what one machine runs.\n\n- **Symptom first.** For an error, a failing service or anything unexpected, search the record with the\n literal text before forming a hypothesis: the records module's `records_search`, then\n `records_read`.\n- **Ask the mesh before changing it**, and change it through the seats' verbs or the catalog.\n- **A licence** through the `anthropic-licence-manager` seat's verbs. Never edit the agent's credentials\n file by hand, never print or ask for a token.\n\n## Hard rules\n\n- A file the mesh manages is changed through the verb or the catalog that owns it, never on disk. If\n unsure, `<node>/node-engine.composition` for the module says what the mesh writes there.\n- Never write to a store's database by hand; schema changes are numbered migrations.\n- Never push to a main branch: a branch, a pull request, and a human approval for every merge.\n- The mesh creates no symlinks, and nobody else does either.\n- A package is declared in a module, never installed by hand.\n\n## Conventions\n\n- Commit messages are concise, in the imperative, about why.\n- Test before pushing: nodes update unattended.\n- The playbooks in the record say how research, decisions, designs, issues and hand-offs are done.\n"
|
||||||
},
|
},
|
||||||
"plain": {
|
"plain": {
|
||||||
"managed-mcp.json": "{\n \"mcpServers\": {\n \"mesh\": {\n \"type\": \"http\",\n \"url\": \"http://127.0.0.1:4270/mcp\"\n }\n }\n}\n",
|
"managed-mcp.json": "{\n \"mcpServers\": {\n \"mesh\": {\n \"type\": \"http\",\n \"url\": \"http://127.0.0.1:4270/mcp\"\n }\n }\n}\n",
|
||||||
"managed-settings.json": "{\n \"attribution\": {\n \"commit\": \"\",\n \"pr\": \"\"\n },\n \"allowAllClaudeAiMcps\": true\n}\n",
|
"managed-settings.json": "{\n \"attribution\": {\n \"commit\": \"\",\n \"pr\": \"\"\n },\n \"allowAllClaudeAiMcps\": true\n}\n",
|
||||||
"CLAUDE.md": "# This machine is a node of a Novox mesh\n\nWritten by the mesh's `claude-code` module. Edit the module's settings or the catalog, never this file:\nit is rewritten whenever the module renders.\n\n## Who this node is\n\n- **Node:** `workstation`\n- **Role:** not stated — set it in this module's settings for the node\n- The other nodes, their roles and what runs where: ask the controller (`mesh-controller.nodes`,\n `mesh-controller.node`). Nothing here lists them, because a copy drifts.\n\n## How a session on this mesh works\n\nThe console is the only way to the mesh: the MCP server named `mesh`. It offers five tools, and\neverything else is an address you find and call through them:\n\n- `mesh_search` — words in, matching addresses out. `mesh_describe` — one address's arguments.\n- `mesh_call` — call an address. A seat the mesh holds once is `<seat>.<verb>` (the mesh's own verbs\n are `mesh-controller.<verb>`: `status`, `plan`, `node`, `assign`, `push`, `settings`);\n a module on a machine is `<node>/<module>.<tool>`.\n- `mesh_overview` and `mesh_machine` — the mesh's seats and machines, and what one machine runs.\n\n- **Symptom first.** For an error, a failing service or anything unexpected, search the record with the\n literal text before forming a hypothesis: the records module's `records_search`, then\n `records_read`.\n- **Ask the mesh before changing it**, and change it through the controller's verbs or the catalog.\n- **A licence** through the `anthropic-licence-manager` seat's verbs. Never edit the agent's credentials\n file by hand, never print or ask for a token.\n\n## Hard rules\n\n- A file the mesh manages is changed through the verb or the catalog that owns it, never on disk. If\n unsure, `mesh-controller.plan` for the node says what the mesh writes there.\n- Never write to a store's database by hand; schema changes are numbered migrations.\n- Never push to a main branch: a branch, a pull request, and a human approval for every merge.\n- The mesh creates no symlinks, and nobody else does either.\n- A package is declared in a module, never installed by hand.\n\n## Conventions\n\n- Commit messages are concise, in the imperative, about why.\n- Test before pushing: nodes update unattended.\n- The playbooks in the record say how research, decisions, designs, issues and hand-offs are done.\n"
|
"CLAUDE.md": "# This machine is a node of a Novox mesh\n\nWritten by the mesh's `claude-code` module. Edit the module's settings or the catalog, never this file:\nit is rewritten whenever the module renders.\n\n## Who this node is\n\n- **Node:** `workstation`\n- **Role:** not stated — set it in this module's settings for the node\n- The other nodes, their roles and what runs where: ask the placement seat (`mesh-placement.placement`),\n or `mesh_overview` and `mesh_machine`. Nothing here lists them, because a copy drifts.\n\n## How a session on this mesh works\n\nThe console is the only way to the mesh: the MCP server named `mesh`. It offers five tools, and\neverything else is an address you find and call through them:\n\n- `mesh_search` — words in, matching addresses out. `mesh_describe` — one address's arguments.\n- `mesh_call` — call an address. A seat the mesh holds once is `<seat>.<verb>` (the mesh's own seats\n include `mesh-placement`, `mesh-delivery`, `mesh-config` and `mesh-conditions`);\n a module on a machine is `<node>/<module>.<tool>`.\n- `mesh_overview` and `mesh_machine` — the mesh's seats and machines, and what one machine runs.\n\n- **Symptom first.** For an error, a failing service or anything unexpected, search the record with the\n literal text before forming a hypothesis: the records module's `records_search`, then\n `records_read`.\n- **Ask the mesh before changing it**, and change it through the seats' verbs or the catalog.\n- **A licence** through the `anthropic-licence-manager` seat's verbs. Never edit the agent's credentials\n file by hand, never print or ask for a token.\n\n## Hard rules\n\n- A file the mesh manages is changed through the verb or the catalog that owns it, never on disk. If\n unsure, `<node>/node-engine.composition` for the module says what the mesh writes there.\n- Never write to a store's database by hand; schema changes are numbered migrations.\n- Never push to a main branch: a branch, a pull request, and a human approval for every merge.\n- The mesh creates no symlinks, and nobody else does either.\n- A package is declared in a module, never installed by hand.\n\n## Conventions\n\n- Commit messages are concise, in the imperative, about why.\n- Test before pushing: nodes update unattended.\n- The playbooks in the record say how research, decisions, designs, issues and hand-offs are done.\n"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@ module claude-code
|
|||||||
|
|
||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101085317-790d1c82d027
|
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791
|
||||||
|
|
||||||
require gopkg.in/yaml.v3 v3.0.1
|
require gopkg.in/yaml.v3 v3.0.1
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101085317-790d1c82d027 h1:a33dsavoaqllh40eVICbhxi6Kero1EGSjt1V+2fa2Hk=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791 h1:7SYIDPMxbPBr8dMB6k9fYsgVt4ZyWVL41q4LMFsfAvs=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101085317-790d1c82d027/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
||||||
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
||||||
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
||||||
|
|||||||
@@ -35,7 +35,10 @@
|
|||||||
"waits"
|
"waits"
|
||||||
],
|
],
|
||||||
"reads": [
|
"reads": [
|
||||||
"claude-licence-manager.bindings"
|
"claude-licence-manager.bindings",
|
||||||
|
"mesh-delivery-assigner.placement",
|
||||||
|
"mesh-delivery-indexer.modules",
|
||||||
|
"mesh-delivery-indexer.manifests"
|
||||||
],
|
],
|
||||||
"tools": [
|
"tools": [
|
||||||
"claude_code_status",
|
"claude_code_status",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ module claude-licence-manager
|
|||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791
|
||||||
github.com/jackc/pgx/v5 v5.11.0
|
github.com/jackc/pgx/v5 v5.11.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915 h1:x8k7IeMirMTxUo8BH+xUO+MDnHYgU+636jl1NIw1wvs=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791 h1:7SYIDPMxbPBr8dMB6k9fYsgVt4ZyWVL41q4LMFsfAvs=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ module clipmenu
|
|||||||
|
|
||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915
|
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/klauspost/compress v1.20.0 // indirect
|
github.com/klauspost/compress v1.20.0 // indirect
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915 h1:x8k7IeMirMTxUo8BH+xUO+MDnHYgU+636jl1NIw1wvs=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791 h1:7SYIDPMxbPBr8dMB6k9fYsgVt4ZyWVL41q4LMFsfAvs=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
||||||
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
||||||
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@ module cups
|
|||||||
|
|
||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915
|
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/klauspost/compress v1.20.0 // indirect
|
github.com/klauspost/compress v1.20.0 // indirect
|
||||||
|
|||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915 h1:x8k7IeMirMTxUo8BH+xUO+MDnHYgU+636jl1NIw1wvs=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791 h1:7SYIDPMxbPBr8dMB6k9fYsgVt4ZyWVL41q4LMFsfAvs=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
||||||
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
||||||
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ module dbus
|
|||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791
|
||||||
github.com/godbus/dbus/v5 v5.1.0
|
github.com/godbus/dbus/v5 v5.1.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915 h1:x8k7IeMirMTxUo8BH+xUO+MDnHYgU+636jl1NIw1wvs=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791 h1:7SYIDPMxbPBr8dMB6k9fYsgVt4ZyWVL41q4LMFsfAvs=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ module deskchannel
|
|||||||
|
|
||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915
|
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/klauspost/compress v1.20.0 // indirect
|
github.com/klauspost/compress v1.20.0 // indirect
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915 h1:x8k7IeMirMTxUo8BH+xUO+MDnHYgU+636jl1NIw1wvs=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791 h1:7SYIDPMxbPBr8dMB6k9fYsgVt4ZyWVL41q4LMFsfAvs=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
||||||
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
||||||
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ module diskload
|
|||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791
|
||||||
github.com/nats-io/nats.go v1.54.0
|
github.com/nats-io/nats.go v1.54.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915 h1:x8k7IeMirMTxUo8BH+xUO+MDnHYgU+636jl1NIw1wvs=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791 h1:7SYIDPMxbPBr8dMB6k9fYsgVt4ZyWVL41q4LMFsfAvs=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
||||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
||||||
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ module dmenu
|
|||||||
|
|
||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915
|
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/klauspost/compress v1.20.0 // indirect
|
github.com/klauspost/compress v1.20.0 // indirect
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915 h1:x8k7IeMirMTxUo8BH+xUO+MDnHYgU+636jl1NIw1wvs=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791 h1:7SYIDPMxbPBr8dMB6k9fYsgVt4ZyWVL41q4LMFsfAvs=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
||||||
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
||||||
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ module dnsmasq
|
|||||||
|
|
||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915
|
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/klauspost/compress v1.20.0 // indirect
|
github.com/klauspost/compress v1.20.0 // indirect
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915 h1:x8k7IeMirMTxUo8BH+xUO+MDnHYgU+636jl1NIw1wvs=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791 h1:7SYIDPMxbPBr8dMB6k9fYsgVt4ZyWVL41q4LMFsfAvs=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
||||||
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
||||||
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ module docker-compose
|
|||||||
|
|
||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915
|
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/klauspost/compress v1.20.0 // indirect
|
github.com/klauspost/compress v1.20.0 // indirect
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915 h1:x8k7IeMirMTxUo8BH+xUO+MDnHYgU+636jl1NIw1wvs=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791 h1:7SYIDPMxbPBr8dMB6k9fYsgVt4ZyWVL41q4LMFsfAvs=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
||||||
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
||||||
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ module docker
|
|||||||
|
|
||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915
|
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/klauspost/compress v1.20.0 // indirect
|
github.com/klauspost/compress v1.20.0 // indirect
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915 h1:x8k7IeMirMTxUo8BH+xUO+MDnHYgU+636jl1NIw1wvs=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791 h1:7SYIDPMxbPBr8dMB6k9fYsgVt4ZyWVL41q4LMFsfAvs=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
||||||
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
||||||
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ module dunst
|
|||||||
|
|
||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915
|
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/klauspost/compress v1.20.0 // indirect
|
github.com/klauspost/compress v1.20.0 // indirect
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915 h1:x8k7IeMirMTxUo8BH+xUO+MDnHYgU+636jl1NIw1wvs=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791 h1:7SYIDPMxbPBr8dMB6k9fYsgVt4ZyWVL41q4LMFsfAvs=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
||||||
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
||||||
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ module fail2ban
|
|||||||
|
|
||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915
|
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/klauspost/compress v1.20.0 // indirect
|
github.com/klauspost/compress v1.20.0 // indirect
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915 h1:x8k7IeMirMTxUo8BH+xUO+MDnHYgU+636jl1NIw1wvs=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791 h1:7SYIDPMxbPBr8dMB6k9fYsgVt4ZyWVL41q4LMFsfAvs=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
||||||
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
||||||
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@ module feh
|
|||||||
|
|
||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915
|
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/klauspost/compress v1.20.0 // indirect
|
github.com/klauspost/compress v1.20.0 // indirect
|
||||||
|
|||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915 h1:x8k7IeMirMTxUo8BH+xUO+MDnHYgU+636jl1NIw1wvs=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791 h1:7SYIDPMxbPBr8dMB6k9fYsgVt4ZyWVL41q4LMFsfAvs=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
||||||
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
||||||
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ module flatpak
|
|||||||
|
|
||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915
|
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/klauspost/compress v1.20.0 // indirect
|
github.com/klauspost/compress v1.20.0 // indirect
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915 h1:x8k7IeMirMTxUo8BH+xUO+MDnHYgU+636jl1NIw1wvs=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791 h1:7SYIDPMxbPBr8dMB6k9fYsgVt4ZyWVL41q4LMFsfAvs=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
||||||
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
||||||
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ module fonts
|
|||||||
|
|
||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915
|
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/klauspost/compress v1.20.0 // indirect
|
github.com/klauspost/compress v1.20.0 // indirect
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915 h1:x8k7IeMirMTxUo8BH+xUO+MDnHYgU+636jl1NIw1wvs=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791 h1:7SYIDPMxbPBr8dMB6k9fYsgVt4ZyWVL41q4LMFsfAvs=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
||||||
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
||||||
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ module forticlient
|
|||||||
|
|
||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915
|
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/klauspost/compress v1.20.0 // indirect
|
github.com/klauspost/compress v1.20.0 // indirect
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915 h1:x8k7IeMirMTxUo8BH+xUO+MDnHYgU+636jl1NIw1wvs=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791 h1:7SYIDPMxbPBr8dMB6k9fYsgVt4ZyWVL41q4LMFsfAvs=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
||||||
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
||||||
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ module gitea
|
|||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101085317-790d1c82d027
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791
|
||||||
github.com/nats-io/nats-server/v2 v2.11.17
|
github.com/nats-io/nats-server/v2 v2.11.17
|
||||||
github.com/nats-io/nats.go v1.54.0
|
github.com/nats-io/nats.go v1.54.0
|
||||||
golang.org/x/crypto v0.57.0
|
golang.org/x/crypto v0.57.0
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101085317-790d1c82d027 h1:a33dsavoaqllh40eVICbhxi6Kero1EGSjt1V+2fa2Hk=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791 h1:7SYIDPMxbPBr8dMB6k9fYsgVt4ZyWVL41q4LMFsfAvs=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101085317-790d1c82d027/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
||||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
||||||
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ module gnomekeyring
|
|||||||
|
|
||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915
|
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/klauspost/compress v1.20.0 // indirect
|
github.com/klauspost/compress v1.20.0 // indirect
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915 h1:x8k7IeMirMTxUo8BH+xUO+MDnHYgU+636jl1NIw1wvs=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791 h1:7SYIDPMxbPBr8dMB6k9fYsgVt4ZyWVL41q4LMFsfAvs=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
||||||
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
||||||
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ module hostname
|
|||||||
|
|
||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915
|
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/klauspost/compress v1.20.0 // indirect
|
github.com/klauspost/compress v1.20.0 // indirect
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915 h1:x8k7IeMirMTxUo8BH+xUO+MDnHYgU+636jl1NIw1wvs=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791 h1:7SYIDPMxbPBr8dMB6k9fYsgVt4ZyWVL41q4LMFsfAvs=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
||||||
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
||||||
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@ module i3
|
|||||||
|
|
||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915
|
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/klauspost/compress v1.20.0 // indirect
|
github.com/klauspost/compress v1.20.0 // indirect
|
||||||
|
|||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915 h1:x8k7IeMirMTxUo8BH+xUO+MDnHYgU+636jl1NIw1wvs=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791 h1:7SYIDPMxbPBr8dMB6k9fYsgVt4ZyWVL41q4LMFsfAvs=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
||||||
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
||||||
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ module i3statusrust
|
|||||||
|
|
||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915
|
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/klauspost/compress v1.20.0 // indirect
|
github.com/klauspost/compress v1.20.0 // indirect
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915 h1:x8k7IeMirMTxUo8BH+xUO+MDnHYgU+636jl1NIw1wvs=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791 h1:7SYIDPMxbPBr8dMB6k9fYsgVt4ZyWVL41q4LMFsfAvs=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
||||||
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
||||||
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ module kernel
|
|||||||
|
|
||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915
|
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/klauspost/compress v1.20.0 // indirect
|
github.com/klauspost/compress v1.20.0 // indirect
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915 h1:x8k7IeMirMTxUo8BH+xUO+MDnHYgU+636jl1NIw1wvs=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791 h1:7SYIDPMxbPBr8dMB6k9fYsgVt4ZyWVL41q4LMFsfAvs=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
||||||
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
||||||
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ module keycloak
|
|||||||
|
|
||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915
|
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/klauspost/compress v1.20.0 // indirect
|
github.com/klauspost/compress v1.20.0 // indirect
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915 h1:x8k7IeMirMTxUo8BH+xUO+MDnHYgU+636jl1NIw1wvs=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791 h1:7SYIDPMxbPBr8dMB6k9fYsgVt4ZyWVL41q4LMFsfAvs=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
||||||
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
||||||
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ module lemurs
|
|||||||
|
|
||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915
|
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/klauspost/compress v1.20.0 // indirect
|
github.com/klauspost/compress v1.20.0 // indirect
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915 h1:x8k7IeMirMTxUo8BH+xUO+MDnHYgU+636jl1NIw1wvs=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791 h1:7SYIDPMxbPBr8dMB6k9fYsgVt4ZyWVL41q4LMFsfAvs=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102112556-abf5eace7791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
||||||
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
||||||
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user