mesh/delivery closed: the pull request closed unmerged
mesh/check pass 96a37f58ca76 on 030f9ee4e780 | unit 1173 run, 3272 kept, 13 skipped | integration 1212 run, 3274 kept, 22 skipped | over bound: unit, …
The controller's declared-config mirror never held de-spiegel's keys, and Resolve skipped an assigned module it held no declarations of, silently: de-spiegel stayed held on novox with its values set. Declarations now come from the indexer's trunk manifest with the wanted build's laid over it per node; which module runs where from the seat mesh-placement alone. A placed module with no readable manifest is listed as unresolved with why, logged, and keeps its last resolved values.
592 lines
24 KiB
Go
592 lines
24 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
"git.novox.be/novox/mesh-sdk/go/asks"
|
|
)
|
|
|
|
// standIn is the operator channel's router and a channel that proves who answered, in memory: it takes the asks
|
|
// mesh-config publishes, keeps its own record of each under mesh-config's name, and records the operator's choice
|
|
// as the router's warrant. Nothing of mesh-config's is trusted by it: it digests the ask it took.
|
|
type standIn struct {
|
|
mu sync.Mutex
|
|
subjects []string
|
|
asks map[string]asks.Ask
|
|
records map[string][]byte
|
|
kinds []stdio.KindHeld
|
|
refuse error
|
|
warranted []string
|
|
// refuseWith makes the router refuse every ask it is published, as the router does for an ask no channel can
|
|
// carry: no record, and its word on the decided subject (heard through hear).
|
|
refuseWith string
|
|
hear func(asks.Warrant)
|
|
// noRecord keeps the router from recording an ask it took (an ask it has not taken yet).
|
|
noRecord bool
|
|
}
|
|
|
|
func newStandIn() *standIn {
|
|
return &standIn{asks: map[string]asks.Ask{}, records: map[string][]byte{}, kinds: []stdio.KindHeld{
|
|
{Seat: asks.ChannelSeat, Kind: "telegram", Module: "telegram", Node: "novox",
|
|
Capabilities: []string{"choice", "exact-render", "verified-sender"}}}}
|
|
}
|
|
|
|
func (r *standIn) Publish(subject string, body any, id string) error {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
if r.refuse != nil {
|
|
return r.refuse
|
|
}
|
|
a, ok := body.(asks.Ask)
|
|
if !ok || a.ID != id {
|
|
return errors.New("not an ask")
|
|
}
|
|
r.subjects = append(r.subjects, subject)
|
|
r.asks[id] = a
|
|
if r.refuseWith != "" {
|
|
w := asks.Warrant{Ask: id, Asker: Module, About: a.About, Outcome: asks.OutcomeRefused, Words: r.refuseWith,
|
|
At: time.Now()}
|
|
go r.hear(w)
|
|
return nil
|
|
}
|
|
if !r.noRecord {
|
|
raw, _ := json.Marshal(map[string]any{"state": "open", "ask": a})
|
|
r.records[id] = raw
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *standIn) Record(id string) (*stdio.StateEntry, error) {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
raw, ok := r.records[id]
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
return &stdio.StateEntry{Key: Module + "." + id, Value: raw}, nil
|
|
}
|
|
|
|
func (r *standIn) Kinds() ([]stdio.KindHeld, error) { return r.kinds, nil }
|
|
|
|
func (r *standIn) Warranted(ask string) error {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
r.warranted = append(r.warranted, ask)
|
|
return nil
|
|
}
|
|
|
|
// choose is the operator choosing an option on a channel that proves who answered: the router's record of the ask
|
|
// it took, closed with its warrant.
|
|
func (r *standIn) choose(t *testing.T, id, option string, tweak func(*asks.Warrant)) asks.Warrant {
|
|
t.Helper()
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
a, ok := r.asks[id]
|
|
if !ok {
|
|
t.Fatalf("the router took no ask %s", id)
|
|
}
|
|
o, ok := a.Option(option)
|
|
if !ok {
|
|
t.Fatalf("the ask %s offers no %s", id, option)
|
|
}
|
|
w := asks.Warrant{Ask: id, Asker: Module, About: a.About, Outcome: asks.OutcomeChosen, Option: o.ID, Label: o.Label,
|
|
Level: o.Level, By: &asks.Person{Who: asks.Operator, Kind: "telegram", Identity: "4242", Verified: "user id verified"},
|
|
Channel: "telegram", Proofs: []string{"P1"}, At: time.Date(2026, 10, 14, 19, 30, 0, 0, time.UTC),
|
|
AskDigest: a.Digest()}
|
|
if tweak != nil {
|
|
tweak(&w)
|
|
}
|
|
raw, _ := json.Marshal(map[string]any{"state": "closed", "ask": a, "warrant": w})
|
|
r.records[id] = raw
|
|
return w
|
|
}
|
|
|
|
// aProposingMesh is aMesh with the stand-in channel, its waits short.
|
|
func aProposingMesh(t *testing.T) (*Config, *memStore, *standIn) {
|
|
t.Helper()
|
|
c, m := aMesh(t)
|
|
r := newStandIn()
|
|
r.hear = func(w asks.Warrant) { c.HeardEnd(w) }
|
|
c.Asks, c.RouterWithin, c.RouterEvery = r, 300*time.Millisecond, 5*time.Millisecond
|
|
return c, m, r
|
|
}
|
|
|
|
func propose(t *testing.T, c *Config, call Call) *ProposeAnswer {
|
|
t.Helper()
|
|
call.Action = "propose"
|
|
if call.Caller == "" {
|
|
call.Caller = "g14.node-tools"
|
|
}
|
|
if call.Why == "" {
|
|
call.Why = "the agents need the new rule"
|
|
}
|
|
a, err := c.Propose(call)
|
|
if err != nil {
|
|
t.Fatalf("propose %s: %v", call.Key, err)
|
|
}
|
|
return a
|
|
}
|
|
|
|
func storedProposal(t *testing.T, m *memStore, id string) Proposal {
|
|
t.Helper()
|
|
e, _ := m.Get(ProposalsState, id)
|
|
if e == nil {
|
|
t.Fatalf("no proposal %s is kept", id)
|
|
}
|
|
var p Proposal
|
|
if err := json.Unmarshal(e.Value, &p); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return p
|
|
}
|
|
|
|
func storedValue(t *testing.T, m *memStore, key string) (Value, bool) {
|
|
t.Helper()
|
|
e, _ := m.Get(ValuesState, key)
|
|
if e == nil {
|
|
return Value{}, false
|
|
}
|
|
var v Value
|
|
if err := json.Unmarshal(e.Value, &v); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return v, true
|
|
}
|
|
|
|
// **A trusted value proposed end to end** (ADR 0348 §8, How it is checked: "A proposal's test"): mesh-config asks
|
|
// under its own name; its ask names the level, the key and the value and says the change takes effect at once;
|
|
// each option binds them; nothing is set until the operator's Approve; on Approve the value is set as a set is,
|
|
// who approved it is kept beside it, and the warrant is recorded in the hand-act log.
|
|
func TestATrustedValueIsProposedAskedUnderItsOwnNameAndSetOnTheOperatorsApprove(t *testing.T) {
|
|
c, m, r := aProposingMesh(t)
|
|
m.events, m.handActs = nil, nil
|
|
value := map[string]any{"permissions": map[string]any{"deny": []any{"Bash(ssh:*)"}}}
|
|
a := propose(t, c, Call{Key: "agent-managed-settings", Value: value})
|
|
|
|
// Nothing is set by a proposal.
|
|
if _, set := storedValue(t, m, "mesh.agent-managed-settings"); set || len(m.events) != 0 || len(m.handActs) != 0 {
|
|
t.Fatalf("a proposal set something: %d events, %d hand-acts", len(m.events), len(m.handActs))
|
|
}
|
|
if !strings.Contains(a.Router, "took the ask") || a.Proposal == "" || len(a.Reaches) != 1 ||
|
|
a.Reaches[0] != "claude-code on g14" {
|
|
t.Errorf("the proposal answered %+v", a)
|
|
}
|
|
// Asked under mesh-config's own name, on the operator channel.
|
|
if len(r.subjects) != 1 || r.subjects[0] != asks.AskSubject("mesh-config") {
|
|
t.Fatalf("asked on %v", r.subjects)
|
|
}
|
|
ask := r.asks[a.Proposal]
|
|
if err := ask.Check(c.now()); err != nil {
|
|
t.Fatalf("the ask: %v", err)
|
|
}
|
|
if err := asks.Glance(ask.Explanation); err != nil {
|
|
t.Errorf("the ask does not read at a glance: %v\n%s", err, ask.Explanation)
|
|
}
|
|
for _, want := range []string{"agent-managed-settings", "mesh config", "takes effect at once", "claude-code",
|
|
"a trusted value"} {
|
|
if !strings.Contains(ask.Explanation, want) {
|
|
t.Errorf("the ask's explanation does not say %q:\n%s", want, ask.Explanation)
|
|
}
|
|
}
|
|
if !strings.Contains(ask.Headline, "agent-managed-settings") || !strings.Contains(ask.Headline, "whole mesh") {
|
|
t.Errorf("the headline is %q", ask.Headline)
|
|
}
|
|
// The value exact where who answers is proven; under the content rule elsewhere.
|
|
exact := `+ agent-managed-settings: {"permissions":{"deny":["Bash(ssh:*)"]}}`
|
|
shown := ask.Explanation
|
|
if ask.Whole != "" {
|
|
shown = ask.Whole
|
|
}
|
|
if !strings.Contains(shown, exact) || !strings.Contains(ask.Details+ask.WholeDetails, a.Proposal) {
|
|
t.Errorf("the ask does not show the value whole:\n%s\n%s", shown, ask.WholeDetails)
|
|
}
|
|
// Each option binds the level, the key and the value.
|
|
p := storedProposal(t, m, a.Proposal)
|
|
for _, o := range ask.Options {
|
|
if o.Level != asks.Approve {
|
|
t.Errorf("the option %s is at %s", o.ID, o.Level)
|
|
}
|
|
act := p.Act(o.ID)
|
|
want, _ := asks.ActDigest(act)
|
|
if o.Binds != want || act["level"] != LevelMesh || act["key"] != "agent-managed-settings" ||
|
|
act["value"] != valueDigest(json.RawMessage(`{"permissions":{"deny":["Bash(ssh:*)"]}}`), true) ||
|
|
act["was"] != valueDigest(nil, false) {
|
|
t.Errorf("the option %s binds %s for %v", o.ID, o.Binds, act)
|
|
}
|
|
}
|
|
// Still nothing while the ask is open.
|
|
if said, _ := c.Decide(); len(said) != 0 {
|
|
t.Errorf("an open ask decided %v", said)
|
|
}
|
|
|
|
r.choose(t, a.Proposal, OptionApprove, nil)
|
|
said, err := c.Decide()
|
|
if err != nil || len(said) != 1 || !strings.Contains(said[0], "approved by the operator and set") {
|
|
t.Fatalf("the Approve decided %v (%v)", said, err)
|
|
}
|
|
v, set := storedValue(t, m, "mesh.agent-managed-settings")
|
|
if !set || string(v.Value) != `{"permissions":{"deny":["Bash(ssh:*)"]}}` || v.By != "the operator" ||
|
|
v.Via != "g14.node-tools" || !strings.Contains(v.Approved, "approved by the operator, as telegram identity 4242, "+
|
|
"via telegram (user id verified)") || !strings.Contains(v.Approved, "ask "+a.Proposal) ||
|
|
!strings.Contains(v.Approved, "proposed by g14.node-tools") {
|
|
t.Fatalf("the value is %+v", v)
|
|
}
|
|
if res := m.resolved(t, "g14", "claude-code", "managed"); res.Source != SourceMesh ||
|
|
string(res.Value) != string(v.Value) {
|
|
t.Errorf("claude-code's managed settings resolve as %+v", res)
|
|
}
|
|
if len(m.events) != 1 || m.events[0].Approved != v.Approved || len(m.handActs) != 1 ||
|
|
!strings.Contains(m.handActs[0], "the operator's Approve of proposal "+a.Proposal) {
|
|
t.Errorf("the set raised %+v and recorded %q", m.events, m.handActs)
|
|
}
|
|
listed, _ := c.List(Call{Key: "agent-managed-settings"})
|
|
if len(listed.Set) != 1 || listed.Set[0].Approved != v.Approved {
|
|
t.Errorf("the listing says %+v", listed.Set)
|
|
}
|
|
if p = storedProposal(t, m, a.Proposal); p.State != ProposalApproved || !p.Recorded ||
|
|
len(r.warranted) != 1 || r.warranted[0] != a.Proposal {
|
|
t.Errorf("the proposal is %+v; warrants recorded %v", p, r.warranted)
|
|
}
|
|
// A warrant heard again, or read again, sets nothing twice.
|
|
if said, _ = c.Decide(); len(said) != 0 || len(m.events) != 1 {
|
|
t.Errorf("the warrant read again decided %v", said)
|
|
}
|
|
}
|
|
|
|
// **An Approve against a changed value is refused** (ADR 0348 §8, ADR 0277 §13; design 57 K4's proof): the value
|
|
// stored at the level changed after the operator was shown the change against it, so nothing is set.
|
|
func TestAnApproveIsRefusedWhenTheStoredValueChangedSince(t *testing.T) {
|
|
c, m, r := aProposingMesh(t)
|
|
a := propose(t, c, Call{Key: "agent-managed-settings", Value: map[string]any{"a": 1.0}})
|
|
// Set at the terminal meanwhile.
|
|
set(t, c, Call{Key: "agent-managed-settings", Value: map[string]any{"b": 2.0}, Caller: ControllerCaller, By: "jochen"})
|
|
m.events = nil
|
|
r.choose(t, a.Proposal, OptionApprove, nil)
|
|
said, _ := c.Decide()
|
|
if len(said) != 1 || !strings.Contains(said[0], "it changed since, so nothing is set") {
|
|
t.Fatalf("the Approve decided %v", said)
|
|
}
|
|
if v, _ := storedValue(t, m, "mesh.agent-managed-settings"); string(v.Value) != `{"b":2}` || v.Approved != "" {
|
|
t.Errorf("the value is %+v", v)
|
|
}
|
|
if len(m.events) != 0 {
|
|
t.Errorf("a refused Approve raised %+v", m.events)
|
|
}
|
|
if p := storedProposal(t, m, a.Proposal); p.State != ProposalRefused {
|
|
t.Errorf("the proposal is %s: %s", p.State, p.Outcome)
|
|
}
|
|
|
|
// And a clear since: the value it was shown against (a value) is no longer stored.
|
|
set(t, c, Call{Key: "font-size", Value: 12.0, Node: "g14"})
|
|
a = propose(t, c, Call{Key: "font-size", Value: 14.0, Node: "g14"})
|
|
set(t, c, Call{Action: "clear", Key: "font-size", Node: "g14"})
|
|
r.choose(t, a.Proposal, OptionApprove, nil)
|
|
if said, _ = c.Decide(); len(said) != 1 || !strings.Contains(said[0], "no value set") {
|
|
t.Fatalf("the Approve after a clear decided %v", said)
|
|
}
|
|
if _, isSet := storedValue(t, m, "node.g14.font-size"); isSet {
|
|
t.Error("an Approve against a cleared value set it")
|
|
}
|
|
}
|
|
|
|
// **A record that changed after it was asked is refused on Approve** (ADR 0277 §12): its value, or the value it was
|
|
// shown against, no longer what the ask bound.
|
|
func TestAnApproveIsRefusedWhenTheProposalChangedAfterItWasAsked(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name string
|
|
tamper func(p *Proposal)
|
|
want string
|
|
}{
|
|
{"its value", func(p *Proposal) { p.Value = json.RawMessage(`{"evil":true}`) }, "its value is not the value"},
|
|
{"its value and digest", func(p *Proposal) {
|
|
p.Value = json.RawMessage(`{"evil":true}`)
|
|
p.Digest = valueDigest(p.Value, true)
|
|
}, "it was not the ask the person was shown"},
|
|
{"the value it was shown against", func(p *Proposal) {
|
|
p.Was, p.HadValue = json.RawMessage(`{"x":1}`), true
|
|
}, "the value it was shown against"},
|
|
{"its level", func(p *Proposal) { p.Level = Level{Kind: LevelNode, Node: "g14"} }, "not the ask the person was shown"},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
c, m, r := aProposingMesh(t)
|
|
a := propose(t, c, Call{Key: "agent-managed-settings", Value: map[string]any{"a": 1.0}})
|
|
r.choose(t, a.Proposal, OptionApprove, nil)
|
|
p := storedProposal(t, m, a.Proposal)
|
|
tc.tamper(&p)
|
|
_ = m.Put(ProposalsState, p.ID, p)
|
|
said, _ := c.Decide()
|
|
if len(said) != 1 || !strings.Contains(said[0], tc.want) {
|
|
t.Fatalf("decided %v, want %q", said, tc.want)
|
|
}
|
|
if keys, _ := m.Keys(ValuesState); len(keys) != 0 {
|
|
t.Errorf("a changed proposal set %v", keys)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// **A warrant that is not the answer to the ask is refused** (ADR 0277 §11): another ask's digest, another level, an
|
|
// answer after expiry, nobody verified.
|
|
func TestAWarrantThatDoesNotAnswerTheAskIsRefused(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name string
|
|
tweak func(w *asks.Warrant)
|
|
}{
|
|
{"another ask's digest", func(w *asks.Warrant) { w.AskDigest = "sha256:00" }},
|
|
{"another level", func(w *asks.Warrant) { w.Level = asks.Acknowledge }},
|
|
{"after its expiry", func(w *asks.Warrant) { w.At = time.Date(2026, 10, 16, 0, 0, 0, 0, time.UTC) }},
|
|
{"an option it did not offer", func(w *asks.Warrant) { w.Option = "force" }},
|
|
{"no person", func(w *asks.Warrant) { w.By = nil }},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
c, m, r := aProposingMesh(t)
|
|
a := propose(t, c, Call{Key: "agent-managed-settings", Value: map[string]any{"a": 1.0}})
|
|
r.choose(t, a.Proposal, OptionApprove, tc.tweak)
|
|
said, _ := c.Decide()
|
|
if len(said) != 1 || !strings.Contains(said[0], "refused") {
|
|
t.Fatalf("decided %v", said)
|
|
}
|
|
if keys, _ := m.Keys(ValuesState); len(keys) != 0 {
|
|
t.Errorf("a warrant that does not hold set %v", keys)
|
|
}
|
|
if len(r.warranted) != 0 {
|
|
t.Errorf("a refused warrant was recorded as acted on: %v", r.warranted)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// **On Decline, at expiry, or on the router's refusal nothing is set** (ADR 0277 §9, §16, §20), and a Decline is a
|
|
// warrant the hand-act log holds.
|
|
func TestDeclineExpiryAndTheRoutersRefusalSetNothing(t *testing.T) {
|
|
c, m, r := aProposingMesh(t)
|
|
a := propose(t, c, Call{Key: "agent-managed-settings", Value: map[string]any{"a": 1.0}})
|
|
r.choose(t, a.Proposal, OptionDecline, nil)
|
|
if said, _ := c.Decide(); len(said) != 1 || !strings.Contains(said[0], "declined by the operator") {
|
|
t.Fatalf("the Decline decided %v", said)
|
|
}
|
|
if p := storedProposal(t, m, a.Proposal); p.State != ProposalDeclined || p.Approved != "" || !p.Recorded {
|
|
t.Errorf("the declined proposal is %+v", p)
|
|
}
|
|
|
|
// Ended without a choice: the router's word.
|
|
a = propose(t, c, Call{Key: "font-size", Value: 12.0, Node: "g14"})
|
|
c.HeardEnd(asks.Warrant{Ask: a.Proposal, Asker: Module, Outcome: asks.OutcomeExpired, At: time.Now()})
|
|
if p := storedProposal(t, m, a.Proposal); p.State != ProposalEnded || !strings.Contains(p.Outcome, "expired") {
|
|
t.Errorf("the expired proposal is %+v", p)
|
|
}
|
|
|
|
// Ended, and the router never recorded it: discarded once its time is past.
|
|
r.noRecord = true
|
|
a = propose(t, c, Call{Key: "font-size", Value: 13.0, Node: "novox"})
|
|
if !strings.Contains(a.Router, "has not said yet") {
|
|
t.Errorf("an ask the router has not taken answered %q", a.Router)
|
|
}
|
|
later := time.Date(2026, 10, 16, 0, 0, 0, 0, time.UTC)
|
|
c.Now = func() time.Time { return later }
|
|
if said, _ := c.Decide(); len(said) != 1 || !strings.Contains(said[0], "keeps no record") {
|
|
t.Errorf("an ask never recorded decided %v", said)
|
|
}
|
|
if keys, _ := m.Keys(ValuesState); len(keys) != 0 {
|
|
t.Errorf("an unanswered proposal set %v", keys)
|
|
}
|
|
|
|
// Refused by the router at once: said to the proposer.
|
|
c2, m2, r2 := aProposingMesh(t)
|
|
r2.refuseWith = "no channel can carry an approval"
|
|
_, err := c2.Propose(Call{Action: "propose", Key: "font-size", Value: 12.0, Caller: "g14.node-tools", Why: "w"})
|
|
if err == nil || !strings.Contains(err.Error(), "no channel can carry an approval") {
|
|
t.Fatalf("the router's refusal answered %v", err)
|
|
}
|
|
all, _ := c2.proposals()
|
|
if len(all) != 1 || all[0].State != ProposalEnded {
|
|
t.Errorf("the refused proposal is %+v", all)
|
|
}
|
|
if keys, _ := m2.Keys(ValuesState); len(keys) != 0 {
|
|
t.Errorf("a refused proposal set %v", keys)
|
|
}
|
|
}
|
|
|
|
// **A proposal is judged as a set before anyone is asked, and refused at once when it cannot be asked** (ADR 0277 §4,
|
|
// §19, §21): a value the verb would refuse; nothing that would change; a value shaped like a secret; no channel that
|
|
// proves who answered; a publish that fails; the same change twice; a fourth open ask.
|
|
func TestAProposalIsJudgedAndRefusedBeforeTheOperatorIsAsked(t *testing.T) {
|
|
c, m, r := aProposingMesh(t)
|
|
set(t, c, Call{Key: "font-size", Value: 12.0})
|
|
for _, tc := range []struct {
|
|
name string
|
|
call Call
|
|
want string
|
|
}{
|
|
{"a key not declared", Call{Key: "colour", Value: "red", Node: "g14", Module: "dunst"}, "it declares:"},
|
|
{"a value failing validation", Call{Key: "font-size", Value: 99.0}, "above the maximum 72"},
|
|
{"a module value without a node", Call{Key: "width", Value: 400.0, Module: "dunst"}, "no module value for the whole mesh"},
|
|
{"nothing that would change", Call{Key: "font-size", Value: 12.0}, "already holds exactly 12"},
|
|
{"a clear of nothing", Call{Key: "font-size", Clear: true, Node: "g14"}, "nothing to clear"},
|
|
{"a clear with a value", Call{Key: "font-size", Clear: true, Value: 1.0}, "a clear carries no value"},
|
|
{"a secret's shape", Call{Key: "agent-managed-settings", Value: map[string]any{"env": map[string]any{
|
|
"API_TOKEN": "ghp_abcdefghijklmnopqrstuvwxyz0123456789"}}}, "leaves the mesh nowhere"},
|
|
{"by", Call{Key: "font-size", Value: 13.0, By: "jochen"}, "by names who gave"},
|
|
{"no why", Call{Key: "font-size", Value: 13.0, Why: " "}, "say why"},
|
|
{"an unproven caller", Call{Key: "font-size", Value: 13.0, Caller: Unproven}, "no proven caller"},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
call := tc.call
|
|
call.Action = "propose"
|
|
if call.Caller == "" {
|
|
call.Caller = "g14.node-tools"
|
|
}
|
|
if call.Why == "" {
|
|
call.Why = "a test"
|
|
}
|
|
if _, err := c.Propose(call); err == nil || !strings.Contains(err.Error(), tc.want) ||
|
|
!strings.Contains(err.Error(), "Nothing was proposed") {
|
|
t.Fatalf("refused with %v, want %q", err, tc.want)
|
|
}
|
|
})
|
|
}
|
|
if len(r.subjects) != 0 {
|
|
t.Fatalf("a refused proposal was asked: %v", r.subjects)
|
|
}
|
|
|
|
r.kinds = []stdio.KindHeld{{Seat: asks.ChannelSeat, Kind: "desktop", Capabilities: []string{"choice"}}}
|
|
if _, err := c.Propose(Call{Action: "propose", Key: "font-size", Value: 13.0, Caller: "g", Why: "w"}); err == nil ||
|
|
!strings.Contains(err.Error(), "no channel that proves who answered") {
|
|
t.Errorf("no approving channel: %v", err)
|
|
}
|
|
r.kinds = newStandIn().kinds
|
|
r.refuse = errors.New("the bus is away")
|
|
if _, err := c.Propose(Call{Action: "propose", Key: "font-size", Value: 13.0, Caller: "g", Why: "w"}); err == nil ||
|
|
!strings.Contains(err.Error(), "could not be asked") {
|
|
t.Errorf("a failed publish: %v", err)
|
|
}
|
|
r.refuse = nil
|
|
|
|
first := propose(t, c, Call{Key: "font-size", Value: 14.0, Node: "g14"})
|
|
if _, err := c.Propose(Call{Action: "propose", Key: "font-size", Value: 14.0, Node: "g14", Caller: "g", Why: "w"}); err == nil ||
|
|
!strings.Contains(err.Error(), "proposed already, as "+first.Proposal) {
|
|
t.Errorf("the same change twice: %v", err)
|
|
}
|
|
// A newer proposal for the same place replaces the older (ADR 0277 §10), and is not counted twice.
|
|
newer := propose(t, c, Call{Key: "font-size", Value: 15.0, Node: "g14"})
|
|
if len(newer.Replaced) != 1 || newer.Replaced[0] != first.Proposal ||
|
|
storedProposal(t, m, first.Proposal).State != ProposalEnded {
|
|
t.Errorf("the newer proposal replaced %v", newer.Replaced)
|
|
}
|
|
propose(t, c, Call{Key: "font-size", Value: 15.0, Node: "novox"})
|
|
propose(t, c, Call{Key: "agent-managed-settings", Value: map[string]any{"a": 1.0}})
|
|
if _, err := c.Propose(Call{Action: "propose", Key: "width", Value: 400.0, Node: "g14", Module: "dunst",
|
|
Caller: "g", Why: "w"}); err == nil || !strings.Contains(err.Error(), "3 proposals already wait") {
|
|
t.Errorf("a fourth open ask: %v", err)
|
|
}
|
|
if keys, _ := m.Keys(ValuesState); len(keys) != 1 {
|
|
t.Errorf("proposals set %v", keys)
|
|
}
|
|
}
|
|
|
|
// **A clear is proposed and approved the same way**, and a trusted value set or cleared through the verb is refused,
|
|
// naming the proposal as the way (ADR 0277 §2).
|
|
func TestAClearIsProposedAndATrustedSetNamesTheProposal(t *testing.T) {
|
|
c, m, r := aProposingMesh(t)
|
|
_, err := c.Change(Call{Action: "set", Key: "agent-managed-settings", Value: map[string]any{}, Caller: "g14.node-tools",
|
|
Why: "w"})
|
|
if err == nil || !strings.Contains(err.Error(), "propose it instead") {
|
|
t.Errorf("a trusted set through the verb: %v", err)
|
|
}
|
|
set(t, c, Call{Key: "managed", Value: map[string]any{"a": 1.0}, Node: "g14", Module: "claude-code",
|
|
Caller: ControllerCaller, By: "jochen"})
|
|
a := propose(t, c, Call{Key: "managed", Clear: true, Node: "g14", Module: "claude-code"})
|
|
ask := r.asks[a.Proposal]
|
|
if !strings.HasPrefix(ask.Headline, "Clear managed for claude-code on g14") ||
|
|
!strings.Contains(ask.Explanation, `- managed (was: {"a":1})`) {
|
|
t.Errorf("the clear asks %q:\n%s", ask.Headline, ask.Explanation)
|
|
}
|
|
r.choose(t, a.Proposal, OptionApprove, nil)
|
|
if said, _ := c.Decide(); len(said) != 1 || !strings.Contains(said[0], "is cleared") {
|
|
t.Fatalf("the clear decided %v", said)
|
|
}
|
|
if _, isSet := storedValue(t, m, "module.g14.claude-code.managed"); isSet {
|
|
t.Error("an approved clear left the value")
|
|
}
|
|
h, _ := c.History(Call{Key: "managed"})
|
|
if last := h[len(h)-1]; last.Action != "clear" || !strings.Contains(last.Approved, "approved by the operator") {
|
|
t.Errorf("the history keeps %+v", last)
|
|
}
|
|
}
|
|
|
|
// **The proposals are listed, and one is read whole** with whether the router's ask is the one composed from it.
|
|
func TestTheProposalsAreListedAndOneIsReadWhole(t *testing.T) {
|
|
c, m, _ := aProposingMesh(t)
|
|
a := propose(t, c, Call{Key: "agent-managed-settings", Value: map[string]any{"a": 1.0}})
|
|
b := propose(t, c, Call{Key: "font-size", Value: 12.0, Node: "g14"})
|
|
rows, err := c.Do(Call{Action: "proposals"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if l := rows.([]ProposalRow); len(l) != 2 || l[0].ID != b.Proposal || l[1].State != ProposalWaiting || l[0].Value != nil {
|
|
t.Errorf("listed %+v", l)
|
|
}
|
|
one, err := c.Proposals(Call{Proposal: a.Proposal})
|
|
if err != nil || len(one) != 1 || string(one[0].Value) != `{"a":1}` || one[0].Fingerprint != a.Fingerprint ||
|
|
!strings.Contains(one[0].Phone, "exactly this proposal") {
|
|
t.Fatalf("read whole %+v (%v)", one, err)
|
|
}
|
|
p := storedProposal(t, m, a.Proposal)
|
|
p.Value, p.Digest = json.RawMessage(`{"a":2}`), valueDigest(json.RawMessage(`{"a":2}`), true)
|
|
_ = m.Put(ProposalsState, p.ID, p)
|
|
if one, _ = c.Proposals(Call{Proposal: a.Proposal}); !strings.Contains(one[0].Phone, "ANOTHER ask") {
|
|
t.Errorf("a changed proposal reads %q", one[0].Phone)
|
|
}
|
|
if _, err := c.Proposals(Call{Proposal: "cfg-none"}); err == nil {
|
|
t.Error("an unknown proposal was read")
|
|
}
|
|
}
|
|
|
|
// **The verb's new arguments are read strictly.**
|
|
func TestTheProposalArgumentsAreReadStrictly(t *testing.T) {
|
|
call, err := callOf(map[string]any{"action": "propose", "key": "k", "clear": true, "proposal": " cfg-1 "})
|
|
if err != nil || !call.Clear || call.Proposal != "cfg-1" {
|
|
t.Errorf("read %+v (%v)", call, err)
|
|
}
|
|
if _, err := callOf(map[string]any{"clear": "yes"}); err == nil {
|
|
t.Error("a clear that is not a boolean was read")
|
|
}
|
|
c, _ := aMesh(t)
|
|
if _, err := c.Do(Call{Action: "set", Key: "font-size", Value: 12.0, Clear: true, Caller: "g", Why: "w"}); err == nil {
|
|
t.Error("a set took clear")
|
|
}
|
|
if _, err := c.Do(Call{Action: "propose", Key: "font-size", Value: 12.0, Caller: "g", Why: "w"}); err == nil ||
|
|
!strings.Contains(err.Error(), "cannot be asked from here") {
|
|
t.Errorf("a proposal with no channel: %v", err)
|
|
}
|
|
}
|
|
|
|
// 1439/AC3: mesh-issues' owners, the catalogue's 134 defaults plus okioki, are proposed to the operator and
|
|
// not refused as carrying a secret: modules/asus-zephyrus-g14, i3status-rust and powerlevel10k are names.
|
|
func TestAnOwnersListOfNamesWithDigitsIsProposed(t *testing.T) {
|
|
c, m, r := aProposingMesh(t)
|
|
m.declare(t, "mesh-issues", `{"module":"mesh-issues","settings":{
|
|
"owners":{"kind":"","default":[],"why":"","type":"list","description":"owners","trusted":true}}}`)
|
|
m.assign(t, "novox", "mesh-issues")
|
|
raw, err := os.ReadFile(filepath.Join("testdata", "owners-1439.json"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var owners []any
|
|
if err := json.Unmarshal(raw, &owners); err != nil || len(owners) != 135 {
|
|
t.Fatalf("testdata holds %d owners (%v), want the 134 defaults plus okioki", len(owners), err)
|
|
}
|
|
a := propose(t, c, Call{Key: "owners", Value: owners, Node: "novox", Module: "mesh-issues"})
|
|
if a.Proposal == "" || len(r.subjects) != 1 {
|
|
t.Fatalf("the owners were not put to the operator: %+v, asked on %v", a, r.subjects)
|
|
}
|
|
}
|