mesh-build-go asks a run's input of the seat verb the ask names, and reads no checker state (issue 1921, B0 review round 1)
The operator's answer: reading mesh-delivery-checker.inputs broke ADR 0596 §2. The manifest drops that read and invokes seat:mesh-delivery-checker.input; the run step asks the verb through the runtime (stdio.Ask) and takes only the bytes the ask names (delivery.RunInput.Read). Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
@@ -150,11 +150,17 @@ func TestAComposeAskFailsLoudly(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// 1622: the module reads the delivery indexer's manifests, which a compose ask names by digest.
|
||||
// 1622: the module reads the delivery indexer's manifests, which a compose ask names by digest. 1921 (B0): a run's input
|
||||
// is asked of the checker seat's verb input, as the manifest invokes it, and no state of the checker's inputs is read
|
||||
// (ADR 0596 §2).
|
||||
func TestTheModuleReadsTheIndexersManifests(t *testing.T) {
|
||||
if m := readManifest(t); !slices.Contains(m.Reads, ManifestsState) {
|
||||
m := readManifest(t)
|
||||
if !slices.Contains(m.Reads, ManifestsState) {
|
||||
t.Errorf("reads = %v, lacks %s", m.Reads, ManifestsState)
|
||||
}
|
||||
if !slices.Contains(m.Invokes, runInputVerb) || slices.Contains(m.Reads, "mesh-delivery-checker.inputs") {
|
||||
t.Errorf("invokes = %v, reads = %v: want %s invoked and no inputs state read", m.Invokes, m.Reads, runInputVerb)
|
||||
}
|
||||
}
|
||||
|
||||
// ADR 0593 §7 (C9): a compose ask naming the node-engine's repository builds its composer command in the Go module at the
|
||||
|
||||
@@ -7,7 +7,6 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/user"
|
||||
@@ -59,7 +58,7 @@ func main() {
|
||||
m.Settings = m.Home + "/settings.json"
|
||||
m.ReadKept = readOf(stdio.State(KeptState)).Get
|
||||
m.ReadManifest = readOf(stdio.State(ManifestsState)).Get
|
||||
m.ReadInput = func(state, key string) (json.RawMessage, error) { return readOf(stdio.State(state)).Get(key) }
|
||||
m.AskInput = stdio.Ask
|
||||
m.Probe = ProbeGo(m.Home, m.Cache)
|
||||
m.Bubblewrap = ProbeBubblewrap
|
||||
// Outside a launch by the runtime — a build, a check — there is no node and no state to write.
|
||||
|
||||
@@ -93,8 +93,9 @@ type Module struct {
|
||||
// ReadManifest reads a key of the delivery indexer's manifests state (ManifestsState), which a compose ask's request
|
||||
// names its manifests by (compose.go); GoModCache, when set, is the Go module cache in place of Cache's (a test).
|
||||
ReadManifest func(key string) (json.RawMessage, error)
|
||||
// ReadInput reads a key of a state the module reads (module.json), where a run ask's input names its digest.
|
||||
ReadInput func(state, key string) (json.RawMessage, error)
|
||||
// AskInput asks a seat verb the module invokes (module.json) as the runtime calls it (stdio.Ask): the verb a run ask's
|
||||
// input names, answering the input by its digest (delivery.RunInput.Read).
|
||||
AskInput func(key string, body any) (json.RawMessage, error)
|
||||
// PlaceHead, when set, lays a run's head at a directory instead of fetching it (a test).
|
||||
PlaceHead func(ctx context.Context, h delivery.RunHead, dir string) error
|
||||
GoModCache string
|
||||
|
||||
@@ -3,8 +3,9 @@ package main
|
||||
// **A run ask runs a program built from a change, over heads it reads** (novox/hq ADR 0644, ADR 0656; issue 1921): the
|
||||
// checking program of a group's check. The module builds the program at the commit the ask names, once per
|
||||
// delivery.RunKey, as a compose ask builds a composer (programOf); fetches every head the ask names and lays each read-only
|
||||
// under the directory MESH_RUN_HEADS names, at `<owner>/<name>`; reads the input the ask names by its digest from the
|
||||
// state the ask names and writes it to the file MESH_RUN_INPUT names; and runs the program shut off with the ask's
|
||||
// under the directory MESH_RUN_HEADS names, at `<owner>/<name>`; asks the seat verb the ask's input names for it by its
|
||||
// digest (as module.json invokes it; never a read of the asker's state, ADR 0596 §2) and writes it to the file
|
||||
// MESH_RUN_INPUT names; and runs the program shut off with the ask's
|
||||
// arguments: no network, none of the account. The answer is delivery.RunAnswer: the program's exit, the last line of its
|
||||
// standard output and the first line of its standard error. A program that fails is an answer, not a fault of the step.
|
||||
|
||||
@@ -54,23 +55,17 @@ func (m *Module) run(ctx context.Context, fetch buildstep.StepFunc, a delivery.S
|
||||
return ans, nil
|
||||
}
|
||||
|
||||
// inputOf is the input a run names, read from the state the ask names (module.json reads it) and checked against its
|
||||
// digest and size.
|
||||
// inputOf is the input a run names, asked of the seat verb the ask names (module.json invokes it) and checked against
|
||||
// its digest and size: bytes that are not the input named refuse the ask.
|
||||
func (m *Module) inputOf(in delivery.RunInput) ([]byte, error) {
|
||||
if m.ReadInput == nil {
|
||||
return nil, errors.New("this module reads no inputs state")
|
||||
if m.AskInput == nil {
|
||||
return nil, errors.New("this module asks no seat verb for a run's input")
|
||||
}
|
||||
raw, err := m.ReadInput(in.State, in.Digest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if raw == nil {
|
||||
return nil, fmt.Errorf("%s keeps no input of digest %s", in.State, in.Digest)
|
||||
}
|
||||
if err := in.Verify(raw); err != nil {
|
||||
raw, err := in.Read(m.AskInput)
|
||||
if err != nil && raw != nil {
|
||||
return nil, buildstep.Refuse("%v", err)
|
||||
}
|
||||
return raw, nil
|
||||
return raw, err
|
||||
}
|
||||
|
||||
// placeHead lays one head's commit as a plain directory at dir, with no symlink in it; a test sets its own.
|
||||
|
||||
@@ -5,6 +5,7 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -21,8 +22,9 @@ import (
|
||||
)
|
||||
|
||||
// 1921/C3: a run ask put on the Go build seat's check stream is pulled by the module started whole on the real bus and
|
||||
// answered on its step key <node>.<ask id>: the program, run shut off over its heads and its input (read from the
|
||||
// state the ask names by digest), answers its last line and exit; a program cannot write under its heads, and one
|
||||
// answered on its step key <node>.<ask id>: the program, run shut off over its heads and its input (asked by digest of the
|
||||
// seat verb the ask names, which a fake checker answers on the bus as the checker's verb input does), answers its last
|
||||
// line and exit; a program cannot write under its heads, and one
|
||||
// that dials the machine's network is shut off, and the answer says it failed.
|
||||
func TestARunAskIsAnsweredOnTheBusAndItsProgramIsShutOff(t *testing.T) {
|
||||
needTools(t, "the shut-off run", "bash", "bwrap")
|
||||
@@ -59,7 +61,33 @@ func TestARunAskIsAnsweredOnTheBusAndItsProgramIsShutOff(t *testing.T) {
|
||||
}
|
||||
m.PlaceHead = func(_ context.Context, h delivery.RunHead, dir string) error { return os.MkdirAll(dir, 0o700) }
|
||||
body := `{"v":1}`
|
||||
m.ReadInput = func(string, string) (json.RawMessage, error) { return json.RawMessage(body), nil }
|
||||
// The checker's verb input on the bus, and the module asking it as the runtime calls a seat verb it invokes.
|
||||
asked := make(chan string, 4)
|
||||
sub, err := nc.Subscribe("mesh.seat.mesh-delivery-checker.tool.input", func(msg *nats.Msg) {
|
||||
var ask delivery.RunInputAsk
|
||||
_ = json.Unmarshal(msg.Data, &ask)
|
||||
asked <- ask.Digest
|
||||
reply, _ := json.Marshal(map[string]any{"result": delivery.RunInputAnswer{Digest: ask.Digest, Input: []byte(body)}})
|
||||
_ = msg.Respond(reply)
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer sub.Unsubscribe()
|
||||
m.AskInput = func(key string, b any) (json.RawMessage, error) {
|
||||
if key != runInputVerb {
|
||||
return nil, errors.New("asked " + key)
|
||||
}
|
||||
data, _ := json.Marshal(b)
|
||||
msg, err := nc.Request("mesh.seat.mesh-delivery-checker.tool.input", data, 5*time.Second)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var r struct {
|
||||
Result json.RawMessage `json:"result"`
|
||||
}
|
||||
return r.Result, json.Unmarshal(msg.Data, &r)
|
||||
}
|
||||
|
||||
a := runAskOf(t, body)
|
||||
key := delivery.RunKey(a.Run.Program, delivery.ToolchainUsed{Version: "go1.26.0", Recipe: m.Recipe.Version()}, nil)
|
||||
@@ -102,4 +130,12 @@ func TestARunAskIsAnsweredOnTheBusAndItsProgramIsShutOff(t *testing.T) {
|
||||
if ans.Key != key || ans.Exit != 7 || strings.Contains(string(ans.Output), "dialed") || string(ans.Output) != `"start"` {
|
||||
t.Fatalf("the answer of a program that dials out = %+v output %s", ans, ans.Output)
|
||||
}
|
||||
select {
|
||||
case d := <-asked:
|
||||
if d != a.Run.Input.Digest {
|
||||
t.Errorf("the verb input was asked %s, want %s", d, a.Run.Input.Digest)
|
||||
}
|
||||
default:
|
||||
t.Error("the run's input was not asked of the checker's verb input")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,18 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||
sdkstep "git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/composer"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
@@ -17,12 +22,23 @@ import (
|
||||
|
||||
const runCommit = "5555555555555555555555555555555555555555"
|
||||
|
||||
// runInputs is the state a run ask's input is kept in, as the checker names it in the ask.
|
||||
const runInputs = "mesh-delivery-checker.inputs"
|
||||
// runInputVerb is the seat verb a run ask's input is served on, as the checker names it in the ask.
|
||||
const runInputVerb = "seat:mesh-delivery-checker.input"
|
||||
|
||||
// answersInput is a seat verb serving body by its digest, as the checker's verb input answers (delivery.RunInputAnswer).
|
||||
func answersInput(body string) func(key string, b any) (json.RawMessage, error) {
|
||||
return func(key string, b any) (json.RawMessage, error) {
|
||||
ask, _ := b.(delivery.RunInputAsk)
|
||||
if sum := sha256.Sum256([]byte(body)); key != runInputVerb || ask.Digest != hex.EncodeToString(sum[:]) {
|
||||
return nil, fmt.Errorf("the checker keeps no input %s", ask.Digest)
|
||||
}
|
||||
return json.Marshal(delivery.RunInputAnswer{Digest: ask.Digest, Input: []byte(body)})
|
||||
}
|
||||
}
|
||||
|
||||
func runAskOf(t *testing.T, body string) delivery.StepAsk {
|
||||
t.Helper()
|
||||
in, err := delivery.RunInputOf(runInputs, []byte(body))
|
||||
in, err := delivery.RunInputOf(runInputVerb, []byte(body))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -39,12 +55,7 @@ func runAskOf(t *testing.T, body string) delivery.StepAsk {
|
||||
func TestAKeptProgramIsRunOverItsHeadsAndInput(t *testing.T) {
|
||||
m := composeModule(t)
|
||||
body := `{"v":1,"case":"x"}`
|
||||
m.ReadInput = func(state, key string) (json.RawMessage, error) {
|
||||
if state != runInputs || key != runAskOf(t, body).Run.Input.Digest {
|
||||
return nil, nil
|
||||
}
|
||||
return json.RawMessage(body), nil
|
||||
}
|
||||
m.AskInput = answersInput(body)
|
||||
m.PlaceHead = func(_ context.Context, h delivery.RunHead, dir string) error {
|
||||
return os.MkdirAll(dir, 0o700)
|
||||
}
|
||||
@@ -103,11 +114,17 @@ func TestAKeptProgramIsRunOverItsHeadsAndInput(t *testing.T) {
|
||||
t.Errorf("an answer over the bound: %v", err)
|
||||
}
|
||||
m.Inside, m.Run = true, StepRunner(t.TempDir())
|
||||
// An input other than the one named is refused, naming it.
|
||||
m.ReadInput = func(string, string) (json.RawMessage, error) { return json.RawMessage(`{"other":1}`), nil }
|
||||
if _, err := m.run(context.Background(), fetch, a); err == nil || !strings.Contains(err.Error(), "not the one the ask names") {
|
||||
// An input other than the one named is refused, naming it; one the verb does not answer fails the step, saying why.
|
||||
m.AskInput = func(_ string, b any) (json.RawMessage, error) {
|
||||
return json.Marshal(delivery.RunInputAnswer{Digest: b.(delivery.RunInputAsk).Digest, Input: []byte(`{"other":1}`)})
|
||||
}
|
||||
if _, err := m.run(context.Background(), fetch, a); err == nil || !strings.Contains(err.Error(), "not the one the ask names") || !errors.As(err, new(*buildstep.Refusal)) {
|
||||
t.Errorf("a wrong input: %v", err)
|
||||
}
|
||||
m.AskInput = answersInput(`{"v":2}`)
|
||||
if _, err := m.run(context.Background(), fetch, a); err == nil || !strings.Contains(err.Error(), "keeps no input") {
|
||||
t.Errorf("an input the verb does not answer: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 1921/C2 (F2): a compose ask's seats reach the composer: the module decodes and fills the request and writes it again,
|
||||
|
||||
@@ -20,10 +20,12 @@
|
||||
}
|
||||
],
|
||||
"reads": [
|
||||
"mesh-delivery-checker.inputs",
|
||||
"mesh-delivery-checker.kept",
|
||||
"mesh-delivery-indexer.manifests"
|
||||
],
|
||||
"invokes": [
|
||||
"seat:mesh-delivery-checker.input"
|
||||
],
|
||||
"config": {
|
||||
"capacity": {
|
||||
"type": "number",
|
||||
|
||||
Reference in New Issue
Block a user