The give verb opened the desk's hidden prompt for a module's own secret, with the desk named. Now the secret-ask verb (secret ask <node> <module> <name> [--at <desk>]) opens the same prompt from anywhere on the mesh, with the desk the module's machine unless named, and give composes the same line. Every ask is recorded in the store before the prompt opens (migration 0091): one open ask per secret, three an hour, so an agent cannot keep a prompt in front of the operator. The prompt names who asked, from the bus's word on the caller cut to a name's characters, never an argument of the call. The value stays typed at the desk, sealed to the one call and then to the module's machine, never in an argument, a log or an event; a secret the mesh makes itself and a trusted party's secret are refused as before.
513 lines
23 KiB
Go
513 lines
23 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/novox/mesh-controller/internal/broker"
|
|
"github.com/novox/mesh-controller/internal/conditions"
|
|
"github.com/novox/mesh-controller/internal/link"
|
|
"github.com/novox/mesh-controller/internal/secrets"
|
|
)
|
|
|
|
const typed = "123456789:AAEhBP0av28P4XFQnIuR-o-7Xnz1kkUzW3g"
|
|
|
|
// aDesk is the desk path with a prompt the test answers as the operator would, and what it was asked kept.
|
|
func aDesk(t *testing.T, answer func(args map[string]any) (json.RawMessage, error)) (deskGive, *[]string, *[]link.HandAct, *[]map[string]any) {
|
|
t.Helper()
|
|
var accepted []string
|
|
var acts []link.HandAct
|
|
var asked []map[string]any
|
|
return deskGive{
|
|
declares: func(module, name string) error {
|
|
if module != "telegram" || name != "telegram-token" {
|
|
return errors.New(module + " does not declare " + name + " as an own secret")
|
|
}
|
|
return nil
|
|
},
|
|
ask: func(machine string, args map[string]any) (json.RawMessage, error) {
|
|
asked = append(asked, args)
|
|
return answer(args)
|
|
},
|
|
accept: func(value string) (bool, error) { accepted = append(accepted, value); return false, nil },
|
|
record: func(a link.HandAct) error { acts = append(acts, a); return nil },
|
|
}, &accepted, &acts, &asked
|
|
}
|
|
|
|
func sealedTo(t *testing.T, value string) func(args map[string]any) (json.RawMessage, error) {
|
|
return func(args map[string]any) (json.RawMessage, error) {
|
|
sealed, err := secrets.Seal(args["seal_to"].(string), []byte(value+"\n"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
raw, _ := json.Marshal(map[string]any{"sealed": sealed})
|
|
return raw, nil
|
|
}
|
|
}
|
|
|
|
// novox/hq ADR 0259 §10: the value typed at the desk is sealed as `secret accept` seals it, and is in no
|
|
// answer, no prompt argument and no act recorded.
|
|
func TestASecretGivenAtTheDeskIsSealedAndSaidNowhere(t *testing.T) {
|
|
d, accepted, acts, asked := aDesk(t, sealedTo(t, typed))
|
|
words, err := d.give("anchor", "telegram", "telegram-token", "laptop")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(*accepted) != 1 || (*accepted)[0] != typed {
|
|
t.Fatalf("the value sealed is not what was typed, its line ending taken off")
|
|
}
|
|
if len(*acts) != 1 || (*acts)[0].Verb != "secret accept" || (*acts)[0].Cause != "given-at-the-desk" ||
|
|
!strings.Contains((*acts)[0].Why, "at the desk on laptop") {
|
|
t.Errorf("the act: %+v", *acts)
|
|
}
|
|
raw, _ := json.Marshal(struct {
|
|
Words string
|
|
Acts []link.HandAct
|
|
Asked []map[string]any
|
|
}{words, *acts, *asked})
|
|
if strings.Contains(string(raw), typed) || strings.Contains(string(raw), "AAEhBP0") {
|
|
t.Fatal("the value appears in what was said, asked or recorded")
|
|
}
|
|
if !strings.Contains(words, "push anchor") || !strings.Contains(words, "given at the desk on laptop") {
|
|
t.Errorf("%q", words)
|
|
}
|
|
if p := (*asked)[0]; p["seal_to"] == "" || p["timeout_seconds"] != deskPromptWithin {
|
|
t.Errorf("the prompt was asked %v", p)
|
|
}
|
|
}
|
|
|
|
func TestNothingIsAskedForASecretTheMeshWouldRefuse(t *testing.T) {
|
|
for _, c := range [][2]string{{"telegram", "chat-id"}, {"nobody", "telegram-token"}} {
|
|
d, accepted, _, asked := aDesk(t, sealedTo(t, typed))
|
|
if _, err := d.give("anchor", c[0], c[1], "laptop"); err == nil || !strings.Contains(err.Error(), "nobody was asked") {
|
|
t.Errorf("%v: %v", c, err)
|
|
}
|
|
if len(*asked) != 0 || len(*accepted) != 0 {
|
|
t.Errorf("%v: the operator was asked anyway", c)
|
|
}
|
|
}
|
|
d, _, _, _ := aDesk(t, sealedTo(t, typed))
|
|
if _, err := d.give("anchor", "telegram", "telegram-token", ""); err == nil {
|
|
t.Error("no desk was refused nowhere")
|
|
}
|
|
}
|
|
|
|
func TestADismissedEmptyLateOrForeignAnswerTakesNothing(t *testing.T) {
|
|
for want, answer := range map[string]func(map[string]any) (json.RawMessage, error){
|
|
"not answered within 25 seconds": func(map[string]any) (json.RawMessage, error) {
|
|
return json.RawMessage(`{"cancelled":true,"timed_out":true}`), nil
|
|
},
|
|
"was dismissed": func(map[string]any) (json.RawMessage, error) { return json.RawMessage(`{"cancelled":true}`), nil },
|
|
"answered empty": sealedTo(t, " "),
|
|
"not sealed to this call": func(map[string]any) (json.RawMessage, error) {
|
|
other, _, _ := secrets.Keypair()
|
|
sealed, _ := secrets.Seal(other, []byte(typed))
|
|
raw, _ := json.Marshal(map[string]any{"sealed": sealed})
|
|
return raw, nil
|
|
},
|
|
"could not be asked": func(map[string]any) (json.RawMessage, error) { return nil, errors.New("no session answers") },
|
|
} {
|
|
d, accepted, acts, _ := aDesk(t, answer)
|
|
_, err := d.give("anchor", "telegram", "telegram-token", "laptop")
|
|
if err == nil || !strings.Contains(err.Error(), want) || strings.Contains(err.Error(), typed) {
|
|
t.Errorf("want %q, got %v", want, err)
|
|
}
|
|
if len(*accepted) != 0 || len(*acts) != 0 {
|
|
t.Errorf("%s: something was taken or recorded", want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTheGiveVerbRunsTheDeskPathAndTheControllerMayAskTheDesk(t *testing.T) {
|
|
argv, err := argvFor("give", map[string]any{"node": "anchor", "module": "telegram", "secret": "telegram-token", "at": "laptop"})
|
|
if err != nil || strings.Join(argv, " ") != "secret ask anchor telegram telegram-token --at laptop" {
|
|
t.Fatalf("%v %v", argv, err)
|
|
}
|
|
if _, err := argvFor("give", map[string]any{"node": "anchor", "module": "telegram", "secret": "telegram-token"}); err == nil {
|
|
t.Error("give without a desk was taken")
|
|
}
|
|
// secret-ask is the same line, with the desk the module's machine unless named (novox/hq ADR 0277).
|
|
argv, err = argvFor("secret-ask", map[string]any{"node": "anchor", "module": "telegram", "secret": "telegram-token"})
|
|
if err != nil || strings.Join(argv, " ") != "secret ask anchor telegram telegram-token" {
|
|
t.Fatalf("%v %v", argv, err)
|
|
}
|
|
argv, err = argvFor("secret-ask", map[string]any{"node": "anchor", "module": "telegram", "secret": "telegram-token", "at": "laptop"})
|
|
if err != nil || strings.Join(argv, " ") != "secret ask anchor telegram telegram-token --at laptop" {
|
|
t.Fatalf("%v %v", argv, err)
|
|
}
|
|
perms, err := broker.PermissionsFor(broker.Principal{Kind: broker.KindController})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
found := false
|
|
for _, p := range perms.Publish {
|
|
found = found || p == "mesh.seat.node-launcher.tool.secret.*"
|
|
}
|
|
if !found {
|
|
t.Error("the controller may not ask the desk's prompt")
|
|
}
|
|
}
|
|
|
|
// The review of 2026-10-09 (M4): the desk's prompt says who asks in words the caller does not choose — the
|
|
// controller, which the bus alone lets ask it — and what for, from names the controller checked; the prompt
|
|
// carries no free text of the caller's.
|
|
func TestThePromptIsAskedByNameNeverByWordsTheCallerChose(t *testing.T) {
|
|
d, _, _, asked := aDesk(t, sealedTo(t, typed))
|
|
if _, err := d.give("anchor", "telegram", "telegram-token", "laptop"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
p := (*asked)[0]
|
|
if p["module"] != "telegram" || p["secret"] != "telegram-token" || p["node"] != "anchor" {
|
|
t.Errorf("the prompt was not asked by name: %v", p)
|
|
}
|
|
for _, free := range []string{"prompt", "message"} {
|
|
if _, there := p[free]; there {
|
|
t.Errorf("the prompt carries the caller's %s: %v", free, p)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Every value given for a module's own secret is announced as a condition, on every channel (the review of
|
|
// 2026-10-09, M4): a bot token changed by somebody else is a channel that now answers for them.
|
|
func TestAValueGivenAtTheDeskIsAnnounced(t *testing.T) {
|
|
d, _, _, _ := aDesk(t, sealedTo(t, typed))
|
|
var said []string
|
|
d.announce = func(node, module, name, how string) error {
|
|
said = append(said, node+" "+module+" "+name+" "+how)
|
|
return nil
|
|
}
|
|
if _, err := d.give("anchor", "telegram", "telegram-token", "laptop"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(said) != 1 || !strings.Contains(said[0], "anchor telegram telegram-token") || !strings.Contains(said[0], "laptop") {
|
|
t.Fatalf("announced %v", said)
|
|
}
|
|
o := secretGivenObservation("anchor", "telegram", "telegram-token", "at the desk on laptop", time.Date(2026, 10, 9, 12, 3, 0, 0, time.UTC))
|
|
if o.Severity != conditions.Urgent || !strings.Contains(o.Explanation, "telegram token") ||
|
|
len(o.Actions) == 0 || o.Key() == "" {
|
|
t.Errorf("the announcement %+v", o)
|
|
}
|
|
if strings.Contains(o.Summary+o.Explanation+o.Said, typed) {
|
|
t.Error("the announcement carries the value")
|
|
}
|
|
}
|
|
|
|
// **The secret-given condition says itself in its own plain words** (hq issue 359): the words it carries pass
|
|
// the plain rule, from the controller's terminal and from a desk, so the keeper keeps them — they once held a
|
|
// clock time, and the operator read "Novox needs a look … a problem it calls secret given" instead. Its
|
|
// severity and its answer stay: it is heard on every channel, and silenced by the operator.
|
|
func TestTheSecretGivenConditionSaysItselfInPlainWords(t *testing.T) {
|
|
at := time.Date(2026, 10, 9, 23, 32, 0, 0, time.Local)
|
|
for _, how := range []string{"at the controller's terminal", "at the desk on laptop"} {
|
|
o := secretGivenObservation("anchor", "telegram", "telegram-token", how, at)
|
|
w := conditions.Words{Headline: o.Headline, Explanation: o.Explanation, Resolved: o.Resolved, Needs: o.Needs,
|
|
Actions: o.Actions}
|
|
if why, ok := conditions.PlainWords(w, o.Machine); !ok {
|
|
t.Fatalf("given %s, the words are not plain: %s", how, why)
|
|
}
|
|
k, _ := withConditionsInMemory(t)
|
|
c, err := k.Observe(t.Context(), o)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if c.Headline != "New secret given for telegram on anchor" || c.Severity != conditions.Urgent ||
|
|
!strings.HasPrefix(c.Explanation, conditions.NeedsYou+" silence this") ||
|
|
!strings.Contains(c.Explanation, "telegram token of telegram on anchor was given "+how) ||
|
|
len(c.Actions) != 1 || c.Actions[0].Label != "Silence for a week" {
|
|
t.Errorf("given %s, the keeper said %q / %q (%s, %v)", how, c.Headline, c.Explanation, c.Severity, c.Actions)
|
|
}
|
|
if strings.Contains(c.Headline+c.Explanation+c.Resolved+c.Needs, typed) {
|
|
t.Error("the words carry the value")
|
|
}
|
|
}
|
|
// A long module name keeps the headline and the resolved line within their bounds.
|
|
for _, module := range []string{"a-module-with-a-rather-long-name-indeed",
|
|
"a-module-with-a-name-so-long-that-no-headline-could-ever-hold-it"} {
|
|
o := secretGivenObservation("anchor", module, "api-key", "at the controller's terminal", at)
|
|
if why, ok := conditions.PlainWords(conditions.Words{Headline: o.Headline, Explanation: o.Explanation,
|
|
Resolved: o.Resolved, Needs: o.Needs, Actions: o.Actions}, o.Machine); !ok {
|
|
t.Errorf("the module %s: %s", module, why)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The bus lets the controller alone ask the desk's prompt (the review of 2026-10-09, M4): the runtime, which
|
|
// carries every agent's calls, and a person granted every tool are denied it, however wide their grant.
|
|
func TestOnlyTheControllerMayAskTheDesksPrompt(t *testing.T) {
|
|
for _, p := range []broker.Principal{
|
|
{Kind: broker.KindNodeTools, Node: "laptop"},
|
|
{Kind: broker.KindPerson, Module: "operator", Invokes: []string{"*"}},
|
|
{Kind: broker.KindModule, Node: "laptop", Module: "lab", Invokes: []string{"seat:node-launcher.secret"}},
|
|
} {
|
|
perms, err := broker.PermissionsFor(p)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, subject := range []string{"mesh.seat.node-launcher.tool.secret.laptop", "mesh.seat.node-launcher.tool.secret",
|
|
"mesh.mod.rofi.tool.node-launcher.secret", "mesh.mod.rofi.tool.node-launcher.secret.laptop"} {
|
|
if broker.MayPublish(perms, subject) {
|
|
t.Errorf("%s may publish %s", p.Username(), subject)
|
|
}
|
|
}
|
|
}
|
|
perms, _ := broker.PermissionsFor(broker.Principal{Kind: broker.KindController})
|
|
if !broker.MayPublish(perms, "mesh.seat.node-launcher.tool.secret.laptop") {
|
|
t.Error("the controller may not ask the desk's prompt")
|
|
}
|
|
}
|
|
|
|
// A value for a secret comes from the terminal or the desk, never through a verb (the review of 2026-10-09,
|
|
// M4): `secret accept` with a value, run for a verb, is refused before anything is read.
|
|
func TestASecretValueIsNeverAcceptedThroughAVerb(t *testing.T) {
|
|
t.Setenv(verbVar, "mesh-controller.command")
|
|
for _, args := range [][]string{
|
|
{"accept", "anchor", "telegram", "telegram-token", "--from", "/dev/null"},
|
|
{"accept", "anchor", "app", "db", "--from", "/dev/null", "--provider", "store"},
|
|
} {
|
|
err := secretCommand(context.Background(), args)
|
|
if err == nil || !strings.Contains(err.Error(), "never through a verb") {
|
|
t.Errorf("%v: %v", args, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The `give` and `secret-ask` verbs' own line passes the terminal-only rule of ADR 0266, and no `secret accept`
|
|
// does: a value, a file, a provider or an extra word is still the terminal's alone (novox/hq ADR 0277).
|
|
func TestOnlyTheGiveLinePassesTheTerminalRuleForSecrets(t *testing.T) {
|
|
for _, argv := range [][]string{
|
|
{"secret", "ask", "anchor", "telegram", "telegram-token", "--at", "laptop"},
|
|
{"secret", "ask", "anchor", "telegram", "telegram-token"},
|
|
} {
|
|
if err := terminalOnly(argv); err != nil {
|
|
t.Errorf("%v refused: %v", argv, err)
|
|
}
|
|
}
|
|
for _, argv := range [][]string{
|
|
{"secret", "accept", "anchor", "telegram", "telegram-token", "--at-desk", "laptop"},
|
|
{"secret", "ask", "anchor", "telegram", "telegram-token", "--from", "/tmp/x"},
|
|
{"secret", "ask", "anchor", "telegram", "telegram-token", "--at", "laptop", "--local"},
|
|
{"secret", "ask", "anchor", "telegram", "--at", "laptop"},
|
|
{"secret", "accept", "anchor", "telegram", "telegram-token"},
|
|
{"secret", "accept", "anchor", "telegram", "telegram-token", "--from", "/tmp/x"},
|
|
{"secret", "accept", "anchor", "telegram", "telegram-token", "--at-desk", "laptop", "--local"},
|
|
{"secret", "accept", "anchor", "telegram", "--provider", "--at-desk", "laptop"},
|
|
{"secret", "export", "anchor", "telegram", "telegram-token", "--at-desk", "laptop"},
|
|
} {
|
|
if err := terminalOnly(argv); err == nil {
|
|
t.Errorf("%v passed the terminal rule", argv)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The confirmation review of 2026-10-09, N1-give: a desk's prompt is answered over the desk machine's bus, and
|
|
// on a desk machine agents run as the operator, who holds its runtime's credential — so a trusted party's
|
|
// secret (a module running as an account of its own: the Telegram bot's token) is never taken at a desk.
|
|
// Refused before anybody is asked to type, whoever called, naming the terminal's line.
|
|
func TestATrustedPartysSecretIsNeverTakenAtADesk(t *testing.T) {
|
|
d, accepted, acts, asked := aDesk(t, sealedTo(t, typed))
|
|
d.trusted = func(module string) (bool, error) { return module == "telegram", nil }
|
|
_, err := d.give("anchor", "telegram", "telegram-token", "laptop")
|
|
if err == nil || !strings.Contains(err.Error(), "controller's terminal alone") ||
|
|
!strings.Contains(err.Error(), "secret accept anchor telegram telegram-token") {
|
|
t.Fatalf("a trusted party's secret was taken at the desk, or refused without the line: %v", err)
|
|
}
|
|
if len(*asked)+len(*accepted)+len(*acts) != 0 {
|
|
t.Errorf("asked %v, accepted %d, recorded %v", *asked, len(*accepted), *acts)
|
|
}
|
|
d.trusted = func(string) (bool, error) { return false, errors.New("the store did not answer") }
|
|
if _, err := d.give("anchor", "telegram", "telegram-token", "laptop"); err == nil || len(*asked) != 0 {
|
|
t.Errorf("a module not known to be untrusted was asked at the desk: %v", err)
|
|
}
|
|
}
|
|
|
|
// And who may answer the desk's prompt at all: only the runtime of the machine it is asked on, carrying the
|
|
// launcher that holds the seat there — never the controller, another machine's runtime, or a module's own
|
|
// account (the confirmation review of 2026-10-09, N1-give).
|
|
func TestOnlyTheDeskMachinesLauncherMayAnswerItsPrompt(t *testing.T) {
|
|
launcher := broker.Declared{Module: "rofi", Holds: []broker.Seat{{Name: "node-launcher", Scope: "node",
|
|
Serves: []string{"run", "secret"}}}}
|
|
subject := "mesh.seat.node-launcher.tool.secret.laptop"
|
|
for _, c := range []struct {
|
|
p broker.Principal
|
|
answers bool
|
|
}{
|
|
{broker.Principal{Kind: broker.KindNodeTools, Node: "laptop", Module: broker.RuntimeModule, Carries: []broker.Declared{launcher}}, true},
|
|
{broker.Principal{Kind: broker.KindNodeTools, Node: "anchor", Module: broker.RuntimeModule, Carries: []broker.Declared{launcher}}, false},
|
|
{broker.Principal{Kind: broker.KindController}, false},
|
|
{broker.Principal{Kind: broker.KindModule, Node: "laptop", Module: "lab"}, false},
|
|
{broker.Principal{Kind: broker.KindNode, Node: "laptop"}, false},
|
|
} {
|
|
perms, err := broker.PermissionsFor(c.p)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := broker.MaySubscribe(perms, subject); got != c.answers {
|
|
t.Errorf("%s may answer %s: %v, want %v", c.p.Username(), subject, got, c.answers)
|
|
}
|
|
}
|
|
}
|
|
|
|
// N1-give at the controller's terminal (the confirmation review of 2026-10-09): a trusted party's secret is
|
|
// announced before it is kept, and not kept when the announcement fails; another module's is kept first and
|
|
// a failed announcement is said, not undone.
|
|
func TestATrustedPartysSecretGivenAtTheTerminalIsAnnouncedBeforeItIsKept(t *testing.T) {
|
|
var order []string
|
|
announce := func(fail bool) func() error {
|
|
return func() error {
|
|
order = append(order, "announce")
|
|
if fail {
|
|
return errors.New("no channel")
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
keep := func() (bool, error) { order = append(order, "keep"); return false, nil }
|
|
|
|
order = nil
|
|
if _, unannounced, err := keepGiven(true, announce(false), keep); err != nil || unannounced != nil ||
|
|
strings.Join(order, ",") != "announce,keep" {
|
|
t.Errorf("trusted: %v %v, order %v; want announced, then kept", unannounced, err, order)
|
|
}
|
|
order = nil
|
|
if _, _, err := keepGiven(true, announce(true), keep); err == nil || strings.Join(order, ",") != "announce" {
|
|
t.Errorf("trusted, announcement failed: %v, order %v; want refused and nothing kept", err, order)
|
|
}
|
|
order = nil
|
|
if _, unannounced, err := keepGiven(false, announce(true), keep); err != nil || unannounced == nil ||
|
|
strings.Join(order, ",") != "keep,announce" {
|
|
t.Errorf("not trusted: %v %v, order %v; want kept, then the failed announcement said", unannounced, err, order)
|
|
}
|
|
order = nil
|
|
failing := func() (bool, error) { order = append(order, "keep"); return false, errors.New("store away") }
|
|
if _, _, err := keepGiven(false, announce(false), failing); err == nil || strings.Join(order, ",") != "keep" {
|
|
t.Errorf("not trusted, keep failed: %v, order %v; want refused and nothing announced", err, order)
|
|
}
|
|
}
|
|
|
|
// A machine the mesh does not know, as the secret's or as the desk, is refused before anybody is asked to type.
|
|
func TestAGiveNamingAMachineTheMeshDoesNotKnowAsksNobody(t *testing.T) {
|
|
for _, unknown := range []string{"elsewhere", "nodesk"} {
|
|
d, accepted, acts, asked := aDesk(t, func(map[string]any) (json.RawMessage, error) {
|
|
t.Fatal("the desk was asked")
|
|
return nil, nil
|
|
})
|
|
d.known = func(machine string) error {
|
|
if machine == unknown {
|
|
return errors.New("no node " + machine)
|
|
}
|
|
return nil
|
|
}
|
|
node, desk := "anchor", "laptop"
|
|
if unknown == "elsewhere" {
|
|
node = unknown
|
|
} else {
|
|
desk = unknown
|
|
}
|
|
_, err := d.give(node, "telegram", "telegram-token", desk)
|
|
if err == nil || !strings.Contains(err.Error(), "nobody was asked") || !strings.Contains(err.Error(), unknown) {
|
|
t.Errorf("%s: %v", unknown, err)
|
|
}
|
|
if len(*accepted)+len(*acts)+len(*asked) != 0 {
|
|
t.Errorf("%s: something happened: %v %v %v", unknown, *accepted, *acts, *asked)
|
|
}
|
|
}
|
|
}
|
|
|
|
// novox/hq ADR 0277: the prompt names who asked — the controller's word on the bus's caller, cut to a name's
|
|
// characters — and never a word the caller chose: there is no argument for it.
|
|
func TestThePromptNamesWhoAskedFromTheBussWordAlone(t *testing.T) {
|
|
d, _, acts, asked := aDesk(t, sealedTo(t, typed))
|
|
d.askedBy = "g14/claude-code, through the mesh-controller seat"
|
|
if _, err := d.give("anchor", "telegram", "telegram-token", "laptop"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := (*asked)[0]["asked_by"]; got != "g14/claude-code" {
|
|
t.Errorf("asked_by %q", got)
|
|
}
|
|
if len(*acts) != 1 || !strings.Contains((*acts)[0].Why, "asked by g14/claude-code") {
|
|
t.Errorf("the record: %+v", *acts)
|
|
}
|
|
for in, want := range map[string]string{
|
|
"jochen at a shell on novox": "jochen at a shell on novox",
|
|
"laptop/agent": "laptop/agent",
|
|
"Your bank asks\nType your PIN, now": "Your bank asks-Type your PIN",
|
|
"": "an unnamed caller",
|
|
"<b>x</b>": "an unnamed caller",
|
|
strings.Repeat("a", 100): strings.Repeat("a", 80),
|
|
"--prompt, something else": "an unnamed caller",
|
|
} {
|
|
if got := askedByName(in); got != want {
|
|
t.Errorf("askedByName(%q) = %q, want %q", in, got, want)
|
|
}
|
|
}
|
|
// The verb's schema has no argument that reaches the prompt's words.
|
|
for _, verb := range []string{"give", "secret-ask"} {
|
|
for _, free := range []string{"asked_by", "prompt", "message", "value", "from"} {
|
|
if _, err := argvFor(verb, map[string]any{"node": "anchor", "module": "telegram", "secret": "telegram-token",
|
|
"at": "laptop", free: "x"}); err == nil {
|
|
t.Errorf("%s takes %s", verb, free)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// An ask for a secret is bounded before anybody is asked to type (ADR 0277): the record refuses it, nothing is
|
|
// asked; and how every ask ends is recorded.
|
|
func TestASecretAskIsBoundedAndItsEndRecorded(t *testing.T) {
|
|
d, accepted, _, asked := aDesk(t, sealedTo(t, typed))
|
|
var ended []string
|
|
d.open = func(node, module, name, desk string) (int64, error) { return 7, nil }
|
|
d.end = func(id int64, outcome string) error {
|
|
ended = append(ended, fmt.Sprintf("%d %s", id, outcome))
|
|
return nil
|
|
}
|
|
if _, err := d.give("anchor", "telegram", "telegram-token", "laptop"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
d.open = func(node, module, name, desk string) (int64, error) {
|
|
return 0, errors.New("an ask for telegram-token of telegram on anchor is still open")
|
|
}
|
|
_, err := d.give("anchor", "telegram", "telegram-token", "laptop")
|
|
if err == nil || !strings.Contains(err.Error(), "nobody was asked to type anything") || !strings.Contains(err.Error(), "still open") {
|
|
t.Errorf("a second ask: %v", err)
|
|
}
|
|
if len(*asked) != 1 || len(*accepted) != 1 {
|
|
t.Errorf("asked %d, accepted %d", len(*asked), len(*accepted))
|
|
}
|
|
d.open = func(node, module, name, desk string) (int64, error) { return 8, nil }
|
|
d.ask = func(string, map[string]any) (json.RawMessage, error) {
|
|
return json.RawMessage(`{"cancelled":true}`), nil
|
|
}
|
|
if _, err := d.give("anchor", "telegram", "telegram-token", "laptop"); !errors.Is(err, errNothingGiven) {
|
|
t.Errorf("a dismissed prompt: %v", err)
|
|
}
|
|
if strings.Join(ended, "; ") != "7 given; 8 dismissed" {
|
|
t.Errorf("ended: %v", ended)
|
|
}
|
|
}
|
|
|
|
// A secret ask cannot be turned into a secret read: the line carries no value, the answer carries none, and every
|
|
// other `secret` line is the terminal's.
|
|
func TestASecretAskIsNeverASecretRead(t *testing.T) {
|
|
t.Setenv(verbVar, "mesh-controller.secret-ask")
|
|
for _, args := range [][]string{
|
|
{"ask", "anchor", "telegram", "telegram-token", "the-value"},
|
|
{"ask", "anchor", "telegram"},
|
|
{"ask", "anchor", "telegram", "telegram-token", "--from", "/dev/null"},
|
|
} {
|
|
if err := secretCommand(context.Background(), args); err == nil {
|
|
t.Errorf("secret %v was taken", args)
|
|
}
|
|
}
|
|
for _, args := range [][]string{{"recover", "anchor", "telegram", "telegram-token"}, {"export"}} {
|
|
if err := terminalOnly(append([]string{"secret"}, args...)); err == nil {
|
|
t.Errorf("secret %v passed the terminal rule", args)
|
|
}
|
|
}
|
|
}
|