asks: prove the reviewed flow — the controller's root-free word asked before an approval, the desk on its own account, a tap only on the words shown, the question saying who asks and what each answer does, and a rehearsal at the terminal for the live acceptance
mesh/merge-gate pass: the change touches no module of the mesh's graph
mesh/repo-check pass: THE CHANGE ALTERS ITS OWN CHECK (merge-check.sh): main's version judged it; the change's judges the pull requests after it merges; it…
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate pass: the change touches no module of the mesh's graph
mesh/repo-check pass: THE CHANGE ALTERS ITS OWN CHECK (merge-check.sh): main's version judged it; the change's judges the pull requests after it merges; it…
mesh/delivery superseded: a newer head of the same pull request
This commit is contained in:
@@ -143,11 +143,32 @@ func (f *fakeTelegram) typed(from int64, chat int64, chatType, text string) {
|
||||
"chat": map[string]any{"id": chat, "type": chatType}}})
|
||||
}
|
||||
|
||||
// tapped is a button tapped on a message the bot sent, carrying the button's data as the service gives it.
|
||||
// tapped is a button tapped on a message the bot sent, carrying the button's data and the message's words as
|
||||
// the service gives them: the words the message shows now (its last send or edit), as on the phone.
|
||||
func (f *fakeTelegram) tapped(from int64, chat int64, chatType string, onMessage int64, data string) {
|
||||
f.tappedOn(from, chat, chatType, onMessage, data, f.textOf(onMessage))
|
||||
}
|
||||
|
||||
// tappedOn is a tap on a message showing the words given: what a message changed under the operator's thumb,
|
||||
// or forwarded and edited elsewhere, carries.
|
||||
func (f *fakeTelegram) tappedOn(from int64, chat int64, chatType string, onMessage int64, data, text string) {
|
||||
f.push(map[string]any{"callback_query": map[string]any{"id": fmt.Sprintf("cb-%d", f.nextID()), "data": data,
|
||||
"from": map[string]any{"id": from, "is_bot": false, "first_name": fmt.Sprintf("Account %d", from)},
|
||||
"message": map[string]any{"message_id": onMessage, "chat": map[string]any{"id": chat, "type": chatType}}}})
|
||||
"from": map[string]any{"id": from, "is_bot": false, "first_name": fmt.Sprintf("Account %d", from)},
|
||||
"message": map[string]any{"message_id": onMessage, "text": text,
|
||||
"chat": map[string]any{"id": chat, "type": chatType}}}})
|
||||
}
|
||||
|
||||
// textOf is what a message the bot sent shows now.
|
||||
func (f *fakeTelegram) textOf(id int64) string {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
text := ""
|
||||
for _, m := range f.sent {
|
||||
if m.ID == id {
|
||||
text = m.Text
|
||||
}
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
func (f *fakeTelegram) nextID() int64 {
|
||||
|
||||
+39
-6
@@ -48,9 +48,12 @@ type lab struct {
|
||||
|
||||
mu sync.Mutex
|
||||
conditions []map[string]any
|
||||
desk []string // what the desk was shown
|
||||
procs []*exec.Cmd
|
||||
logs map[string]*bytes.Buffer
|
||||
// notFree are the machines the controller's root-free verb answers as not root-free.
|
||||
notFree map[string]string
|
||||
rootAsked int
|
||||
desk []string // what the desk was shown
|
||||
procs []*exec.Cmd
|
||||
logs map[string]*bytes.Buffer
|
||||
}
|
||||
|
||||
// clones is where each core repository the proof builds is checked out: side by side in MESH_LAB_REPOS (beside
|
||||
@@ -116,7 +119,9 @@ func (l *lab) run(dir string, env []string, name string, args ...string) string
|
||||
|
||||
// compose asks the controller at its commit for the bus it would compose, then (bus set) to raise it.
|
||||
func (l *lab) compose(bus string) {
|
||||
env := []string{"GOFLAGS=-mod=vendor", "GOPROXY=off", "MESH_LAB_ASKS_OUT=" + l.dir,
|
||||
// The anchor composed as a machine the controller measured root-free: verified-sender reaches the router
|
||||
// only then (ADR 0259 §8). The not-free case is played live, through the root-free verb, in the proof.
|
||||
env := []string{"GOFLAGS=-mod=vendor", "GOPROXY=off", "MESH_LAB_ASKS_OUT=" + l.dir, "MESH_LAB_ASKS_ROOT_FREE=true",
|
||||
"MESH_LAB_ASKS_CATALOGUE=" + filepath.Join(l.repos.catalogue, "modules"),
|
||||
"MESH_LAB_ASKS_RUNTIME=" + filepath.Join(l.repos.tools, "node-tools", "module.json")}
|
||||
if bus != "" {
|
||||
@@ -343,9 +348,36 @@ func (w *lockedWriter) Write(p []byte) (int, error) {
|
||||
return w.w.Write(p)
|
||||
}
|
||||
|
||||
// playController answers the controller's `conditions` verb from the lab's list, as the router asks it.
|
||||
// playController answers the controller's `conditions` verb from the lab's list, and its `root-free` verb from
|
||||
// the lab's word on each machine (free unless notFree names it), as the router asks them. That a machine is
|
||||
// root-free is the controller's own judgement, proven in its repository (probe_agent_root_test.go); here the
|
||||
// lab says it, so the proof can show what the router does with each answer.
|
||||
func (l *lab) playController() {
|
||||
l.t.Helper()
|
||||
free, err := l.observer.Subscribe("mesh.seat.mesh-controller.tool.root-free", func(m *nats.Msg) {
|
||||
var args struct {
|
||||
Machines []string `json:"machines"`
|
||||
}
|
||||
_ = json.Unmarshal(m.Data, &args)
|
||||
var out []map[string]any
|
||||
l.mu.Lock()
|
||||
for _, name := range args.Machines {
|
||||
why, not := l.notFree[name]
|
||||
if !not {
|
||||
why = "the lab says no agent can become root here"
|
||||
}
|
||||
out = append(out, map[string]any{"machine": name, "free": !not, "why": why,
|
||||
"judged": time.Now().UTC().Format(time.RFC3339Nano)})
|
||||
}
|
||||
l.rootAsked++
|
||||
l.mu.Unlock()
|
||||
raw, _ := json.Marshal(map[string]any{"result": map[string]any{"machines": out}, "node": machine})
|
||||
_ = m.Respond(raw)
|
||||
})
|
||||
if err != nil {
|
||||
l.t.Fatal(err)
|
||||
}
|
||||
l.t.Cleanup(func() { _ = free.Unsubscribe() })
|
||||
sub, err := l.observer.Subscribe("mesh.seat.mesh-controller.tool.conditions", func(m *nats.Msg) {
|
||||
l.mu.Lock()
|
||||
list := append([]map[string]any{}, l.conditions...)
|
||||
@@ -409,7 +441,8 @@ func (l *lab) sayCondition(event string, c map[string]any) {
|
||||
// carries the desk channel: it says it is ready, and keeps what the router shows it — the link's code.
|
||||
func (l *lab) playDesk(ctx context.Context) {
|
||||
l.t.Helper()
|
||||
nc, _ := l.as(machine + ".node-tools")
|
||||
// The desk channel runs as an account of its own (it carries a link's code), on its own credential.
|
||||
nc, _ := l.as(machine + ".desk-channel")
|
||||
stand := func() {
|
||||
raw, _ := json.Marshal(map[string]any{"ready": true, "at": time.Now().UTC()})
|
||||
js, _ := nc.JetStream()
|
||||
|
||||
+10
-10
@@ -3,9 +3,9 @@
|
||||
# terminal on the control node. The lab's proof (TestTheOperatorsAnswerEndToEnd) ran the same flow with a fake
|
||||
# Telegram; this runs it once for real, with a question that changes nothing.
|
||||
#
|
||||
# sh live-acceptance.sh check, ask the drill, wait for the answer, read the record
|
||||
# sh live-acceptance.sh check, ask the rehearsal, wait for the answer, read the record
|
||||
#
|
||||
# It reads and asks only: no setting, assignment or push. The one thing it starts is the drill, a question the
|
||||
# It reads and asks only: no setting, assignment or push. The one thing it starts is a rehearsal, a question the
|
||||
# operator answers on the phone; approving it performs nothing and is recorded as the operator's decision.
|
||||
#
|
||||
# It stops at the first check that fails and says what to do. Exit 0 means: the question reached the phone
|
||||
@@ -31,11 +31,11 @@ ids=$($mc ask messenger messenger_identities) || stop "the router does not answe
|
||||
echo "$ids"
|
||||
echo "$ids" | grep -q '"telegram"' || stop "no Telegram account is linked: send /start to the bot, then the code from the desk"
|
||||
|
||||
echo "4. The drill: a question on your phone. Approve it there within 15 minutes."
|
||||
said=$($mc drill --for 15m) || stop "the drill could not be asked"
|
||||
echo "4. The rehearsal: a question on your phone. Approve it there within 15 minutes."
|
||||
said=$($mc rehearse --for 15m) || stop "the rehearsal could not be asked"
|
||||
echo "$said"
|
||||
id=$(echo "$said" | sed -n 's/^drill \([A-Za-z0-9_-]*\) asked.*/\1/p')
|
||||
[ -n "$id" ] || stop "the drill did not say its id"
|
||||
id=$(echo "$said" | sed -n 's/^rehearsal \([A-Za-z0-9_-]*\) asked.*/\1/p')
|
||||
[ -n "$id" ] || stop "the rehearsal did not say its id"
|
||||
|
||||
echo "5. Waiting for your answer in the hand-act log (every 10 s, at most 16 minutes)."
|
||||
i=0
|
||||
@@ -44,12 +44,12 @@ while [ $i -lt 96 ]; do
|
||||
mine=$(echo "$acts" | tr -d '\n' | grep -o "{[^{}]*\"ask\": *\"$id\"[^{}]*}" || true)
|
||||
if [ -n "$mine" ]; then
|
||||
echo "$mine"
|
||||
echo "$mine" | grep -q 'drill=decline' && stop "the drill was declined: run it again and choose Approve to see an approval recorded"
|
||||
echo "$mine" | grep -q 'drill=approve' || stop "the drill's record names no approval"
|
||||
echo "$mine" | grep -q 'rehearsal=decline' && stop "the rehearsal was declined: run it again and choose Approve to see an approval recorded"
|
||||
echo "$mine" | grep -q 'rehearsal=approve' || stop "the rehearsal's record names no approval"
|
||||
echo "$mine" | grep -q '"via": *"telegram (telegram), user id verified"' || stop "the record does not say the answer came through Telegram from a verified account"
|
||||
echo "$mine" | grep -q '"P1"' || stop "the record does not carry the proof"
|
||||
echo "$mine" | grep -q '"outcome": *"done"' || stop "the drill's act did not end done"
|
||||
echo "ACCEPTED: the drill was approved on Telegram, acted on once, and recorded as your decision."
|
||||
echo "$mine" | grep -q '"outcome": *"done"' || stop "the rehearsal's act did not end done"
|
||||
echo "ACCEPTED: the rehearsal was approved on Telegram, acted on once, and recorded as your decision."
|
||||
exit 0
|
||||
fi
|
||||
i=$((i + 1))
|
||||
|
||||
+53
-29
@@ -109,13 +109,19 @@ func TestTheOperatorsAnswerEndToEnd(t *testing.T) {
|
||||
first = a.ask("a1", "Flip the lab's switch?", time.Hour)
|
||||
firstMessage = l.tg.waitFor("the first question with its buttons", operatorAccount, since, 90*time.Second,
|
||||
func(m tgMessage) bool { return !m.Edited && button(m, "Approve") != "" && button(m, "Decline") != "" })
|
||||
if !strings.Contains(firstMessage.Text, "Flip the lab's switch?") || !strings.Contains(firstMessage.Text, "approve or decline") {
|
||||
t.Errorf("the question does not say what is asked and why: %q", firstMessage.Text)
|
||||
for _, says := range []string{"Flip the lab's switch?", "approve or decline", "lab-asker",
|
||||
"the switch is flipped, once", "nothing is flipped"} {
|
||||
if !strings.Contains(firstMessage.Text, says) {
|
||||
t.Errorf("the question does not say %q (what is asked, why, by whom, what each answer does, what "+
|
||||
"no answer does): %q", says, firstMessage.Text)
|
||||
}
|
||||
}
|
||||
tapped := time.Now()
|
||||
l.tg.tapped(operatorAccount, operatorAccount, "private", firstMessage.ID, button(firstMessage, "Approve"))
|
||||
l.tg.waitFor("the refusal of an approval inside the hour", operatorAccount, tapped, 90*time.Second,
|
||||
func(m tgMessage) bool { return strings.Contains(m.Text, "can approve from") })
|
||||
func(m tgMessage) bool {
|
||||
return strings.Contains(m.Text, "That answer was not taken") && strings.Contains(m.Text, "linked at")
|
||||
})
|
||||
if !a.noWordOn("a1", 2*time.Second) || len(a.performed()) != 0 {
|
||||
t.Fatalf("an approval inside the hour was a warrant: %v", a.performed())
|
||||
}
|
||||
@@ -152,7 +158,7 @@ func TestTheOperatorsAnswerEndToEnd(t *testing.T) {
|
||||
if w.AskDigest != first.Digest() {
|
||||
t.Errorf("the warrant names the digest %s, not the ask's %s", w.AskDigest, first.Digest())
|
||||
}
|
||||
if got := a.performed(); len(got) != 1 || got[0]["switch"] != "approve" {
|
||||
if got := a.performed(); len(got) != 1 || got[0]["arg.switch"] != "approve" {
|
||||
t.Fatalf("performed %v, want the one bound act once", got)
|
||||
}
|
||||
// Every copy says how it ended, its buttons gone.
|
||||
@@ -197,20 +203,19 @@ func TestTheOperatorsAnswerEndToEnd(t *testing.T) {
|
||||
m := l.tg.waitFor("the second question", operatorAccount, since, 90*time.Second,
|
||||
func(m tgMessage) bool { return !m.Edited && button(m, "Approve") != "" })
|
||||
// The buttons' data reached a stranger (a forwarded question), who taps in their own chat with the bot:
|
||||
// refused, and the operator is told which account tried. The stranger is told nothing.
|
||||
tapped := time.Now()
|
||||
// the channel never showed that message there, so the tap goes nowhere, and the stranger is told
|
||||
// nothing. (A stranger's answer that does reach the router is refused there and told to the operator by
|
||||
// name: the router's own test.)
|
||||
l.tg.tapped(strangerAccount, strangerAccount, "private", m.ID, button(m, "Approve"))
|
||||
l.tg.waitFor("the operator told another account tried", operatorAccount, tapped, 90*time.Second,
|
||||
func(m tgMessage) bool {
|
||||
return strings.Contains(m.Text, "Another account tried to answer") && strings.Contains(m.Text, fmt.Sprint(strangerAccount))
|
||||
})
|
||||
if got := l.tg.since(strangerAccount, since); len(got) != 0 {
|
||||
t.Errorf("the bot answered the stranger: %+v", got)
|
||||
}
|
||||
// The operator taps a message whose words are not the ones the channel put there.
|
||||
l.tg.tappedOn(operatorAccount, operatorAccount, "private", m.ID, button(m, "Approve"), "Flip ALL the switches?")
|
||||
// The operator taps in a group the bot is in: not their own chat, dropped by the channel.
|
||||
l.tg.tapped(operatorAccount, groupChat, "supergroup", m.ID, button(m, "Approve"))
|
||||
if !a.noWordOn("a2", 3*time.Second) {
|
||||
t.Fatal("a tap from another account, or from a group, was a warrant")
|
||||
t.Fatal("a tap from another account, on changed words, or from a group, was a warrant")
|
||||
}
|
||||
if got := l.tg.since(strangerAccount, since); len(got) != 0 {
|
||||
t.Errorf("the bot answered the stranger: %+v", got)
|
||||
}
|
||||
if got := l.tg.since(groupChat, since); len(got) != 0 {
|
||||
t.Errorf("the bot answered in a group: %+v", got)
|
||||
@@ -278,23 +283,42 @@ func TestTheOperatorsAnswerEndToEnd(t *testing.T) {
|
||||
})
|
||||
|
||||
// --- 7. While an agent can become root where the router runs, nothing proven there approves.
|
||||
t.Run("while an agent can become root on the router's machine, approve is not offered", func(t *testing.T) {
|
||||
key := "machine." + machine + ".agent-root"
|
||||
l.raise(map[string]any{"key": key, "kind": "agent-root", "severity": "urgent",
|
||||
"summary": "an agent can become root on anchor without a person",
|
||||
"headline": "Root without you on anchor", "explanation": "A program working for you can become root.",
|
||||
"subject": map[string]any{"scope": "machine", "id": machine, "machine": machine},
|
||||
"raised": time.Now().UTC().Format(time.RFC3339Nano)})
|
||||
time.Sleep(2 * time.Second)
|
||||
t.Run("while the controller cannot say root is free where the router runs, nothing approves", func(t *testing.T) {
|
||||
// An approval the operator already sees on the phone, then root stops being free.
|
||||
since := time.Now()
|
||||
a.ask("a3", "Flip it while root is open?", time.Hour)
|
||||
m := l.tg.waitFor("the third question", operatorAccount, since, 90*time.Second,
|
||||
func(m tgMessage) bool { return !m.Edited && button(m, "Decline") != "" })
|
||||
if button(m, "Approve") != "" {
|
||||
t.Errorf("approve was offered on Telegram while an agent can become root there: %v", labels(m))
|
||||
a.ask("a5", "Flip it while root is checked?", time.Hour)
|
||||
m := l.tg.waitFor("the question asked while root was free", operatorAccount, since, 90*time.Second,
|
||||
func(m tgMessage) bool { return !m.Edited && button(m, "Approve") != "" })
|
||||
l.mu.Lock()
|
||||
l.notFree = map[string]string{machine: "an agent can become root without a person"}
|
||||
asked := l.rootAsked
|
||||
l.mu.Unlock()
|
||||
tapped := time.Now()
|
||||
l.tg.tapped(operatorAccount, operatorAccount, "private", m.ID, button(m, "Approve"))
|
||||
l.tg.waitFor("the refusal of an approval while root is not free", operatorAccount, tapped, 90*time.Second,
|
||||
func(m tgMessage) bool { return strings.Contains(m.Text, "That answer was not taken") })
|
||||
l.mu.Lock()
|
||||
askedAgain := l.rootAsked > asked
|
||||
l.mu.Unlock()
|
||||
if !askedAgain {
|
||||
t.Error("the router judged an approval without asking the controller whether root is free")
|
||||
}
|
||||
l.clear(key)
|
||||
if err := a.client.Cancel("a3"); err != nil {
|
||||
if !a.noWordOn("a5", 2*time.Second) {
|
||||
t.Fatal("an approval was a warrant while root was not free")
|
||||
}
|
||||
// A new ask that needs an approval is refused to its asker, in words: no channel can carry it now.
|
||||
a.ask("a3", "Flip it while root is not free?", time.Hour)
|
||||
w := a.warrantFor("a3", 30*time.Second)
|
||||
if w.Outcome != asks.OutcomeRefused || w.By != nil {
|
||||
t.Errorf("an approving ask while root is not free: %+v", w)
|
||||
}
|
||||
if got := a.performed(); len(got) != 1 {
|
||||
t.Fatalf("performed %v while root was not free", got)
|
||||
}
|
||||
l.mu.Lock()
|
||||
l.notFree = nil
|
||||
l.mu.Unlock()
|
||||
if err := a.client.Cancel("a5"); err != nil {
|
||||
t.Errorf("taking the ask back: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user