diff --git a/modules/claude-code/cmd/claude-code/insteadof_test.go b/modules/claude-code/cmd/claude-code/insteadof_test.go index 0162459ff..f36a0d549 100644 --- a/modules/claude-code/cmd/claude-code/insteadof_test.go +++ b/modules/claude-code/cmd/claude-code/insteadof_test.go @@ -105,6 +105,13 @@ func TestTheMeshsToolsAreReadFromTheBus(t *testing.T) { if got := strings.Join(addresses, ","); got != "mesh-module-assigner.placement,node-service-manager.status,node-service-manager.journal,docker.docker_logs" { t.Errorf("replacements: %s", got) } + t.Run("the again tool answers the operator and the healers only", func(t *testing.T) { + for caller, refused := range map[string]bool{"person.jochen": false, "shanks.mesh-healers": false, "shanks.mesh-config": true} { + if err := seatlist.Refusal("claude_code_seat_list_again", seatListAgainCallers, caller); (err != nil) != refused { + t.Errorf("%s: refused=%v, err %v", caller, refused, err) + } + } + }) t.Run("the seats are the seat list read from the bus", func(t *testing.T) { // A table made from what the seat mesh-catalog answers (ADR 0635 §1): no compiled list, so before the reader // has started nothing replaces a command, and after it the seats' verbs are the seat's own. @@ -116,13 +123,11 @@ func TestTheMeshsToolsAreReadFromTheBus(t *testing.T) { } startSeatList(listBus{"mesh-module-assigner": someSeats()[0], "node-service-manager": someSeats()[1]}) defer func() { seatListMu.Lock(); seatList = nil; seatListMu.Unlock() }() - // The reader reads on its own goroutine: wait for the change it tells (no sleeping). - for !seatList.Status().Heard { - select { - case <-meshChanged: - case <-time.After(10 * time.Second): - t.Fatalf("the seat list was not read: %+v", seatList.Status()) - } + // The reader's first read ends when Ready closes (no sleeping). + select { + case <-seatList.Ready(): + case <-time.After(10 * time.Second): + t.Fatalf("the seat list was not read: %+v", seatList.Status()) } got, err := ReadMeshToolsFrom(aMesh(t), definedSeats(), resolving) var addrs []string diff --git a/modules/claude-code/cmd/claude-code/main.go b/modules/claude-code/cmd/claude-code/main.go index 40a2a206e..3fd46e20c 100644 --- a/modules/claude-code/cmd/claude-code/main.go +++ b/modules/claude-code/cmd/claude-code/main.go @@ -661,11 +661,14 @@ func refusals(n int) string { return fmt.Sprintf(" (after %d refusal(s))", n) } +// seatListAgainCallers may read the seat list again: every operator's session and the healers (ADR 0636 §7). +var seatListAgainCallers = []string{stdio.AnyPerson, "mesh-healers"} + // seatListTools are the seat list reader's two tools: what the list holds and what it waits for, and a read now. The // reader starts once the runtime serves, after the tools are listed, so each tool finds it when it is called. func seatListTools() []stdio.Tool { names := []string{"claude_code_seat_list", "claude_code_seat_list_again"} - callers := stdio.Callers{"mesh-healers"} + callers := seatListAgainCallers shape := seatlist.Tools(&seatlist.Reader{}, names[0], names[1], callers) for i := range shape { run := i