claude-code: leave the judging of a proposal's channel to the router, and hear its refusal (hq ADR 0274)
mesh/merge-gate pass: builds claude-code → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
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 → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
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
A module that only asks is issued no channel kinds, so the empty list was read as 'no channel proves its sender' and every proposal was refused on the live mesh with Telegram linked. The router judges; Propose waits for its word, and a refusal heard on the decided subject discards the proposal on every node.
This commit is contained in:
@@ -642,3 +642,78 @@ func TestANodeSectionIsNotReplacedFromOtherNodes(t *testing.T) {
|
||||
t.Error("a removal for other nodes was taken for the section on ace")
|
||||
}
|
||||
}
|
||||
|
||||
// A module that only asks is issued no channel kinds (they come with a kinded bench a module uses or watches): an
|
||||
// empty list is no refusal, and the router, which judges, is asked (the live failure of 2026-10-10).
|
||||
func TestAnAskerIssuedNoKindsLeavesTheJudgingToTheRouter(t *testing.T) {
|
||||
p, _ := node(t, "novox")
|
||||
store, r := &memProposals{m: map[string]json.RawMessage{}}, newRouter()
|
||||
r.kinds = nil
|
||||
out, err := Propose(p, ProposeInput{Scope: ScopeMesh, Name: "x", Text: "Text.\n"}, store, r, t0)
|
||||
if err != nil {
|
||||
t.Fatalf("refused with no kinds issued: %v", err)
|
||||
}
|
||||
if !strings.Contains(out["router"].(string), "took the ask") {
|
||||
t.Errorf("router: %v", out["router"])
|
||||
}
|
||||
}
|
||||
|
||||
// The router's refusal reaches the proposer at once, and discards the proposal on every node that hears it.
|
||||
func TestTheRoutersRefusalIsSaidAndDiscards(t *testing.T) {
|
||||
p, _ := node(t, "novox")
|
||||
store := &memProposals{m: map[string]json.RawMessage{}}
|
||||
r := newRouter()
|
||||
r.kinds = nil
|
||||
refusing := &refusingRouter{router: r, p: p, store: store}
|
||||
RouterAnswersWithin, RouterAnswersEvery = time.Second, 10*time.Millisecond
|
||||
_, err := Propose(p, ProposeInput{Scope: ScopeMesh, Name: "x", Text: "Text.\n"}, store, refusing, t0)
|
||||
if err == nil || !strings.Contains(err.Error(), "no channel can carry any of its answers now") ||
|
||||
!strings.Contains(err.Error(), "mesh-cli settings set claude-code") {
|
||||
t.Fatalf("refusal: %v", err)
|
||||
}
|
||||
if got := outcomeHere(t, p, refusing.id); !strings.HasPrefix(got, "discarded: the ask refused") {
|
||||
t.Errorf("decided %q", got)
|
||||
}
|
||||
if changed, _, _ := Reconcile(p, store, refusing, nil, t0.Add(time.Hour)); changed {
|
||||
t.Error("a refused proposal changed what renders")
|
||||
}
|
||||
}
|
||||
|
||||
// A proposal whose ask ended and that the router keeps no record of is discarded, never left waiting.
|
||||
func TestAnAskTheRouterKeepsNoRecordOfIsDiscardedAfterItsTime(t *testing.T) {
|
||||
p, _ := node(t, "novox")
|
||||
store, r := &memProposals{m: map[string]json.RawMessage{}}, newRouter()
|
||||
id := proposeHandover(t, p, store, r)
|
||||
delete(r.records, id)
|
||||
if _, _, err := Reconcile(p, store, r, nil, t0.Add(time.Hour)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := outcomeHere(t, p, id); got != "" {
|
||||
t.Fatalf("decided before its time: %q", got)
|
||||
}
|
||||
if _, _, err := Reconcile(p, store, r, nil, t0.Add(ProposalLasts+2*time.Hour)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := outcomeHere(t, p, id); !strings.HasPrefix(got, "discarded: the router keeps no record") {
|
||||
t.Errorf("decided %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
// refusingRouter refuses every ask as the router does when no channel can carry it: on the decided subject, with
|
||||
// no record.
|
||||
type refusingRouter struct {
|
||||
*router
|
||||
p Paths
|
||||
store ProposalStore
|
||||
id string
|
||||
}
|
||||
|
||||
func (r *refusingRouter) Publish(subject string, body any, id string) error {
|
||||
r.id = id
|
||||
go func() {
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
_, _ = HeardEnd(r.p, r.store, asks.Warrant{Ask: id, Asker: Module, Outcome: asks.OutcomeRefused,
|
||||
Words: "no channel can carry any of its answers now: telegram is not linked", At: t0}, t0)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user