B5g3-4: mesh-build-go takes a run's input of any size from the chunked verb input

Builds: design 47 G §712, §354, A-237, A-249

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
2026-11-12 09:24:45 +01:00
parent 584fc94baa
commit 5b77eb18f8
2 changed files with 52 additions and 32 deletions
+29 -24
View File
@@ -11,6 +11,7 @@ package main
import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
@@ -18,7 +19,7 @@ import (
"path/filepath"
"strings"
checkerclient "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshdeliverycheckerclient"
checker "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshdeliverychecker"
sdkstep "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/buildstep"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/steprun"
)
@@ -45,41 +46,45 @@ func (m *Module) run(ctx context.Context, fetch steprun.StepFunc, a sdkstep.Step
return nil, err
}
ans.Built, ans.Key = built, key
// The answer is written on the step key as one value: one over the bus's value limit (the input's bound) fails the
// step, naming its size, before it is written.
if raw, err := json.Marshal(ans); err != nil {
return nil, err
} else if len(raw) > sdkstep.RunInputBound {
return nil, fmt.Errorf("the checking program's answer is %d bytes, over the bound of %d bytes a step key holds", len(raw), sdkstep.RunInputBound)
}
return ans, nil
}
// 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.
// its digest and size: bytes that are not the input named refuse the ask. A verb marked chunks (the seat mesh-catalog's
// input, of any size) is asked by the plain seat call: the tool runner receives the chunks and hands this module the
// whole file in the member `file` (design 47 G A-237, A-249); another seat's input answers its bytes in `input`.
func (m *Module) inputOf(in sdkstep.RunInput) ([]byte, error) {
if m.AskInput == nil {
return nil, errors.New("this module asks no seat verb for a run's input")
}
// A verb that does not answer fails the step; an answer that is not the input named refuses the ask.
var askErr error
// The verb is the one the asker's ask names: the runner's, the indexer's (mesh-catalog's input) or the assigner's
// (mesh-wants' input) own, each serving the input by its digest alike (design 47 G §125, §206, §338, §354).
raw, err := checkerclient.ReadInput(func(_ string, body any) (json.RawMessage, error) {
out, err := m.AskInput(in.Verb, body)
askErr = err
return out, err
}, in.Digest)
raw, err := m.AskInput(in.Verb, checker.InputInput{Digest: in.Digest})
if err != nil {
if askErr != nil {
return nil, err
}
return nil, fmt.Errorf("asking %s for the input %s: %w", in.Verb, in.Digest, err)
}
var a struct {
Digest string `json:"digest"`
File string `json:"file"`
Input string `json:"input"`
}
if err := json.Unmarshal(raw, &a); err != nil {
return nil, steprun.Refuse("%s answered no input for %s: %v", in.Verb, in.Digest, err)
}
if a.Digest != in.Digest {
return nil, steprun.Refuse("%s answered the input %q, asked %s", in.Verb, a.Digest, in.Digest)
}
text := a.File
if text == "" {
text = a.Input
}
body, err := base64.StdEncoding.DecodeString(text)
if err != nil {
return nil, steprun.Refuse("%s answered the input %s in no base64: %v", in.Verb, in.Digest, err)
}
if err := in.Verify(body); err != nil {
return nil, steprun.Refuse("%v", err)
}
if err := in.Verify(raw); err != nil {
return nil, steprun.Refuse("%v", err)
}
return raw, nil
return body, nil
}
// placeHead lays one head's commit as a plain directory at dir, with no symlink in it; a test sets its own.
@@ -109,19 +109,12 @@ func TestAKeptProgramIsRunOverItsHeadsAndInput(t *testing.T) {
if ws < 0 || heads < ws || !strings.Contains(line, " "+sdkstep.RunHeadsEnv+" "+argv[heads+1]+" ") {
t.Errorf("the heads are not bound read-only over the workspace: %s", line)
}
// An answer over the bus's value limit fails the step, naming its size.
m.Run = func(context.Context, []string, string, ...string) forgekey.Ran {
return forgekey.Ran{Status: 0, Stdout: `"` + strings.Repeat("x", sdkstep.RunInputBound) + `"`}
}
if _, err := m.run(context.Background(), fetch, a); err == nil || !strings.Contains(err.Error(), "over the bound") {
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; one the verb does not answer fails the step, saying why.
m.AskInput = func(_ string, b any) (json.RawMessage, error) {
return json.Marshal(checker.InputAnswer{Digest: b.(checker.InputInput).Digest, Input: base64.StdEncoding.EncodeToString([]byte(`{"other":1}`))})
}
if _, err := m.run(context.Background(), fetch, a); err == nil || !strings.Contains(err.Error(), "whose digest is") || !errors.As(err, new(*steprun.Refusal)) {
if _, err := m.run(context.Background(), fetch, a); err == nil || !strings.Contains(err.Error(), "is not the one the ask names") || !errors.As(err, new(*steprun.Refusal)) {
t.Errorf("a wrong input: %v", err)
}
m.AskInput = answersInput(`{"v":2}`)
@@ -196,3 +189,25 @@ func answersInputOn(wanted, body string) func(key string, b any) (json.RawMessag
return json.Marshal(checkerclient.InputAnswerOf([]byte(body)))
}
}
// Design 47 G A-237, A-249: the input of a run is of any size: the mesh-catalog seat's verb input, marked chunks, is asked by the
// plain seat call and answers {digest, chunks, file}, the file whole as the tool runner received it; the bytes are checked.
func TestARunsInputOfAnySizeIsTakenFromTheChunkedVerb(t *testing.T) {
body := strings.Repeat("0123456789", 300000) // 3 MB
sum := sha256.Sum256([]byte(body))
in := sdkstep.RunInput{Verb: "seat:mesh-catalog.input", Digest: hex.EncodeToString(sum[:]), Size: len(body)}
m := &Module{AskInput: func(key string, b any) (json.RawMessage, error) {
if ask, _ := b.(checker.InputInput); key != in.Verb || ask.Digest != in.Digest {
return nil, fmt.Errorf("%s keeps no input %v", key, b)
}
return json.Marshal(map[string]any{"digest": in.Digest, "chunks": 6, "file": base64.StdEncoding.EncodeToString([]byte(body))})
}}
got, err := m.inputOf(in)
if err != nil || string(got) != body {
t.Fatalf("the input of %d bytes: %d bytes, %v", len(body), len(got), err)
}
in.Size--
if _, err := m.inputOf(in); err == nil || !errors.As(err, new(*steprun.Refusal)) {
t.Errorf("a file of another size than the ask names: %v", err)
}
}