Merge main, and close what the confirmation review found (hq ADR 0272)
mesh/merge-gate pass: builds mesh-host → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered

- Any failure of a request the link may have delivered now says the line
  may have run; "nothing ran" is said only of a request the link knows it
  never asked (no link open, nobody listening: link.ErrNotAsked).
- A test holds that a connection saying nothing is answered within its
  deadline, set at accept, and gives its slot back.
- The README says the socket refuses a process outside the machine's own
  namespaces, so flatpak, toolbox and distrobox shells are refused.
This commit is contained in:
jochen
2026-10-09 13:09:48 +02:00
parent 655f515aa7
commit f684967c3b
7 changed files with 101 additions and 11 deletions
+9 -4
View File
@@ -8,6 +8,7 @@ import (
"time"
"git.novox.be/novox/mesh-sdk/go/cli"
"github.com/novox/mesh-host/internal/link"
)
// The controller answers a line as it answers every call (novox/hq ADR 0272, review): in an envelope, `result` or
@@ -15,6 +16,9 @@ import (
// followed by the same account on the same node until it ends. Its side holds the same field names (mesh-controller
// internal/link, the mesh-cli field names' test).
// ErrNotAsked is the link's: a request it knows it never delivered.
var ErrNotAsked = link.ErrNotAsked
// AskFunc asks the controller one request on this node's subject and returns its reply.
type AskFunc func(ctx context.Context, body []byte) ([]byte, error)
@@ -40,11 +44,12 @@ func AskTheController(ctx context.Context, ask AskFunc, asked Asked) cli.Answer
body, _ := json.Marshal(asked)
reply, err := ask(ctx, body)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) || ctx.Err() != nil {
return cli.NotKnown("", "no answer from the controller in time")
// **Only a request the link knows it never asked ran nothing**: no link open, nobody listening. Any other
// failure may come after the request was delivered, and the line may have run (review of ADR 0272).
if errors.Is(err, ErrNotAsked) {
return cli.Refusal("the controller was not reached, so nothing ran: " + err.Error())
}
// Never delivered to a controller: no link, no subscriber, the bus refusing the publish.
return cli.Refusal("the controller was not reached, so nothing ran: " + err.Error())
return cli.NotKnown("", "no answer from the controller ("+err.Error()+")")
}
call := ""
for {
+27 -2
View File
@@ -83,16 +83,41 @@ func TestNoAnswerInTimeSaysTheLineMayHaveRun(t *testing.T) {
// A request the bus never carried to a controller ran nothing, and says so.
func TestARequestNobodyTookSaysNothingRan(t *testing.T) {
c := &controllerSays{script: []func(Asked) ([]byte, error){func(Asked) ([]byte, error) {
return nil, fmt.Errorf("nothing on this machine answers mesh.control.laptop.cli")
return nil, fmt.Errorf("%w: nothing on this machine answers mesh.control.laptop.cli", ErrNotAsked)
}}}
a := AskTheController(context.Background(), c.ask, Asked{Line: []string{"status"}, Account: "op"})
if !strings.Contains(a.Refused, "nothing ran") || a.Unknown != "" {
t.Fatalf("a request nobody took reads %+v", a)
}
c = &controllerSays{script: []func(Asked) ([]byte, error){func(Asked) ([]byte, error) {
return nil, errors.New("this machine has no link to the bus open now")
return nil, fmt.Errorf("%w: this machine has no link to the bus open now", ErrNotAsked)
}}}
if a := AskTheController(context.Background(), c.ask, Asked{Line: []string{"status"}}); !strings.Contains(a.Refused, "nothing ran") {
t.Fatalf("no link reads %+v", a)
}
}
// **Any error once the request may have been delivered is not known, never "nothing ran"** (review of ADR 0272):
// only a request the link says it never asked — no link open, nobody listening — ran nothing.
func TestOnlyARequestNeverAskedSaysNothingRan(t *testing.T) {
for _, c := range []struct {
err error
nothing bool
}{
{fmt.Errorf("%w: this machine has no link to the bus open now", ErrNotAsked), true},
{fmt.Errorf("%w: nothing on this machine answers mesh.control.laptop.cli", ErrNotAsked), true},
{errors.New("nats: connection closed"), false},
{errors.New("mesh.control.laptop.cli: permissions violation"), false},
{context.DeadlineExceeded, false},
} {
err := c.err
ctl := &controllerSays{script: []func(Asked) ([]byte, error){func(Asked) ([]byte, error) { return nil, err }}}
a := AskTheController(context.Background(), ctl.ask, Asked{Line: []string{"push"}, Account: "op"})
if c.nothing && (!strings.Contains(a.Refused, "nothing ran") || a.Unknown != "") {
t.Errorf("%v reads %+v; it was never asked", err, a)
}
if !c.nothing && (a.Refused != "" || !strings.Contains(a.Unknown, "may have run")) {
t.Errorf("%v reads %+v; it may have been asked", err, a)
}
}
}
+42
View File
@@ -0,0 +1,42 @@
package meshcli
import (
"context"
"net"
"strings"
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/cli"
)
// **A connection that says nothing is bounded from the moment it is accepted** (review of ADR 0272): its deadline is
// set before any lookup, so it is answered and closed within readWithin, holds no slot after, and asks nothing.
func TestAConnectionThatSaysNothingIsAnsweredWithinItsDeadline(t *testing.T) {
was := readWithin
readWithin = 200 * time.Millisecond
t.Cleanup(func() { readWithin = was })
asked := false
socket := serving(t, func(context.Context, Asked) cli.Answer {
asked = true
return cli.Answer{}
})
conn, err := net.Dial("unix", socket)
if err != nil {
t.Fatal(err)
}
defer conn.Close()
_ = conn.SetDeadline(time.Now().Add(5 * time.Second))
a, err := cli.ReadAnswer(conn)
if err != nil {
t.Fatalf("a silent connection was not answered within its deadline: %v", err)
}
if asked || a.Refused == "" || !strings.Contains(a.Refused, "nothing was asked") {
t.Fatalf("a silent connection was asked (%v) or answered %+v", asked, a)
}
// Its slot was given back: the account's next line is not busy.
b, err := cli.Ask(context.Background(), socket, []string{"status"})
if err != nil || strings.Contains(b.Refused, "busy") || !asked {
t.Fatalf("the next line after a silent one reads %+v (%v), asked %v", b, err, asked)
}
}
+2 -2
View File
@@ -42,8 +42,8 @@ type Asked struct {
type Forward func(ctx context.Context, asked Asked) cli.Answer
// readWithin bounds how long a connection may take to say its line: a program that connects and says nothing
// holds nothing for long.
const readWithin = 30 * time.Second
// holds nothing for long. A variable so a test need not wait.
var readWithin = 30 * time.Second
// atOnce bounds how many lines are asked at the same time, and perAccount how many of them one account's: a person
// types one at a time, so more is a program on the machine. One over either bound is answered busy at once — the