From 4021de1d4271cd9703afe08fe3763bd278f9266d Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 12 Nov 2026 05:44:53 +0100 Subject: [PATCH] HRUN: mesh-compose reads its pipeline input from MESH_RUN_INPUT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The composer answers the assigner's composition step: the request is read from the file named by MESH_RUN_INPUT and the verdict is the last line of standard output, exit 0; the compose-job path stays. Builds: design 47 G §354, §63, §22; lead answer A-167 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h --- cmd/mesh-compose/main.go | 14 +++++++++++--- cmd/mesh-compose/main_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/cmd/mesh-compose/main.go b/cmd/mesh-compose/main.go index bd30708d..d8f5d8a6 100644 --- a/cmd/mesh-compose/main.go +++ b/cmd/mesh-compose/main.go @@ -11,9 +11,12 @@ // checkout or `git archive` of the catalogue repository at a ref: the command fetches nothing). -node adds a machine to // compose; -declarations answers each machine's declared paths with their owners and modes. // -// It opens no network connection, bus, store or file beyond its standard input, the -request file and the -catalogue -// directory, and reads -// no environment. Exit codes: 0 the verdict is written (a machine that does not compose is a verdict, not an error); +// Run as a pipeline step it reads its input, a composer.Request at depth compose, from the file named by the environment +// variable MESH_RUN_INPUT and answers the run's output as its composer.Verdict on the last line of standard output +// (design 47 G A-167). +// +// It opens no network connection, bus, store or file beyond its standard input, the -request file, the file +// MESH_RUN_INPUT names and the -catalogue directory, and reads no other environment. Exit codes: 0 the verdict is written (a machine that does not compose is a verdict, not an error); // 1 the request cannot be read or run, said on standard error; 2 the flags are wrong. package main @@ -65,6 +68,11 @@ func run(args []string, stdin io.Reader, stdout, stderr io.Writer) int { } var raw []byte var err error + // A pipeline step hands its input as a file named by MESH_RUN_INPUT (design 47 G A-167); -request, when given, wins. + runInput := os.Getenv("MESH_RUN_INPUT") + if *requestFile == "" && runInput != "" { + *requestFile = runInput + } if *requestFile != "" { raw, err = os.ReadFile(*requestFile) } else { diff --git a/cmd/mesh-compose/main_test.go b/cmd/mesh-compose/main_test.go index 88022a04..eeef56c4 100644 --- a/cmd/mesh-compose/main_test.go +++ b/cmd/mesh-compose/main_test.go @@ -267,6 +267,31 @@ func TestTheRequestIsReadFromAFile(t *testing.T) { } } +// A pipeline step names its input file in MESH_RUN_INPUT: the command reads the request there and the verdict is the last +// line of standard output (design 47 G A-167). +func TestThePipelineStepReadsMESH_RUN_INPUT(t *testing.T) { + file := filepath.Join(t.TempDir(), "input.json") + r := composer.Request{V: composer.Version, Depth: composer.DepthCompose, Cases: []composer.Case{{Name: "only", Manifests: map[string]composer.Manifest{ + "demo": {Raw: json.RawMessage(`{"module":"demo","version":"1"}`)}}}}} + if err := os.WriteFile(file, encode(t, r), 0o600); err != nil { + t.Fatal(err) + } + t.Setenv("MESH_RUN_INPUT", file) + var out, errOut bytes.Buffer + if code := run(nil, bytes.NewReader(nil), &out, &errOut); code != 0 { + t.Fatalf("exit %d: %s", code, errOut.String()) + } + lines := strings.Split(strings.TrimRight(out.String(), "\n"), "\n") + var v composer.Verdict + if err := json.Unmarshal([]byte(lines[len(lines)-1]), &v); err != nil || len(v.Cases) != 1 || !v.Cases[0].Manifests["demo"].Accepted { + t.Fatalf("last line is no verdict: %v\n%s", err, out.String()) + } + t.Setenv("MESH_RUN_INPUT", file+".gone") + if code := run(nil, bytes.NewReader(nil), &out, &errOut); code != 1 || !strings.Contains(errOut.String(), "cannot be read") { + t.Errorf("a missing input file: exit %d, %q", code, errOut.String()) + } +} + // The exit codes: 1 for a request that cannot be read or run, 2 for wrong flags, each said on standard error. func TestTheExitCodesSayWhatFailed(t *testing.T) { unfilled := encode(t, composer.Request{V: composer.Version, Cases: []composer.Case{{Name: "c", Manifests: map[string]composer.Manifest{"x": {Digest: "abc"}}}}})