mesh-issues: check every new issue for a duplicate; claude-code: one judgement with no tools (hq ADR 0278)
mesh/merge-gate pass: builds claude-code, mesh-issues → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 o…
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate pass: builds claude-code, mesh-issues → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 o…
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer head of the same pull request
The operator asked for duplicate issues to be consolidated as they are opened. The tracker consumes its own opened event and asks the agent on its node, through claude-code's new claude_code_judge, whether an older issue not resolved asks the same question; it closes only the newer issue of a certain duplicate, keeping its report on the older one, and fails loudly when it cannot judge. groom runs the same judgement over the backlog as a dry run, applied on confirm.
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func testJudge(confined bool, licensed bool, exec func(ctx context.Context, argv []string, stdin []byte) ([]byte, error)) *Judge {
|
||||
p := Paths{Node: "control", Operator: "op", Agent: "op"}
|
||||
if confined {
|
||||
p.Agent = "nox"
|
||||
}
|
||||
return &Judge{P: p, Licensed: func() bool { return licensed }, Exec: exec, Now: time.Now, turn: make(chan struct{}, 1)}
|
||||
}
|
||||
|
||||
var verdictSchema = map[string]any{"type": "object", "properties": map[string]any{"verdict": map[string]any{"type": "string"}}}
|
||||
|
||||
// The session can do nothing but answer: built-in tools off, safe mode, no session kept, as the agent's account.
|
||||
func TestAJudgementRunsTheAgentWithNoToolsAsItsAccount(t *testing.T) {
|
||||
var got []string
|
||||
var input string
|
||||
j := testJudge(true, true, func(_ context.Context, argv []string, stdin []byte) ([]byte, error) {
|
||||
got, input = argv, string(stdin)
|
||||
return []byte(`{"is_error":false,"subtype":"success","structured_output":{"verdict":"none"}}`), nil
|
||||
})
|
||||
out, err := j.Judge(JudgeAsk{Question: "is 1 the same as 2?", Schema: verdictSchema})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(out.Answer) != `{"verdict":"none"}` || out.Model != "sonnet" || out.Node != "control" {
|
||||
t.Fatalf("%+v", out)
|
||||
}
|
||||
if input != "is 1 the same as 2?" {
|
||||
t.Fatalf("the question goes on the input, never the command line: %q", input)
|
||||
}
|
||||
line := strings.Join(got, " ")
|
||||
if !strings.HasPrefix(line, "sudo -n -H -u nox -- "+JudgeProgram+" -p ") {
|
||||
t.Fatalf("not run as the agent's account: %s", line)
|
||||
}
|
||||
i := slices.Index(got, "--tools")
|
||||
if i < 0 || got[i+1] != "" {
|
||||
t.Fatalf("built-in tools are not off: %q", got)
|
||||
}
|
||||
for _, want := range []string{"--safe-mode", "--no-session-persistence", "--json-schema"} {
|
||||
if !slices.Contains(got, want) {
|
||||
t.Errorf("%s missing: %q", want, got)
|
||||
}
|
||||
}
|
||||
for _, a := range got {
|
||||
if strings.Contains(a, "is 1 the same") {
|
||||
t.Fatal("the question is on the command line")
|
||||
}
|
||||
}
|
||||
|
||||
// Where agents run as the operator's account, no sudo.
|
||||
j = testJudge(false, true, func(_ context.Context, argv []string, _ []byte) ([]byte, error) {
|
||||
got = argv
|
||||
return []byte(`{"structured_output":{"verdict":"none"}}`), nil
|
||||
})
|
||||
if _, err := j.Judge(JudgeAsk{Question: "q", Schema: verdictSchema, Model: "haiku"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got[0] != JudgeProgram || !slices.Contains(got, "haiku") {
|
||||
t.Fatalf("%q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAJudgementRefusesWhatItCannotAnswer(t *testing.T) {
|
||||
ran := false
|
||||
ok := func(context.Context, []string, []byte) ([]byte, error) {
|
||||
ran = true
|
||||
return []byte(`{"structured_output":{}}`), nil
|
||||
}
|
||||
cases := map[string]struct {
|
||||
j *Judge
|
||||
ask JudgeAsk
|
||||
say string
|
||||
}{
|
||||
"empty": {testJudge(true, true, ok), JudgeAsk{Question: " ", Schema: verdictSchema}, "needs a question"},
|
||||
"too large": {testJudge(true, true, ok), JudgeAsk{Question: strings.Repeat("x", MostQuestion+1), Schema: verdictSchema}, "64 KiB"},
|
||||
"no schema": {testJudge(true, true, ok), JudgeAsk{Question: "q"}, "needs schema"},
|
||||
"not object": {testJudge(true, true, ok), JudgeAsk{Question: "q", Schema: map[string]any{"type": "string"}}, "object"},
|
||||
"model": {testJudge(true, true, ok), JudgeAsk{Question: "q", Schema: verdictSchema, Model: "gpt"}, "not one of"},
|
||||
"no licence": {testJudge(true, false, ok), JudgeAsk{Question: "q", Schema: verdictSchema}, "holds no licence"},
|
||||
}
|
||||
for name, c := range cases {
|
||||
_, err := c.j.Judge(c.ask)
|
||||
if err == nil || !strings.Contains(err.Error(), c.say) {
|
||||
t.Errorf("%s: %v", name, err)
|
||||
}
|
||||
}
|
||||
if ran {
|
||||
t.Fatal("a refused judgement ran the agent")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAJudgementThatFailsSaysWhy(t *testing.T) {
|
||||
answers := map[string][]byte{
|
||||
"an error": []byte(`{"is_error":true,"subtype":"error_during_execution","result":"rate limited"}`),
|
||||
"no structure": []byte(`{"is_error":false,"result":"it is a duplicate"}`),
|
||||
"not an object": []byte(`{"structured_output":[1,2]}`),
|
||||
"not json": []byte(`Invalid API key`),
|
||||
}
|
||||
for name, out := range answers {
|
||||
j := testJudge(true, true, func(context.Context, []string, []byte) ([]byte, error) { return out, nil })
|
||||
if _, err := j.Judge(JudgeAsk{Question: "q", Schema: verdictSchema}); err == nil || !strings.Contains(err.Error(), "not in the shape") {
|
||||
t.Errorf("%s: %v", name, err)
|
||||
}
|
||||
}
|
||||
j := testJudge(true, true, func(context.Context, []string, []byte) ([]byte, error) {
|
||||
return nil, errors.New("exit status 1: sudo: a password is required")
|
||||
})
|
||||
if _, err := j.Judge(JudgeAsk{Question: "q", Schema: verdictSchema}); err == nil || !strings.Contains(err.Error(), "could not judge") {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// One at a time on a node, and never past the bound: a judgement still waiting at the bound is refused.
|
||||
func TestAJudgementWaitsItsTurnWithinTheBound(t *testing.T) {
|
||||
j := testJudge(true, true, func(ctx context.Context, _ []string, _ []byte) ([]byte, error) {
|
||||
<-ctx.Done()
|
||||
return nil, ctx.Err()
|
||||
})
|
||||
j.Bound = 300 * time.Millisecond
|
||||
j.turn <- struct{}{} // another judgement holds the agent
|
||||
start := time.Now()
|
||||
_, err := j.Judge(JudgeAsk{Question: "q", Schema: verdictSchema})
|
||||
if err == nil || !strings.Contains(err.Error(), "another judgement") {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
if time.Since(start) > 2*time.Second {
|
||||
t.Fatal("waited past the bound")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAJudgementPastTheBoundIsEnded(t *testing.T) {
|
||||
j := testJudge(true, true, func(ctx context.Context, _ []string, _ []byte) ([]byte, error) {
|
||||
<-ctx.Done()
|
||||
return nil, ctx.Err()
|
||||
})
|
||||
j.Bound = 200 * time.Millisecond
|
||||
if _, err := j.Judge(JudgeAsk{Question: "q", Schema: verdictSchema}); err == nil || !strings.Contains(err.Error(), "did not answer within") {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTheToolTakesOnlyItsArguments(t *testing.T) {
|
||||
j := testJudge(true, true, func(context.Context, []string, []byte) ([]byte, error) {
|
||||
return []byte(`{"structured_output":{"verdict":"none"}}`), nil
|
||||
})
|
||||
tool := judgeTool(j)
|
||||
if _, err := tool.Run(map[string]any{"question": "q", "schema": verdictSchema, "by": "x"}); err == nil {
|
||||
t.Fatal("an unknown argument was taken")
|
||||
}
|
||||
out, err := tool.Run(map[string]any{"question": "q", "schema": verdictSchema})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
raw, _ := json.Marshal(out)
|
||||
if !strings.Contains(string(raw), `"answer":{"verdict":"none"}`) {
|
||||
t.Fatalf("%s", raw)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user