diff --git a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker-tools/main.go b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker-tools/main.go index 81101036..b800cfae 100644 --- a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker-tools/main.go +++ b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker-tools/main.go @@ -229,6 +229,20 @@ func SeatTryTools(tr Trying) []stdio.Tool { return out } +// SeatInputTools are the seat's verb input (check.InputVerb): a checking program's input as the checker keeps it, asked by +// the Go build module that runs the program (delivery.RunInput.Read), never read from the checker's state by it. +func SeatInputTools(r check.Reader) []stdio.Tool { + return []stdio.Tool{{Name: Seat + "." + delivery.CheckerInputVerb, + Description: "A checking program's input the checker keeps, by the sha256 of its exact bytes, as a run ask names it: the digest " + + "and the bytes, base64. A digest it keeps no input of, or bytes that read back as another digest, is an error. Read-only.", + Input: map[string]any{"type": "object", "required": []string{"digest"}, "properties": map[string]any{ + "digest": map[string]any{"type": "string", "description": "the input's sha256, 64 lowercase hex digits"}}}, + Run: func(args map[string]any) (any, error) { + digest, _ := args["digest"].(string) + return r.Input(digest) + }}} +} + // Tools are the read tools over the checker's states. func Tools(r check.Reader) []stdio.Tool { return []stdio.Tool{ @@ -381,6 +395,7 @@ func main() { return err }} tools := append(append(append(append(Tools(r), TryTools(trying)...), OpenTriesTools(trying)...), SeatTryTools(trying)...), RecoveryTools(r, written)...) + tools = append(tools, SeatInputTools(r)...) if err := stdio.Serve("", logged(tools)); err != nil { fmt.Fprintf(os.Stderr, "[mesh-delivery-checker-tools] %v\n", err) os.Exit(1) diff --git a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker-tools/main_test.go b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker-tools/main_test.go index 3ef71945..cb5da15c 100644 --- a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker-tools/main_test.go +++ b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker-tools/main_test.go @@ -9,6 +9,7 @@ import ( "time" "git.novox.be/novox/mesh-delivery/sdk/tryevent" + "git.novox.be/novox/mesh-sdk/go/delivery" "mesh-delivery-checker/internal/check" ) @@ -108,6 +109,21 @@ func TestTheTryToolsAskAndReadATry(t *testing.T) { // ADR 0501 §5, the agreed i if _, err := verbs[0].Run(map[string]any{"repository": "novox/hq", "head": strings.Repeat("1", 40)}); err == nil { t.Error("the verb tried a repository nothing checks") } + // The seat's verb input (issue 1921, ADR 0596 §2): a checking program's input as kept, which the Go build module asks. + body := []byte(`{"v":1}`) + in, _ := delivery.RunInputOf(check.InputVerb, body) + inputs := SeatInputTools(check.Reader{InputState: check.StateRead{Get: func(key string) (json.RawMessage, error) { + if key == in.Digest { + return body, nil + } + return nil, nil + }}}) + if len(inputs) != 1 || inputs[0].Name != Seat+".input" { + t.Fatalf("input verbs = %+v", inputs) + } + if a, err := inputs[0].Run(map[string]any{"digest": in.Digest}); err != nil || string(a.(delivery.RunInputAnswer).Input) != string(body) { + t.Errorf("verb input = %+v, %v", a, err) + } // A group try (issue 1921): the members, each an exact commit; members and one repository together are refused; the // description says what it does in the words a search for "try a group" finds. later := TryTools(Trying{Tries: store, Ender: store, Now: func() time.Time { return time.Unix(2, 5) }, diff --git a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/integration_test.go b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/integration_test.go index f85f0add..6f465286 100644 --- a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/integration_test.go +++ b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/integration_test.go @@ -93,9 +93,7 @@ type fakeBuildModules struct { beside map[string][]string // names are the nodes' names the last check ask carried, for the names check (ADR 0567 §12). names []string - // inputs is the checker's state of run inputs; runs counts the run asks answered, and broken, when set, is why the - // checking program does not build (issue 1921). - inputs jetstream.KeyValue + // runs counts the run asks answered, and broken, when set, is why the checking program does not build (issue 1921). runs atomic.Int32 broken atomic.Value // drop, when set, drops every run ask unanswered and counts it in dropped, as a build module that does not know the job @@ -125,20 +123,20 @@ func (d droppingAsks) Next(ctx context.Context, kind string, wait time.Duration) } // run answers a run ask as mesh-build-go does: the checking program's work (rules.Program) over the input the ask names, -// read from the checker's state inputs and checked against its digest, with no heads of its own laid. +// asked of the seat verb it names on the bus and checked against its digest (delivery.RunInput.Read, as mesh-build-go +// reads it), with no heads of its own laid. func (f *fakeBuildModules) run(ctx context.Context, a delivery.StepAsk) (any, error) { f.runs.Add(1) if why, _ := f.broken.Load().(string); why != "" { return nil, errors.New(why) } answer := delivery.RunAnswer{Built: true, Key: "program-0123456789abcdef"} - out, err := rules.Program(a.Run.Args, func() ([]byte, error) { - e, err := f.inputs.Get(ctx, a.Run.Input.Digest) - if err != nil { - return nil, err - } - return e.Value(), a.Run.Input.Verify(e.Value()) - }, fstest.MapFS{}) + // As mesh-build-go does, the input is asked before the program runs, and a step whose input cannot be had fails. + body, err := a.Run.Input.Read(func(key string, body any) (json.RawMessage, error) { return askVerb(f.js.Conn(), key, body) }) + if err != nil { + return nil, err + } + out, err := rules.Program(a.Run.Args, func() ([]byte, error) { return body, nil }, fstest.MapFS{}) if err != nil { answer.Exit, answer.Said = 2, err.Error() } @@ -146,6 +144,66 @@ func (f *fakeBuildModules) run(ctx context.Context, a delivery.StepAsk) (any, er return answer, nil } +// askVerb asks a seat verb on the subject that names no caller and reads the runtime's reply, {result, error}. +func askVerb(nc *nats.Conn, key string, body any) (json.RawMessage, error) { + seat, verb, ok := strings.Cut(strings.TrimPrefix(key, "seat:"), ".") + if !ok || !strings.HasPrefix(key, "seat:") { + return nil, fmt.Errorf("%q names no seat verb", key) + } + data, err := json.Marshal(body) + if err != nil { + return nil, err + } + msg, err := nc.Request("mesh.seat."+seat+".tool."+verb, data, 5*time.Second) + if err != nil { + return nil, err + } + var r struct { + Result json.RawMessage `json:"result"` + Error string `json:"error"` + } + if err := json.Unmarshal(msg.Data, &r); err != nil { + return nil, err + } + if r.Error != "" { + return nil, errors.New(r.Error) + } + return r.Result, nil +} + +// serveInputVerb answers the checker seat's verb input on the bus as the checker's tools do (check.Reader.Input over the +// checker's state inputs), and counts what it answered (issue 1921, ADR 0596 §2). +func (b *bed) serveInputVerb(t *testing.T, nc *nats.Conn) { + t.Helper() + kv := b.buckets[check.InputsBucket] + r := check.Reader{InputState: check.StateRead{Get: func(key string) (json.RawMessage, error) { + e, err := kv.Get(b.ctx, key) + if errors.Is(err, jetstream.ErrKeyNotFound) { + return nil, nil + } + if err != nil { + return nil, err + } + return e.Value(), nil + }}} + if _, err := nc.Subscribe("mesh.seat.mesh-delivery-checker.tool."+delivery.CheckerInputVerb, func(m *nats.Msg) { + var ask delivery.RunInputAsk + reply := map[string]any{} + if err := json.Unmarshal(m.Data, &ask); err != nil { + reply["error"] = err.Error() + } else if a, err := r.Input(ask.Digest); err != nil { + reply["error"] = err.Error() + } else { + reply["result"] = a + b.inputsAnswered.Add(1) + } + data, _ := json.Marshal(reply) + _ = m.Respond(data) + }); err != nil { + t.Fatal(err) + } +} + func (f *fakeBuildModules) count(tool string) int { f.mu.Lock() defer f.mu.Unlock() @@ -312,6 +370,8 @@ type bed struct { js jetstream.JetStream buckets map[string]jetstream.KeyValue tools map[string]jetstream.KeyValue + // inputsAnswered counts the inputs the seat's verb input answered (issue 1921). + inputsAnswered atomic.Int32 fake *fakeBuildModules srvURL string announce []string @@ -476,7 +536,8 @@ func startBed(t *testing.T) *bed { b.setHealth(t, "n1", module) } b.serveForge(t, ctl) - b.fake = &fakeBuildModules{js: js, ctx: ctx, calls: map[string]int{}, inputs: b.buckets[check.InputsBucket]} + b.serveInputVerb(t, ctl) + b.fake = &fakeBuildModules{js: js, ctx: ctx, calls: map[string]int{}} b.fake.serve(t, "mesh-build-go", b.buckets[delivery.CheckerKeptBucket]) b.fake.serve(t, "mesh-build-node", b.buckets[delivery.CheckerKeptBucket]) b.report(t, "mesh-build-go", "n1", "go", "go1.27.1", "1") @@ -704,7 +765,7 @@ func TestCheckerJudgesACandidateAndOwesItsStatus(t *testing.T) { // N6/AC5, 1300 t.Errorf("n1's engine was asked %+v, want the check's candidate %s", got, key) } // Issue 1921 (B0): the verdict is the checking program's, built at the group's mesh-delivery head over its input kept in - // the state inputs, two run asks a check (the seat list, the verdict); the verdict names the program's build. + // the state inputs and asked of the seat's verb input, two run asks a check (the seat list, the verdict); the verdict names the program's build. if by := v.JudgedBy; by == nil || by.Program.Commit != head1 || by.Program.Package != check.CheckingProgramPackage || by.Key != "program-0123456789abcdef" || len(by.Heads) != 1 || by.Heads[0].Commit != head1 { t.Errorf("judged by %+v, want the checking program at the head %s", v.JudgedBy, head1) @@ -712,6 +773,10 @@ func TestCheckerJudgesACandidateAndOwesItsStatus(t *testing.T) { // N6/AC5, 1300 if n := b.fake.runs.Load(); n != 4 { t.Errorf("%d run asks, want two for each of the two checks", n) } + // The operator's answer on B0: each run's input reached the build module through the seat's verb input on the bus. + if n := b.inputsAnswered.Load(); n != 4 { + t.Errorf("the verb input answered %d inputs, want one for each run ask", n) + } b.fake.mu.Lock() names := b.fake.names b.fake.mu.Unlock() @@ -1069,7 +1134,7 @@ func TestTheCheckerSaysOwedReadableOnTheBus(t *testing.T) { // R2-1 } if said.Module != "mesh-delivery-checker" || said.Node != "n1" || !said.Keeps("owed") || !said.Readable("owed") || !said.Readable("previews") || !said.Readable("verdicts") || !said.Readable("kept") || !said.Readable("tries") || said.Readable("hearing") || - !said.Keeps("asks") || said.Readable("asks") || !said.Readable("inputs") || len(said.States) != 8 { - t.Fatalf("the checker said %+v, want its eight states, asks and hearing its own, with inputs, kept, owed, previews, tries and verdicts readable, tries by the delivery view, inputs by the Go build module (1929/AC4; 1887 A5b; build_usage; 1921)", said) + !said.Keeps("asks") || said.Readable("asks") || !said.Keeps("inputs") || said.Readable("inputs") || len(said.States) != 8 { + t.Fatalf("the checker said %+v, want its eight states, asks, hearing and inputs its own, with kept, owed, previews, tries and verdicts readable, tries by the delivery view, inputs asked through the seat's verb input (1929/AC4; 1887 A5b; build_usage; 1921)", said) } } diff --git a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/judge.go b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/judge.go index 8c2c3430..f90335c2 100644 --- a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/judge.go +++ b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/judge.go @@ -24,7 +24,7 @@ import ( // judgeDeps are the parts the judge of a check is made of. type judgeDeps struct { - // inputs keeps the checking program's inputs (check.InputsState). + // inputs keeps the checking program's inputs (check.InputsStateName, served on check.InputVerb). inputs check.Inputs // beginBound is how long a run ask may wait for a build node to begin it; check.DefaultBeginBound when 0. beginBound time.Duration diff --git a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/kept_test.go b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/kept_test.go index fb84bd24..c36810ad 100644 --- a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/kept_test.go +++ b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/kept_test.go @@ -38,8 +38,8 @@ func TestTheCheckerAnnouncesEveryStateItKeepsAndOwedReadable(t *testing.T) { // t.Errorf("announced %v, the manifest keeps %v", announced, manifest) } // kept is read by the Go build module for a check ask (issue 1887 design limit 1); tries by the delivery view (build_usage). - // inputs by the Go build module, which hands one to the checking program a run ask names (issue 1921). - if want := []string{"inputs", "kept", "owed", "previews", "tries", "verdicts"}; !equal(readable, want) { + // inputs are the checker's own: the Go build module asks the seat's verb input for one (ADR 0596 §2, issue 1921). + if want := []string{"kept", "owed", "previews", "tries", "verdicts"}; !equal(readable, want) { t.Errorf("readable %v, want %v", readable, want) } } diff --git a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/main.go b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/main.go index 375cf192..7d9eb21f 100644 --- a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/main.go +++ b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/main.go @@ -72,9 +72,10 @@ var keptStates = []announce.State{ {Name: "hearing"}, // The asks each running check put (check.AsksState): read by the checker's own checker_ask_again alone. {Name: "asks"}, - // The checking program's inputs by their digest (check.InputsState): read by the Go build module, which hands one to - // the program a run ask names (issue 1921). - {Name: check.InputsStateName, Readable: true}, + // The checking program's inputs by their digest (check.InputsStateName): the checker's own, read by its tools alone, + // which serve one on the seat's verb input to the Go build module that runs the program (check.InputVerb; ADR 0596 §2, + // issue 1921). + {Name: check.InputsStateName}, {Name: "kept", Readable: true}, {Name: "owed", Readable: true}, {Name: "previews", Readable: true}, diff --git a/modules/mesh-delivery-checker/internal/check/examiner_test.go b/modules/mesh-delivery-checker/internal/check/examiner_test.go index a755ef7a..700e9507 100644 --- a/modules/mesh-delivery-checker/internal/check/examiner_test.go +++ b/modules/mesh-delivery-checker/internal/check/examiner_test.go @@ -429,7 +429,7 @@ func TestACleanMergeIsFetchedAtItsTrunkAndBothLevelsPass(t *testing.T) { // 1679 } in := b.runs[1].Run.Input // the facts kept as their exact bytes under their digest body, _, found, _ := b.inputs.Get(context.Background(), in.Digest) - if !found || in.Verify(body) != nil || in.State != InputsState || + if !found || in.Verify(body) != nil || in.Verb != InputVerb || !strings.Contains(string(body), `"group":"feat/x"`) { t.Errorf("%s: the verdict's input %+v is not kept whole: %s", row.name, in, body) } diff --git a/modules/mesh-delivery-checker/internal/check/judge.go b/modules/mesh-delivery-checker/internal/check/judge.go index d079873c..bc063e33 100644 --- a/modules/mesh-delivery-checker/internal/check/judge.go +++ b/modules/mesh-delivery-checker/internal/check/judge.go @@ -138,7 +138,7 @@ type Examiner struct { // and fingerprints a group try's manifest validator and composition judge (issue 1921); nil leaves a whole try with no // manifest or composition judged, said. Candidates TryCandidates - // Inputs keeps the checking program's inputs (the state InputsState); BeginBound is how long a run ask may wait for a + // Inputs keeps the checking program's inputs (the state InputsStateName, served on InputVerb); BeginBound is how long a run ask may wait for a // build node to begin it (DefaultBeginBound when zero). Inputs Inputs BeginBound time.Duration diff --git a/modules/mesh-delivery-checker/internal/check/programs.go b/modules/mesh-delivery-checker/internal/check/programs.go index 5572a0fc..802a63fa 100644 --- a/modules/mesh-delivery-checker/internal/check/programs.go +++ b/modules/mesh-delivery-checker/internal/check/programs.go @@ -9,6 +9,7 @@ import ( "time" "git.novox.be/novox/mesh-sdk/go/buildstep" + "git.novox.be/novox/mesh-sdk/go/catalogue" "git.novox.be/novox/mesh-sdk/go/delivery" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun" @@ -22,12 +23,14 @@ import ( // verdict of the facts the asking gathered (`-verdict`). The running checker asks, keeps and records; it judges nothing. const ( - // InputsStateName is the checker's own state of run inputs, InputsBucket its bucket and InputsState its name as a run - // ask's input names it (delivery.RunInput.State): every input by the sha256 of its exact bytes, read by the Go build - // module, which checks the digest (delivery.RunInput.Verify). + // InputsStateName is the checker's own state of run inputs and InputsBucket its bucket: every input by the sha256 of + // its exact bytes, read by the checker alone. InputVerb is the seat verb a run ask's input names + // (delivery.RunInput.Verb), which the checker's tools serve from that state (Reader.Input): the Go build module asks it + // as its manifest's invokes grants it and checks the digest (delivery.RunInput.Read), never reading the state (ADR 0596 + // §2; the operator's answer on milestone 47/B0). InputsStateName = "inputs" InputsBucket = delivery.CheckerModule + "_" + InputsStateName - InputsState = delivery.CheckerModule + "." + InputsStateName + InputVerb = "seat:" + catalogue.CheckerSeat + "." + delivery.CheckerInputVerb // CheckingProgramDir and CheckingProgramPackage are where in mesh-delivery the checking program is built. CheckingProgramDir = "modules/mesh-delivery-checker" CheckingProgramPackage = "mesh-delivery-checker/cmd/mesh-checking-program" @@ -83,14 +86,14 @@ func (st *state) runProgram(ctx context.Context, args []string, body []byte) (js return nil, nil, fmt.Errorf("it cannot be built: the trunk head of %s is not known", DeliveryRepository) } if st.j.Inputs == nil { - return nil, nil, fmt.Errorf("its input cannot be kept: the checker keeps no state %s", InputsState) + return nil, nil, fmt.Errorf("its input cannot be kept: the checker keeps no state %s", InputsStateName) } - in, err := delivery.RunInputOf(InputsState, body) + in, err := delivery.RunInputOf(InputVerb, body) if err != nil { return nil, nil, err } if err := st.j.Inputs.Keep(ctx, in.Digest, body); err != nil { - return nil, nil, fmt.Errorf("its input could not be kept in %s: %w", InputsState, err) + return nil, nil, fmt.Errorf("its input could not be kept in %s: %w", InputsStateName, err) } job := delivery.RunJob{Program: prog, Heads: st.runHeads(), Input: in, Args: args, Reader: "the checking program at " + at} var ids []string diff --git a/modules/mesh-delivery-checker/internal/check/read.go b/modules/mesh-delivery-checker/internal/check/read.go index 39c6f61e..4722ba0d 100644 --- a/modules/mesh-delivery-checker/internal/check/read.go +++ b/modules/mesh-delivery-checker/internal/check/read.go @@ -1,6 +1,8 @@ package check import ( + "crypto/sha256" + "encoding/hex" "encoding/json" "fmt" "slices" @@ -37,7 +39,7 @@ type Reader struct { VerdictState, KeptState, CandidateState StateRead PreviewState StateRead // the checker's merge previews - InputState StateRead // the checking program's inputs (InputsState) + InputState StateRead // the checking program's inputs (InputsStateName) StepStates map[string]StateRead // the build modules' steps states, by module HaveState StateRead // the node-engine's have: whether a build module is on a node and running StepBound time.Duration // how long the checker waits for one step; DefaultStepBound when 0 @@ -263,10 +265,33 @@ func (r Reader) Kept(level, digest string) (map[string]json.RawMessage, string, return out, cut, nil } +// Input is the answer of the seat's verb input (InputVerb): the input kept under digest, its exact bytes, which the Go build +// module hands the checking program a run ask names (delivery.RunInput.Read). A digest that is no sha256, one the checker +// keeps nothing under, and bytes that read back as another digest are errors, never an answer. +func (r Reader) Input(digest string) (delivery.RunInputAnswer, error) { + if r.InputState.Get == nil { + return delivery.RunInputAnswer{}, fmt.Errorf("the state %s cannot be read here", InputsStateName) + } + if len(digest) != 64 || strings.Trim(digest, "0123456789abcdef") != "" { + return delivery.RunInputAnswer{}, fmt.Errorf("%q is no sha256 digest of 64 lowercase hex digits", digest) + } + raw, err := r.InputState.Get(digest) + if err != nil { + return delivery.RunInputAnswer{}, fmt.Errorf("the input %s cannot be read: %w", digest, err) + } + if len(raw) == 0 { + return delivery.RunInputAnswer{}, fmt.Errorf("the checker keeps no input %s (an input is kept a day)", digest) + } + if sum := sha256.Sum256(raw); hex.EncodeToString(sum[:]) != digest { + return delivery.RunInputAnswer{}, fmt.Errorf("the input kept under %s reads back as %d bytes of another digest", digest, len(raw)) + } + return delivery.RunInputAnswer{Digest: digest, Input: raw}, nil +} + // Inputs are the checking program's kept inputs: with a digest that input as kept, else every digest with its size. func (r Reader) Inputs(digest string) (map[string]json.RawMessage, string, error) { if r.InputState.Keys == nil || r.InputState.Get == nil { - return nil, "", fmt.Errorf("the state %s cannot be read here", InputsState) + return nil, "", fmt.Errorf("the state %s cannot be read here", InputsStateName) } if digest != "" { raw, err := r.InputState.Get(digest) diff --git a/modules/mesh-delivery-checker/internal/check/read_test.go b/modules/mesh-delivery-checker/internal/check/read_test.go index 4da7a1ec..4f7cbe19 100644 --- a/modules/mesh-delivery-checker/internal/check/read_test.go +++ b/modules/mesh-delivery-checker/internal/check/read_test.go @@ -125,7 +125,7 @@ func TestTheKeptToolAnswersTheKeysAsStoredByLevelAndByDigest(t *testing.T) { // // Issue 1921: checker_inputs answers the checking program's inputs, every digest with its size, or one as kept. inputs := newMemKV() body := []byte(`{"v":1,"group":"feat/x"}`) - in, _ := delivery.RunInputOf(InputsState, body) + in, _ := delivery.RunInputOf(InputVerb, body) _ = memInputs{inputs}.Keep(context.Background(), in.Digest, body) rd.InputState = kvRead(inputs) if all, _, err := rd.Inputs(""); err != nil || string(all[in.Digest]) != fmt.Sprintf(`{"size":%d}`, len(body)) { @@ -137,6 +137,26 @@ func TestTheKeptToolAnswersTheKeysAsStoredByLevelAndByDigest(t *testing.T) { // if _, _, err := rd.Inputs(strings.Repeat("0", 64)); err == nil { t.Error("an input nothing kept was answered") } + // The operator's answer on B0: the seat's verb input answers the exact bytes, which the build module's read takes; + // a digest that is none, one nothing is kept under, and bytes kept under another digest are errors. + if got, err := in.Read(func(key string, b any) (json.RawMessage, error) { + if key != "seat:mesh-delivery-checker.input" { + return nil, fmt.Errorf("asked %s", key) + } + a, err := rd.Input(b.(delivery.RunInputAsk).Digest) + if err != nil { + return nil, err + } + return json.Marshal(a) + }); err != nil || string(got) != string(body) { + t.Errorf("the input through the verb = %s, %v", got, err) + } + _, _ = inputs.Create(context.Background(), strings.Repeat("1", 64), []byte(`{"v":2}`)) + for _, digest := range []string{"d1", strings.Repeat("0", 64), strings.Repeat("1", 64)} { + if a, err := rd.Input(digest); err == nil { + t.Errorf("the verb answered %s: %+v", digest, a) + } + } } // 1680 live acceptance: checker_previews shows the preview key of an open pull request, as stored, narrowed by repository and head. diff --git a/modules/mesh-delivery-checker/manifest_test.go b/modules/mesh-delivery-checker/manifest_test.go index c483b32a..f088657d 100644 --- a/modules/mesh-delivery-checker/manifest_test.go +++ b/modules/mesh-delivery-checker/manifest_test.go @@ -167,10 +167,11 @@ func TestManifestDeclarations(t *testing.T) { // 1678/AC1 if want := []string{"checker_ask_again", "checker_inputs", "checker_kept", "checker_previews", "checker_pull", "checker_status", "checker_tries", "checker_try", "checker_try_end", "checker_try_read", "checker_verdicts"}; !reflect.DeepEqual(sorted(m.Tools), want) { t.Errorf("tools = %v, want %v", m.Tools, want) } - // The try is the checker's own seat's verbs (ADR 0547 §10, issue 774), served beside the tools by the same code. + // The try is the checker's own seat's verbs (ADR 0547 §10, issue 774), served beside the tools by the same code; so is + // input, a checking program's input the Go build module asks (issue 1921, ADR 0596 §2). if len(m.Claims) != 1 || m.Claims[0].Name != "mesh-delivery-checker" || m.Claims[0].Scope != "mesh" || - !reflect.DeepEqual(m.Claims[0].Serves, []string{"try", "try-read", "try-end"}) { - t.Errorf("claims = %+v, want the mesh seat mesh-delivery-checker serving try, try-read and try-end", m.Claims) + !reflect.DeepEqual(m.Claims[0].Serves, []string{"try", "try-read", "try-end", "input"}) { + t.Errorf("claims = %+v, want the mesh seat mesh-delivery-checker serving try, try-read, try-end and input", m.Claims) } for _, k := range []string{"logs", "emits", "provides", "requires", "defines-seats", "seat", "seats", "bus"} { // config: compose-answer-bound (ADR 0593 §7) if _, ok := top[k]; ok { diff --git a/modules/mesh-delivery-checker/module.json b/modules/mesh-delivery-checker/module.json index b162fdeb..6e6f9c9c 100644 --- a/modules/mesh-delivery-checker/module.json +++ b/modules/mesh-delivery-checker/module.json @@ -9,7 +9,8 @@ "serves": [ "try", "try-read", - "try-end" + "try-end", + "input" ] } ], diff --git a/modules/mesh-delivery-indexer/judge/rules_test.go b/modules/mesh-delivery-indexer/judge/rules_test.go index 5e3c4325..d7b8f230 100644 --- a/modules/mesh-delivery-indexer/judge/rules_test.go +++ b/modules/mesh-delivery-indexer/judge/rules_test.go @@ -4848,7 +4848,7 @@ func TestTheSeatsAreAClosedSetAndEachNamesItsDecision(t *testing.T) { } } } - if strings.Join(checkerVerbs, ",") != "try,try-read,try-end" { + if strings.Join(checkerVerbs, ",") != "try,try-read,try-end,input" { // input: a checking program's input (issue 1921) t.Errorf("the checker seat serves %v", checkerVerbs) } // The four health seats (novox/hq ADR 0564 clause 1): at mesh scope, each held once, each naming ADR 0564, with the