From bcb3478462cae5389686c72afb0786dd2592c260 Mon Sep 17 00:00:00 2001 From: mesh-tools Date: Thu, 15 Oct 2026 21:58:25 +0000 Subject: [PATCH] Pin the feature's SDK and node-engine so the release controller builds mesh-host's new code (issue 804) mesh-host's declaration code now imports mesh-sdk's seal package (issue 755); the release branch's controller vendors mesh-host, so it needs both pins before mesh-host's own pull request can be judged by it. --- go.mod | 4 +- go.sum | 14 +- .../novox/mesh-sdk/go/seal/seal.go | 203 ++++++++++++++++++ .../internal/declaration/declaration.go | 172 ++++++++++++++- vendor/modules.txt | 7 +- 5 files changed, 382 insertions(+), 18 deletions(-) create mode 100644 vendor/git.novox.be/novox/mesh-sdk/go/seal/seal.go diff --git a/go.mod b/go.mod index 499b4afe..aa638d77 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/novox/mesh-controller go 1.26.0 require ( - git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013182924-5933eb757d44 + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261015210729-d3e182503c19 github.com/jackc/pgx/v5 v5.10.0 github.com/nats-io/nats-server/v2 v2.11.17 github.com/nats-io/nats.go v1.54.0 @@ -36,4 +36,4 @@ require ( // committed. Every build (the build agent's `go build`, the Dockerfile) compiles from vendor/ and // fetches nothing; go refuses to build when vendor/ and this file disagree, so a pin moved without // `go mod vendor` fails loudly, at once, everywhere. -replace github.com/novox/mesh-host => git.novox.be/novox/mesh-host v0.0.0-20261013220009-b9a293f8c8bc +replace github.com/novox/mesh-host => git.novox.be/novox/mesh-host v0.0.0-20261015211328-5a7ec3775b98 diff --git a/go.sum b/go.sum index e871778f..61f5077c 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,7 @@ -git.novox.be/novox/mesh-host v0.0.0-20261013220009-b9a293f8c8bc h1:h5v2AbdjM/clMY1YxS9p7VuyYtIjkcw85w9Oz1cOYQc= -git.novox.be/novox/mesh-host v0.0.0-20261013220009-b9a293f8c8bc/go.mod h1:tcTK4LMs1d6JpZUwy3hSHMFG/MIuDr/XFs7/nbeaW/c= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261010191001-33917f91ac3c h1:l5onJwoIeH8yE/PjVlEeoPyXBsHp2NQiWLllz2fwX7s= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261010191001-33917f91ac3c/go.mod h1:GFuZUElBZ9A++mxgIKo97aXXo+kV0uJ/UkbhQPPIbrY= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013141520-9588871d6629 h1:7TYFV1Q6Mcz+lsLP/sds87tysET4rKCn3rRIQV8gNmA= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013141520-9588871d6629/go.mod h1:GFuZUElBZ9A++mxgIKo97aXXo+kV0uJ/UkbhQPPIbrY= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013145622-f4745447ba6d h1:HEJyqorKktrkJPeeJdwEo0kdrJjH3fm/YVS9MGftCqY= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013145622-f4745447ba6d/go.mod h1:GFuZUElBZ9A++mxgIKo97aXXo+kV0uJ/UkbhQPPIbrY= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013182924-5933eb757d44 h1:r5xIo5qWOXQdCSMxvqJy8kV4U+P/QWXFK6971Ha6wU8= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013182924-5933eb757d44/go.mod h1:GFuZUElBZ9A++mxgIKo97aXXo+kV0uJ/UkbhQPPIbrY= +git.novox.be/novox/mesh-host v0.0.0-20261015211328-5a7ec3775b98 h1:27nPn3XO9yfrHoAOuTuuJPVZTA3Bdnbqlc2wRI7S/LY= +git.novox.be/novox/mesh-host v0.0.0-20261015211328-5a7ec3775b98/go.mod h1:5QOm3cIfu/k9fN6QqgDbKGCcDst7Ul1UlcfsiMVm9j4= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261015210729-d3e182503c19 h1:mAMt3KFmrHzDiBv4MLL94opWcG9y73ISMrBUyisapQE= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261015210729-d3e182503c19/go.mod h1:GFuZUElBZ9A++mxgIKo97aXXo+kV0uJ/UkbhQPPIbrY= 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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/vendor/git.novox.be/novox/mesh-sdk/go/seal/seal.go b/vendor/git.novox.be/novox/mesh-sdk/go/seal/seal.go new file mode 100644 index 00000000..0aeb8caa --- /dev/null +++ b/vendor/git.novox.be/novox/mesh-sdk/go/seal/seal.go @@ -0,0 +1,203 @@ +// Package seal is the mesh's sealed box (novox/hq ADR 0183, ADR 0206): a value sealed to one recipient's +// public key, which only that recipient's private key opens. X25519 for the agreement, HKDF-SHA256 for the +// key, AES-256-GCM for the box — the box the licence manager made and opened before it lived here, byte +// for byte, and the one the SDK's TypeScript `seal` makes and opens. +// +// A box is `{ v: 1, eph, iv, tag, ct }`, every field standard base64; `eph` is the one-time public key as +// SPKI DER. The key is derived with the salt eph-DER ‖ the recipient's raw public key and the info +// "novox-mesh sealed box v1", so a box cannot be re-addressed to another key. +// +// Keys travel as PEM: the public key as SPKI ("PUBLIC KEY"), the private key as PKCS #8 ("PRIVATE KEY"). +// Standard library only. +package seal + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/ecdh" + "crypto/hmac" + "crypto/rand" + "crypto/sha256" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "errors" + "fmt" +) + +// Box is a value sealed to one recipient, as it travels in JSON. +type Box struct { + V int `json:"v"` + Eph string `json:"eph"` + IV string `json:"iv"` + Tag string `json:"tag"` + Ct string `json:"ct"` +} + +// KeyPair is a recipient's keypair as the two PEM strings it is kept and sent as. +type KeyPair struct { + PublicKey string `json:"publicKey"` + PrivateKey string `json:"privateKey"` +} + +// Version is the only box version Open opens. +const Version = 1 + +const info = "novox-mesh sealed box v1" + +// GenerateKeyPair makes a fresh X25519 keypair, PEM-encoded. +func GenerateKeyPair() (KeyPair, error) { + priv, err := ecdh.X25519().GenerateKey(rand.Reader) + if err != nil { + return KeyPair{}, err + } + pubDER, err := x509.MarshalPKIXPublicKey(priv.PublicKey()) + if err != nil { + return KeyPair{}, err + } + privDER, err := x509.MarshalPKCS8PrivateKey(priv) + if err != nil { + return KeyPair{}, err + } + return KeyPair{ + PublicKey: string(pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: pubDER})), + PrivateKey: string(pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privDER})), + }, nil +} + +// Seal seals plaintext to the recipient's PEM public key, with a fresh one-time key and iv. +func Seal(plaintext, recipientPEM string) (Box, error) { + eph, err := ecdh.X25519().GenerateKey(rand.Reader) + if err != nil { + return Box{}, err + } + iv := make([]byte, 12) + if _, err := rand.Read(iv); err != nil { + return Box{}, err + } + return sealWith(plaintext, recipientPEM, eph, iv) +} + +// sealWith is Seal with its two sources of randomness given, for the byte-for-byte tests. +func sealWith(plaintext, recipientPEM string, eph *ecdh.PrivateKey, iv []byte) (Box, error) { + recipient, err := publicFromPEM(recipientPEM) + if err != nil { + return Box{}, err + } + secret, err := eph.ECDH(recipient) + if err != nil { + return Box{}, err + } + ephDER, err := x509.MarshalPKIXPublicKey(eph.PublicKey()) + if err != nil { + return Box{}, err + } + gcm, err := newGCM(boxKey(secret, ephDER, recipient.Bytes())) + if err != nil { + return Box{}, err + } + out := gcm.Seal(nil, iv, []byte(plaintext), nil) + ct, tag := out[:len(out)-gcm.Overhead()], out[len(out)-gcm.Overhead():] + b64 := base64.StdEncoding.EncodeToString + return Box{V: Version, Eph: b64(ephDER), IV: b64(iv), Tag: b64(tag), Ct: b64(ct)}, nil +} + +// Open opens a box with the recipient's PEM private key. It fails for a box sealed to another key, one +// changed in any field, or one of another version. +func Open(box Box, privatePEM string) (string, error) { + if box.V != Version { + return "", fmt.Errorf("a sealed box of version %d, not %d", box.V, Version) + } + priv, err := privateFromPEM(privatePEM) + if err != nil { + return "", err + } + d := base64.StdEncoding.DecodeString + ephDER, err := d(box.Eph) + if err != nil { + return "", fmt.Errorf("the box's eph: %w", err) + } + ephKey, err := x509.ParsePKIXPublicKey(ephDER) + if err != nil { + return "", fmt.Errorf("the box's eph: %w", err) + } + eph, ok := ephKey.(*ecdh.PublicKey) + if !ok || eph.Curve() != ecdh.X25519() { + return "", errors.New("the box's eph is not an X25519 key") + } + secret, err := priv.ECDH(eph) + if err != nil { + return "", err + } + iv, err1 := d(box.IV) + tag, err2 := d(box.Tag) + ct, err3 := d(box.Ct) + if err := errors.Join(err1, err2, err3); err != nil { + return "", fmt.Errorf("the box's fields: %w", err) + } + gcm, err := newGCM(boxKey(secret, ephDER, priv.PublicKey().Bytes())) + if err != nil { + return "", err + } + if len(iv) != gcm.NonceSize() || len(tag) != gcm.Overhead() { + return "", errors.New("the box does not open with this key") + } + plain, err := gcm.Open(nil, iv, append(append([]byte{}, ct...), tag...), nil) + if err != nil { + return "", errors.New("the box does not open with this key") + } + return string(plain), nil +} + +func publicFromPEM(p string) (*ecdh.PublicKey, error) { + block, _ := pem.Decode([]byte(p)) + if block == nil { + return nil, errors.New("not a PEM public key") + } + k, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return nil, err + } + pub, ok := k.(*ecdh.PublicKey) + if !ok || pub.Curve() != ecdh.X25519() { + return nil, errors.New("not an X25519 public key") + } + return pub, nil +} + +func privateFromPEM(p string) (*ecdh.PrivateKey, error) { + block, _ := pem.Decode([]byte(p)) + if block == nil { + return nil, errors.New("not a PEM private key") + } + k, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + return nil, err + } + priv, ok := k.(*ecdh.PrivateKey) + if !ok || priv.Curve() != ecdh.X25519() { + return nil, errors.New("not an X25519 private key") + } + return priv, nil +} + +// boxKey is HKDF-SHA256 (RFC 5869) for one 32-byte block: written out because crypto/hkdf needs Go 1.24 +// and this module promises 1.22; the vectors prove it equals the licence manager's hkdf.Key. +func boxKey(secret, ephDER, recipientRaw []byte) []byte { + salt := append(append([]byte{}, ephDER...), recipientRaw...) + extract := hmac.New(sha256.New, salt) + extract.Write(secret) + prk := extract.Sum(nil) + expand := hmac.New(sha256.New, prk) + expand.Write([]byte(info)) + expand.Write([]byte{1}) + return expand.Sum(nil) +} + +func newGCM(key []byte) (cipher.AEAD, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + return cipher.NewGCM(block) +} diff --git a/vendor/github.com/novox/mesh-host/internal/declaration/declaration.go b/vendor/github.com/novox/mesh-host/internal/declaration/declaration.go index a265afa1..c66e9e7e 100644 --- a/vendor/github.com/novox/mesh-host/internal/declaration/declaration.go +++ b/vendor/github.com/novox/mesh-host/internal/declaration/declaration.go @@ -19,6 +19,8 @@ import ( "sort" "strconv" "strings" + + "git.novox.be/novox/mesh-sdk/go/seal" ) // Version is the vocabulary this host speaks. A declaration naming any other version is @@ -205,6 +207,12 @@ type File struct { // by looking rather than by knowing which field won. Sealed string `json:"sealed,omitempty"` + // VaultSealed is content the vault sealed to this node's sealing key, in the mesh's sealed box (novox/hq ADR + // 0206): a core module's own credential, which the controller carries in the declaration and cannot open + // (ADR 0349 §16, issue 755). The same promise as Sealed, from the vault rather than the controller; sent only to + // an engine that states VaultBoxContract. Exclusive with Content, Sealed and Bytes. + VaultSealed *seal.Box `json:"vault-sealed,omitempty"` + // Trusted is the catalogue's word on whether the settings this file's content took are trusted (novox/hq // issue 339). It is the controller's to act on, and a controller takes it out before it sends a declaration. // Accepted here, and nothing is done with it, so that a controller that passes it on — an older one, or one @@ -237,11 +245,48 @@ type File struct { // Owner is the user this belongs to, by name. Absent means root. Owner string `json:"owner,omitempty"` + + // OwnerAtRuntime says who owns the file once it is placed (novox/hq ADR 0349 §5): "owned", the mesh's + // whole, written back whenever it differs; "seed", written once and then the program's; or "managed", + // only its named keys or its marked block the mesh's, the program keeping the rest. + // + // **Three words for what the forms above already do.** Owned is an ordinary file, seed is create-once + // and managed is a file written into, so the word changes no write: a file that says it is applied + // exactly as its old form is, and one that does not say is owned by its old form (Ownership). The word + // is what the provisioner's files say of themselves, which the old forms only implied. Seed is enough + // alone; managed still needs `into`, which says the format. A word that contradicts the form is refused. + OwnerAtRuntime string `json:"owner-at-runtime,omitempty"` } +// The words a file says who owns it at runtime with (novox/hq ADR 0349 §5). +const ( + RuntimeOwned = "owned" + RuntimeSeed = "seed" + RuntimeManaged = "managed" +) + +// Ownership is who owns the file at runtime: the word it says, or the one its old form implies — a file +// written into is managed, one created once is a seed, and any other is owned. +func (f *File) Ownership() string { + switch { + case f.OwnerAtRuntime != "": + return f.OwnerAtRuntime + case f.Into != "": + return RuntimeManaged + case f.CreateOnce: + return RuntimeSeed + default: + return RuntimeOwned + } +} + +// Seeded reports whether the file is written only when absent: created once, by create-once or by the +// word seed. +func (f *File) Seeded() bool { return f.CreateOnce || f.OwnerAtRuntime == RuntimeSeed } + // Secret reports whether this file arrived sealed, which is what decides both that it must be // opened before writing and that its contents must never appear in a report. -func (f *File) Secret() bool { return f.Sealed != "" } +func (f *File) Secret() bool { return f.Sealed != "" || f.VaultSealed != nil } func (f *File) Identity() string { return f.ID } func (f *File) Kind() Type { return TypeFile } @@ -314,12 +359,13 @@ func (f *File) validate(where string, _ bool) []string { where, f.At)) } if f.Into == IntoJSON || f.Into == IntoBlock { - if f.Sealed != "" || f.Bytes != "" || len(f.Secrets) > 0 || f.CreateOnce { + if f.Secret() || f.Bytes != "" || len(f.Secrets) > 0 || f.CreateOnce { problems = append(problems, where+ ": a file written into says only its part, in content — not sealed, bytes, "+ "secrets or create-once") } } + problems = append(problems, f.checkOwnership(where)...) var said []string for name, value := range map[string]string{ "content": f.Content, "sealed": f.Sealed, "bytes": f.Bytes, @@ -328,6 +374,16 @@ func (f *File) validate(where string, _ bool) []string { said = append(said, name) } } + if f.VaultSealed != nil { + said = append(said, "vault-sealed") + if f.VaultSealed.V != seal.Version { + problems = append(problems, fmt.Sprintf("%s: vault-sealed is a sealed box of version %d, and this "+ + "node-engine opens version %d", where, f.VaultSealed.V, seal.Version)) + } + if len(f.Secrets) > 0 { + problems = append(problems, where+": a file the vault sealed whole has no holes for secrets") + } + } if len(said) > 1 { sort.Strings(said) problems = append(problems, where+ @@ -361,6 +417,42 @@ func (f *File) validate(where string, _ bool) []string { return append(problems, checkMode(where, f.Mode)...) } +// checkOwnership refuses a word that contradicts the file's form (novox/hq ADR 0349 §5): owned is a whole +// file written back, so not created once nor written into; a seed is a whole file created once, so not +// written into; managed is a file written into, which says its format. Managed and created once together +// is already refused by the rule that a file written into is not created once. +func (f *File) checkOwnership(where string) []string { + switch f.OwnerAtRuntime { + case "": + case RuntimeOwned: + var also []string + if f.CreateOnce { + also = append(also, "create-once") + } + if f.Into != "" { + also = append(also, "into") + } + if len(also) > 0 { + return []string{fmt.Sprintf("%s: owned at runtime is the mesh's whole file, written back whenever it "+ + "differs, and this also says %s", where, strings.Join(also, " and "))} + } + case RuntimeSeed: + if f.Into != "" { + return []string{where + ": a seed is a whole file written once, and this says into; a file the " + + "program shares with the mesh is managed"} + } + case RuntimeManaged: + if f.Into == "" { + return []string{where + ": a managed file names the format its keys or block are written into, " + + "with into \"json\" or \"block\""} + } + default: + return []string{fmt.Sprintf("%s: owner-at-runtime %q; a placed file is %q, %q or %q, or omits it to be "+ + "owned by its form", where, f.OwnerAtRuntime, RuntimeOwned, RuntimeSeed, RuntimeManaged)} + } + return nil +} + // User is a login on the machine. // // The thing that makes a shell, a chat client or a desktop expressible at all: each is a package @@ -1565,6 +1657,71 @@ type Declaration struct { // this is its home, whether the operator's warrant to take it was carried out, the sub-paths its modules ask for, // and the tool runner's account to put in its group (§11). Absent is a controller older than ADR 0328. Libraries []Library + + // Filled is the paths of the files the provisioner fills on this machine (novox/hq ADR 0349 §4, §5): each a + // file this declaration declares, which says who owns it at runtime. The provisioner's client has the + // node-engine write only these. Absent is a controller that hands no file to the provisioner, and every + // file is written from the declaration as before. + Filled []string + + // Provisioner is the account the provisioner's client runs as on this machine, by name (novox/hq ADR 0349 §4): + // the only account whose write of a filled file the node-engine's local write socket accepts. Absent is a + // controller that names none, and every such write is refused. + Provisioner string +} + +// checkProvisioner refuses a provisioner client's account that is no account name, or is root: the account whose +// writes the node-engine accepts must be one that could not write the file without it. +func checkProvisioner(name string) []string { + switch { + case name == "": + return nil + case !accountName.MatchString(name): + return []string{fmt.Sprintf("provisioner_account %q is not an account name", name)} + case name == "root": + return []string{"provisioner_account is root; the provisioner's client runs as an account of its own"} + } + return nil +} + +// Fills reports whether the provisioner fills the file at path on this machine. +func (d *Declaration) Fills(path string) bool { return slices.Contains(d.Filled, path) } + +// checkFilled refuses a filled path that is not a declared file saying who owns it at runtime, or that is named +// twice or empty. Every file declared at the path must say it: the provisioner writes the path, and a file there +// the mesh writes some other way would be a second writer of it (ADR 0349 §5). +func checkFilled(filled []string, resources []Resource) []string { + var problems []string + files := map[string][]*File{} + for _, r := range resources { + if f, ok := r.(*File); ok { + files[f.Path] = append(files[f.Path], f) + } + } + seen := map[string]bool{} + for _, path := range filled { + switch { + case path == "": + problems = append(problems, "filled_files names a file with no path") + continue + case seen[path]: + problems = append(problems, fmt.Sprintf("filled_files names %q twice", path)) + continue + } + seen[path] = true + if len(files[path]) == 0 { + problems = append(problems, fmt.Sprintf("filled_files names %q, and this declaration declares no "+ + "file there", path)) + continue + } + for _, f := range files[path] { + if f.OwnerAtRuntime == "" { + problems = append(problems, fmt.Sprintf("resource %q: the provisioner fills %q, and a file it "+ + "fills says who owns it at runtime with owner-at-runtime", f.ID, path)) + } + } + } + return problems } // Identity is one module's identity, as the controller allocated it (ADR 0328 §4): its user `mesh-` with @@ -1904,6 +2061,12 @@ type envelope struct { // sends them only to a host whose health says link.LibraryContract. Groups []Group `json:"groups,omitempty"` Libraries []Library `json:"libraries,omitempty"` + // Filled is optional too (novox/hq ADR 0349 §5), as is a file's `owner-at-runtime`. **An older host refuses + // these keys**; a controller sends them only to a host whose health says link.OwnershipContract. + Filled []string `json:"filled_files,omitempty"` + // Provisioner is optional too (novox/hq ADR 0349 §4). **An older host refuses this key**; a controller sends it + // only to a host whose health says link.ProvisionerContract. + Provisioner string `json:"provisioner_account,omitempty"` } func parse(raw []byte, allowActions bool) (*Declaration, error) { @@ -1922,8 +2085,10 @@ func parse(raw []byte, allowActions bool) (*Declaration, error) { d := &Declaration{Version: env.Version, For: env.For, Adoption: env.Adoption, Sequence: env.Sequence, Epoch: env.Epoch, LeftOut: env.LeftOut, Generation: env.Generation, PutBack: env.PutBack, - Identities: env.Identities, Groups: env.Groups, Libraries: env.Libraries} + Identities: env.Identities, Groups: env.Groups, Libraries: env.Libraries, Filled: env.Filled, + Provisioner: env.Provisioner} problems := checkIdentities(env.Identities, env.Groups) + problems = append(problems, checkProvisioner(env.Provisioner)...) problems = append(problems, checkLibraries(env.Groups, env.Libraries)...) if env.Generation < 0 { // Below zero would read as "none claimed" and pass every refusal of an older generation. @@ -2015,6 +2180,7 @@ func parse(raw []byte, allowActions bool) (*Declaration, error) { problems = append(problems, checkWhileStopped(d.Resources)...) problems = append(problems, checkAdoption(env.Adoption, d.Resources, allowActions)...) problems = append(problems, checkReplaces(d.Resources)...) + problems = append(problems, checkFilled(env.Filled, d.Resources)...) if env.Adoption == nil { for _, r := range d.Resources { if r.Kind() == TypeOpening { diff --git a/vendor/modules.txt b/vendor/modules.txt index d31a622d..4990747b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,6 +1,7 @@ -# git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013182924-5933eb757d44 +# git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261015210729-d3e182503c19 ## explicit; go 1.22 git.novox.be/novox/mesh-sdk/go/asks +git.novox.be/novox/mesh-sdk/go/seal # github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op ## explicit; go 1.24.0 github.com/antithesishq/antithesis-sdk-go/assert @@ -78,7 +79,7 @@ github.com/nats-io/nkeys # github.com/nats-io/nuid v1.0.1 ## explicit github.com/nats-io/nuid -# github.com/novox/mesh-host v0.0.0 => git.novox.be/novox/mesh-host v0.0.0-20261013220009-b9a293f8c8bc +# github.com/novox/mesh-host v0.0.0 => git.novox.be/novox/mesh-host v0.0.0-20261015211328-5a7ec3775b98 ## explicit; go 1.26.0 github.com/novox/mesh-host/internal/declaration github.com/novox/mesh-host/rootsearch @@ -138,4 +139,4 @@ golang.org/x/text/width # golang.org/x/time v0.15.0 ## explicit; go 1.25.0 golang.org/x/time/rate -# github.com/novox/mesh-host => git.novox.be/novox/mesh-host v0.0.0-20261013220009-b9a293f8c8bc +# github.com/novox/mesh-host => git.novox.be/novox/mesh-host v0.0.0-20261015211328-5a7ec3775b98