From ea0b6a075b38869f5d631a6b3336f985a5c53fe0 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 7 Nov 2026 02:27:44 +0100 Subject: [PATCH] The checker's seat serves a checking program's input, a verdict names the program that judged it, and the build step's part carries the run ask (issue 1921, milestone 47/G D1) B0's run job, its input verb and judged-by move from mesh-sdk's go/delivery into the Delivery domain's contracts, with B0's JSON names: the part build-step carries StepAsk.run and the defs RunJob, RunProgram, RunHead, RunInput and RunAnswer beside compose (the lead's ruling on Q1), and a step's result the run's exit, output and said. Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i --- .../go/seats/meshdeliverychecker/contract.go | 42 +++++- .../meshdeliverychecker/contract_test.go | 10 ++ contracts/go/seats/nodebuildgo/contract.go | 67 ++++++++- .../go/seats/nodebuildgo/contract_test.go | 4 + contracts/go/seats/nodebuildimage/contract.go | 67 ++++++++- contracts/go/seats/nodebuildnode/contract.go | 67 ++++++++- contracts/parts/build-step.json | 140 +++++++++++++++++- .../fixtures/states.json | 36 +++++ .../mesh-delivery-checker/fixtures/verbs.json | 17 +++ .../seats/mesh-delivery-checker/seat.json | 93 ++++++++++++ .../seats/node-build-go/fixtures/states.json | 59 ++++++++ .../ts/seats/meshdeliverychecker/src/index.ts | 38 +++++ contracts/ts/seats/nodebuildgo/src/index.ts | 5 + .../seats/nodebuildgo/src/parts/build-step.ts | 55 ++++++- .../ts/seats/nodebuildimage/src/index.ts | 5 + .../nodebuildimage/src/parts/build-step.ts | 55 ++++++- contracts/ts/seats/nodebuildnode/src/index.ts | 5 + .../nodebuildnode/src/parts/build-step.ts | 55 ++++++- 18 files changed, 794 insertions(+), 26 deletions(-) create mode 100644 contracts/seats/mesh-delivery-checker/fixtures/verbs.json diff --git a/contracts/go/seats/meshdeliverychecker/contract.go b/contracts/go/seats/meshdeliverychecker/contract.go index a32ae583..00a8c8d8 100644 --- a/contracts/go/seats/meshdeliverychecker/contract.go +++ b/contracts/go/seats/meshdeliverychecker/contract.go @@ -17,11 +17,12 @@ const ( VerbTry = "try" VerbTryRead = "try-read" VerbTryEnd = "try-end" + VerbInput = "input" ) // Verbs is every verb the seat serves, in its definition's order: a new copy each call. func Verbs() []string { - return []string{VerbTry, VerbTryRead, VerbTryEnd} + return []string{VerbTry, VerbTryRead, VerbTryEnd, VerbInput} } // The holder's own events, listed beside the seat and published under the holder's subjects (ADR 0620 §2). @@ -273,6 +274,20 @@ type TryEndAnswer struct { ID string `json:"id"` } +// InputInput is what input takes. +type InputInput struct { + // the input's sha256, 64 lowercase hex digits, as the run ask names it + Digest string `json:"digest"` +} + +// InputAnswer is what input answers. +type InputAnswer struct { + // the digest asked + Digest string `json:"digest"` + // the input's exact bytes, base64, so no encoding on the way changes a byte the digest covers + Input string `json:"input"` +} + // VerdictHolderEventSeriesItem is a type of the contract. type VerdictHolderEventSeriesItem struct { // its artifact, or the package's ecosystem name as the module's packages name it @@ -343,6 +358,29 @@ type VerdictsByGroupHeadsValueFailuresItem struct { Words string `json:"words,omitempty"` } +// VerdictsByGroupHeadsValueJudgedByHeadsItem is a type of the contract. +type VerdictsByGroupHeadsValueJudgedByHeadsItem struct { + Commit string `json:"commit"` + Repository string `json:"repository"` +} + +// VerdictsByGroupHeadsValueJudgedByProgram is a type of the contract. +type VerdictsByGroupHeadsValueJudgedByProgram struct { + Commit string `json:"commit"` + Dir string `json:"dir"` + Package string `json:"package"` + Repository string `json:"repository"` +} + +// VerdictsByGroupHeadsValueJudgedBy is the checking program that judged the check: where it was built, the key its build is kept under, whether this check's run built it, and the heads it read (ADR 0644, ADR 0656). Absent in a value written before it. +type VerdictsByGroupHeadsValueJudgedBy struct { + Built bool `json:"built,omitempty"` + Heads []VerdictsByGroupHeadsValueJudgedByHeadsItem `json:"heads"` + // program- and 16 hex digits + Key string `json:"key"` + Program VerdictsByGroupHeadsValueJudgedByProgram `json:"program"` +} + // VerdictsByGroupHeadsValueLevelsValue is a type of the contract. type VerdictsByGroupHeadsValueLevelsValue struct { Kept int64 `json:"kept"` @@ -379,6 +417,8 @@ type VerdictsByGroupHeadsValue struct { Group string `json:"group"` // 12 hex digits Heads string `json:"heads"` + // the checking program that judged the check: where it was built, the key its build is kept under, whether this check's run built it, and the heads it read (ADR 0644, ADR 0656). Absent in a value written before it + JudgedBy *VerdictsByGroupHeadsValueJudgedBy `json:"judged-by,omitempty"` // by level, unit or integration Levels map[string]VerdictsByGroupHeadsValueLevelsValue `json:"levels,omitempty"` Machines []string `json:"machines,omitempty"` diff --git a/contracts/go/seats/meshdeliverychecker/contract_test.go b/contracts/go/seats/meshdeliverychecker/contract_test.go index 17611c6a..65d9e0c4 100644 --- a/contracts/go/seats/meshdeliverychecker/contract_test.go +++ b/contracts/go/seats/meshdeliverychecker/contract_test.go @@ -29,6 +29,12 @@ func TestFixtures(t *testing.T) { err := json.Unmarshal(b, &v) return v, err }, subjectIs: nil}, + {name: "a pass judged by the checking program built at a mesh-delivery head", subject: "", given: "{\"group\":\"feat/registry\",\"heads\":\"0123456789ab\",\"judged-by\":{\"built\":true,\"heads\":[{\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"repository\":\"novox/mesh-delivery\"}],\"key\":\"program-0123456789abcdef\",\"program\":{\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"dir\":\"modules/mesh-delivery-checker\",\"package\":\"./cmd/mesh-checking-program\",\"repository\":\"novox/mesh-delivery\"}},\"levels\":{\"integration\":{\"kept\":0,\"run\":3},\"unit\":{\"kept\":0,\"run\":12}},\"state\":\"pass\",\"v\":1}", wire: "{\"group\":\"feat/registry\",\"heads\":\"0123456789ab\",\"judged-by\":{\"built\":true,\"heads\":[{\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"repository\":\"novox/mesh-delivery\"}],\"key\":\"program-0123456789abcdef\",\"program\":{\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"dir\":\"modules/mesh-delivery-checker\",\"package\":\"./cmd/mesh-checking-program\",\"repository\":\"novox/mesh-delivery\"}},\"levels\":{\"integration\":{\"kept\":0,\"run\":3},\"unit\":{\"kept\":0,\"run\":12}},\"state\":\"pass\",\"v\":1}", + decode: func(b []byte) (any, error) { + var v VerdictsByGroupHeadsValue + err := json.Unmarshal(b, &v) + return v, err + }, subjectIs: nil}, {name: "a kept pass", subject: "", given: "{\"at\":\"2026-11-05T10:00:00Z\",\"level\":\"unit\",\"package\":\"go/keys\",\"repository\":\"novox/mesh-sdk\",\"state\":\"pass\",\"tests\":4,\"took\":120,\"toolchain\":\"go1.26\",\"v\":1}", wire: "{\"at\":\"2026-11-05T10:00:00Z\",\"level\":\"unit\",\"package\":\"go/keys\",\"repository\":\"novox/mesh-sdk\",\"state\":\"pass\",\"tests\":4,\"took\":120,\"toolchain\":\"go1.26\",\"v\":1}", decode: func(b []byte) (any, error) { var v KeptByLevelDigestValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, {name: "the status owed for a head", subject: "", given: "{\"act\":\"status=mesh/check\",\"context\":\"mesh/check\",\"state\":\"success\",\"v\":1}", wire: "{\"act\":\"status=mesh/check\",\"context\":\"mesh/check\",\"state\":\"success\",\"v\":1}", @@ -43,6 +49,10 @@ func TestFixtures(t *testing.T) { err := json.Unmarshal(b, &v) return v, err }, subjectIs: nil}, + {name: "a checking program asks its input by its digest", subject: "", given: "{\"digest\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\"}", wire: "{\"digest\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\"}", + decode: func(b []byte) (any, error) { var v InputInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "the input answered as its exact bytes, base64", subject: "", given: "{\"digest\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"input\":\"eyJ2IjoxfQ==\"}", wire: "{\"digest\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"input\":\"eyJ2IjoxfQ==\"}", + decode: func(b []byte) (any, error) { var v InputAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, } for _, c := range cases { v, err := c.decode([]byte(c.given)) diff --git a/contracts/go/seats/nodebuildgo/contract.go b/contracts/go/seats/nodebuildgo/contract.go index 34d570c1..5deeb05e 100644 --- a/contracts/go/seats/nodebuildgo/contract.go +++ b/contracts/go/seats/nodebuildgo/contract.go @@ -386,6 +386,51 @@ type Program struct { Path string `json:"path"` } +// RunAnswer is what a run ask's step answers: whether this run built the program or took the one kept under its key, the key, the program's exit, the last line of its standard output and the first of its standard error. +type RunAnswer struct { + Built bool `json:"built"` + Exit int64 `json:"exit"` + Key string `json:"key"` + // raw when it is JSON, else a JSON string + Output any `json:"output,omitempty"` + Said string `json:"said,omitempty"` +} + +// RunHead is one commit a run's program reads. +type RunHead struct { + Commit string `json:"commit"` + Repository string `json:"repository"` +} + +// RunInput is a run's input by reference: the seat verb the asker serves it on (seat:.), the sha256 of its bytes and its size, at most 256 KiB. +type RunInput struct { + // 64 lowercase hex digits + Digest string `json:"digest"` + // bytes + Size int64 `json:"size"` + Verb string `json:"verb"` +} + +// RunJob is a run ask's job: where its program is built, the heads it reads (each laid read-only at its repository's path), its input by reference, its arguments and what it is for. +type RunJob struct { + // the program's arguments, each a word or a flag + Args []string `json:"args,omitempty"` + // the commits the program reads, a repository at most once + Heads []RunHead `json:"heads"` + Input RunInput `json:"input"` + Program RunProgram `json:"program"` + // what the run is for, for a reader of the ask + Reader string `json:"reader,omitempty"` +} + +// RunProgram is where a run's program is built: the repository and full commit fetched, the Go module's directory in it and the program's import path. +type RunProgram struct { + Commit string `json:"commit"` + Dir string `json:"dir"` + Package string `json:"package"` + Repository string `json:"repository"` +} + // Step is one step a build module runs on a node. type Step struct { // when (RFC 3339 time) @@ -460,25 +505,33 @@ type StepAsk struct { Needs []string `json:"needs,omitempty"` // package to the tagged version a build pins Packages map[string]string `json:"packages,omitempty"` + // a run: a program built from a change and run in the build seat's sandbox over the heads, its input asked of a verb of the asker's seat; the Go seat's check stream alone carries it, as a compose + Run *RunJob `json:"run,omitempty"` // the layout version V int64 `json:"v,omitempty"` } -// StepResult is what a step answered, by the kind of ask: a build's artifacts, toolchain, imports and out-of-step modules; a check's fetch and levels; a compose's built, key and verdict; a declined ask's declined, needs and lacks. The fields of the kinds do not meet, so one layout holds the four. +// StepResult is what a step answered, by the kind of ask: a build's artifacts, toolchain, imports and out-of-step modules; a check's fetch and levels; a compose's built, key and verdict; a run's built, key, exit, output and said; a declined ask's declined, needs and lacks. The fields of the kinds do not meet, so one layout holds the five. type StepResult struct { Artifacts []BuiltArtifact `json:"artifacts,omitempty"` Built bool `json:"built,omitempty"` // the holder declined the ask: its node lacks what the ask needs - Declined bool `json:"declined,omitempty"` - Fetch *FetchAnswer `json:"fetch,omitempty"` - Imports []string `json:"imports,omitempty"` - Key string `json:"key,omitempty"` + Declined bool `json:"declined,omitempty"` + // a run's program's exit status + Exit int64 `json:"exit,omitempty"` + Fetch *FetchAnswer `json:"fetch,omitempty"` + Imports []string `json:"imports,omitempty"` + Key string `json:"key,omitempty"` // what the holder's node lacks of it Lacks []string `json:"lacks,omitempty"` Levels []LevelAnswer `json:"levels,omitempty"` // what the declined ask needs - Needs []string `json:"needs,omitempty"` - OutOfStep []string `json:"out-of-step,omitempty"` + Needs []string `json:"needs,omitempty"` + OutOfStep []string `json:"out-of-step,omitempty"` + // a run's program's last line of standard output: raw when it is JSON, else a JSON string + Output any `json:"output,omitempty"` + // a run's program's first line of standard error, or why the step could not run it + Said string `json:"said,omitempty"` Toolchain *ToolchainUsed `json:"toolchain,omitempty"` // the compose-candidate verdict (the composer's own layout) Verdict any `json:"verdict,omitempty"` diff --git a/contracts/go/seats/nodebuildgo/contract_test.go b/contracts/go/seats/nodebuildgo/contract_test.go index 9148353a..f120459d 100644 --- a/contracts/go/seats/nodebuildgo/contract_test.go +++ b/contracts/go/seats/nodebuildgo/contract_test.go @@ -49,6 +49,10 @@ func TestFixtures(t *testing.T) { decode: func(b []byte) (any, error) { var v StepsByNodeIDValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, {name: "a finished check step whose head does not merge", subject: "", given: "{\"ended\":\"2026-11-05T10:01:00Z\",\"id\":\"c3d5\",\"node\":\"ace\",\"result\":{\"fetch\":{\"base\":\"abcdef0123456789abcdef0123456789abcdef01\",\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"conflicts\":[\"a.go\",\"sub/b.go\"],\"modules\":[\".\"],\"workspace\":\"mvii1iwz5050eb\"}},\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"passed\",\"tool\":\"check\",\"v\":1}", wire: "{\"ended\":\"2026-11-05T10:01:00Z\",\"id\":\"c3d5\",\"node\":\"ace\",\"result\":{\"fetch\":{\"base\":\"abcdef0123456789abcdef0123456789abcdef01\",\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"conflicts\":[\"a.go\",\"sub/b.go\"],\"modules\":[\".\"],\"workspace\":\"mvii1iwz5050eb\"}},\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"passed\",\"tool\":\"check\",\"v\":1}", decode: func(b []byte) (any, error) { var v StepsByNodeIDValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "a run ask of the checking program over two heads", subject: "mesh.seat.node-build-go.accept.check", given: "{\"asker\":\"mesh-delivery-checker\",\"at\":\"2026-11-05T10:00:00Z\",\"id\":\"r5e6\",\"kind\":\"check\",\"language\":\"go\",\"run\":{\"args\":[\"-verdict\"],\"heads\":[{\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"repository\":\"novox/mesh-delivery\"},{\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"repository\":\"novox/mesh-sdk\"}],\"input\":{\"digest\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"size\":7,\"verb\":\"seat:mesh-delivery-checker.input\"},\"program\":{\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"dir\":\"modules/mesh-delivery-checker\",\"package\":\"./cmd/mesh-checking-program\",\"repository\":\"novox/mesh-delivery\"},\"reader\":\"checking program of mesh-delivery head\"},\"v\":1}", wire: "{\"asker\":\"mesh-delivery-checker\",\"at\":\"2026-11-05T10:00:00Z\",\"id\":\"r5e6\",\"kind\":\"check\",\"language\":\"go\",\"run\":{\"args\":[\"-verdict\"],\"heads\":[{\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"repository\":\"novox/mesh-delivery\"},{\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"repository\":\"novox/mesh-sdk\"}],\"input\":{\"digest\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"size\":7,\"verb\":\"seat:mesh-delivery-checker.input\"},\"program\":{\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"dir\":\"modules/mesh-delivery-checker\",\"package\":\"./cmd/mesh-checking-program\",\"repository\":\"novox/mesh-delivery\"},\"reader\":\"checking program of mesh-delivery head\"},\"v\":1}", + decode: func(b []byte) (any, error) { var v CheckAccept; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-build-go\\.accept\\.check$")}, + {name: "a finished run step whose program exited non-zero", subject: "", given: "{\"ended\":\"2026-11-05T10:01:00Z\",\"id\":\"r5e6\",\"node\":\"ace\",\"result\":{\"built\":true,\"exit\":2,\"key\":\"program-0123456789abcdef\",\"said\":\"the facts name no head\"},\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"failed\",\"tool\":\"check\",\"v\":1}", wire: "{\"ended\":\"2026-11-05T10:01:00Z\",\"id\":\"r5e6\",\"node\":\"ace\",\"result\":{\"built\":true,\"exit\":2,\"key\":\"program-0123456789abcdef\",\"said\":\"the facts name no head\"},\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"failed\",\"tool\":\"check\",\"v\":1}", + decode: func(b []byte) (any, error) { var v StepsByNodeIDValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, } for _, c := range cases { v, err := c.decode([]byte(c.given)) diff --git a/contracts/go/seats/nodebuildimage/contract.go b/contracts/go/seats/nodebuildimage/contract.go index d80d6348..dde300d1 100644 --- a/contracts/go/seats/nodebuildimage/contract.go +++ b/contracts/go/seats/nodebuildimage/contract.go @@ -386,6 +386,51 @@ type Program struct { Path string `json:"path"` } +// RunAnswer is what a run ask's step answers: whether this run built the program or took the one kept under its key, the key, the program's exit, the last line of its standard output and the first of its standard error. +type RunAnswer struct { + Built bool `json:"built"` + Exit int64 `json:"exit"` + Key string `json:"key"` + // raw when it is JSON, else a JSON string + Output any `json:"output,omitempty"` + Said string `json:"said,omitempty"` +} + +// RunHead is one commit a run's program reads. +type RunHead struct { + Commit string `json:"commit"` + Repository string `json:"repository"` +} + +// RunInput is a run's input by reference: the seat verb the asker serves it on (seat:.), the sha256 of its bytes and its size, at most 256 KiB. +type RunInput struct { + // 64 lowercase hex digits + Digest string `json:"digest"` + // bytes + Size int64 `json:"size"` + Verb string `json:"verb"` +} + +// RunJob is a run ask's job: where its program is built, the heads it reads (each laid read-only at its repository's path), its input by reference, its arguments and what it is for. +type RunJob struct { + // the program's arguments, each a word or a flag + Args []string `json:"args,omitempty"` + // the commits the program reads, a repository at most once + Heads []RunHead `json:"heads"` + Input RunInput `json:"input"` + Program RunProgram `json:"program"` + // what the run is for, for a reader of the ask + Reader string `json:"reader,omitempty"` +} + +// RunProgram is where a run's program is built: the repository and full commit fetched, the Go module's directory in it and the program's import path. +type RunProgram struct { + Commit string `json:"commit"` + Dir string `json:"dir"` + Package string `json:"package"` + Repository string `json:"repository"` +} + // Step is one step a build module runs on a node. type Step struct { // when (RFC 3339 time) @@ -460,25 +505,33 @@ type StepAsk struct { Needs []string `json:"needs,omitempty"` // package to the tagged version a build pins Packages map[string]string `json:"packages,omitempty"` + // a run: a program built from a change and run in the build seat's sandbox over the heads, its input asked of a verb of the asker's seat; the Go seat's check stream alone carries it, as a compose + Run *RunJob `json:"run,omitempty"` // the layout version V int64 `json:"v,omitempty"` } -// StepResult is what a step answered, by the kind of ask: a build's artifacts, toolchain, imports and out-of-step modules; a check's fetch and levels; a compose's built, key and verdict; a declined ask's declined, needs and lacks. The fields of the kinds do not meet, so one layout holds the four. +// StepResult is what a step answered, by the kind of ask: a build's artifacts, toolchain, imports and out-of-step modules; a check's fetch and levels; a compose's built, key and verdict; a run's built, key, exit, output and said; a declined ask's declined, needs and lacks. The fields of the kinds do not meet, so one layout holds the five. type StepResult struct { Artifacts []BuiltArtifact `json:"artifacts,omitempty"` Built bool `json:"built,omitempty"` // the holder declined the ask: its node lacks what the ask needs - Declined bool `json:"declined,omitempty"` - Fetch *FetchAnswer `json:"fetch,omitempty"` - Imports []string `json:"imports,omitempty"` - Key string `json:"key,omitempty"` + Declined bool `json:"declined,omitempty"` + // a run's program's exit status + Exit int64 `json:"exit,omitempty"` + Fetch *FetchAnswer `json:"fetch,omitempty"` + Imports []string `json:"imports,omitempty"` + Key string `json:"key,omitempty"` // what the holder's node lacks of it Lacks []string `json:"lacks,omitempty"` Levels []LevelAnswer `json:"levels,omitempty"` // what the declined ask needs - Needs []string `json:"needs,omitempty"` - OutOfStep []string `json:"out-of-step,omitempty"` + Needs []string `json:"needs,omitempty"` + OutOfStep []string `json:"out-of-step,omitempty"` + // a run's program's last line of standard output: raw when it is JSON, else a JSON string + Output any `json:"output,omitempty"` + // a run's program's first line of standard error, or why the step could not run it + Said string `json:"said,omitempty"` Toolchain *ToolchainUsed `json:"toolchain,omitempty"` // the compose-candidate verdict (the composer's own layout) Verdict any `json:"verdict,omitempty"` diff --git a/contracts/go/seats/nodebuildnode/contract.go b/contracts/go/seats/nodebuildnode/contract.go index c40fa83d..ba461926 100644 --- a/contracts/go/seats/nodebuildnode/contract.go +++ b/contracts/go/seats/nodebuildnode/contract.go @@ -386,6 +386,51 @@ type Program struct { Path string `json:"path"` } +// RunAnswer is what a run ask's step answers: whether this run built the program or took the one kept under its key, the key, the program's exit, the last line of its standard output and the first of its standard error. +type RunAnswer struct { + Built bool `json:"built"` + Exit int64 `json:"exit"` + Key string `json:"key"` + // raw when it is JSON, else a JSON string + Output any `json:"output,omitempty"` + Said string `json:"said,omitempty"` +} + +// RunHead is one commit a run's program reads. +type RunHead struct { + Commit string `json:"commit"` + Repository string `json:"repository"` +} + +// RunInput is a run's input by reference: the seat verb the asker serves it on (seat:.), the sha256 of its bytes and its size, at most 256 KiB. +type RunInput struct { + // 64 lowercase hex digits + Digest string `json:"digest"` + // bytes + Size int64 `json:"size"` + Verb string `json:"verb"` +} + +// RunJob is a run ask's job: where its program is built, the heads it reads (each laid read-only at its repository's path), its input by reference, its arguments and what it is for. +type RunJob struct { + // the program's arguments, each a word or a flag + Args []string `json:"args,omitempty"` + // the commits the program reads, a repository at most once + Heads []RunHead `json:"heads"` + Input RunInput `json:"input"` + Program RunProgram `json:"program"` + // what the run is for, for a reader of the ask + Reader string `json:"reader,omitempty"` +} + +// RunProgram is where a run's program is built: the repository and full commit fetched, the Go module's directory in it and the program's import path. +type RunProgram struct { + Commit string `json:"commit"` + Dir string `json:"dir"` + Package string `json:"package"` + Repository string `json:"repository"` +} + // Step is one step a build module runs on a node. type Step struct { // when (RFC 3339 time) @@ -460,25 +505,33 @@ type StepAsk struct { Needs []string `json:"needs,omitempty"` // package to the tagged version a build pins Packages map[string]string `json:"packages,omitempty"` + // a run: a program built from a change and run in the build seat's sandbox over the heads, its input asked of a verb of the asker's seat; the Go seat's check stream alone carries it, as a compose + Run *RunJob `json:"run,omitempty"` // the layout version V int64 `json:"v,omitempty"` } -// StepResult is what a step answered, by the kind of ask: a build's artifacts, toolchain, imports and out-of-step modules; a check's fetch and levels; a compose's built, key and verdict; a declined ask's declined, needs and lacks. The fields of the kinds do not meet, so one layout holds the four. +// StepResult is what a step answered, by the kind of ask: a build's artifacts, toolchain, imports and out-of-step modules; a check's fetch and levels; a compose's built, key and verdict; a run's built, key, exit, output and said; a declined ask's declined, needs and lacks. The fields of the kinds do not meet, so one layout holds the five. type StepResult struct { Artifacts []BuiltArtifact `json:"artifacts,omitempty"` Built bool `json:"built,omitempty"` // the holder declined the ask: its node lacks what the ask needs - Declined bool `json:"declined,omitempty"` - Fetch *FetchAnswer `json:"fetch,omitempty"` - Imports []string `json:"imports,omitempty"` - Key string `json:"key,omitempty"` + Declined bool `json:"declined,omitempty"` + // a run's program's exit status + Exit int64 `json:"exit,omitempty"` + Fetch *FetchAnswer `json:"fetch,omitempty"` + Imports []string `json:"imports,omitempty"` + Key string `json:"key,omitempty"` // what the holder's node lacks of it Lacks []string `json:"lacks,omitempty"` Levels []LevelAnswer `json:"levels,omitempty"` // what the declined ask needs - Needs []string `json:"needs,omitempty"` - OutOfStep []string `json:"out-of-step,omitempty"` + Needs []string `json:"needs,omitempty"` + OutOfStep []string `json:"out-of-step,omitempty"` + // a run's program's last line of standard output: raw when it is JSON, else a JSON string + Output any `json:"output,omitempty"` + // a run's program's first line of standard error, or why the step could not run it + Said string `json:"said,omitempty"` Toolchain *ToolchainUsed `json:"toolchain,omitempty"` // the compose-candidate verdict (the composer's own layout) Verdict any `json:"verdict,omitempty"` diff --git a/contracts/parts/build-step.json b/contracts/parts/build-step.json index 82602f89..37c20cb4 100644 --- a/contracts/parts/build-step.json +++ b/contracts/parts/build-step.json @@ -174,6 +174,10 @@ "request" ] }, + "run": { + "$ref": "#/defs/RunJob", + "description": "a run: a program built from a change and run in the build seat's sandbox over the heads, its input asked of a verb of the asker's seat; the Go seat's check stream alone carries it, as a compose" + }, "needs": { "type": "array", "items": { @@ -259,6 +263,129 @@ "built" ] }, + "RunJob": { + "type": "object", + "description": "a run ask's job: where its program is built, the heads it reads (each laid read-only at its repository's path), its input by reference, its arguments and what it is for", + "properties": { + "program": { + "$ref": "#/defs/RunProgram" + }, + "heads": { + "type": "array", + "items": { + "$ref": "#/defs/RunHead" + }, + "description": "the commits the program reads, a repository at most once" + }, + "input": { + "$ref": "#/defs/RunInput" + }, + "args": { + "type": "array", + "items": { + "type": "string" + }, + "description": "the program's arguments, each a word or a flag" + }, + "reader": { + "type": "string", + "description": "what the run is for, for a reader of the ask" + } + }, + "required": [ + "program", + "heads", + "input" + ] + }, + "RunProgram": { + "type": "object", + "description": "where a run's program is built: the repository and full commit fetched, the Go module's directory in it and the program's import path", + "properties": { + "repository": { + "type": "string" + }, + "commit": { + "type": "string" + }, + "dir": { + "type": "string" + }, + "package": { + "type": "string" + } + }, + "required": [ + "repository", + "commit", + "dir", + "package" + ] + }, + "RunHead": { + "type": "object", + "description": "one commit a run's program reads", + "properties": { + "repository": { + "type": "string" + }, + "commit": { + "type": "string" + } + }, + "required": [ + "repository", + "commit" + ] + }, + "RunInput": { + "type": "object", + "description": "a run's input by reference: the seat verb the asker serves it on (seat:.), the sha256 of its bytes and its size, at most 256 KiB", + "properties": { + "verb": { + "type": "string" + }, + "digest": { + "type": "string", + "description": "64 lowercase hex digits" + }, + "size": { + "type": "integer", + "description": "bytes" + } + }, + "required": [ + "verb", + "digest", + "size" + ] + }, + "RunAnswer": { + "type": "object", + "description": "what a run ask's step answers: whether this run built the program or took the one kept under its key, the key, the program's exit, the last line of its standard output and the first of its standard error", + "properties": { + "built": { + "type": "boolean" + }, + "key": { + "type": "string" + }, + "exit": { + "type": "integer" + }, + "output": { + "description": "raw when it is JSON, else a JSON string" + }, + "said": { + "type": "string" + } + }, + "required": [ + "built", + "key", + "exit" + ] + }, "Step": { "type": "object", "properties": { @@ -678,7 +805,7 @@ }, "StepResult": { "type": "object", - "description": "what a step answered, by the kind of ask: a build's artifacts, toolchain, imports and out-of-step modules; a check's fetch and levels; a compose's built, key and verdict; a declined ask's declined, needs and lacks. The fields of the kinds do not meet, so one layout holds the four", + "description": "what a step answered, by the kind of ask: a build's artifacts, toolchain, imports and out-of-step modules; a check's fetch and levels; a compose's built, key and verdict; a run's built, key, exit, output and said; a declined ask's declined, needs and lacks. The fields of the kinds do not meet, so one layout holds the five", "properties": { "artifacts": { "type": "array", @@ -736,6 +863,17 @@ "type": "string" }, "description": "what the holder's node lacks of it" + }, + "exit": { + "type": "integer", + "description": "a run's program's exit status" + }, + "output": { + "description": "a run's program's last line of standard output: raw when it is JSON, else a JSON string" + }, + "said": { + "type": "string", + "description": "a run's program's first line of standard error, or why the step could not run it" } } }, diff --git a/contracts/seats/mesh-delivery-checker/fixtures/states.json b/contracts/seats/mesh-delivery-checker/fixtures/states.json index ead92035..a2666bc7 100644 --- a/contracts/seats/mesh-delivery-checker/fixtures/states.json +++ b/contracts/seats/mesh-delivery-checker/fixtures/states.json @@ -9,6 +9,42 @@ "state": "running" } }, + { + "name": "a pass judged by the checking program built at a mesh-delivery head", + "of": "state:verdicts/.", + "given": { + "v": 1, + "group": "feat/registry", + "heads": "0123456789ab", + "state": "pass", + "levels": { + "unit": { + "run": 12, + "kept": 0 + }, + "integration": { + "run": 3, + "kept": 0 + } + }, + "judged-by": { + "program": { + "repository": "novox/mesh-delivery", + "commit": "0123456789abcdef0123456789abcdef01234567", + "dir": "modules/mesh-delivery-checker", + "package": "./cmd/mesh-checking-program" + }, + "key": "program-0123456789abcdef", + "built": true, + "heads": [ + { + "repository": "novox/mesh-delivery", + "commit": "0123456789abcdef0123456789abcdef01234567" + } + ] + } + } + }, { "name": "a kept pass", "of": "state:kept/.", diff --git a/contracts/seats/mesh-delivery-checker/fixtures/verbs.json b/contracts/seats/mesh-delivery-checker/fixtures/verbs.json new file mode 100644 index 00000000..af54d39f --- /dev/null +++ b/contracts/seats/mesh-delivery-checker/fixtures/verbs.json @@ -0,0 +1,17 @@ +[ + { + "name": "a checking program asks its input by its digest", + "of": "verb:input.input", + "given": { + "digest": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + { + "name": "the input answered as its exact bytes, base64", + "of": "verb:input.answer", + "given": { + "digest": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "input": "eyJ2IjoxfQ==" + } + } +] diff --git a/contracts/seats/mesh-delivery-checker/seat.json b/contracts/seats/mesh-delivery-checker/seat.json index 90f99cc3..4354b5ca 100644 --- a/contracts/seats/mesh-delivery-checker/seat.json +++ b/contracts/seats/mesh-delivery-checker/seat.json @@ -140,6 +140,40 @@ "ended" ] } + }, + { + "name": "input", + "description": "A checking program's input the checker keeps, by the sha256 of its exact bytes: the facts of a check its verdict is asked of, or the heads its seat list is asked of, as a run ask names it (its input's verb and digest). Answers the digest and the bytes, base64; a digest it keeps no input of is an error. Asked by the build module that runs the program, as its manifest's invokes grants it. Read-only.", + "optional": true, + "input": { + "type": "object", + "properties": { + "digest": { + "type": "string", + "description": "the input's sha256, 64 lowercase hex digits, as the run ask names it" + } + }, + "required": [ + "digest" + ] + }, + "answer": { + "type": "object", + "properties": { + "digest": { + "type": "string", + "description": "the digest asked" + }, + "input": { + "type": "string", + "description": "the input's exact bytes, base64, so no encoding on the way changes a byte the digest covers" + } + }, + "required": [ + "digest", + "input" + ] + } } ], "description": "The merge check's tries: start one, read one, end one. The checker's own events stay under its own subjects (ADR 0620 §2).", @@ -455,6 +489,65 @@ "to" ] } + }, + "judged-by": { + "type": "object", + "description": "the checking program that judged the check: where it was built, the key its build is kept under, whether this check's run built it, and the heads it read (ADR 0644, ADR 0656). Absent in a value written before it", + "properties": { + "program": { + "type": "object", + "properties": { + "repository": { + "type": "string" + }, + "commit": { + "type": "string" + }, + "dir": { + "type": "string" + }, + "package": { + "type": "string" + } + }, + "required": [ + "repository", + "commit", + "dir", + "package" + ] + }, + "key": { + "type": "string", + "description": "program- and 16 hex digits" + }, + "built": { + "type": "boolean" + }, + "heads": { + "type": "array", + "items": { + "type": "object", + "properties": { + "repository": { + "type": "string" + }, + "commit": { + "type": "string" + } + }, + "required": [ + "repository", + "commit" + ] + } + } + }, + "required": [ + "program", + "key", + "heads" + ] } }, "required": [ diff --git a/contracts/seats/node-build-go/fixtures/states.json b/contracts/seats/node-build-go/fixtures/states.json index 1a231fd5..bde9d6d6 100644 --- a/contracts/seats/node-build-go/fixtures/states.json +++ b/contracts/seats/node-build-go/fixtures/states.json @@ -253,5 +253,64 @@ } } } + }, + { + "name": "a run ask of the checking program over two heads", + "of": "accept:check", + "subject": "mesh.seat.node-build-go.accept.check", + "given": { + "v": 1, + "id": "r5e6", + "kind": "check", + "language": "go", + "asker": "mesh-delivery-checker", + "at": "2026-11-05T10:00:00Z", + "run": { + "program": { + "repository": "novox/mesh-delivery", + "commit": "0123456789abcdef0123456789abcdef01234567", + "dir": "modules/mesh-delivery-checker", + "package": "./cmd/mesh-checking-program" + }, + "heads": [ + { + "repository": "novox/mesh-delivery", + "commit": "0123456789abcdef0123456789abcdef01234567" + }, + { + "repository": "novox/mesh-sdk", + "commit": "0123456789abcdef0123456789abcdef01234567" + } + ], + "input": { + "verb": "seat:mesh-delivery-checker.input", + "digest": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "size": 7 + }, + "args": [ + "-verdict" + ], + "reader": "checking program of mesh-delivery head" + } + } + }, + { + "name": "a finished run step whose program exited non-zero", + "of": "state:steps/.", + "given": { + "v": 1, + "id": "r5e6", + "tool": "check", + "node": "ace", + "state": "failed", + "started": "2026-11-05T10:00:00Z", + "ended": "2026-11-05T10:01:00Z", + "result": { + "built": true, + "key": "program-0123456789abcdef", + "exit": 2, + "said": "the facts name no head" + } + } } ] diff --git a/contracts/ts/seats/meshdeliverychecker/src/index.ts b/contracts/ts/seats/meshdeliverychecker/src/index.ts index 814286b7..91608f2d 100644 --- a/contracts/ts/seats/meshdeliverychecker/src/index.ts +++ b/contracts/ts/seats/meshdeliverychecker/src/index.ts @@ -8,6 +8,7 @@ export const verbs = { try: "try", tryRead: "try-read", tryEnd: "try-end", + input: "input", } as const; export const holderEvents = { @@ -181,6 +182,20 @@ export interface TryEndAnswer { "id": string; } +/** is what input takes. */ +export interface InputInput { + /** the input's sha256, 64 lowercase hex digits, as the run ask names it */ + "digest": string; +} + +/** is what input answers. */ +export interface InputAnswer { + /** the digest asked */ + "digest": string; + /** the input's exact bytes, base64, so no encoding on the way changes a byte the digest covers */ + "input": string; +} + export interface VerdictHolderEventSeriesItem { /** its artifact, or the package's ecosystem name as the module's packages name it */ "artifact": string; @@ -247,6 +262,27 @@ export interface VerdictsByGroupHeadsValueFailuresItem { "words"?: string; } +export interface VerdictsByGroupHeadsValueJudgedByHeadsItem { + "commit": string; + "repository": string; +} + +export interface VerdictsByGroupHeadsValueJudgedByProgram { + "commit": string; + "dir": string; + "package": string; + "repository": string; +} + +/** the checking program that judged the check: where it was built, the key its build is kept under, whether this check's run built it, and the heads it read (ADR 0644, ADR 0656). Absent in a value written before it */ +export interface VerdictsByGroupHeadsValueJudgedBy { + "built"?: boolean; + "heads": VerdictsByGroupHeadsValueJudgedByHeadsItem[]; + /** program- and 16 hex digits */ + "key": string; + "program": VerdictsByGroupHeadsValueJudgedByProgram; +} + export interface VerdictsByGroupHeadsValueLevelsValue { "kept": number; "run": number; @@ -281,6 +317,8 @@ export interface VerdictsByGroupHeadsValue { "group": string; /** 12 hex digits */ "heads": string; + /** the checking program that judged the check: where it was built, the key its build is kept under, whether this check's run built it, and the heads it read (ADR 0644, ADR 0656). Absent in a value written before it */ + "judged-by"?: VerdictsByGroupHeadsValueJudgedBy; /** by level, unit or integration */ "levels"?: Record; "machines"?: string[]; diff --git a/contracts/ts/seats/nodebuildgo/src/index.ts b/contracts/ts/seats/nodebuildgo/src/index.ts index 1898a63d..0c6834dc 100644 --- a/contracts/ts/seats/nodebuildgo/src/index.ts +++ b/contracts/ts/seats/nodebuildgo/src/index.ts @@ -129,6 +129,11 @@ export type PlacedBeside = buildStep.PlacedBeside; export type Program = buildStep.Program; export type ResumeAnswer = buildStep.ResumeAnswer; export type ResumeInput = buildStep.ResumeInput; +export type RunAnswer = buildStep.RunAnswer; +export type RunHead = buildStep.RunHead; +export type RunInput = buildStep.RunInput; +export type RunJob = buildStep.RunJob; +export type RunProgram = buildStep.RunProgram; export type Step = buildStep.Step; export type StepAsk = buildStep.StepAsk; export type StepAskBuild = buildStep.StepAskBuild; diff --git a/contracts/ts/seats/nodebuildgo/src/parts/build-step.ts b/contracts/ts/seats/nodebuildgo/src/parts/build-step.ts index 1b4aabcc..c8843899 100644 --- a/contracts/ts/seats/nodebuildgo/src/parts/build-step.ts +++ b/contracts/ts/seats/nodebuildgo/src/parts/build-step.ts @@ -197,6 +197,51 @@ export interface Program { "path": string; } +/** what a run ask's step answers: whether this run built the program or took the one kept under its key, the key, the program's exit, the last line of its standard output and the first of its standard error */ +export interface RunAnswer { + "built": boolean; + "exit": number; + "key": string; + /** raw when it is JSON, else a JSON string */ + "output"?: unknown; + "said"?: string; +} + +/** one commit a run's program reads */ +export interface RunHead { + "commit": string; + "repository": string; +} + +/** a run's input by reference: the seat verb the asker serves it on (seat:.), the sha256 of its bytes and its size, at most 256 KiB */ +export interface RunInput { + /** 64 lowercase hex digits */ + "digest": string; + /** bytes */ + "size": number; + "verb": string; +} + +/** a run ask's job: where its program is built, the heads it reads (each laid read-only at its repository's path), its input by reference, its arguments and what it is for */ +export interface RunJob { + /** the program's arguments, each a word or a flag */ + "args"?: string[]; + /** the commits the program reads, a repository at most once */ + "heads": RunHead[]; + "input": RunInput; + "program": RunProgram; + /** what the run is for, for a reader of the ask */ + "reader"?: string; +} + +/** where a run's program is built: the repository and full commit fetched, the Go module's directory in it and the program's import path */ +export interface RunProgram { + "commit": string; + "dir": string; + "package": string; + "repository": string; +} + /** one step a build module runs on a node */ export interface Step { /** when (RFC 3339 time) */ @@ -268,16 +313,20 @@ export interface StepAsk { "needs"?: string[]; /** package to the tagged version a build pins */ "packages"?: Record; + /** a run: a program built from a change and run in the build seat's sandbox over the heads, its input asked of a verb of the asker's seat; the Go seat's check stream alone carries it, as a compose */ + "run"?: RunJob; /** the layout version */ "v"?: number; } -/** what a step answered, by the kind of ask: a build's artifacts, toolchain, imports and out-of-step modules; a check's fetch and levels; a compose's built, key and verdict; a declined ask's declined, needs and lacks. The fields of the kinds do not meet, so one layout holds the four */ +/** what a step answered, by the kind of ask: a build's artifacts, toolchain, imports and out-of-step modules; a check's fetch and levels; a compose's built, key and verdict; a run's built, key, exit, output and said; a declined ask's declined, needs and lacks. The fields of the kinds do not meet, so one layout holds the five */ export interface StepResult { "artifacts"?: BuiltArtifact[]; "built"?: boolean; /** the holder declined the ask: its node lacks what the ask needs */ "declined"?: boolean; + /** a run's program's exit status */ + "exit"?: number; "fetch"?: FetchAnswer; "imports"?: string[]; "key"?: string; @@ -287,6 +336,10 @@ export interface StepResult { /** what the declined ask needs */ "needs"?: string[]; "out-of-step"?: string[]; + /** a run's program's last line of standard output: raw when it is JSON, else a JSON string */ + "output"?: unknown; + /** a run's program's first line of standard error, or why the step could not run it */ + "said"?: string; "toolchain"?: ToolchainUsed; /** the compose-candidate verdict (the composer's own layout) */ "verdict"?: unknown; diff --git a/contracts/ts/seats/nodebuildimage/src/index.ts b/contracts/ts/seats/nodebuildimage/src/index.ts index 3c05df65..91e5d235 100644 --- a/contracts/ts/seats/nodebuildimage/src/index.ts +++ b/contracts/ts/seats/nodebuildimage/src/index.ts @@ -129,6 +129,11 @@ export type PlacedBeside = buildStep.PlacedBeside; export type Program = buildStep.Program; export type ResumeAnswer = buildStep.ResumeAnswer; export type ResumeInput = buildStep.ResumeInput; +export type RunAnswer = buildStep.RunAnswer; +export type RunHead = buildStep.RunHead; +export type RunInput = buildStep.RunInput; +export type RunJob = buildStep.RunJob; +export type RunProgram = buildStep.RunProgram; export type Step = buildStep.Step; export type StepAsk = buildStep.StepAsk; export type StepAskBuild = buildStep.StepAskBuild; diff --git a/contracts/ts/seats/nodebuildimage/src/parts/build-step.ts b/contracts/ts/seats/nodebuildimage/src/parts/build-step.ts index 1b4aabcc..c8843899 100644 --- a/contracts/ts/seats/nodebuildimage/src/parts/build-step.ts +++ b/contracts/ts/seats/nodebuildimage/src/parts/build-step.ts @@ -197,6 +197,51 @@ export interface Program { "path": string; } +/** what a run ask's step answers: whether this run built the program or took the one kept under its key, the key, the program's exit, the last line of its standard output and the first of its standard error */ +export interface RunAnswer { + "built": boolean; + "exit": number; + "key": string; + /** raw when it is JSON, else a JSON string */ + "output"?: unknown; + "said"?: string; +} + +/** one commit a run's program reads */ +export interface RunHead { + "commit": string; + "repository": string; +} + +/** a run's input by reference: the seat verb the asker serves it on (seat:.), the sha256 of its bytes and its size, at most 256 KiB */ +export interface RunInput { + /** 64 lowercase hex digits */ + "digest": string; + /** bytes */ + "size": number; + "verb": string; +} + +/** a run ask's job: where its program is built, the heads it reads (each laid read-only at its repository's path), its input by reference, its arguments and what it is for */ +export interface RunJob { + /** the program's arguments, each a word or a flag */ + "args"?: string[]; + /** the commits the program reads, a repository at most once */ + "heads": RunHead[]; + "input": RunInput; + "program": RunProgram; + /** what the run is for, for a reader of the ask */ + "reader"?: string; +} + +/** where a run's program is built: the repository and full commit fetched, the Go module's directory in it and the program's import path */ +export interface RunProgram { + "commit": string; + "dir": string; + "package": string; + "repository": string; +} + /** one step a build module runs on a node */ export interface Step { /** when (RFC 3339 time) */ @@ -268,16 +313,20 @@ export interface StepAsk { "needs"?: string[]; /** package to the tagged version a build pins */ "packages"?: Record; + /** a run: a program built from a change and run in the build seat's sandbox over the heads, its input asked of a verb of the asker's seat; the Go seat's check stream alone carries it, as a compose */ + "run"?: RunJob; /** the layout version */ "v"?: number; } -/** what a step answered, by the kind of ask: a build's artifacts, toolchain, imports and out-of-step modules; a check's fetch and levels; a compose's built, key and verdict; a declined ask's declined, needs and lacks. The fields of the kinds do not meet, so one layout holds the four */ +/** what a step answered, by the kind of ask: a build's artifacts, toolchain, imports and out-of-step modules; a check's fetch and levels; a compose's built, key and verdict; a run's built, key, exit, output and said; a declined ask's declined, needs and lacks. The fields of the kinds do not meet, so one layout holds the five */ export interface StepResult { "artifacts"?: BuiltArtifact[]; "built"?: boolean; /** the holder declined the ask: its node lacks what the ask needs */ "declined"?: boolean; + /** a run's program's exit status */ + "exit"?: number; "fetch"?: FetchAnswer; "imports"?: string[]; "key"?: string; @@ -287,6 +336,10 @@ export interface StepResult { /** what the declined ask needs */ "needs"?: string[]; "out-of-step"?: string[]; + /** a run's program's last line of standard output: raw when it is JSON, else a JSON string */ + "output"?: unknown; + /** a run's program's first line of standard error, or why the step could not run it */ + "said"?: string; "toolchain"?: ToolchainUsed; /** the compose-candidate verdict (the composer's own layout) */ "verdict"?: unknown; diff --git a/contracts/ts/seats/nodebuildnode/src/index.ts b/contracts/ts/seats/nodebuildnode/src/index.ts index 140f27c3..faba1e6a 100644 --- a/contracts/ts/seats/nodebuildnode/src/index.ts +++ b/contracts/ts/seats/nodebuildnode/src/index.ts @@ -129,6 +129,11 @@ export type PlacedBeside = buildStep.PlacedBeside; export type Program = buildStep.Program; export type ResumeAnswer = buildStep.ResumeAnswer; export type ResumeInput = buildStep.ResumeInput; +export type RunAnswer = buildStep.RunAnswer; +export type RunHead = buildStep.RunHead; +export type RunInput = buildStep.RunInput; +export type RunJob = buildStep.RunJob; +export type RunProgram = buildStep.RunProgram; export type Step = buildStep.Step; export type StepAsk = buildStep.StepAsk; export type StepAskBuild = buildStep.StepAskBuild; diff --git a/contracts/ts/seats/nodebuildnode/src/parts/build-step.ts b/contracts/ts/seats/nodebuildnode/src/parts/build-step.ts index 1b4aabcc..c8843899 100644 --- a/contracts/ts/seats/nodebuildnode/src/parts/build-step.ts +++ b/contracts/ts/seats/nodebuildnode/src/parts/build-step.ts @@ -197,6 +197,51 @@ export interface Program { "path": string; } +/** what a run ask's step answers: whether this run built the program or took the one kept under its key, the key, the program's exit, the last line of its standard output and the first of its standard error */ +export interface RunAnswer { + "built": boolean; + "exit": number; + "key": string; + /** raw when it is JSON, else a JSON string */ + "output"?: unknown; + "said"?: string; +} + +/** one commit a run's program reads */ +export interface RunHead { + "commit": string; + "repository": string; +} + +/** a run's input by reference: the seat verb the asker serves it on (seat:.), the sha256 of its bytes and its size, at most 256 KiB */ +export interface RunInput { + /** 64 lowercase hex digits */ + "digest": string; + /** bytes */ + "size": number; + "verb": string; +} + +/** a run ask's job: where its program is built, the heads it reads (each laid read-only at its repository's path), its input by reference, its arguments and what it is for */ +export interface RunJob { + /** the program's arguments, each a word or a flag */ + "args"?: string[]; + /** the commits the program reads, a repository at most once */ + "heads": RunHead[]; + "input": RunInput; + "program": RunProgram; + /** what the run is for, for a reader of the ask */ + "reader"?: string; +} + +/** where a run's program is built: the repository and full commit fetched, the Go module's directory in it and the program's import path */ +export interface RunProgram { + "commit": string; + "dir": string; + "package": string; + "repository": string; +} + /** one step a build module runs on a node */ export interface Step { /** when (RFC 3339 time) */ @@ -268,16 +313,20 @@ export interface StepAsk { "needs"?: string[]; /** package to the tagged version a build pins */ "packages"?: Record; + /** a run: a program built from a change and run in the build seat's sandbox over the heads, its input asked of a verb of the asker's seat; the Go seat's check stream alone carries it, as a compose */ + "run"?: RunJob; /** the layout version */ "v"?: number; } -/** what a step answered, by the kind of ask: a build's artifacts, toolchain, imports and out-of-step modules; a check's fetch and levels; a compose's built, key and verdict; a declined ask's declined, needs and lacks. The fields of the kinds do not meet, so one layout holds the four */ +/** what a step answered, by the kind of ask: a build's artifacts, toolchain, imports and out-of-step modules; a check's fetch and levels; a compose's built, key and verdict; a run's built, key, exit, output and said; a declined ask's declined, needs and lacks. The fields of the kinds do not meet, so one layout holds the five */ export interface StepResult { "artifacts"?: BuiltArtifact[]; "built"?: boolean; /** the holder declined the ask: its node lacks what the ask needs */ "declined"?: boolean; + /** a run's program's exit status */ + "exit"?: number; "fetch"?: FetchAnswer; "imports"?: string[]; "key"?: string; @@ -287,6 +336,10 @@ export interface StepResult { /** what the declined ask needs */ "needs"?: string[]; "out-of-step"?: string[]; + /** a run's program's last line of standard output: raw when it is JSON, else a JSON string */ + "output"?: unknown; + /** a run's program's first line of standard error, or why the step could not run it */ + "said"?: string; "toolchain"?: ToolchainUsed; /** the compose-candidate verdict (the composer's own layout) */ "verdict"?: unknown;