Review round 1 for the checks, ssh-client and the Go build module: sshdir (1484), socket path (1486), over_bound (1290, 1486), toolchain.reported and streams (1483), re-pin to mesh-sdk 36d6f774; delta part 2 of 4

This commit is contained in:
2026-10-20 21:09:36 +00:00
parent bb5d420538
commit 7e7cc27c30
16 changed files with 258 additions and 33 deletions
+53 -7
View File
@@ -61,7 +61,7 @@ func Check(root string) ([]Finding, error) {
if module == SSHModule {
continue
}
walk(m, "", func(key, value string) {
walk(m, "", dirsOf(m), func(key, value string) {
out = append(out, Finding{Module: module, Key: key, Value: value})
})
}
@@ -69,7 +69,7 @@ func Check(root string) ([]Finding, error) {
}
// walk calls found for every string under v, outside Prose, that names a path under ~/.ssh.
func walk(v any, key string, found func(key, value string)) {
func walk(v any, key string, dirs map[string]string, found func(key, value string)) {
switch x := v.(type) {
case map[string]any:
keys := make([]string, 0, len(x))
@@ -79,22 +79,68 @@ func walk(v any, key string, found func(key, value string)) {
sort.Strings(keys)
for _, k := range keys {
if !Prose[k] {
walk(x[k], k, found)
walk(x[k], k, dirs, found)
}
}
case []any:
for _, e := range x {
walk(e, key, found)
walk(e, key, dirs, found)
}
case string:
// A path under a directory of the module's own (${dir:<id>}, as a runs-as module's home) is that account's
// own .ssh, not the operator's ~/.ssh that ssh-client sweeps (issue 1484, ADR 0466 §4).
if underSSH.MatchString(x) && !strings.HasPrefix(x, "${dir:") {
// A path under a directory the mesh places for the module (${dir:<id>} of a directory with a place and no path,
// as a runs-as module's home) is that account's own .ssh, not the operator's ~/.ssh that ssh-client sweeps
// (issue 1484, ADR 0466 §4). Any other ${dir:<id>} is resolved to the path its resource declares and judged.
if resolved, placed := resolveDir(x, dirs); !placed && underSSH.MatchString(resolved) {
found(key, x)
}
}
}
var ofDir = regexp.MustCompile(`^\$\{dir:([a-z0-9][a-z0-9_-]*)\}`)
// placedDir marks a directory the mesh places for the module: it has no path of its own.
const placedDir = "\x00placed"
// dirsOf are the module's directories by id: the path each declares, or placedDir for one with a place and no path.
func dirsOf(m any) map[string]string {
dirs := map[string]string{}
obj, _ := m.(map[string]any)
list, _ := obj["resources"].([]any)
for _, r := range list {
res, _ := r.(map[string]any)
id, _ := res["id"].(string)
path, _ := res["path"].(string)
switch {
case id == "":
case path != "":
dirs[id] = path
case res["type"] == "directory":
dirs[id] = placedDir
}
}
return dirs
}
// resolveDir follows a leading ${dir:<id>} through the module's directories. It answers the path with the references
// resolved, and whether it ends under a placed directory.
func resolveDir(x string, dirs map[string]string) (string, bool) {
for i := 0; i < 8; i++ {
m := ofDir.FindStringSubmatch(x)
if m == nil {
return x, false
}
to, ok := dirs[m[1]]
if !ok {
return x, false
}
if to == placedDir {
return x, true
}
x = to + strings.TrimPrefix(x, m[0])
}
return x, false
}
func main() {
root := "."
if len(os.Args) > 1 {
+16 -1
View File
@@ -41,7 +41,7 @@ func TestAnotherModuleNamingSSHIsRefused(t *testing.T) {
// that is not the operator's ~/.ssh, which only ssh-client names. A path outside any ${dir:} is still refused.
func TestAModulesOwnDirectoryMayHoldADotSSH(t *testing.T) {
root := t.TempDir()
manifest(t, root, "builder", `{"module":"builder","resources":[{"path":"${dir:state}/.ssh"},{"path":"${dir:ssh-dir}/config"}]}`)
manifest(t, root, "builder", `{"module":"builder","resources":[{"id":"state","type":"directory","place":"."},{"id":"ssh-dir","type":"directory","path":"${dir:state}/.ssh"},{"path":"${dir:ssh-dir}/config"}]}`)
manifest(t, root, "other", `{"module":"other","resources":[{"path":"${machine:account-home}/.ssh/config"}]}`)
found, err := Check(root)
if err != nil {
@@ -52,6 +52,21 @@ func TestAModulesOwnDirectoryMayHoldADotSSH(t *testing.T) {
}
}
// Issue 1484 review: only a directory the mesh places is the module's own home. A ${dir:} that resolves to the
// operator's home is judged by the path it resolves to.
func TestADirResolvingToTheOperatorsHomeIsStillRefused(t *testing.T) {
root := t.TempDir()
manifest(t, root, "escape", `{"module":"escape","resources":[{"id":"h","type":"directory","path":"${machine:account-home}","unknown":"module"},{"path":"${dir:h}/.ssh/config"}]}`)
manifest(t, root, "placed", `{"module":"placed","resources":[{"id":"state","type":"directory","place":".","unknown":"program"},{"id":"s","type":"directory","path":"${dir:state}/.ssh"},{"path":"${dir:s}/config"}]}`)
found, err := Check(root)
if err != nil {
t.Fatal(err)
}
if len(found) != 1 || found[0].Module != "escape" {
t.Fatalf("refused %+v; want only escape", found)
}
}
func TestTheCatalogueHasNone(t *testing.T) {
found, err := Check("../..")
if err != nil {
+2 -2
View File
@@ -42,7 +42,7 @@ time. The unit level is `go test -json` over every package without the `integrat
with a `go.mod`, judged by its one bound: its running time, that of the slowest package (compile not counted), within
five seconds. The step ends passed when the level ran, and its result says whether the level did: `passed`, the
packages with their times, the tests that failed with their first lines of output, the five slowest tests, and `why`
when it failed (a failed test, a build that failed, a bound kept or not). A step ends failed only when go test could not
when it failed (a failed test, a build that failed) or ran over a bound. `passed` is the tests' own verdict: a level over its bound still passes and says `over_bound` (and `over_which`, setup or run) with the slowest tests, and the checker judges the bounds (ADR 0453 §9). A step ends failed only when go test could not
run. A repository with no tests at the level has none, and the result says so: that is a pass. `testdata/captured` holds
the output of the real go for the fixture repositories in `testdata/repos`; the unit tests read it, and the
integration-tagged test runs the real go on the fixtures.
@@ -59,7 +59,7 @@ loopback port of its own, its address on the first line, stopped when its input
Both run inside the step, are stopped, and the run's directory is removed whatever the tests did.
Setup (compiling the tests, the store, the bus) is judged within 30 s and the tests' running time (the wall time of
`go test`) within 60 s, apart; a level over either fails and names the bound, with the slowest tests for the running
`go test`) within 60 s, apart; a level over either says `over_bound` and names the bound, with the slowest tests for the running
time. A machine whose installed postgres major is not the recipe's refuses this level, naming both; the unit level
still runs. The result names the postgres major and the nats-server version it ran against. The mesh's own store major
is not named: no state or grant gives it to this module (ADR 0470 §4 asks for it when it differs), so only the recipe's
@@ -0,0 +1,55 @@
package main
import (
"context"
"encoding/json"
"os"
"path/filepath"
"slices"
"testing"
)
// Issues 1483 and 1488 (for the indexer, issue 1444): the guard emits toolchain.reported after each write of the
// toolchains key, with the node and the language.
func TestTheGuardEmitsToolchainReportedAfterEachWrite(t *testing.T) {
m := moduleWith(newStates(), "go1.27.1")
type event struct {
typ string
body map[string]string
}
var got []event
m.Emit = func(typ string, body any) error {
raw, _ := json.Marshal(body)
var b map[string]string
json.Unmarshal(raw, &b)
got = append(got, event{typ, b})
return nil
}
if err := m.Check(context.Background()); err != nil {
t.Fatal(err)
}
if len(got) != 1 || got[0].typ != "toolchain.reported" || got[0].body["node"] != "ace" || got[0].body["language"] != "go" || len(got[0].body) != 2 {
t.Errorf("events = %+v, want one toolchain.reported for ace, go", got)
}
m.Check(context.Background())
if len(got) != 2 {
t.Errorf("a second guard pass emitted %d events in all, want 2", len(got))
}
}
// The event is carried by the module's stream, not named in emits (ADR 0363: one place); the validator checks that.
func TestTheManifestDeclaresTheToolchainsStream(t *testing.T) {
raw, err := os.ReadFile(filepath.Join("..", "..", "module.json"))
if err != nil {
t.Fatal(err)
}
var m struct {
Streams []struct {
Name string `json:"name"`
Events []string `json:"events"`
} `json:"streams"`
}
if err := json.Unmarshal(raw, &m); err != nil || len(m.Streams) != 1 || m.Streams[0].Name != "toolchains-go" || !slices.Equal(m.Streams[0].Events, []string{"toolchain.**"}) {
t.Errorf("streams = %+v (%v)", m.Streams, err)
}
}
@@ -155,7 +155,12 @@ func TestTheStoreListensOnASocketUnderTheRunAndNowhereElse(t *testing.T) {
if !strings.HasPrefix(run, filepath.Join(m.Work, "ws1", "run-")) {
t.Errorf("the run directory is %s, not under the workspace's", run)
}
for _, want := range []string{"listen_addresses=''", "-k " + filepath.Join(run, "pg", "socket"), "fsync=off", "-w"} {
_, afterK, _ := strings.Cut(start, "-k ")
socket, _, _ := strings.Cut(afterK, " ")
if len(socket)+len(socketName) >= 100 {
t.Errorf("the socket %q is too long for postgres", socket)
}
for _, want := range []string{"listen_addresses=''", "-k " + socket, "fsync=off", "-w"} {
if !strings.Contains(start, want) {
t.Errorf("the store's start %q lacks %q", start, want)
}
@@ -181,7 +186,7 @@ func TestTheStoreListensOnASocketUnderTheRunAndNowhereElse(t *testing.T) {
env[k] = v
}
u, err := url.Parse(env["MESH_TEST_POSTGRES"])
if err != nil || u.Scheme != "postgres" || u.Query().Get("host") != filepath.Join(run, "pg", "socket") || u.Host != "" {
if err != nil || u.Scheme != "postgres" || u.Query().Get("host") != socket || u.Host != "" {
t.Errorf("MESH_TEST_POSTGRES = %q", env["MESH_TEST_POSTGRES"])
}
if strings.Count(strings.TrimPrefix(env["MESH_TEST_POSTGRES"], "postgres://"), "/") != 1 {
@@ -220,7 +225,7 @@ func TestSetupAndRunningTimeAreJudgedApart(t *testing.T) {
f.advance["compile"], f.advance["start"] = 20*time.Second, 15*time.Second
m, states := integModule(t, f)
res, _ := runLevelTool(t, m, states, "integration")
if res.Passed || res.Setup != 35 || !strings.Contains(res.Why, "setup took 35.00 s, over its setup bound of 30 s") || strings.Contains(res.Why, "slowest tests") {
if !res.Passed || !res.OverBound || res.OverWhich != "setup" || res.Setup != 35 || !strings.Contains(res.Why, "setup took 35.00 s, over its setup bound of 30 s") || strings.Contains(res.Why, "slowest tests") {
t.Errorf("a setup of 35 s: %+v", res)
}
@@ -228,14 +233,14 @@ func TestSetupAndRunningTimeAreJudgedApart(t *testing.T) {
g.advance["test"] = 61 * time.Second
m, states = integModule(t, g)
res, _ = runLevelTool(t, m, states, "integration")
if res.Passed || res.Elapsed != 61 || !strings.Contains(res.Why, "took 61.00 s, over its bound of 60 s; the slowest tests: fixture/slow TestTakesAWhile") || strings.Contains(res.Why, "setup") {
if !res.Passed || res.OverWhich != "run" || res.Elapsed != 61 || !strings.Contains(res.Why, "took 61.00 s, over its bound of 60 s; the slowest tests: fixture/slow TestTakesAWhile") || strings.Contains(res.Why, "setup") {
t.Errorf("a run of 61 s: %+v", res)
}
h := newIntegFake(t, "slow")
h.advance["compile"], h.advance["test"] = 29*time.Second, 59*time.Second
m, states = integModule(t, h)
if res, _ = runLevelTool(t, m, states, "integration"); !res.Passed || res.Setup != 29 || res.Elapsed != 59 {
if res, _ = runLevelTool(t, m, states, "integration"); !res.Passed || res.OverBound || res.Setup != 29 || res.Elapsed != 59 {
t.Errorf("29 s of setup and 59 s of running are within both bounds: %+v", res)
}
}
@@ -316,3 +321,46 @@ func TestThePostgresMajorIsReadFromTheVersionLine(t *testing.T) {
t.Errorf("accepted a line that is not postgres's")
}
}
// Issue 1486 review: postgres refuses a socket path of 108 bytes and over. A run directory deep in the work directory
// gets a short directory of its own for the socket, removed with the store; a short one keeps it under the run.
func TestTheSocketPathStaysUnderTheLimitForALongRunDirectory(t *testing.T) {
long := filepath.Join(t.TempDir(), strings.Repeat("deep", 20), "ws1", "run-mvh5uc96142806")
if err := os.MkdirAll(long, 0o700); err != nil {
t.Fatal(err)
}
dir, short, err := socketDir(long)
if err != nil || !short || len(dir)+len(socketName) >= 100 {
t.Fatalf("socketDir(long) = %q, %v, %v", dir, short, err)
}
if _, err := os.Stat(dir); err != nil {
t.Errorf("the short directory was not made: %v", err)
}
os.RemoveAll(dir)
near := t.TempDir()
if dir, short, err := socketDir(near); err != nil || (len(near) < 50 && (short || !strings.HasPrefix(dir, near))) {
t.Errorf("socketDir(short) = %q, %v, %v", dir, short, err)
}
// Through the store: the short directory is removed when the store is stopped.
f := newIntegFake(t, "slow")
m, _ := integModule(t, f)
_, stop, err := m.startStore(context.Background(), long)
if err != nil {
t.Fatal(err)
}
var sock string
for _, c := range f.calls {
if _, a, ok := strings.Cut(c.line, "-k "); ok && strings.HasPrefix(c.line, "pg_ctl") {
sock, _, _ = strings.Cut(a, " ")
}
}
if sock == "" || len(sock)+len(socketName) >= 100 {
t.Fatalf("the store was started with the socket %q", sock)
}
stop()
if _, err := os.Stat(sock); !os.IsNotExist(err) {
t.Errorf("the short socket directory %s outlived the store (%v)", sock, err)
}
}
@@ -77,8 +77,12 @@ type TestResult struct {
Failed []Failure `json:"failed,omitempty"`
// Store and Bus are what the integration level ran against: the postgres major version and the nats-server
// version (ADR 0470 §4).
Store string `json:"store,omitempty"`
Bus string `json:"bus,omitempty"`
// OverBound says a bound was kept no longer, and OverWhich which: setup, run or both. The level's verdict, Passed, is
// the tests' own: the checker judges the bounds (ADR 0453 §9), with the slowest tests this result names.
OverBound bool `json:"over_bound"`
OverWhich string `json:"over_which,omitempty"`
Store string `json:"store,omitempty"`
Bus string `json:"bus,omitempty"`
}
// event is one line of `go test -json` (test2json, and the build events go test adds).
@@ -221,7 +225,7 @@ func (res *TestResult) judge(b Bounds) {
res.Slowest = res.Slowest[:5]
}
res.Bound, res.SetupBound = b.Run.Seconds(), b.Setup.Seconds()
var why []string
var why, over []string
if n := len(res.Failed); n > 0 {
names := []string{}
for _, f := range res.Failed {
@@ -229,28 +233,27 @@ func (res *TestResult) judge(b Bounds) {
}
why = append(why, fmt.Sprintf("%d failed: %s", n, strings.Join(names, ", ")))
}
res.Passed = len(res.Failed) == 0
if b.Setup > 0 && res.Setup > b.Setup.Seconds() {
over = append(over, "setup")
why = append(why, fmt.Sprintf("the %s level's setup took %.2f s, over its setup bound of %s", res.Level, res.Setup, seconds(b.Setup)))
}
if b.Run > 0 && res.Elapsed > b.Run.Seconds() {
over = append(over, "run")
slow := []string{}
for _, s := range res.Slowest {
slow = append(slow, fmt.Sprintf("%s %s (%.2f s)", s.Package, s.Test, s.Elapsed))
}
why = append(why, fmt.Sprintf("the %s level took %.2f s, over its bound of %s; the slowest tests: %s", res.Level, res.Elapsed, seconds(b.Run), strings.Join(slow, ", ")))
}
res.OverBound, res.OverWhich = len(over) > 0, strings.Join(over, ",")
res.NoTests = res.Tests == 0 && len(res.Failed) == 0
switch {
case len(why) > 0:
res.Why = strings.Join(why, "; ")
case res.NoTests:
res.Passed, res.Why = true, "the repository has no tests at the "+res.Level+" level"
return
default:
res.Passed = true
return
res.Why = "the repository has no tests at the " + res.Level + " level"
}
res.Passed = false
}
// seconds is a duration as seconds, without trailing zeros: "5 s", "0.01 s".
@@ -35,6 +35,7 @@ func main() {
Recipe: Current(),
Probe: ProbeGo,
Toolchains: stdio.State(delivery.ToolchainsState).Put,
Emit: func(t string, b any) error { return stdio.Emit(t, b) },
Steps: stdio.State(delivery.StepsState).Put,
Ctx: ctx,
Say: logf,
@@ -24,6 +24,9 @@ type Module struct {
Probe func(ctx context.Context) (string, error)
// Toolchains and Steps are the Put of the states of those names.
Toolchains, Steps buildstep.Put
// Emit tells the bus an event (stdio.Emit); nil emits nothing. The guard emits toolchain.reported after each write of
// the toolchains key, so the indexer, which holds no timer, learns that the bucket exists (issue 1444).
Emit func(eventType string, body any) error
// Ctx is the process's context, which a step runs under (the tool call's is cut at 30 s, the step's is not).
Ctx context.Context
Say func(format string, a ...any)
@@ -43,7 +46,7 @@ type Module struct {
}
func (m *Module) guard() buildstep.Guard {
return buildstep.Guard{Language: "go", Want: m.Recipe.Go, Recipe: m.Recipe.Version(), Probe: m.Probe, Node: m.Node, Put: m.Toolchains}
return buildstep.Guard{Language: "go", Want: m.Recipe.Go, Recipe: m.Recipe.Version(), Probe: m.Probe, Node: m.Node, Put: m.reportingPut()}
}
// Check reads the installed toolchain, writes <node>.go of the toolchains state and answers nil, or the refusal
@@ -123,3 +126,21 @@ func (m *Module) Status(ctx context.Context) map[string]any {
}
return out
}
// ToolchainReported is the event the guard emits after it writes the toolchains key.
const ToolchainReported = "toolchain.reported"
// reportingPut is the toolchains Put that then emits toolchain.reported with the node and the language. A write that
// failed emits nothing; an emit that failed is said and does not refuse the step.
func (m *Module) reportingPut() buildstep.Put {
return func(key string, value any) (uint64, error) {
rev, err := m.Toolchains(key, value)
if err != nil || m.Emit == nil {
return rev, err
}
if eerr := m.Emit(ToolchainReported, map[string]string{"node": m.Node, "language": "go"}); eerr != nil && m.Say != nil {
m.Say("the event %s could not be emitted: %v", ToolchainReported, eerr)
}
return rev, nil
}
}
@@ -49,6 +49,24 @@ func (m *Module) checkPostgres(ctx context.Context) (int, error) {
return major, nil
}
// socketLimit is the longest path of a socket postgres accepts, with the file's name: it refuses 108 bytes and over.
const socketLimit = 100
// socketName is the file postgres makes in the socket directory.
const socketName = "/.s.PGSQL.5432"
// socketDir makes the directory of the store's socket: under the run when the socket's path stays under socketLimit,
// else a short directory of its own under /tmp (answering short, so the caller removes it). A unix socket's path is
// bounded far below a file's, and a work directory deep in a data root is over it.
func socketDir(run string) (dir string, short bool, err error) {
dir = filepath.Join(run, "pg", "socket")
if len(dir)+len(socketName) < socketLimit {
return dir, false, os.MkdirAll(dir, 0o700)
}
dir, err = os.MkdirTemp("/tmp", "pg")
return dir, true, err
}
// storeSettings are the server's own settings: no TCP address, a socket directory, and no durability.
func storeOptions(socket string) string {
return "-k " + socket + " -c listen_addresses='' -c fsync=off -c synchronous_commit=off -c full_page_writes=off"
@@ -59,11 +77,14 @@ func storeOptions(socket string) string {
// tests cut the URL at its last slash to name their own databases.
func (m *Module) startStore(ctx context.Context, run string) (string, func(), error) {
data := filepath.Join(run, "pg", "data")
socket := filepath.Join(run, "pg", "socket")
if err := os.MkdirAll(socket, 0o700); err != nil {
socket, short, err := socketDir(run)
if err != nil {
return "", nil, err
}
stop := func() {
if short {
defer os.RemoveAll(socket)
}
sctx, cancel := context.WithTimeout(context.Background(), storeStopBound)
defer cancel()
m.Run(sctx, nil, "pg_ctl", "-D", data, "-m", "immediate", "-w", "stop")
@@ -85,7 +85,12 @@ func processesNamed(text string) []string {
// It starts postgres and a bus, so it runs in the merge check and in a try, never on the way.
func TestIntegrationLevelRunsAgainstARealStoreAndBusAndLeavesNothing(t *testing.T) {
tree := repository(t, "store")
work := t.TempDir()
// A short work directory: a unix socket's path is bounded at 108 bytes.
work, err := os.MkdirTemp("/tmp", "w")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { os.RemoveAll(work) })
if err := os.MkdirAll(filepath.Join(work, "ws1"), 0o755); err != nil {
t.Fatal(err)
}
@@ -107,8 +107,8 @@ func TestUnitLevelOverItsBoundFailsNamingTheBoundAndTheSlowestTests(t *testing.T
m, states, ws, _ := workspaceFor(t, "slow")
m.UnitBound = 10 * time.Millisecond
res, _ := runUnit(t, m, states, ws)
if res.Passed {
t.Fatalf("a level of %.3f s passed under a bound of %v", res.Elapsed, m.UnitBound)
if !res.Passed || !res.OverBound || res.OverWhich != "run" {
t.Fatalf("a level of %.3f s under a bound of %v: passed %v, over_bound %v (%s); the tests passed and the bound was kept no longer", res.Elapsed, m.UnitBound, res.Passed, res.OverBound, res.OverWhich)
}
for _, want := range []string{"over its bound of 0.01 s", "fixture/slow TestTakesAWhile"} {
if !strings.Contains(res.Why, want) {
@@ -120,7 +120,7 @@ func TestUnitLevelOverItsBoundFailsNamingTheBoundAndTheSlowestTests(t *testing.T
}
m2, states2, ws2, _ := workspaceFor(t, "slow")
if res, _ := runUnit(t, m2, states2, ws2); !res.Passed || res.Bound != 5 {
if res, _ := runUnit(t, m2, states2, ws2); !res.Passed || res.OverBound || res.Bound != 5 {
t.Errorf("under the bound of five seconds: %+v", res)
}
}
+1 -1
View File
@@ -3,7 +3,7 @@ module meshbuildgo
go 1.26.0
require (
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261020203945-1572e50e0ef0
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261020204626-36d6f7745c8d
github.com/nats-io/nats-server/v2 v2.11.17
)
+2
View File
@@ -2,6 +2,8 @@ git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261020200715-9e296295c05e h1:uf2o2A6d
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261020200715-9e296295c05e/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261020203945-1572e50e0ef0 h1:mr9HyARwzeZT7FxFHv1Xc2z7QErvDZ/F/pBMIpjGSd0=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261020203945-1572e50e0ef0/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261020204626-36d6f7745c8d h1:mmOCJphjQN15jNVh+sMQqnmJ3UiYltiuQRg/zw3wFtw=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261020204626-36d6f7745c8d/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/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
+6
View File
@@ -19,6 +19,12 @@
"build_go_status",
"build_go_health"
],
"streams": [
{
"name": "toolchains-go",
"events": ["toolchain.**"]
}
],
"requires": [
"forge-account"
],
+1 -1
View File
@@ -3,7 +3,7 @@ module sshclient
go 1.26.0
require (
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261020203945-1572e50e0ef0
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261020204626-36d6f7745c8d
github.com/nats-io/nats-server/v2 v2.11.17
github.com/nats-io/nats.go v1.54.0
)
+2
View File
@@ -4,6 +4,8 @@ git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261020200715-9e296295c05e h1:uf2o2A6d
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261020200715-9e296295c05e/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261020203945-1572e50e0ef0 h1:mr9HyARwzeZT7FxFHv1Xc2z7QErvDZ/F/pBMIpjGSd0=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261020203945-1572e50e0ef0/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261020204626-36d6f7745c8d h1:mmOCJphjQN15jNVh+sMQqnmJ3UiYltiuQRg/zw3wFtw=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261020204626-36d6f7745c8d/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/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=