Merge feat/modules-name-no-node into bulk-a
This commit is contained in:
@@ -52,7 +52,7 @@ through an injected runner and a fake root in the tests.
|
||||
|
||||
## What changes when it is assigned
|
||||
|
||||
| | g14 | shanks |
|
||||
| | node-b | node-d |
|
||||
|---|---|---|
|
||||
| package | none: `blueman` 2.4.6 is installed, explicitly, from `extra` | the same |
|
||||
| start | none: dex starts the applet from the package's entry, in the login session's scope; the tray icon with it | none on disk. **The applet running now came from the predecessor's window-manager line** (a child of i3, since the session of 2026-10-04 16:00). That session began before the `i3` module dropped the line and installed `dex`, so the next login is the first that starts it from the entry |
|
||||
@@ -62,7 +62,7 @@ The workstations are already in the state this module describes.
|
||||
|
||||
## Migration (ADR 0182)
|
||||
|
||||
Nothing is required on either machine. On shanks, log out and in once, or run `blueman_restart`, and
|
||||
Nothing is required on either machine. On node-d, log out and in once, or run `blueman_restart`, and
|
||||
the applet runs from its one start. `blueman_check` then answers `ok`.
|
||||
|
||||
## Leaves as found
|
||||
|
||||
@@ -22,7 +22,7 @@ Research 027 asked for "`cups` with the printer's driver". Measured on 2026-10-0
|
||||
| workstation | queue | device | prints through | driver package installed |
|
||||
|---|---|---|---|---|
|
||||
| laptop | `Brother` (MFC-L8690CDW) | `ipp://` on the LAN | **IPP Everywhere, driverless** | `brother-mfc-l8690cdw` (AUR), unused |
|
||||
| desktop | `Brother_MFC_Novox` (default) | `ipp://` on the LAN | **IPP Everywhere, driverless** | `brother-mfc-l8390cdw` and its `-debug` (AUR), unused |
|
||||
| desktop | `Brother_MFC_NodeC` (default) | `ipp://` on the LAN | **IPP Everywhere, driverless** | `brother-mfc-l8390cdw` and its `-debug` (AUR), unused |
|
||||
| desktop | `Kanjuro` (Canon PIXMA MG4200) | `cnijnet:` | the vendor's PPD and filter | `cnijfilter-mg4200` 3.80 (AUR) |
|
||||
|
||||
**Both Brother queues already print without a vendor driver.** CUPS's own IPP Everywhere support,
|
||||
|
||||
@@ -36,13 +36,13 @@ func theDesktop(t *testing.T, more func(line string, c Cmd) (Result, bool)) *fak
|
||||
case "lpstat -r":
|
||||
return ok("scheduler is running\n")
|
||||
case "lpstat -d":
|
||||
return ok("system default destination: Brother_MFC_Novox\n")
|
||||
return ok("system default destination: Brother_MFC_NodeC\n")
|
||||
case "lpstat -p":
|
||||
return ok("printer Brother_MFC_Novox is idle. enabled since Mon Jun 29 21:34:30 2026\n" +
|
||||
return ok("printer Brother_MFC_NodeC is idle. enabled since Mon Jun 29 21:34:30 2026\n" +
|
||||
"printer Kanjuro disabled since Sun Jun 9 11:16:03 2024 -\n\tPaused\n")
|
||||
case "lpstat -a":
|
||||
return ok("Brother_MFC_Novox accepting requests since Mon Jun 29 21:34:30 2026\nKanjuro not accepting requests since Sun Jun 9 11:16:03 2024 -\n\tRejecting Jobs\n")
|
||||
case "lpoptions -p Brother_MFC_Novox":
|
||||
return ok("Brother_MFC_NodeC accepting requests since Mon Jun 29 21:34:30 2026\nKanjuro not accepting requests since Sun Jun 9 11:16:03 2024 -\n\tRejecting Jobs\n")
|
||||
case "lpoptions -p Brother_MFC_NodeC":
|
||||
return ok(`copies=1 device-uri=ipp://192.0.2.171/ipp/port1 finishings=3 marker-levels=70,100,8,100 marker-low-levels=10,10,10,10 marker-names='Black\ Toner\ Cartridge,Cyan\ Toner\ Cartridge,Magenta\ Toner\ Cartridge,Yellow\ Toner\ Cartridge' marker-types=toner,toner,toner,toner printer-is-shared=false printer-make-and-model='Printer - IPP Everywhere' printer-state-reasons=none`)
|
||||
case "lpoptions -p Kanjuro":
|
||||
return ok(`device-uri=cnijnet:/18-0C-AC-B0-62-83 printer-is-shared=false printer-make-and-model='Canon MG4200 series Ver.3.80' printer-state-reasons=paused`)
|
||||
@@ -54,7 +54,7 @@ func theDesktop(t *testing.T, more func(line string, c Cmd) (Result, bool)) *fak
|
||||
func TestPrintersReadsStateDeviceModelAndSupplies(t *testing.T) {
|
||||
theDesktop(t, nil)
|
||||
got, err := Printers()
|
||||
if err != nil || len(got.Printers) != 2 || got.Default != "Brother_MFC_Novox" || got.Scheduler != "scheduler is running" {
|
||||
if err != nil || len(got.Printers) != 2 || got.Default != "Brother_MFC_NodeC" || got.Scheduler != "scheduler is running" {
|
||||
t.Fatalf("%+v %v", got, err)
|
||||
}
|
||||
b, k := got.Printers[0], got.Printers[1]
|
||||
@@ -186,11 +186,11 @@ func TestDefaultReadsAndSetsThroughSudo(t *testing.T) {
|
||||
return Result{}, false
|
||||
})
|
||||
got, err := Default("")
|
||||
if err != nil || got["default"] != "Brother_MFC_Novox" {
|
||||
if err != nil || got["default"] != "Brother_MFC_NodeC" {
|
||||
t.Fatalf("%v %v", got, err)
|
||||
}
|
||||
got, err = Default("Kanjuro")
|
||||
if err != nil || got["default"] != "Kanjuro" || got["was"] != "Brother_MFC_Novox" {
|
||||
if err != nil || got["default"] != "Kanjuro" || got["was"] != "Brother_MFC_NodeC" {
|
||||
t.Fatalf("%v %v", got, err)
|
||||
}
|
||||
if l := f.lines(); l[len(l)-1] != "sudo -n lpadmin -d Kanjuro" {
|
||||
|
||||
@@ -133,7 +133,7 @@ func TestANotifierThatStartedAgainIsShownNothingFromMemory(t *testing.T) {
|
||||
d, shown, said, store := newDesk(t, "laptop")
|
||||
d.Paused("laptop", false)
|
||||
d.SayStanding()
|
||||
take(t, d, asks.Show, asks.Message{Handle: "open", Title: "2 failed services on shanks",
|
||||
take(t, d, asks.Show, asks.Message{Handle: "open", Title: "2 failed services on node-d",
|
||||
Buttons: []asks.Button{{Label: "Silence for a week", Ticket: "t-open"}}})
|
||||
take(t, d, asks.Show, asks.Message{Handle: "silenced", Title: "Agent-home shrank",
|
||||
Buttons: []asks.Button{{Label: "Silence for a week", Ticket: "t-silenced"}}})
|
||||
@@ -162,7 +162,7 @@ func TestANotifierThatStartedAgainIsShownNothingFromMemory(t *testing.T) {
|
||||
t.Fatalf("the standing after the start: %d said, last %s", len(got)-standings, got[len(got)-1])
|
||||
}
|
||||
// The router says the open one again under its handle: in place of its notification, with its answers.
|
||||
take(t, d, asks.Show, asks.Message{Handle: "open", Title: "2 failed services on shanks",
|
||||
take(t, d, asks.Show, asks.Message{Handle: "open", Title: "2 failed services on node-d",
|
||||
Buttons: []asks.Button{{Label: "Silence for a week", Ticket: "t-open"}}})
|
||||
last := (*shown)[len(*shown)-1]
|
||||
if len(*shown) != before+1 || last.args["replace_id"] != openID || last.args["actions"] == nil {
|
||||
@@ -329,7 +329,7 @@ func TestItRunsAsItsOwnAccount(t *testing.T) {
|
||||
// was not shown; a message under a new handle replaces nothing.
|
||||
func TestAMessageSaidAgainUnderItsHandleReplacesItsNotification(t *testing.T) {
|
||||
d, shown, _, store := newDesk(t, "laptop", "desktop")
|
||||
take(t, d, asks.Send, asks.Message{Handle: "m1", Title: "2 failed services on shanks"})
|
||||
take(t, d, asks.Send, asks.Message{Handle: "m1", Title: "2 failed services on node-d"})
|
||||
take(t, d, asks.Send, asks.Message{Handle: "m2", Title: "another problem"})
|
||||
if len(*shown) != 4 {
|
||||
t.Fatalf("shown %d", len(*shown))
|
||||
@@ -342,7 +342,7 @@ func TestAMessageSaidAgainUnderItsHandleReplacesItsNotification(t *testing.T) {
|
||||
first := store["m1"].On
|
||||
// The desk now names a third machine as well.
|
||||
d.Machines = func() []string { return []string{"laptop", "desktop", "third"} }
|
||||
take(t, d, asks.Show, asks.Message{Handle: "m1", Title: "Still open: 2 failed services on shanks", Urgent: true,
|
||||
take(t, d, asks.Show, asks.Message{Handle: "m1", Title: "Still open: 2 failed services on node-d", Urgent: true,
|
||||
Buttons: []asks.Button{{Label: "Silence for a week", Ticket: "t1"}}})
|
||||
again := (*shown)[4:]
|
||||
if len(again) != 3 {
|
||||
@@ -367,7 +367,7 @@ func TestAMessageSaidAgainUnderItsHandleReplacesItsNotification(t *testing.T) {
|
||||
// later edit or clearing still reaches it; failing everywhere, it is said to the router as failed.
|
||||
func TestAFailedReplaceKeepsTheNotificationItWasToReplace(t *testing.T) {
|
||||
d, shown, said, store := newDesk(t, "laptop", "desktop")
|
||||
take(t, d, asks.Send, asks.Message{Handle: "m1", Title: "2 failed services on shanks"})
|
||||
take(t, d, asks.Send, asks.Message{Handle: "m1", Title: "2 failed services on node-d"})
|
||||
first := store["m1"].On
|
||||
notify := d.Notify
|
||||
d.Notify = func(machine string, args map[string]any) (json.RawMessage, error) {
|
||||
@@ -376,12 +376,12 @@ func TestAFailedReplaceKeepsTheNotificationItWasToReplace(t *testing.T) {
|
||||
}
|
||||
return notify(machine, args)
|
||||
}
|
||||
take(t, d, asks.Send, asks.Message{Handle: "m1", Title: "Still open: 2 failed services on shanks"})
|
||||
take(t, d, asks.Send, asks.Message{Handle: "m1", Title: "Still open: 2 failed services on node-d"})
|
||||
if got := store["m1"].On["desktop"]; got != first["desktop"] {
|
||||
t.Fatalf("the desktop's notification %d was forgotten: %v", first["desktop"], store["m1"].On)
|
||||
}
|
||||
d.Notify = func(string, map[string]any) (json.RawMessage, error) { return nil, errors.New("away") }
|
||||
take(t, d, asks.Send, asks.Message{Handle: "m1", Title: "Now urgent: 2 failed services on shanks"})
|
||||
take(t, d, asks.Send, asks.Message{Handle: "m1", Title: "Now urgent: 2 failed services on node-d"})
|
||||
if len(said[asks.IntakeSubject(asks.FailedWhat, Kind)]) != 1 {
|
||||
t.Fatalf("shown nowhere, and not said as failed")
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ func TestANotificationsChosenAnswerIsSaidAsAnEvent(t *testing.T) {
|
||||
withBus(t)
|
||||
bin := fakeBinaries(t, map[string]string{"notify-send": `for a in "$@"; do printf '[%s]' "$a"; done > "$LOG"; echo 42; sleep 0.2; echo "$CHOSEN"`})
|
||||
t.Setenv("LOG", filepath.Join(bin, "log"))
|
||||
t.Setenv("MESH_NODE", "g14")
|
||||
t.Setenv("MESH_NODE", "node-b")
|
||||
var mu sync.Mutex
|
||||
var said []map[string]any
|
||||
before := emit
|
||||
@@ -58,7 +58,7 @@ func TestANotificationsChosenAnswerIsSaidAsAnEvent(t *testing.T) {
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
}
|
||||
mu.Lock()
|
||||
if len(said) != 1 || said[0]["token"] != "c3d4" || said[0]["id"] != 42 || said[0]["machine"] != "g14" {
|
||||
if len(said) != 1 || said[0]["token"] != "c3d4" || said[0]["id"] != 42 || said[0]["machine"] != "node-b" {
|
||||
t.Fatalf("said %v", said)
|
||||
}
|
||||
said = nil
|
||||
@@ -96,7 +96,7 @@ func TestActionsAreRefusedForWhatTheyCannotBe(t *testing.T) {
|
||||
// **A bundle that started says so**, so the operator channel shows again the answers lost with the one
|
||||
// before.
|
||||
func TestABundleThatStartedSaysSo(t *testing.T) {
|
||||
t.Setenv("MESH_NODE", "g14")
|
||||
t.Setenv("MESH_NODE", "node-b")
|
||||
var said []string
|
||||
before := emit
|
||||
emit = func(event string, body any) error {
|
||||
@@ -105,7 +105,7 @@ func TestABundleThatStartedSaysSo(t *testing.T) {
|
||||
}
|
||||
t.Cleanup(func() { emit = before })
|
||||
sayStarted()
|
||||
if len(said) != 1 || said[0] != "started map[machine:g14]" {
|
||||
if len(said) != 1 || said[0] != "started map[machine:node-b]" {
|
||||
t.Fatalf("%v", said)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,8 +99,8 @@ On the laptop:
|
||||
|
||||
1. `systemctl --user disable --now mem-guard.service`, then delete
|
||||
`~/.config/systemd/user/mem-guard.service` and `~/scripts/mem-guard.sh`.
|
||||
2. `/etc/systemd/oomd.conf.d/g14-oomd.conf`: the same values as the module's drop-in.
|
||||
3. `/etc/sysctl.d/90-g14-zram.conf`: the same key as the module's file.
|
||||
2. `/etc/systemd/oomd.conf.d/node-b-oomd.conf`: the same values as the module's drop-in.
|
||||
3. `/etc/sysctl.d/90-node-b-zram.conf`: the same key as the module's file.
|
||||
|
||||
The desktop had none of these.
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ func testFiles(t *testing.T) *rig {
|
||||
r.Root = g.root
|
||||
r.Now = func() time.Time { return time.Date(2026, 10, 16, 12, 0, 0, 0, time.UTC) }
|
||||
seq := 0
|
||||
g.Files = &Files{Reader: r, State: filepath.Join(dir, "state"), Owned: map[string]string{}, Node: "shanks",
|
||||
g.Files = &Files{Reader: r, State: filepath.Join(dir, "state"), Owned: map[string]string{}, Node: "node-d",
|
||||
OwnerOf: func(p string) (Ident, error) {
|
||||
if id, ok := g.owners[p]; ok {
|
||||
return id, nil
|
||||
@@ -344,7 +344,7 @@ func TestAnOwnedPathIsRefusedNamingItsOwnerAndPassesWithAnOverride(t *testing.T)
|
||||
if len(lines) != 1 || lines[0]["override"] != ov.Override {
|
||||
t.Errorf("log: %v", lines)
|
||||
}
|
||||
if len(g.told) != 1 || !strings.HasPrefix(g.told[0], "mesh-files on shanks | ") ||
|
||||
if len(g.told) != 1 || !strings.HasPrefix(g.told[0], "mesh-files on node-d | ") ||
|
||||
!strings.Contains(g.told[0], ov.Override) || !strings.Contains(g.told[0], "mesh-hosts (node-hostname)") ||
|
||||
!strings.Contains(g.told[0], "person.jochen") {
|
||||
t.Errorf("told: %v", g.told)
|
||||
@@ -384,7 +384,7 @@ func TestACredentialFileIsRefusedToReadButMayBeCopied(t *testing.T) {
|
||||
func TestATargetOnAnotherNodeIsRefused(t *testing.T) {
|
||||
g := testFiles(t)
|
||||
write(t, g.path("a"), "x")
|
||||
for _, to := range []string{"ace:/tmp/a", "novox:/etc/hosts"} {
|
||||
for _, to := range []string{"node-a:/tmp/a", "node-c:/etc/hosts"} {
|
||||
for _, run := range []func() error{
|
||||
func() error { _, err := g.Copy(by, g.path("a"), to); return err },
|
||||
func() error { _, err := g.Move(by, g.path("a"), to); return err },
|
||||
|
||||
@@ -80,7 +80,7 @@ func TestUploadSendsTheFileUnderItsHash(t *testing.T) {
|
||||
g, st, lv := transferRig(t)
|
||||
body := bytes.Repeat([]byte("a document\n"), 1000)
|
||||
write(t, g.path("home/ops/doc.pdf"), string(body))
|
||||
ans, err := g.Upload(g.path("home/ops/doc.pdf"), "mesh-shanks-brother", "")
|
||||
ans, err := g.Upload(g.path("home/ops/doc.pdf"), "mesh-node-d-brother", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -88,10 +88,10 @@ func TestUploadSendsTheFileUnderItsHash(t *testing.T) {
|
||||
if ans.Key != want || ans.SHA256 != want || ans.Size != int64(len(body)) {
|
||||
t.Fatalf("answer %+v", ans)
|
||||
}
|
||||
if !bytes.Equal(st.objects["/mesh-shanks-brother/"+want], body) {
|
||||
if !bytes.Equal(st.objects["/mesh-node-d-brother/"+want], body) {
|
||||
t.Fatal("the store did not receive exactly the file's bytes")
|
||||
}
|
||||
if len(lv.asks) != 1 || lv.asks[0] != "put mesh-shanks-brother/"+want {
|
||||
if len(lv.asks) != 1 || lv.asks[0] != "put mesh-node-d-brother/"+want {
|
||||
t.Fatalf("link asks %v", lv.asks)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,14 @@ import "testing"
|
||||
|
||||
// TestSendKeyNamesThisMachine proves Send asks its own machine's node-notifier, not the bare seat: a bare
|
||||
// "seat:node-notifier.send" is answered by whichever machine's node-notifier takes it first, so notices sent
|
||||
// from g14 and shanks were lost to "503 no responders" or another machine's desktop (novox/hq issue 865).
|
||||
// from node-b and node-d were lost to "503 no responders" or another machine's desktop (novox/hq issue 865).
|
||||
// desk-channel names the machine it asks the same way ("seat:node-notifier.send@"+machine,
|
||||
// modules/desk-channel/cmd/desk-channel/main.go); sendKey must read this machine's own name the same way the
|
||||
// SDK's own Emit does, from MESH_NODE, which the runtime sets.
|
||||
func TestSendKeyNamesThisMachine(t *testing.T) {
|
||||
t.Setenv("MESH_NODE", "g14")
|
||||
t.Setenv("MESH_NODE", "node-b")
|
||||
|
||||
if got, want := sendKey(), "seat:node-notifier.send@g14"; got != want {
|
||||
if got, want := sendKey(), "seat:node-notifier.send@node-b"; got != want {
|
||||
t.Fatalf("sendKey() = %q, want %q (it must name this machine, as desk-channel does)", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ func coreOnABus(t *testing.T) (ask func(name, key string) (Answer, error), ancho
|
||||
const conf = `listen: 127.0.0.1:-1
|
||||
authorization {
|
||||
users = [
|
||||
{ user: "novox.mesh-vault", password: "pw", permissions: {
|
||||
{ user: "node-c.mesh-vault", password: "pw", permissions: {
|
||||
publish: { deny: [">"] }
|
||||
subscribe: { allow: ["mesh.seat.mesh-vault.call.get.*"] }
|
||||
allow_responses: true } }
|
||||
@@ -150,7 +150,7 @@ authorization {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
vaultConn := connectAs(t, url, "novox.mesh-vault")
|
||||
vaultConn := connectAs(t, url, "node-c.mesh-vault")
|
||||
if _, err := vaultConn.Subscribe("mesh.seat.mesh-vault.call.get.*", func(m *nats.Msg) {
|
||||
parts := strings.Split(m.Subject, ".")
|
||||
var args struct {
|
||||
|
||||
@@ -6,25 +6,25 @@ import "testing"
|
||||
// `state`) reads as a removed key, whichever way the vault reads it; the fields the controller adds change nothing.
|
||||
func TestAnEndedAssignmentIsNotAnAssignmentToTheVault(t *testing.T) {
|
||||
plan := `"provisioning":{"templates":{"source":"x","digest":"y"},"files":[],"secrets":[{"name":"token"}],` +
|
||||
`"provisions":[{"provision":"gitea-read","provider":"novox","provider-module":"gitea"},` +
|
||||
`{"name":"gitea-read","provision":"gitea-read","provider":"novox","provider-module":"gitea"}]}`
|
||||
head := `{"node":"g14","module":"dunst","assigned":"2026-10-14T20:00:00Z",`
|
||||
`"provisions":[{"provision":"gitea-read","provider":"node-c","provider-module":"gitea"},` +
|
||||
`{"name":"gitea-read","provision":"gitea-read","provider":"node-c","provider-module":"gitea"}]}`
|
||||
head := `{"node":"node-b","module":"dunst","assigned":"2026-10-14T20:00:00Z",`
|
||||
mirror := assignments{
|
||||
"g14.dunst": head + plan + `}`,
|
||||
"g14.ended": `{"node":"g14","module":"ended","ended":"2026-10-18T01:00:00Z",` + plan + `}`,
|
||||
"g14.wait": `{"node":"g14","module":"wait","state":"pending",` + plan + `}`,
|
||||
"g14.new": `{"node":"g14","module":"new","build":"b1","artifact":{"name":"x","version":"1"},"commit":"c1",` +
|
||||
"node-b.dunst": head + plan + `}`,
|
||||
"node-b.ended": `{"node":"node-b","module":"ended","ended":"2026-10-18T01:00:00Z",` + plan + `}`,
|
||||
"node-b.wait": `{"node":"node-b","module":"wait","state":"pending",` + plan + `}`,
|
||||
"node-b.new": `{"node":"node-b","module":"new","build":"b1","artifact":{"name":"x","version":"1"},"commit":"c1",` +
|
||||
`"rollout":"r1","since":"2026-10-18T00:00:00Z","unknown-field":[1],"ended":"","state":"",` + plan + `}`,
|
||||
}
|
||||
rule := Declared{Read: mirror.read}
|
||||
for module, want := range map[string]bool{"dunst": true, "new": true, "ended": false, "wait": false} {
|
||||
if ok, why := rule.May("provisioner.g14", "own/g14/"+module+"/token"); ok != want {
|
||||
if ok, why := rule.May("provisioner.node-b", "own/node-b/"+module+"/token"); ok != want {
|
||||
t.Errorf("own secret of %s: allowed %v (%s), want %v", module, ok, why, want)
|
||||
}
|
||||
if ok, why := rule.May("provisioner.g14", "provision/novox/gitea-read/g14/"+module); ok != want {
|
||||
if ok, why := rule.May("provisioner.node-b", "provision/node-c/gitea-read/node-b/"+module); ok != want {
|
||||
t.Errorf("credential of %s: allowed %v (%s), want %v", module, ok, why, want)
|
||||
}
|
||||
if why := pairRowRefusal(mirror.read, "g14", []string{"pair", "novox", "gitea-read", "g14", module}); (why == "") != want {
|
||||
if why := pairRowRefusal(mirror.read, "node-b", []string{"pair", "node-c", "gitea-read", "node-b", module}); (why == "") != want {
|
||||
t.Errorf("pair row of %s: refusal %q, want allowed %v", module, why, want)
|
||||
}
|
||||
}
|
||||
@@ -33,11 +33,11 @@ func TestAnEndedAssignmentIsNotAnAssignmentToTheVault(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for module, want := range map[string]bool{"dunst": true, "new": true, "ended": false, "wait": false} {
|
||||
if seen.assigned["g14."+module] != want || seen.handed["g14."+module] != want {
|
||||
t.Errorf("binding of %s: assigned %v, want %v", module, seen.assigned["g14."+module], want)
|
||||
if seen.assigned["node-b."+module] != want || seen.handed["node-b."+module] != want {
|
||||
t.Errorf("binding of %s: assigned %v, want %v", module, seen.assigned["node-b."+module], want)
|
||||
}
|
||||
}
|
||||
if got := seen.bound[pairKey{"novox", "gitea-read"}]; got == nil || len(got.holders) != 2 {
|
||||
if got := seen.bound[pairKey{"node-c", "gitea-read"}]; got == nil || len(got.holders) != 2 {
|
||||
t.Errorf("the bound holders of the provision: %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ func TestARolledRecordOfHandOutsIsSaid(t *testing.T) {
|
||||
v.Store.HandedMax = 600
|
||||
asker, _ := seal.GenerateKeyPair()
|
||||
for i := 0; i < 20; i++ {
|
||||
_, _ = v.Get("g14.flood", "pair/postgres/gitea", asker.PublicKey)
|
||||
_, _ = v.Get("node-b.flood", "pair/postgres/gitea", asker.PublicKey)
|
||||
}
|
||||
rolled := 0
|
||||
for _, s := range said {
|
||||
|
||||
@@ -13,8 +13,8 @@ import (
|
||||
func TestANewSharedProviderGetsOneFirstValueForEveryHolder(t *testing.T) {
|
||||
mirror := assignments{
|
||||
"anchor.stand-in": handedWith("anchor", "stand-in"),
|
||||
"g14.app": handedWith("g14", "app", "stand-in-api@anchor/stand-in+shared"),
|
||||
"ace.other": handedWith("ace", "other", "stand-in-api@anchor/stand-in+shared"),
|
||||
"node-b.app": handedWith("node-b", "app", "stand-in-api@anchor/stand-in+shared"),
|
||||
"node-a.other": handedWith("node-a", "other", "stand-in-api@anchor/stand-in+shared"),
|
||||
}
|
||||
v := pairVault(t, mirror)
|
||||
if o := pass(t, v, standIn); o.Outcome != PairMade || o.Generation != 1 {
|
||||
@@ -24,7 +24,7 @@ func TestANewSharedProviderGetsOneFirstValueForEveryHolder(t *testing.T) {
|
||||
if err != nil || value == "" {
|
||||
t.Fatalf("the provider's machine was refused its first value: %v", err)
|
||||
}
|
||||
for _, c := range []string{"provisioner.g14", "provisioner.ace"} {
|
||||
for _, c := range []string{"provisioner.node-b", "provisioner.node-a"} {
|
||||
if got, _, err := asks(v, c, standIn); err != nil || got != value {
|
||||
t.Fatalf("%s got %q (%v), not the one value", c, got, err)
|
||||
}
|
||||
@@ -42,11 +42,11 @@ func TestANewSharedProviderGetsOneFirstValueForEveryHolder(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestANewPerLoginProviderGetsAFirstLoginOfItsOwnForEachConsumer(t *testing.T) {
|
||||
const pg = "provision/novox/postgres-database"
|
||||
const pg = "provision/node-c/postgres-database"
|
||||
mirror := assignments{
|
||||
"novox.postgres": handedWith("novox", "postgres"),
|
||||
"g14.gitea": handedWith("g14", "gitea", "postgres-database@novox/postgres+pair"),
|
||||
"ace.umami": handedWith("ace", "umami", "postgres-database@novox/postgres#stats+pair"),
|
||||
"node-c.postgres": handedWith("node-c", "postgres"),
|
||||
"node-b.gitea": handedWith("node-b", "gitea", "postgres-database@node-c/postgres+pair"),
|
||||
"node-a.umami": handedWith("node-a", "umami", "postgres-database@node-c/postgres#stats+pair"),
|
||||
}
|
||||
v := pairVault(t, mirror)
|
||||
if o := pass(t, v, pg); o.Outcome != PairMade {
|
||||
@@ -55,23 +55,23 @@ func TestANewPerLoginProviderGetsAFirstLoginOfItsOwnForEachConsumer(t *testing.T
|
||||
if _, held, _ := v.Store.Read(pg); held {
|
||||
t.Fatal("one shared value was made for a provider whose consumers each keep a login of their own")
|
||||
}
|
||||
gitea, umami := "pair/novox/postgres-database/g14/gitea", "pair/novox/postgres-database/ace/umami/stats"
|
||||
ofGitea, _, err := asks(v, "provisioner.g14", gitea)
|
||||
gitea, umami := "pair/node-c/postgres-database/node-b/gitea", "pair/node-c/postgres-database/node-a/umami/stats"
|
||||
ofGitea, _, err := asks(v, "provisioner.node-b", gitea)
|
||||
if err != nil || ofGitea == "" {
|
||||
t.Fatalf("gitea's machine was refused its first login: %v", err)
|
||||
}
|
||||
ofUmami, _, err := asks(v, "provisioner.ace", umami)
|
||||
ofUmami, _, err := asks(v, "provisioner.node-a", umami)
|
||||
if err != nil || ofUmami == "" {
|
||||
t.Fatalf("umami's machine was refused its first login: %v", err)
|
||||
}
|
||||
if ofGitea == ofUmami {
|
||||
t.Fatal("two consumers of a per-login provider were given one value")
|
||||
}
|
||||
if _, _, err := asks(v, "provisioner.g14", umami); err == nil {
|
||||
if _, _, err := asks(v, "provisioner.node-b", umami); err == nil {
|
||||
t.Fatal("gitea's machine was handed umami's login")
|
||||
}
|
||||
for name, want := range map[string]string{gitea: ofGitea, umami: ofUmami} {
|
||||
if got, _, err := asks(v, "provisioner.novox", name); err != nil || got != want {
|
||||
if got, _, err := asks(v, "provisioner.node-c", name); err != nil || got != want {
|
||||
t.Fatalf("the provider's machine got %q for %s (%v)", got, name, err)
|
||||
}
|
||||
}
|
||||
@@ -79,7 +79,7 @@ func TestANewPerLoginProviderGetsAFirstLoginOfItsOwnForEachConsumer(t *testing.T
|
||||
if o := pass(t, v, pg); o.Outcome != PairPerPair {
|
||||
t.Fatalf("the pass after the first logins: %+v", o)
|
||||
}
|
||||
if got, gen, _ := asks(v, "provisioner.g14", gitea); got != ofGitea || gen != 1 {
|
||||
if got, gen, _ := asks(v, "provisioner.node-b", gitea); got != ofGitea || gen != 1 {
|
||||
t.Fatal("a consumer's login was remade by the next pass")
|
||||
}
|
||||
}
|
||||
@@ -89,33 +89,33 @@ func TestAProviderWithRowsIsUnchangedByTheFormItsPlansName(t *testing.T) {
|
||||
t.Run("stamped rows", func(t *testing.T) {
|
||||
mirror := assignments{
|
||||
"anchor.stand-in": handedWith("anchor", "stand-in"),
|
||||
"g14.app": handedWith("g14", "app", "stand-in-api@anchor/stand-in+shared"),
|
||||
"node-b.app": handedWith("node-b", "app", "stand-in-api@anchor/stand-in+shared"),
|
||||
}
|
||||
v := pairVault(t, mirror)
|
||||
movedRow(t, v, "g14", "app", "the-value-they-share", "stamp-1", OriginMade)
|
||||
movedRow(t, v, "node-b", "app", "the-value-they-share", "stamp-1", OriginMade)
|
||||
if o := pass(t, v, standIn); o.Outcome != PairAdopted {
|
||||
t.Fatalf("stamped rows: %+v", o)
|
||||
}
|
||||
if got, gen, _ := asks(v, "provisioner.g14", standIn); got != "the-value-they-share" || gen != 1 {
|
||||
if got, gen, _ := asks(v, "provisioner.node-b", standIn); got != "the-value-they-share" || gen != 1 {
|
||||
t.Fatal("the adopted value is not the move's")
|
||||
}
|
||||
})
|
||||
t.Run("logins of their own", func(t *testing.T) {
|
||||
mirror := assignments{
|
||||
"anchor.stand-in": handedWith("anchor", "stand-in"),
|
||||
"g14.app": handedWith("g14", "app", "stand-in-api@anchor/stand-in+shared"),
|
||||
"ace.other": handedWith("ace", "other", "stand-in-api@anchor/stand-in+pair"),
|
||||
"node-b.app": handedWith("node-b", "app", "stand-in-api@anchor/stand-in+shared"),
|
||||
"node-a.other": handedWith("node-a", "other", "stand-in-api@anchor/stand-in+pair"),
|
||||
}
|
||||
v := pairVault(t, mirror)
|
||||
movedRow(t, v, "g14", "app", "login-of-app", "", OriginMade)
|
||||
movedRow(t, v, "ace", "other", "login-of-other", "", OriginMade)
|
||||
movedRow(t, v, "node-b", "app", "login-of-app", "", OriginMade)
|
||||
movedRow(t, v, "node-a", "other", "login-of-other", "", OriginMade)
|
||||
if o := pass(t, v, standIn); o.Outcome != PairPerPair {
|
||||
t.Fatalf("unstamped rows: %+v", o)
|
||||
}
|
||||
if _, held, _ := v.Store.Read(standIn); held {
|
||||
t.Fatal("a shared value was made for a provider whose rows are logins of their own")
|
||||
}
|
||||
if got, gen, _ := asks(v, "provisioner.g14", "pair/anchor/stand-in-api/g14/app"); got != "login-of-app" ||
|
||||
if got, gen, _ := asks(v, "provisioner.node-b", "pair/anchor/stand-in-api/node-b/app"); got != "login-of-app" ||
|
||||
gen != 1 {
|
||||
t.Fatal("a consumer's own login changed")
|
||||
}
|
||||
@@ -150,8 +150,8 @@ func TestTheVaultMakesNothingForAFormItDoesNotMakeOrCannotTrust(t *testing.T) {
|
||||
}
|
||||
mirror := assignments{
|
||||
"anchor.stand-in": provider,
|
||||
"g14.app": handedWith("g14", "app", entry(first)),
|
||||
"ace.other": handedWith("ace", "other", entry(second)),
|
||||
"node-b.app": handedWith("node-b", "app", entry(first)),
|
||||
"node-a.other": handedWith("node-a", "other", entry(second)),
|
||||
}
|
||||
v := pairVault(t, mirror)
|
||||
if o := pass(t, v, standIn); o.Outcome != PairHeld || !strings.Contains(o.Why, said) {
|
||||
@@ -172,25 +172,25 @@ func TestTheVaultMakesNothingForAFormItDoesNotMakeOrCannotTrust(t *testing.T) {
|
||||
func TestANewConsumerOfAPerLoginProviderWithRowsGetsALoginOfItsOwn(t *testing.T) {
|
||||
mirror := assignments{
|
||||
"anchor.stand-in": handedWith("anchor", "stand-in"),
|
||||
"g14.app": handedWith("g14", "app", "stand-in-api@anchor/stand-in+pair"),
|
||||
"ace.other": handedWith("ace", "other", "stand-in-api@anchor/stand-in+pair"),
|
||||
"node-b.app": handedWith("node-b", "app", "stand-in-api@anchor/stand-in+pair"),
|
||||
"node-a.other": handedWith("node-a", "other", "stand-in-api@anchor/stand-in+pair"),
|
||||
}
|
||||
v := pairVault(t, mirror)
|
||||
movedRow(t, v, "g14", "app", "login-of-app", "", OriginMade)
|
||||
movedRow(t, v, "ace", "other", "login-of-other", "", OriginMade)
|
||||
app, other := "pair/anchor/stand-in-api/g14/app", "pair/anchor/stand-in-api/ace/other"
|
||||
third := "pair/anchor/stand-in-api/novox/third"
|
||||
movedRow(t, v, "node-b", "app", "login-of-app", "", OriginMade)
|
||||
movedRow(t, v, "node-a", "other", "login-of-other", "", OriginMade)
|
||||
app, other := "pair/anchor/stand-in-api/node-b/app", "pair/anchor/stand-in-api/node-a/other"
|
||||
third := "pair/anchor/stand-in-api/node-c/third"
|
||||
before, said := map[string]Record{}, map[string]int{}
|
||||
for _, n := range []string{app, other} {
|
||||
r, _, _ := v.Store.Read(n)
|
||||
before[n], said[n] = r, changedSaid(t, v, n, "")
|
||||
}
|
||||
|
||||
mirror["novox.third"] = handedWith("novox", "third", "stand-in-api@anchor/stand-in+pair")
|
||||
mirror["node-c.third"] = handedWith("node-c", "third", "stand-in-api@anchor/stand-in+pair")
|
||||
if o := pass(t, v, standIn); o.Outcome != PairMade {
|
||||
t.Fatalf("a third consumer binding: %+v", o)
|
||||
}
|
||||
got, _, err := asks(v, "provisioner.novox", third)
|
||||
got, _, err := asks(v, "provisioner.node-c", third)
|
||||
if err != nil || got == "" || got == "login-of-app" || got == "login-of-other" {
|
||||
t.Fatalf("the third consumer has no login of its own: %q (%v)", got, err)
|
||||
}
|
||||
@@ -215,7 +215,7 @@ func TestANewConsumerOfAPerLoginProviderWithRowsGetsALoginOfItsOwn(t *testing.T)
|
||||
if o := pass(t, v, standIn); o.Outcome != PairPerPair {
|
||||
t.Fatalf("the pass after the new login: %+v", o)
|
||||
}
|
||||
if got2, _, _ := asks(v, "provisioner.novox", third); got2 != got {
|
||||
if got2, _, _ := asks(v, "provisioner.node-c", third); got2 != got {
|
||||
t.Fatal("the new login was remade by the next pass")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,18 +83,18 @@ func telegramAsk(by string) AskOf {
|
||||
// it; the typed value opens only in the vault; the controller carries ciphertext only.
|
||||
func TestAnAskIsRecordedBeforeThePromptAndTheTypedValueOpensOnlyInTheVault(t *testing.T) {
|
||||
v := newVault(t, AllowList{"anchor.node-provisioner": {"own/anchor/telegram/telegram-token"}})
|
||||
opened, err := v.AskOpen(ControllerCaller, telegramAsk("g14/claude-code, via the console"))
|
||||
opened, err := v.AskOpen(ControllerCaller, telegramAsk("node-b/claude-code, via the console"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Recorded before any prompt opens: the record holds the ask now, with no end.
|
||||
asks, _ := v.Store.Asks(10)
|
||||
if len(asks) != 1 || asks[0].Event != AskOpened || asks[0].ID != opened.Call || asks[0].AskedBy != "g14/claude-code" ||
|
||||
if len(asks) != 1 || asks[0].Event != AskOpened || asks[0].ID != opened.Call || asks[0].AskedBy != "node-b/claude-code" ||
|
||||
asks[0].Caller != ControllerCaller || asks[0].Desk != "laptop" || asks[0].Secret != "own/anchor/telegram/telegram-token" {
|
||||
t.Fatalf("the record before the prompt: %+v", asks)
|
||||
}
|
||||
// The prompt names the asker as the bus named it, cut to a name: what the vault answers is what it recorded.
|
||||
if opened.AskedBy != "g14/claude-code" {
|
||||
if opened.AskedBy != "node-b/claude-code" {
|
||||
t.Errorf("the prompt would name %q", opened.AskedBy)
|
||||
}
|
||||
// The desk seals what is typed to the call's key; the controller passes it on.
|
||||
@@ -102,14 +102,14 @@ func TestAnAskIsRecordedBeforeThePromptAndTheTypedValueOpensOnlyInTheVault(t *te
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if changed.Generation != 1 || changed.By != "g14/claude-code" {
|
||||
if changed.Generation != 1 || changed.By != "node-b/claude-code" {
|
||||
t.Errorf("kept as %+v", changed)
|
||||
}
|
||||
if got := handedTo(t, v, "anchor.node-provisioner", "own/anchor/telegram/telegram-token"); got != typedAtTheDesk {
|
||||
t.Errorf("the vault holds %q, not what was typed with its line ending taken off", got)
|
||||
}
|
||||
rec, _, _ := v.Store.Read("own/anchor/telegram/telegram-token")
|
||||
if rec.Origin != OriginTyped || rec.ChangedBy != "g14/claude-code" || rec.ChangedVia != ControllerCaller {
|
||||
if rec.Origin != OriginTyped || rec.ChangedBy != "node-b/claude-code" || rec.ChangedVia != ControllerCaller {
|
||||
t.Errorf("the record says origin %q, by %q, via %q", rec.Origin, rec.ChangedBy, rec.ChangedVia)
|
||||
}
|
||||
asks, _ = v.Store.Asks(10)
|
||||
@@ -124,16 +124,16 @@ func TestAnAskIsRecordedBeforeThePromptAndTheTypedValueOpensOnlyInTheVault(t *te
|
||||
// One open ask per secret, and three an hour (ADR 0277 §24), read from the vault's own record.
|
||||
func TestAnAskIsBoundedToOneOpenPerSecretAndThreeAnHour(t *testing.T) {
|
||||
v := newVault(t, nil)
|
||||
first, err := v.AskOpen(ControllerCaller, telegramAsk("g14.claude-code"))
|
||||
first, err := v.AskOpen(ControllerCaller, telegramAsk("node-b.claude-code"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := v.AskOpen(ControllerCaller, telegramAsk("ace.claude-code")); err == nil ||
|
||||
!strings.Contains(err.Error(), "still open (asked by g14.claude-code)") {
|
||||
if _, err := v.AskOpen(ControllerCaller, telegramAsk("node-a.claude-code")); err == nil ||
|
||||
!strings.Contains(err.Error(), "still open (asked by node-b.claude-code)") {
|
||||
t.Fatalf("a second ask while one is open: %v", err)
|
||||
}
|
||||
// Another secret is not held back by it.
|
||||
other := telegramAsk("ace.claude-code")
|
||||
other := telegramAsk("node-a.claude-code")
|
||||
other.Name = "webhook-secret"
|
||||
if _, err := v.AskOpen(ControllerCaller, other); err != nil {
|
||||
t.Fatalf("another secret's ask was held back: %v", err)
|
||||
@@ -142,7 +142,7 @@ func TestAnAskIsBoundedToOneOpenPerSecretAndThreeAnHour(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for i := 0; i < 2; i++ {
|
||||
o, err := v.AskOpen(ControllerCaller, telegramAsk("g14.claude-code"))
|
||||
o, err := v.AskOpen(ControllerCaller, telegramAsk("node-b.claude-code"))
|
||||
if err != nil {
|
||||
t.Fatalf("ask %d of the hour: %v", i+2, err)
|
||||
}
|
||||
@@ -150,7 +150,7 @@ func TestAnAskIsBoundedToOneOpenPerSecretAndThreeAnHour(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if _, err := v.AskOpen(ControllerCaller, telegramAsk("g14.claude-code")); err == nil ||
|
||||
if _, err := v.AskOpen(ControllerCaller, telegramAsk("node-b.claude-code")); err == nil ||
|
||||
!strings.Contains(err.Error(), "3 times in the last hour") {
|
||||
t.Fatalf("a fourth ask in the hour: %v", err)
|
||||
}
|
||||
@@ -166,11 +166,11 @@ func TestAnAskIsBoundedToOneOpenPerSecretAndThreeAnHour(t *testing.T) {
|
||||
w := newVault(t, nil)
|
||||
clock := time.Date(2026, 10, 14, 9, 0, 0, 0, time.UTC)
|
||||
w.Now = func() time.Time { return clock }
|
||||
if _, err := w.AskOpen(ControllerCaller, telegramAsk("g14.claude-code")); err != nil {
|
||||
if _, err := w.AskOpen(ControllerCaller, telegramAsk("node-b.claude-code")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
clock = clock.Add(SecretAskOpenFor + time.Second)
|
||||
if _, err := w.AskOpen(ControllerCaller, telegramAsk("g14.claude-code")); err != nil {
|
||||
if _, err := w.AskOpen(ControllerCaller, telegramAsk("node-b.claude-code")); err != nil {
|
||||
t.Errorf("an ask left open past its time still held the secret: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -179,7 +179,7 @@ func TestAnAskIsBoundedToOneOpenPerSecretAndThreeAnHour(t *testing.T) {
|
||||
// call's key, or a call that waited too long, keeps nothing.
|
||||
func TestAnAskIsTakenFromTheControllerAloneAndOnlySealedToItsCall(t *testing.T) {
|
||||
v := newVault(t, nil)
|
||||
for _, caller := range []string{"g14.node-tools", "unproven", "", "person.jochen"} {
|
||||
for _, caller := range []string{"node-b.node-tools", "unproven", "", "person.jochen"} {
|
||||
if _, err := v.AskOpen(caller, telegramAsk("x")); err == nil {
|
||||
t.Errorf("%q opened an ask", caller)
|
||||
}
|
||||
@@ -187,11 +187,11 @@ func TestAnAskIsTakenFromTheControllerAloneAndOnlySealedToItsCall(t *testing.T)
|
||||
if asks, _ := v.Store.Asks(10); len(asks) != 0 {
|
||||
t.Fatalf("a refused caller filled the record: %+v", asks)
|
||||
}
|
||||
opened, err := v.AskOpen(ControllerCaller, telegramAsk("g14.claude-code"))
|
||||
opened, err := v.AskOpen(ControllerCaller, telegramAsk("node-b.claude-code"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := v.AskEnd("g14.node-tools", opened.Call, sealToCall(t, opened.SealTo, "x"), ""); err == nil {
|
||||
if _, err := v.AskEnd("node-b.node-tools", opened.Call, sealToCall(t, opened.SealTo, "x"), ""); err == nil {
|
||||
t.Error("another caller ended the ask")
|
||||
}
|
||||
other, _, _ := box.GenerateKey(rand.Reader)
|
||||
@@ -223,7 +223,7 @@ func TestAnAskIsTakenFromTheControllerAloneAndOnlySealedToItsCall(t *testing.T)
|
||||
}
|
||||
// An empty answer gives nothing and says so.
|
||||
clock = clock.Add(time.Hour)
|
||||
empty, _ := v.AskOpen(ControllerCaller, telegramAsk("g14.claude-code"))
|
||||
empty, _ := v.AskOpen(ControllerCaller, telegramAsk("node-b.claude-code"))
|
||||
if _, err := v.AskEnd(ControllerCaller, empty.Call, sealToCall(t, empty.SealTo, " \n"), ""); err == nil {
|
||||
t.Error("an empty answer was kept")
|
||||
}
|
||||
@@ -278,7 +278,7 @@ func TestAnIssuingProviderAnswersOnlyTheVaultsCallSealedToTheVault(t *testing.T)
|
||||
provider := standInProvider(&software)
|
||||
|
||||
// The provider's tool refuses every caller but the vault's account and the controller, whatever it asks.
|
||||
for _, caller := range []string{"anchor.node-tools", "g14.claude-code", "unproven", ""} {
|
||||
for _, caller := range []string{"anchor.node-tools", "node-b.claude-code", "unproven", ""} {
|
||||
if _, err := provider(caller, map[string]any{"provision": "plex-api", "seal_to": "x"}); err == nil {
|
||||
t.Errorf("the provider's tool answered %q", caller)
|
||||
}
|
||||
@@ -351,11 +351,11 @@ func TestAnIssuingProviderAnswersOnlyTheVaultsCallSealedToTheVault(t *testing.T)
|
||||
// A value given at the controller's terminal is sealed there to the vault's call and opened here alone; an
|
||||
// accepted provision's credential is never remade; a module's own given value rotates as a made one (ADR 0228).
|
||||
func TestAnAcceptedValueOpensInTheVaultAloneAndIsNotRemade(t *testing.T) {
|
||||
v := newVault(t, AllowList{"g14.node-provisioner": {"provision/novox/postgres", "own/g14/dunst/token"}})
|
||||
if _, err := v.AcceptOpen("g14.claude-code", "provision/novox/postgres", "", ""); err == nil {
|
||||
v := newVault(t, AllowList{"node-b.node-provisioner": {"provision/node-c/postgres", "own/node-b/dunst/token"}})
|
||||
if _, err := v.AcceptOpen("node-b.claude-code", "provision/node-c/postgres", "", ""); err == nil {
|
||||
t.Error("accept was opened for a caller that is not the controller")
|
||||
}
|
||||
for name, value := range map[string]string{"provision/novox/postgres": "pw-of-last-year", "own/g14/dunst/token": "tok"} {
|
||||
for name, value := range map[string]string{"provision/node-c/postgres": "pw-of-last-year", "own/node-b/dunst/token": "tok"} {
|
||||
opened, err := v.AcceptOpen(ControllerCaller, name, "jochen", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -367,7 +367,7 @@ func TestAnAcceptedValueOpensInTheVaultAloneAndIsNotRemade(t *testing.T) {
|
||||
if changed.By != "jochen" {
|
||||
t.Errorf("%s accepted by %q", name, changed.By)
|
||||
}
|
||||
if got := handedTo(t, v, "g14.node-provisioner", name); got != value {
|
||||
if got := handedTo(t, v, "node-b.node-provisioner", name); got != value {
|
||||
t.Errorf("%s holds %q", name, got)
|
||||
}
|
||||
rec, _, _ := v.Store.Read(name)
|
||||
@@ -375,15 +375,15 @@ func TestAnAcceptedValueOpensInTheVaultAloneAndIsNotRemade(t *testing.T) {
|
||||
t.Errorf("%s: origin %q via %q", name, rec.Origin, rec.ChangedVia)
|
||||
}
|
||||
}
|
||||
if _, err := v.Rotate(ControllerCaller, "provision/novox/postgres", "jochen", "leaked"); err == nil ||
|
||||
if _, err := v.Rotate(ControllerCaller, "provision/node-c/postgres", "jochen", "leaked"); err == nil ||
|
||||
!strings.Contains(err.Error(), "not remade") {
|
||||
t.Errorf("rotate of an accepted credential: %v", err)
|
||||
}
|
||||
rotated, err := v.Rotate(ControllerCaller, "own/g14/dunst/token", "jochen", "leaked")
|
||||
rotated, err := v.Rotate(ControllerCaller, "own/node-b/dunst/token", "jochen", "leaked")
|
||||
if err != nil || rotated.Generation != 2 {
|
||||
t.Fatalf("rotate of a module's own given value: %+v %v", rotated, err)
|
||||
}
|
||||
if got := handedTo(t, v, "g14.node-provisioner", "own/g14/dunst/token"); got == "tok" || len(got) != 40 {
|
||||
if got := handedTo(t, v, "node-b.node-provisioner", "own/node-b/dunst/token"); got == "tok" || len(got) != 40 {
|
||||
t.Errorf("rotated to %q", got)
|
||||
}
|
||||
if strings.Contains(everything(t, v), "pw-of-last-year") {
|
||||
@@ -395,19 +395,19 @@ func TestAnAcceptedValueOpensInTheVaultAloneAndIsNotRemade(t *testing.T) {
|
||||
// person the controller names when it carries the act — both kept.
|
||||
func TestRotateKeepsWhoAskedAndWhoCarriedIt(t *testing.T) {
|
||||
v := newVault(t, nil)
|
||||
if _, err := v.Rotate(ControllerCaller, "own/g14/dunst/token", "jochen", "leaked"); err == nil {
|
||||
if _, err := v.Rotate(ControllerCaller, "own/node-b/dunst/token", "jochen", "leaked"); err == nil {
|
||||
t.Error("a secret not held was rotated")
|
||||
}
|
||||
if _, err := v.Mint("own/g14/dunst/token", "the test"); err != nil {
|
||||
if _, err := v.Mint("own/node-b/dunst/token", "the test"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := v.Rotate("unproven", "own/g14/dunst/token", "", "leaked"); err == nil {
|
||||
if _, err := v.Rotate("unproven", "own/node-b/dunst/token", "", "leaked"); err == nil {
|
||||
t.Error("an unproven caller rotated")
|
||||
}
|
||||
if _, err := v.Rotate(ControllerCaller, "own/g14/dunst/token", "jochen", "leaked"); err != nil {
|
||||
if _, err := v.Rotate(ControllerCaller, "own/node-b/dunst/token", "jochen", "leaked"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rec, _, _ := v.Store.Read("own/g14/dunst/token")
|
||||
rec, _, _ := v.Store.Read("own/node-b/dunst/token")
|
||||
if rec.ChangedBy != "jochen" || rec.ChangedVia != ControllerCaller || rec.Generation != 2 {
|
||||
t.Errorf("by %q via %q generation %d", rec.ChangedBy, rec.ChangedVia, rec.Generation)
|
||||
}
|
||||
@@ -418,35 +418,35 @@ func TestRotateKeepsWhoAskedAndWhoCarriedIt(t *testing.T) {
|
||||
// drift from the value the controller holds; and a rotation without a reason is refused. Neither changes anything.
|
||||
func TestRotateIsTheControllersAloneAndSaysWhy(t *testing.T) {
|
||||
v := newVault(t, nil)
|
||||
if _, err := v.Mint("own/g14/dunst/token", "the test"); err != nil {
|
||||
if _, err := v.Mint("own/node-b/dunst/token", "the test"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, caller := range []string{"g14.claude-code", "g14.node-provisioner", "unproven", ""} {
|
||||
if _, err := v.Rotate(caller, "own/g14/dunst/token", "jochen", "leaked"); err == nil ||
|
||||
for _, caller := range []string{"node-b.claude-code", "node-b.node-provisioner", "unproven", ""} {
|
||||
if _, err := v.Rotate(caller, "own/node-b/dunst/token", "jochen", "leaked"); err == nil ||
|
||||
!strings.Contains(err.Error(), "controller alone") {
|
||||
t.Errorf("%q rotated: %v", caller, err)
|
||||
}
|
||||
}
|
||||
for _, why := range []string{"", " "} {
|
||||
if _, err := v.Rotate(ControllerCaller, "own/g14/dunst/token", "jochen", why); err == nil ||
|
||||
if _, err := v.Rotate(ControllerCaller, "own/node-b/dunst/token", "jochen", why); err == nil ||
|
||||
!strings.Contains(err.Error(), "why") {
|
||||
t.Errorf("a rotation without a why (%q): %v", why, err)
|
||||
}
|
||||
}
|
||||
if rec, _, _ := v.Store.Read("own/g14/dunst/token"); rec.Generation != 1 {
|
||||
if rec, _, _ := v.Store.Read("own/node-b/dunst/token"); rec.Generation != 1 {
|
||||
t.Fatalf("a refused rotation changed the secret: generation %d", rec.Generation)
|
||||
}
|
||||
}
|
||||
|
||||
// The names the vault keeps them under are names, never paths.
|
||||
func TestTheNamesOfWhatIsTakenIn(t *testing.T) {
|
||||
if n, err := OwnSecret("g14", "dunst", "token"); err != nil || n != "own/g14/dunst/token" {
|
||||
if n, err := OwnSecret("node-b", "dunst", "token"); err != nil || n != "own/node-b/dunst/token" {
|
||||
t.Errorf("%q %v", n, err)
|
||||
}
|
||||
if n, err := ProvisionSecret("novox", "postgres"); err != nil || n != "provision/novox/postgres" {
|
||||
if n, err := ProvisionSecret("node-c", "postgres"); err != nil || n != "provision/node-c/postgres" {
|
||||
t.Errorf("%q %v", n, err)
|
||||
}
|
||||
for _, bad := range [][3]string{{"..", "x", "y"}, {"g14", "a/b", "y"}, {"g14", "x", ""}, {"G14", "x", "y"}} {
|
||||
for _, bad := range [][3]string{{"..", "x", "y"}, {"node-b", "a/b", "y"}, {"node-b", "x", ""}, {"NODE-B", "x", "y"}} {
|
||||
if _, err := OwnSecret(bad[0], bad[1], bad[2]); err == nil {
|
||||
t.Errorf("%v was taken", bad)
|
||||
}
|
||||
@@ -466,7 +466,7 @@ func TestTheVerbsTakeTheirCallerFromTheRuntime(t *testing.T) {
|
||||
"plex.plex_issued_credential@anchor": standInProvider(&software)}}).ask
|
||||
ask := toolNamed(t, defs, Seat+".secret-ask")
|
||||
args := map[string]any{"node": "anchor", "module": "telegram", "name": "telegram-token", "desk": "laptop",
|
||||
"asked_by": "g14.claude-code", "caller": ControllerCaller}
|
||||
"asked_by": "node-b.claude-code", "caller": ControllerCaller}
|
||||
if _, err := ask.Run(args); err == nil {
|
||||
t.Error("an ask opened for a caller named in the arguments")
|
||||
}
|
||||
@@ -480,10 +480,10 @@ func TestTheVerbsTakeTheirCallerFromTheRuntime(t *testing.T) {
|
||||
}
|
||||
|
||||
accept := toolNamed(t, defs, Seat+".accept")
|
||||
if _, err := accept.runAs(ControllerCaller, map[string]any{"node": "g14", "provision": "postgres"}); err == nil {
|
||||
if _, err := accept.runAs(ControllerCaller, map[string]any{"node": "node-b", "provision": "postgres"}); err == nil {
|
||||
t.Error("accept took a name both ways")
|
||||
}
|
||||
answer, err = accept.runAs(ControllerCaller, map[string]any{"provider": "novox", "provision": "postgres", "by": "jochen"})
|
||||
answer, err = accept.runAs(ControllerCaller, map[string]any{"provider": "node-c", "provision": "postgres", "by": "jochen"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -492,7 +492,7 @@ func TestTheVerbsTakeTheirCallerFromTheRuntime(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rotate := toolNamed(t, defs, Seat+".rotate")
|
||||
if _, err := rotate.runAs("g14.claude-code", map[string]any{"node": "g14", "module": "dunst", "name": "token"}); err == nil {
|
||||
if _, err := rotate.runAs("node-b.claude-code", map[string]any{"node": "node-b", "module": "dunst", "name": "token"}); err == nil {
|
||||
t.Error("a secret not held was rotated through the verb")
|
||||
}
|
||||
|
||||
@@ -514,7 +514,7 @@ func TestTheVerbsTakeTheirCallerFromTheRuntime(t *testing.T) {
|
||||
// With the lock held elsewhere, AskEnd waits, and writes nothing until it is let go.
|
||||
func TestTheEndOfAnAskIsWrittenUnderTheRecordsLock(t *testing.T) {
|
||||
v := newVault(t, nil)
|
||||
opened, err := v.AskOpen(ControllerCaller, telegramAsk("g14.claude-code"))
|
||||
opened, err := v.AskOpen(ControllerCaller, telegramAsk("node-b.claude-code"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ func handIssued(t *testing.T, v testVault, caller, secret, value string) (Change
|
||||
}
|
||||
|
||||
func TestAHandedBusAccountIsKeptServedReplacedAndNeverMadeByTheVault(t *testing.T) {
|
||||
const moved = "own/g14/moved-bot/broker"
|
||||
v := newVault(t, AllowList{"provisioner.g14": {moved}})
|
||||
const moved = "own/node-b/moved-bot/broker"
|
||||
v := newVault(t, AllowList{"provisioner.node-b": {moved}})
|
||||
|
||||
first, err := handIssued(t, v, ControllerCaller, moved, `{"password":"first-pw"}`)
|
||||
if err != nil || first.Generation != 1 {
|
||||
@@ -30,7 +30,7 @@ func TestAHandedBusAccountIsKeptServedReplacedAndNeverMadeByTheVault(t *testing.
|
||||
if !held || rec.Origin != OriginIssued || rec.ChangedVia != ControllerCaller {
|
||||
t.Fatalf("kept as origin %q via %q (held %v), want issued via the controller", rec.Origin, rec.ChangedVia, held)
|
||||
}
|
||||
if got := handedTo(t, v, "provisioner.g14", moved); got != `{"password":"first-pw"}` {
|
||||
if got := handedTo(t, v, "provisioner.node-b", moved); got != `{"password":"first-pw"}` {
|
||||
t.Errorf("the moved module's client is handed %q", got)
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ func TestAHandedBusAccountIsKeptServedReplacedAndNeverMadeByTheVault(t *testing.
|
||||
if err != nil || second.Generation != 2 || second.Unchanged {
|
||||
t.Fatalf("the re-issue: %+v %v", second, err)
|
||||
}
|
||||
if got := handedTo(t, v, "provisioner.g14", moved); got != `{"password":"second-pw"}` {
|
||||
if got := handedTo(t, v, "provisioner.node-b", moved); got != `{"password":"second-pw"}` {
|
||||
t.Errorf("after the re-issue the client is handed %q: a stale value stayed", got)
|
||||
}
|
||||
changedEvents := 0
|
||||
@@ -57,21 +57,21 @@ func TestAHandedBusAccountIsKeptServedReplacedAndNeverMadeByTheVault(t *testing.
|
||||
if _, err := v.Rotate(ControllerCaller, moved, "jochen", "leaked"); err == nil || !strings.Contains(err.Error(), "ADR 0374") {
|
||||
t.Errorf("the vault rotated a covered account: %v", err)
|
||||
}
|
||||
if _, err := v.Rotate(ControllerCaller, "own/g14/moved-bot/provisioner-broker", "jochen", "leaked"); err == nil {
|
||||
if _, err := v.Rotate(ControllerCaller, "own/node-b/moved-bot/provisioner-broker", "jochen", "leaked"); err == nil {
|
||||
t.Error("the vault made a first value for a covered account by rotating it")
|
||||
}
|
||||
if got := handedTo(t, v, "provisioner.g14", moved); got != `{"password":"second-pw"}` {
|
||||
if got := handedTo(t, v, "provisioner.node-b", moved); got != `{"password":"second-pw"}` {
|
||||
t.Errorf("a refused rotation changed the value to %q", got)
|
||||
}
|
||||
|
||||
// A secret that is not a covered account is not handed with origin issued; and only the controller hands one.
|
||||
if _, err := v.AcceptOpen(ControllerCaller, "own/g14/moved-bot/token", "x", OriginIssued); err == nil {
|
||||
if _, err := v.AcceptOpen(ControllerCaller, "own/node-b/moved-bot/token", "x", OriginIssued); err == nil {
|
||||
t.Error("origin issued was taken for a secret that is not a bus account")
|
||||
}
|
||||
if _, err := v.AcceptOpen(ControllerCaller, "provision/anchor/postgres", "x", OriginIssued); err == nil {
|
||||
t.Error("origin issued was taken for a provision's credential")
|
||||
}
|
||||
if _, err := v.AcceptOpen("g14.moved-bot", moved, "x", OriginIssued); err == nil {
|
||||
if _, err := v.AcceptOpen("node-b.moved-bot", moved, "x", OriginIssued); err == nil {
|
||||
t.Error("a caller that is not the controller handed a covered account")
|
||||
}
|
||||
if _, err := v.AcceptOpen(ControllerCaller, moved, "x", "made"); err == nil {
|
||||
|
||||
@@ -121,21 +121,21 @@ func TestAMovedValueKeepsWhereItCameFrom(t *testing.T) {
|
||||
// tools, not another machine's node-engine.
|
||||
func TestOnlyTheNodeEngineOfAMachineTheValueWasSealedToMovesIt(t *testing.T) {
|
||||
v := newVault(t, nil)
|
||||
for _, caller := range []string{ControllerCaller, "anchor.node-tools", "anchor.mesh-vault", "node.shanks", "",
|
||||
for _, caller := range []string{ControllerCaller, "anchor.node-tools", "anchor.mesh-vault", "node.node-d", "",
|
||||
"node.", "node.anchor.x"} {
|
||||
if _, err := v.MoveOpen(caller, superuser()); err == nil {
|
||||
t.Errorf("%q opened a move of anchor's value", caller)
|
||||
}
|
||||
}
|
||||
// A pair credential was sealed to both its ends: either may move it, and nobody else.
|
||||
pair := MoveOf{Provider: "anchor", Provision: "database", Consumer: "shanks", ConsumerModule: "gitea",
|
||||
pair := MoveOf{Provider: "anchor", Provision: "database", Consumer: "node-d", ConsumerModule: "gitea",
|
||||
Origin: "made", Generation: "g-1"}
|
||||
for _, caller := range []string{"node.anchor", "node.shanks"} {
|
||||
for _, caller := range []string{"node.anchor", "node.node-d"} {
|
||||
if _, err := v.MoveOpen(caller, pair); err != nil {
|
||||
t.Errorf("%s, an end of the pair, was refused: %v", caller, err)
|
||||
}
|
||||
}
|
||||
if _, err := v.MoveOpen("node.g14", pair); err == nil {
|
||||
if _, err := v.MoveOpen("node.node-b", pair); err == nil {
|
||||
t.Error("a machine at neither end opened a move of the pair")
|
||||
}
|
||||
// The second step is the first step's caller's alone.
|
||||
@@ -144,7 +144,7 @@ func TestOnlyTheNodeEngineOfAMachineTheValueWasSealedToMovesIt(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
key, _, _ := aReadBackKey(t)
|
||||
if _, err := v.MoveTake("node.shanks", opened.Call, sealToCall(t, opened.SealTo, movedValue), key); err == nil {
|
||||
if _, err := v.MoveTake("node.node-d", opened.Call, sealToCall(t, opened.SealTo, movedValue), key); err == nil {
|
||||
t.Error("another machine's node-engine took the second step")
|
||||
}
|
||||
if _, held, _ := v.Store.Read("own/anchor/postgres/superuser-password"); held {
|
||||
@@ -156,18 +156,18 @@ func TestOnlyTheNodeEngineOfAMachineTheValueWasSealedToMovesIt(t *testing.T) {
|
||||
// secret in the vault, kept as it is.
|
||||
func TestAPairCredentialMovesUnderTheNameOfItsRow(t *testing.T) {
|
||||
v := newVault(t, nil)
|
||||
pair := MoveOf{Provider: "anchor", Provision: "database", Consumer: "shanks", ConsumerModule: "gitea",
|
||||
pair := MoveOf{Provider: "anchor", Provision: "database", Consumer: "node-d", ConsumerModule: "gitea",
|
||||
Local: "replica", Origin: "accepted", Generation: "g-7"}
|
||||
moved, err := moveIn(t, v, "node.shanks", pair, "the-pair-value")
|
||||
moved, err := moveIn(t, v, "node.node-d", pair, "the-pair-value")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if moved.Name != "pair/anchor/database/shanks/gitea/replica" {
|
||||
if moved.Name != "pair/anchor/database/node-d/gitea/replica" {
|
||||
t.Errorf("named %s", moved.Name)
|
||||
}
|
||||
pair.Local = ""
|
||||
if moved, err = moveIn(t, v, "node.anchor", pair, "the-ordinary-value"); err != nil ||
|
||||
moved.Name != "pair/anchor/database/shanks/gitea" {
|
||||
moved.Name != "pair/anchor/database/node-d/gitea" {
|
||||
t.Errorf("the ordinary one: %+v, %v", moved, err)
|
||||
}
|
||||
}
|
||||
@@ -176,31 +176,31 @@ func TestAPairCredentialMovesUnderTheNameOfItsRow(t *testing.T) {
|
||||
// twice), and a different one is refused, so a value the vault holds is never put back to an older one.
|
||||
func TestTheMoveNeverReplacesAValueTheVaultHolds(t *testing.T) {
|
||||
v := newVault(t, nil)
|
||||
pair := MoveOf{Provider: "anchor", Provision: "database", Consumer: "shanks", ConsumerModule: "gitea",
|
||||
pair := MoveOf{Provider: "anchor", Provision: "database", Consumer: "node-d", ConsumerModule: "gitea",
|
||||
Origin: "made", Generation: "g-1"}
|
||||
if _, err := moveIn(t, v, "node.anchor", pair, "the-pair-value"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
opened, err := v.MoveOpen("node.shanks", pair)
|
||||
opened, err := v.MoveOpen("node.node-d", pair)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
key, pub, priv := aReadBackKey(t)
|
||||
again, err := v.MoveTake("node.shanks", opened.Call, sealToCall(t, opened.SealTo, "the-pair-value"), key)
|
||||
again, err := v.MoveTake("node.node-d", opened.Call, sealToCall(t, opened.SealTo, "the-pair-value"), key)
|
||||
if err != nil || !again.Unchanged || again.Generation != 1 {
|
||||
t.Fatalf("the other end's same value: %+v, %v", again, err)
|
||||
}
|
||||
if got := openReadBack(t, again.ReadBack, pub, priv); got != "the-pair-value" {
|
||||
t.Errorf("read back %q", got)
|
||||
}
|
||||
if _, err := v.Rotate(ControllerCaller, "pair/anchor/database/shanks/gitea", "jochen", "a test"); err != nil {
|
||||
if _, err := v.Rotate(ControllerCaller, "pair/anchor/database/node-d/gitea", "jochen", "a test"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := moveIn(t, v, "node.anchor", pair, "the-pair-value"); err == nil ||
|
||||
!strings.Contains(err.Error(), "never replaces") {
|
||||
t.Fatalf("a move over a value the vault changed since: %v", err)
|
||||
}
|
||||
rec, _, _ := v.Store.Read("pair/anchor/database/shanks/gitea")
|
||||
rec, _, _ := v.Store.Read("pair/anchor/database/node-d/gitea")
|
||||
if rec.Generation != 2 || rec.Origin != OriginMade {
|
||||
t.Errorf("the vault's own value was touched: %+v", rec)
|
||||
}
|
||||
@@ -240,11 +240,11 @@ func TestTheRecordOfAsksIsCopiedAsItIsOnce(t *testing.T) {
|
||||
now := v.Now()
|
||||
ended := now.Add(-30 * time.Second)
|
||||
asks := []MovedAsk{
|
||||
{ID: 41, Node: "anchor", Module: "telegram", Name: "telegram-token", Desk: "laptop", AskedBy: "g14/claude-code",
|
||||
{ID: 41, Node: "anchor", Module: "telegram", Name: "telegram-token", Desk: "laptop", AskedBy: "node-b/claude-code",
|
||||
OpenedAt: now.Add(-50 * time.Minute), EndedAt: &ended, Outcome: "given"},
|
||||
{ID: 42, Node: "anchor", Module: "telegram", Name: "telegram-token", Desk: "laptop", AskedBy: "g14/claude-code",
|
||||
{ID: 42, Node: "anchor", Module: "telegram", Name: "telegram-token", Desk: "laptop", AskedBy: "node-b/claude-code",
|
||||
OpenedAt: now.Add(-40 * time.Minute), EndedAt: &ended, Outcome: "dismissed"},
|
||||
{ID: 43, Node: "anchor", Module: "telegram", Name: "telegram-token", Desk: "laptop", AskedBy: "ace/agent",
|
||||
{ID: 43, Node: "anchor", Module: "telegram", Name: "telegram-token", Desk: "laptop", AskedBy: "node-a/agent",
|
||||
OpenedAt: now.Add(-20 * time.Minute)},
|
||||
}
|
||||
if _, err := v.MoveAsks("node.anchor", asks); err == nil {
|
||||
@@ -268,12 +268,12 @@ func TestTheRecordOfAsksIsCopiedAsItIsOnce(t *testing.T) {
|
||||
opened[r.ID] = r
|
||||
}
|
||||
}
|
||||
if r := opened["controller-41"]; r.Secret != "own/anchor/telegram/telegram-token" || r.AskedBy != "g14/claude-code" ||
|
||||
if r := opened["controller-41"]; r.Secret != "own/anchor/telegram/telegram-token" || r.AskedBy != "node-b/claude-code" ||
|
||||
r.Desk != "laptop" || !r.At.Equal(asks[0].OpenedAt) || r.Caller != ControllerCaller {
|
||||
t.Errorf("ask 41: %+v", r)
|
||||
}
|
||||
// Three asks in the hour, from the controller's record: the vault's bound holds them.
|
||||
if _, err := v.AskOpen(ControllerCaller, telegramAsk("g14/claude-code")); err == nil ||
|
||||
if _, err := v.AskOpen(ControllerCaller, telegramAsk("node-b/claude-code")); err == nil ||
|
||||
!strings.Contains(err.Error(), "in the last hour") {
|
||||
t.Errorf("an ask after three copied in the hour: %v", err)
|
||||
}
|
||||
@@ -313,7 +313,7 @@ func TestMoveInThroughTheTool(t *testing.T) {
|
||||
}
|
||||
asks := toolNamed(t, definitions(&Env{Vault: v.Vault}), Seat+".move-asks")
|
||||
moved, err := asks.runAs("controller", map[string]any{"asks": []any{map[string]any{"id": 7, "node": "anchor",
|
||||
"module": "telegram", "name": "telegram-token", "desk": "laptop", "asked_by": "g14/claude-code",
|
||||
"module": "telegram", "name": "telegram-token", "desk": "laptop", "asked_by": "node-b/claude-code",
|
||||
"opened_at": "2026-10-01T10:00:00Z", "ended_at": "2026-10-01T10:00:20Z", "outcome": "given"}}})
|
||||
if err != nil || moved.(AsksMoved).Copied != 1 {
|
||||
t.Fatalf("asks through the tool: %+v, %v", moved, err)
|
||||
|
||||
@@ -7,38 +7,38 @@ package main
|
||||
import "testing"
|
||||
|
||||
func TestANamedPairEntryHandsTheConsumersMachineItsOwnLoginAndNoOtherConsumers(t *testing.T) {
|
||||
const pg = "provision/novox/postgres-database"
|
||||
const pg = "provision/node-c/postgres-database"
|
||||
mirror := assignments{
|
||||
"novox.postgres": handedWith("novox", "postgres"),
|
||||
"g14.gitea": handedWith("g14", "gitea", "postgres-database@novox/postgres=database+pair"),
|
||||
"ace.umami": handedWith("ace", "umami", "postgres-database@novox/postgres#stats=database-stats+pair"),
|
||||
"shanks.other": handedWith("shanks", "other", "postgres-database@novox/postgres=database+pair"),
|
||||
"node-c.postgres": handedWith("node-c", "postgres"),
|
||||
"node-b.gitea": handedWith("node-b", "gitea", "postgres-database@node-c/postgres=database+pair"),
|
||||
"node-a.umami": handedWith("node-a", "umami", "postgres-database@node-c/postgres#stats=database-stats+pair"),
|
||||
"node-d.other": handedWith("node-d", "other", "postgres-database@node-c/postgres=database+pair"),
|
||||
}
|
||||
v := pairVault(t, mirror)
|
||||
if o := pass(t, v, pg); o.Outcome != PairMade {
|
||||
t.Fatalf("a named pair entry held the provider back: %+v", o)
|
||||
}
|
||||
gitea, umami := "pair/novox/postgres-database/g14/gitea", "pair/novox/postgres-database/ace/umami/stats"
|
||||
ofGitea, _, err := asks(v, "provisioner.g14", gitea)
|
||||
gitea, umami := "pair/node-c/postgres-database/node-b/gitea", "pair/node-c/postgres-database/node-a/umami/stats"
|
||||
ofGitea, _, err := asks(v, "provisioner.node-b", gitea)
|
||||
if err != nil || ofGitea == "" {
|
||||
t.Fatalf("the consumer's machine was refused its own login: %v", err)
|
||||
}
|
||||
if ofUmami, _, err := asks(v, "provisioner.ace", umami); err != nil || ofUmami == "" || ofUmami == ofGitea {
|
||||
if ofUmami, _, err := asks(v, "provisioner.node-a", umami); err != nil || ofUmami == "" || ofUmami == ofGitea {
|
||||
t.Fatalf("the second consumer's login: %q (%v)", ofUmami, err)
|
||||
}
|
||||
if _, _, err := asks(v, "provisioner.g14", umami); err == nil {
|
||||
if _, _, err := asks(v, "provisioner.node-b", umami); err == nil {
|
||||
t.Fatal("a consumer's machine was handed another consumer's login")
|
||||
}
|
||||
if _, _, err := asks(v, "provisioner.shanks", gitea); err == nil {
|
||||
if _, _, err := asks(v, "provisioner.node-d", gitea); err == nil {
|
||||
t.Fatal("a machine was handed the login of a module that is not its own")
|
||||
}
|
||||
// A named entry of another form is still not a pair row's.
|
||||
other := assignments{
|
||||
"novox.postgres": handedWith("novox", "postgres"),
|
||||
"g14.gitea": handedWith("g14", "gitea", "postgres-database@novox/postgres=database+issued"),
|
||||
"node-c.postgres": handedWith("node-c", "postgres"),
|
||||
"node-b.gitea": handedWith("node-b", "gitea", "postgres-database@node-c/postgres=database+issued"),
|
||||
}
|
||||
w := pairVault(t, other)
|
||||
if _, _, err := asks(w, "provisioner.g14", gitea); err == nil {
|
||||
if _, _, err := asks(w, "provisioner.node-b", gitea); err == nil {
|
||||
t.Fatal("a named entry of the issued form was handed a pair row")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,20 +31,20 @@ func changedNames(p *published) []string {
|
||||
func TestANodeEngineStatesItsOwnKeyAndAChangeRaisesItsCoreSecrets(t *testing.T) {
|
||||
v := newVault(t, nil)
|
||||
tool := toolNamed(t, definitions(&Env{Vault: v.Vault}), Seat+".node-key")
|
||||
// ace holds two core secrets and a leaf module's; g14 holds a core secret too.
|
||||
// node-a holds two core secrets and a leaf module's; node-b holds a core secret too.
|
||||
old, _ := aSealingKey(t)
|
||||
for _, m := range []MoveOf{
|
||||
{Node: "ace", Module: "mesh-provisioner-client", Name: "provisioner-broker", Origin: "made", Generation: "g-1", SealingKey: old},
|
||||
{Node: "ace", Module: "nats", Name: "broker", Origin: "made", Generation: "g-1"},
|
||||
{Node: "ace", Module: "postgres", Name: "superuser-password", Origin: "made", Generation: "g-1"},
|
||||
{Node: "g14", Module: "nats", Name: "broker", Origin: "made", Generation: "g-1"},
|
||||
{Node: "node-a", Module: "mesh-provisioner-client", Name: "provisioner-broker", Origin: "made", Generation: "g-1", SealingKey: old},
|
||||
{Node: "node-a", Module: "nats", Name: "broker", Origin: "made", Generation: "g-1"},
|
||||
{Node: "node-a", Module: "postgres", Name: "superuser-password", Origin: "made", Generation: "g-1"},
|
||||
{Node: "node-b", Module: "nats", Name: "broker", Origin: "made", Generation: "g-1"},
|
||||
} {
|
||||
if _, err := moveIn(t, v, "node."+m.Node, m, movedValue); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
// ace never stated a key in the moves above except through the first row; forget it to be a machine that never moved.
|
||||
if err := os.Remove(v.Store.nodeKeyPath("ace")); err != nil {
|
||||
// node-a never stated a key in the moves above except through the first row; forget it to be a machine that never moved.
|
||||
if err := os.Remove(v.Store.nodeKeyPath("node-a")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
before := len(changedNames(v.bus))
|
||||
@@ -56,27 +56,27 @@ func TestANodeEngineStatesItsOwnKeyAndAChangeRaisesItsCoreSecrets(t *testing.T)
|
||||
if _, err := tool.runAs("", map[string]any{"key": pub}); err == nil {
|
||||
t.Error("an unproven caller stated a key")
|
||||
}
|
||||
if _, err := tool.runAs("node.ace", map[string]any{"key": "c2hvcnQ="}); err == nil {
|
||||
if _, err := tool.runAs("node.node-a", map[string]any{"key": "c2hvcnQ="}); err == nil {
|
||||
t.Error("a short key was taken")
|
||||
}
|
||||
if _, err := tool.runAs("node.ace", map[string]any{"node": "g14", "key": pub}); err != nil {
|
||||
if _, err := tool.runAs("node.node-a", map[string]any{"node": "node-b", "key": pub}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// The machine is the caller's; an argument naming another one changes nothing.
|
||||
if got, held, _ := v.Store.NodeKey("ace"); !held || got != pub {
|
||||
t.Errorf("ace's key is %q, %v", got, held)
|
||||
if got, held, _ := v.Store.NodeKey("node-a"); !held || got != pub {
|
||||
t.Errorf("node-a's key is %q, %v", got, held)
|
||||
}
|
||||
if _, held, _ := v.Store.NodeKey("g14"); held {
|
||||
t.Error("a key was recorded for g14 by ace's engine")
|
||||
if _, held, _ := v.Store.NodeKey("node-b"); held {
|
||||
t.Error("a key was recorded for node-b by node-a's engine")
|
||||
}
|
||||
got := changedNames(v.bus)[before:]
|
||||
want := []string{"own/ace/mesh-provisioner-client/provisioner-broker", "own/ace/nats/broker"}
|
||||
want := []string{"own/node-a/mesh-provisioner-client/provisioner-broker", "own/node-a/nats/broker"}
|
||||
if strings.Join(got, ",") != strings.Join(want, ",") {
|
||||
t.Errorf("secret-changed was raised for %v, want %v", got, want)
|
||||
}
|
||||
|
||||
// The same key again changes nothing and says nothing.
|
||||
if _, err := tool.runAs("node.ace", map[string]any{"key": pub}); err != nil {
|
||||
if _, err := tool.runAs("node.node-a", map[string]any{"key": pub}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n := len(changedNames(v.bus)); n != before+2 {
|
||||
@@ -84,13 +84,13 @@ func TestANodeEngineStatesItsOwnKeyAndAChangeRaisesItsCoreSecrets(t *testing.T)
|
||||
}
|
||||
// A different key raises them again.
|
||||
other, _ := aSealingKey(t)
|
||||
if _, err := tool.runAs("node.ace", map[string]any{"key": other}); err != nil {
|
||||
if _, err := tool.runAs("node.node-a", map[string]any{"key": other}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n := len(changedNames(v.bus)); n != before+4 {
|
||||
t.Errorf("a changed key raised %d, want 2", n-before-2)
|
||||
}
|
||||
if got, _, _ := v.Store.NodeKey("ace"); got != other {
|
||||
if got, _, _ := v.Store.NodeKey("node-a"); got != other {
|
||||
t.Error("the changed key was not recorded")
|
||||
}
|
||||
}
|
||||
@@ -98,22 +98,22 @@ func TestANodeEngineStatesItsOwnKeyAndAChangeRaisesItsCoreSecrets(t *testing.T)
|
||||
const nodeKeyBus = `listen: 127.0.0.1:-1
|
||||
authorization {
|
||||
users = [
|
||||
{ user: "novox.mesh-vault", password: "pw", permissions: {
|
||||
{ user: "node-c.mesh-vault", password: "pw", permissions: {
|
||||
publish: { deny: [">"] }
|
||||
subscribe: { allow: ["mesh.seat.mesh-vault.call.node-key.*"] }
|
||||
allow_responses: true } }
|
||||
{ user: "node.ace", password: "pw", permissions: {
|
||||
publish: { allow: ["mesh.seat.mesh-vault.call.node-key.node_ace"] }
|
||||
subscribe: { allow: ["_INBOX.node.ace.>"] } } }
|
||||
{ user: "node.g14", password: "pw", permissions: {
|
||||
publish: { allow: ["mesh.seat.mesh-vault.call.node-key.node_g14"] }
|
||||
subscribe: { allow: ["_INBOX.node.g14.>"] } } }
|
||||
{ user: "node.node-a", password: "pw", permissions: {
|
||||
publish: { allow: ["mesh.seat.mesh-vault.call.node-key.node_node-a"] }
|
||||
subscribe: { allow: ["_INBOX.node.node-a.>"] } } }
|
||||
{ user: "node.node-b", password: "pw", permissions: {
|
||||
publish: { allow: ["mesh.seat.mesh-vault.call.node-key.node_node-b"] }
|
||||
subscribe: { allow: ["_INBOX.node.node-b.>"] } } }
|
||||
]
|
||||
}
|
||||
`
|
||||
|
||||
// On a real bus whose accounts may publish only their own subject: node.ace's statement records ace's key; node.g14
|
||||
// cannot speak in ace's name, and what it states is g14's.
|
||||
// On a real bus whose accounts may publish only their own subject: node.node-a's statement records node-a's key; node.node-b
|
||||
// cannot speak in node-a's name, and what it states is node-b's.
|
||||
func TestOnARealBusANodeEngineStatesOnlyItsOwnKey(t *testing.T) {
|
||||
file := filepath.Join(t.TempDir(), "bus.conf")
|
||||
if err := os.WriteFile(file, []byte(nodeKeyBus), 0o600); err != nil {
|
||||
@@ -136,7 +136,7 @@ func TestOnARealBusANodeEngineStatesOnlyItsOwnKey(t *testing.T) {
|
||||
|
||||
v := newVault(t, nil)
|
||||
tool := toolNamed(t, definitions(&Env{Vault: v.Vault}), Seat+".node-key")
|
||||
vaultConn := connectAs(t, s.ClientURL(), "novox.mesh-vault")
|
||||
vaultConn := connectAs(t, s.ClientURL(), "node-c.mesh-vault")
|
||||
if _, err := vaultConn.Subscribe("mesh.seat.mesh-vault.call.node-key.*", func(m *nats.Msg) {
|
||||
parts := strings.Split(m.Subject, ".")
|
||||
var args map[string]any
|
||||
@@ -167,25 +167,25 @@ func TestOnARealBusANodeEngineStatesOnlyItsOwnKey(t *testing.T) {
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
aceKey, _ := aSealingKey(t)
|
||||
g14Key, _ := aSealingKey(t)
|
||||
if _, err := say("node.ace", "mesh.seat.mesh-vault.call.node-key.node_ace", aceKey); err != nil {
|
||||
nodeAKey, _ := aSealingKey(t)
|
||||
nodeBKey, _ := aSealingKey(t)
|
||||
if _, err := say("node.node-a", "mesh.seat.mesh-vault.call.node-key.node_node-a", nodeAKey); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got, held, _ := v.Store.NodeKey("ace"); !held || got != aceKey {
|
||||
t.Errorf("ace's key is %q, %v", got, held)
|
||||
if got, held, _ := v.Store.NodeKey("node-a"); !held || got != nodeAKey {
|
||||
t.Errorf("node-a's key is %q, %v", got, held)
|
||||
}
|
||||
// g14 may not publish on ace's subject: the bus refuses it, so the vault never hears it.
|
||||
if _, err := say("node.g14", "mesh.seat.mesh-vault.call.node-key.node_ace", g14Key); err == nil {
|
||||
t.Error("node.g14 spoke in ace's name")
|
||||
// node-b may not publish on node-a's subject: the bus refuses it, so the vault never hears it.
|
||||
if _, err := say("node.node-b", "mesh.seat.mesh-vault.call.node-key.node_node-a", nodeBKey); err == nil {
|
||||
t.Error("node.node-b spoke in node-a's name")
|
||||
}
|
||||
if got, _, _ := v.Store.NodeKey("ace"); got != aceKey {
|
||||
t.Error("ace's key was replaced by g14's")
|
||||
if got, _, _ := v.Store.NodeKey("node-a"); got != nodeAKey {
|
||||
t.Error("node-a's key was replaced by node-b's")
|
||||
}
|
||||
if _, err := say("node.g14", "mesh.seat.mesh-vault.call.node-key.node_g14", g14Key); err != nil {
|
||||
if _, err := say("node.node-b", "mesh.seat.mesh-vault.call.node-key.node_node-b", nodeBKey); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got, held, _ := v.Store.NodeKey("g14"); !held || got != g14Key {
|
||||
t.Errorf("g14's key is %q, %v", got, held)
|
||||
if got, held, _ := v.Store.NodeKey("node-b"); !held || got != nodeBKey {
|
||||
t.Errorf("node-b's key is %q, %v", got, held)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ func TestAnAssignmentThatCannotBeReadIsNotYetAndNotARefusal(t *testing.T) {
|
||||
read := func(string) ([]byte, error) { return nil, down }
|
||||
v := newVault(t, Declared{Read: read})
|
||||
asker, _ := seal.GenerateKeyPair()
|
||||
for _, name := range []string{"own/g14/claude-code/token", "provision/novox/gitea-read/g14/claude-code"} {
|
||||
_, err := v.Get("provisioner.g14", name, asker.PublicKey)
|
||||
for _, name := range []string{"own/node-b/claude-code/token", "provision/node-c/gitea-read/node-b/claude-code"} {
|
||||
_, err := v.Get("provisioner.node-b", name, asker.PublicKey)
|
||||
if err == nil {
|
||||
t.Fatalf("%s: answered while the assignment cannot be read and the vault holds nothing", name)
|
||||
}
|
||||
@@ -36,13 +36,13 @@ func TestAnAssignmentThatCannotBeReadIsNotYetAndNotARefusal(t *testing.T) {
|
||||
// assignment reads as absent stays "not on".
|
||||
func TestAPairRowWhoseAssignmentCannotBeReadIsNotYet(t *testing.T) {
|
||||
down := errors.New("nats: no responders available for request")
|
||||
parts := []string{"pair", "novox", "db", "g14", "claude-code"}
|
||||
why := pairRowRefusal(func(string) ([]byte, error) { return nil, down }, "g14", parts)
|
||||
parts := []string{"pair", "node-c", "db", "node-b", "claude-code"}
|
||||
why := pairRowRefusal(func(string) ([]byte, error) { return nil, down }, "node-b", parts)
|
||||
if !strings.HasPrefix(why, notYetPrefix) || !strings.Contains(why, "no responders") {
|
||||
t.Errorf("a read error answered %q, want a not-yet carrying the reason", why)
|
||||
}
|
||||
why = pairRowRefusal(func(string) ([]byte, error) { return nil, nil }, "g14", parts)
|
||||
if strings.HasPrefix(why, notYetPrefix) || !strings.Contains(why, "is not on g14") {
|
||||
why = pairRowRefusal(func(string) ([]byte, error) { return nil, nil }, "node-b", parts)
|
||||
if strings.HasPrefix(why, notYetPrefix) || !strings.Contains(why, "is not on node-b") {
|
||||
t.Errorf("an absent assignment answered %q, want the refusal 'not on'", why)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,14 +11,14 @@ import (
|
||||
// value for each; the next pass leaves them be, and a holder bound later is the only one asked.
|
||||
func TestThePassAsksAnIssuingProvidersToolForTheHoldersItHoldsNothingFor(t *testing.T) {
|
||||
mirror := assignments{
|
||||
"novox.gitea": handedWith("novox", "gitea"),
|
||||
"anchor.claude-code": handedWith("anchor", "claude-code", "gitea-read@novox/gitea=gitea-read+issued~gitea_agent_token"),
|
||||
"node-c.gitea": handedWith("node-c", "gitea"),
|
||||
"anchor.claude-code": handedWith("anchor", "claude-code", "gitea-read@node-c/gitea=gitea-read+issued~gitea_agent_token"),
|
||||
}
|
||||
v := pairVault(t, mirror)
|
||||
var asked []map[string]any
|
||||
v.Ask = forgeMesh(perHolderProvider(agentTokens, &asked)).ask
|
||||
|
||||
o := pass(t, v, "provision/novox/gitea-read")
|
||||
o := pass(t, v, "provision/node-c/gitea-read")
|
||||
if o.Outcome != PairPerHolder {
|
||||
t.Fatalf("outcome %+v", o)
|
||||
}
|
||||
@@ -33,14 +33,14 @@ func TestThePassAsksAnIssuingProvidersToolForTheHoldersItHoldsNothingFor(t *test
|
||||
}
|
||||
|
||||
// Held is left untouched: no second ask, so the forge does not mint anew (rotation is the software's).
|
||||
pass(t, v, "provision/novox/gitea-read")
|
||||
pass(t, v, "provision/node-c/gitea-read")
|
||||
if len(asked) != 1 {
|
||||
t.Errorf("the tool was asked again for a holder that holds its value (%d asks)", len(asked))
|
||||
}
|
||||
|
||||
// A holder bound since is the only one asked.
|
||||
mirror["laptop.claude-code"] = handedWith("laptop", "claude-code", "gitea-read@novox/gitea=gitea-read+issued~gitea_agent_token")
|
||||
pass(t, v, "provision/novox/gitea-read")
|
||||
mirror["laptop.claude-code"] = handedWith("laptop", "claude-code", "gitea-read@node-c/gitea=gitea-read+issued~gitea_agent_token")
|
||||
pass(t, v, "provision/node-c/gitea-read")
|
||||
if len(asked) != 2 || strings.Contains(toString(asked[1]["holders"]), "anchor") {
|
||||
t.Fatalf("asks %v, want a second naming laptop alone", asked)
|
||||
}
|
||||
@@ -53,28 +53,28 @@ func TestThePassAsksAnIssuingProvidersToolForTheHoldersItHoldsNothingFor(t *test
|
||||
// tool is said, not guessed.
|
||||
func TestAFailedAskOfTheToolIsAskedAgainAndNoToolIsSaid(t *testing.T) {
|
||||
mirror := assignments{
|
||||
"novox.gitea": handedWith("novox", "gitea"),
|
||||
"anchor.claude-code": handedWith("anchor", "claude-code", "gitea-read@novox/gitea=gitea-read+issued~gitea_agent_token"),
|
||||
"node-c.gitea": handedWith("node-c", "gitea"),
|
||||
"anchor.claude-code": handedWith("anchor", "claude-code", "gitea-read@node-c/gitea=gitea-read+issued~gitea_agent_token"),
|
||||
}
|
||||
v := pairVault(t, mirror)
|
||||
v.Ask = forgeMesh(func(string, map[string]any) (map[string]any, error) { return nil, errTest }).ask
|
||||
o := pass(t, v, "provision/novox/gitea-read")
|
||||
o := pass(t, v, "provision/node-c/gitea-read")
|
||||
if o.Outcome != PairHeld || !strings.Contains(o.Why, "could not be asked") {
|
||||
t.Fatalf("a failed ask is %+v", o)
|
||||
}
|
||||
var asked []map[string]any
|
||||
v.Ask = forgeMesh(perHolderProvider(agentTokens, &asked)).ask
|
||||
pass(t, v, "provision/novox/gitea-read")
|
||||
pass(t, v, "provision/node-c/gitea-read")
|
||||
if _, held, _ := v.Store.Read(anchorsToken); !held || len(asked) != 1 {
|
||||
t.Fatal("the next pass did not ask again and keep the value")
|
||||
}
|
||||
|
||||
noTool := pairVault(t, assignments{
|
||||
"novox.gitea": handedWith("novox", "gitea"),
|
||||
"anchor.claude-code": handedWith("anchor", "claude-code", "gitea-read@novox/gitea=gitea-read+issued"),
|
||||
"node-c.gitea": handedWith("node-c", "gitea"),
|
||||
"anchor.claude-code": handedWith("anchor", "claude-code", "gitea-read@node-c/gitea=gitea-read+issued"),
|
||||
})
|
||||
noTool.Ask = v.Ask
|
||||
if o := pass(t, noTool, "provision/novox/gitea-read"); o.Outcome != PairHeld || !strings.Contains(o.Why, "name no tool") {
|
||||
if o := pass(t, noTool, "provision/node-c/gitea-read"); o.Outcome != PairHeld || !strings.Contains(o.Why, "name no tool") {
|
||||
t.Errorf("a plan with no tool is %+v", o)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
)
|
||||
|
||||
// vaultsAccount is the vault's bus user as its own runtime names it to a tool it calls.
|
||||
const vaultsAccount = "novox.mesh-vault"
|
||||
const vaultsAccount = "node-c.mesh-vault"
|
||||
|
||||
// aMesh stands in for the bus the vault's runtime calls on: each call is answered by the desk's prompt or a
|
||||
// provider's tool, named by the caller the vault's own account is, and everything that crosses is kept.
|
||||
@@ -97,11 +97,11 @@ func TestTheVaultAsksTheDesksPromptItselfAndOnlyASealedValueCrosses(t *testing.T
|
||||
mesh := &aMesh{desk: aDesk(typedAtTheDesk, &asked)}
|
||||
v.Ask = mesh.ask
|
||||
|
||||
got, err := v.AskAtDesk(ControllerCaller, telegramAsk("g14/claude-code, via the console"))
|
||||
got, err := v.AskAtDesk(ControllerCaller, telegramAsk("node-b/claude-code, via the console"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got.Outcome != "given" || got.Generation != 1 || got.AskedBy != "g14/claude-code" ||
|
||||
if got.Outcome != "given" || got.Generation != 1 || got.AskedBy != "node-b/claude-code" ||
|
||||
got.Secret != "own/anchor/telegram/telegram-token" {
|
||||
t.Errorf("answered %+v", got)
|
||||
}
|
||||
@@ -109,7 +109,7 @@ func TestTheVaultAsksTheDesksPromptItselfAndOnlyASealedValueCrosses(t *testing.T
|
||||
t.Fatalf("the vault asked %v", mesh.keys)
|
||||
}
|
||||
p := asked[0]
|
||||
if p["asked_by"] != "g14/claude-code" || p["module"] != "telegram" || p["secret"] != "telegram-token" ||
|
||||
if p["asked_by"] != "node-b/claude-code" || p["module"] != "telegram" || p["secret"] != "telegram-token" ||
|
||||
p["node"] != "anchor" || p["seal_to"] == "" || p["timeout_seconds"] != float64(DeskPromptFor) {
|
||||
t.Errorf("the prompt was asked %v", p)
|
||||
}
|
||||
@@ -117,7 +117,7 @@ func TestTheVaultAsksTheDesksPromptItselfAndOnlyASealedValueCrosses(t *testing.T
|
||||
t.Errorf("the vault holds %q", v)
|
||||
}
|
||||
rec, _, _ := v.Store.Read("own/anchor/telegram/telegram-token")
|
||||
if rec.Origin != OriginTyped || rec.ChangedBy != "g14/claude-code" || rec.ChangedVia != ControllerCaller {
|
||||
if rec.Origin != OriginTyped || rec.ChangedBy != "node-b/claude-code" || rec.ChangedVia != ControllerCaller {
|
||||
t.Errorf("the record: origin %q by %q via %q", rec.Origin, rec.ChangedBy, rec.ChangedVia)
|
||||
}
|
||||
asks, _ := v.Store.Asks(10)
|
||||
@@ -133,7 +133,7 @@ func TestTheVaultAsksTheDesksPromptItselfAndOnlyASealedValueCrosses(t *testing.T
|
||||
w := newVault(t, nil)
|
||||
var a []map[string]any
|
||||
w.Ask = (&aMesh{desk: aDesk(typed, &a)}).ask
|
||||
got, err := w.AskAtDesk(ControllerCaller, telegramAsk("g14.claude-code"))
|
||||
got, err := w.AskAtDesk(ControllerCaller, telegramAsk("node-b.claude-code"))
|
||||
if err != nil || got.Outcome != outcome {
|
||||
t.Errorf("%s: %+v %v", outcome, got, err)
|
||||
}
|
||||
@@ -148,13 +148,13 @@ func TestTheVaultAsksTheDesksPromptItselfAndOnlyASealedValueCrosses(t *testing.T
|
||||
e := newVault(t, nil)
|
||||
var ea []map[string]any
|
||||
e.Ask = (&aMesh{desk: aDesk(" ", &ea)}).ask
|
||||
if got, err := e.AskAtDesk(ControllerCaller, telegramAsk("g14.claude-code")); err != nil || got.Outcome != "empty" {
|
||||
if got, err := e.AskAtDesk(ControllerCaller, telegramAsk("node-b.claude-code")); err != nil || got.Outcome != "empty" {
|
||||
t.Errorf("empty: %+v %v", got, err)
|
||||
}
|
||||
// The desk cannot be asked: refused, recorded so.
|
||||
r := newVault(t, nil)
|
||||
r.Ask = (&aMesh{}).ask
|
||||
if _, err := r.AskAtDesk(ControllerCaller, telegramAsk("g14.claude-code")); err == nil {
|
||||
if _, err := r.AskAtDesk(ControllerCaller, telegramAsk("node-b.claude-code")); err == nil {
|
||||
t.Error("an ask nobody answered was said to be done")
|
||||
}
|
||||
if asks, _ := r.Store.Asks(1); asks[0].Outcome != "refused" {
|
||||
@@ -164,7 +164,7 @@ func TestTheVaultAsksTheDesksPromptItselfAndOnlyASealedValueCrosses(t *testing.T
|
||||
n := newVault(t, nil)
|
||||
var na []map[string]any
|
||||
n.Ask = (&aMesh{desk: aDesk(typedAtTheDesk, &na)}).ask
|
||||
for _, caller := range []string{"g14.node-tools", "g14.claude-code", "unproven", ""} {
|
||||
for _, caller := range []string{"node-b.node-tools", "node-b.claude-code", "unproven", ""} {
|
||||
if _, err := n.AskAtDesk(caller, telegramAsk("x")); err == nil {
|
||||
t.Errorf("%q had the vault ask a desk", caller)
|
||||
}
|
||||
@@ -205,7 +205,7 @@ func TestTheVaultAsksTheIssuingToolItselfAndOnlyASealedValueCrosses(t *testing.T
|
||||
}
|
||||
// Only the controller asks the vault to take it; a tool's name that is not one is refused; a tool that is not
|
||||
// answered, or answers no sealed value, keeps nothing.
|
||||
for _, caller := range []string{"anchor.node-tools", "g14.claude-code", "unproven", ""} {
|
||||
for _, caller := range []string{"anchor.node-tools", "node-b.claude-code", "unproven", ""} {
|
||||
if _, err := v.TakeIssued(caller, "plex-api", "anchor", "plex", "plex_issued_credential"); err == nil {
|
||||
t.Errorf("%q had the vault ask a provider", caller)
|
||||
}
|
||||
|
||||
@@ -130,10 +130,10 @@ func changedSaid(t *testing.T, v testVault, name, value string) int {
|
||||
func TestAConsumerBindingOrUnbindingRemakesTheProvidersOneCredentialForEveryHolder(t *testing.T) {
|
||||
mirror := assignments{
|
||||
"anchor.stand-in": handedWith("anchor", "stand-in"),
|
||||
"g14.app": handedWith("g14", "app", "stand-in-api@anchor/stand-in"),
|
||||
"node-b.app": handedWith("node-b", "app", "stand-in-api@anchor/stand-in"),
|
||||
}
|
||||
v := pairVault(t, mirror)
|
||||
movedRow(t, v, "g14", "app", "shared-from-the-move", "stamp-1", OriginMade)
|
||||
movedRow(t, v, "node-b", "app", "shared-from-the-move", "stamp-1", OriginMade)
|
||||
|
||||
// The move's shared value is adopted, and said to have changed in the vault.
|
||||
if o := pass(t, v, standIn); o.Outcome != PairAdopted || o.Generation != 1 {
|
||||
@@ -143,13 +143,13 @@ func TestAConsumerBindingOrUnbindingRemakesTheProvidersOneCredentialForEveryHold
|
||||
if err != nil {
|
||||
t.Fatalf("the provider's machine was refused its credential: %v", err)
|
||||
}
|
||||
if got, _, err := asks(v, "provisioner.g14", standIn); err != nil || got != first {
|
||||
if got, _, err := asks(v, "provisioner.node-b", standIn); err != nil || got != first {
|
||||
t.Fatalf("the consumer got %q (%v), the provider %q", got, err, first)
|
||||
}
|
||||
if first != "shared-from-the-move" || changedSaid(t, v, standIn, "") != 1 {
|
||||
t.Fatal("the adopted value is not the move's, or secret-changed was not said once for it")
|
||||
}
|
||||
if _, _, err := asks(v, "provisioner.ace", standIn); err == nil {
|
||||
if _, _, err := asks(v, "provisioner.node-a", standIn); err == nil {
|
||||
t.Fatal("a machine holding nothing of it was handed the credential")
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ func TestAConsumerBindingOrUnbindingRemakesTheProvidersOneCredentialForEveryHold
|
||||
}
|
||||
|
||||
// A second consumer binds: the vault remakes it without the provider asking, one new value for all three.
|
||||
mirror["ace.other"] = handedWith("ace", "other", "stand-in-api@anchor/stand-in")
|
||||
mirror["node-a.other"] = handedWith("node-a", "other", "stand-in-api@anchor/stand-in")
|
||||
if o := pass(t, v, standIn); o.Outcome != PairRemade || o.Generation != 2 {
|
||||
t.Fatalf("a consumer binding: %+v", o)
|
||||
}
|
||||
@@ -167,7 +167,7 @@ func TestAConsumerBindingOrUnbindingRemakesTheProvidersOneCredentialForEveryHold
|
||||
if second == first || gen != 2 {
|
||||
t.Fatal("the value was not remade when a consumer bound")
|
||||
}
|
||||
for _, c := range []string{"provisioner.g14", "provisioner.ace"} {
|
||||
for _, c := range []string{"provisioner.node-b", "provisioner.node-a"} {
|
||||
if got, _, err := asks(v, c, standIn); err != nil || got != second {
|
||||
t.Fatalf("%s got %q (%v) after the bind, not the one new value", c, got, err)
|
||||
}
|
||||
@@ -177,24 +177,24 @@ func TestAConsumerBindingOrUnbindingRemakesTheProvidersOneCredentialForEveryHold
|
||||
}
|
||||
|
||||
// It unbinds (its plan no longer names it): remade for the holders left, and it is handed nothing more.
|
||||
mirror["ace.other"] = handedWith("ace", "other")
|
||||
mirror["node-a.other"] = handedWith("node-a", "other")
|
||||
if o := pass(t, v, standIn); o.Outcome != PairRemade || o.Generation != 3 {
|
||||
t.Fatalf("a consumer unbinding: %+v", o)
|
||||
}
|
||||
third, _, _ := asks(v, "provisioner.anchor", standIn)
|
||||
if got, _, _ := asks(v, "provisioner.g14", standIn); third == second || got != third {
|
||||
if got, _, _ := asks(v, "provisioner.node-b", standIn); third == second || got != third {
|
||||
t.Fatal("the holders left do not share one new value after the unbind")
|
||||
}
|
||||
if _, _, err := asks(v, "provisioner.ace", standIn); err == nil {
|
||||
if _, _, err := asks(v, "provisioner.node-a", standIn); err == nil {
|
||||
t.Fatal("a consumer that unbound was still handed the credential")
|
||||
}
|
||||
|
||||
// Its assignment goes: an unbind too.
|
||||
delete(mirror, "g14.app")
|
||||
delete(mirror, "node-b.app")
|
||||
if o := pass(t, v, standIn); o.Outcome != PairRemade {
|
||||
t.Fatalf("a consumer taken off its machine: %+v", o)
|
||||
}
|
||||
if _, _, err := asks(v, "provisioner.g14", standIn); err == nil {
|
||||
if _, _, err := asks(v, "provisioner.node-b", standIn); err == nil {
|
||||
t.Fatal("a machine whose consumer left was still handed the credential")
|
||||
}
|
||||
}
|
||||
@@ -203,23 +203,23 @@ func TestAConsumerBindingOrUnbindingRemakesTheProvidersOneCredentialForEveryHold
|
||||
// 757) has no one shared value.
|
||||
func TestLocalNamesAreBindsAndAPerHolderCredentialIsNotShared(t *testing.T) {
|
||||
mirror := assignments{
|
||||
"anchor.stand-in": handedWith("anchor", "stand-in"),
|
||||
"g14.app": handedWith("g14", "app", "stand-in-api@anchor/stand-in#reader"),
|
||||
"novox.gitea": handedWith("novox", "gitea"),
|
||||
"g14.claude-code": handedWith("g14", "claude-code", "gitea-read@novox/gitea=gitea-read"),
|
||||
"anchor.stand-in": handedWith("anchor", "stand-in"),
|
||||
"node-b.app": handedWith("node-b", "app", "stand-in-api@anchor/stand-in#reader"),
|
||||
"node-c.gitea": handedWith("node-c", "gitea"),
|
||||
"node-b.claude-code": handedWith("node-b", "claude-code", "gitea-read@node-c/gitea=gitea-read"),
|
||||
}
|
||||
v := pairVault(t, mirror)
|
||||
movedRow(t, v, "g14", "app", "shared", "stamp-1", OriginMade, "reader")
|
||||
movedRow(t, v, "node-b", "app", "shared", "stamp-1", OriginMade, "reader")
|
||||
pass(t, v, standIn)
|
||||
mirror["g14.app"] = handedWith("g14", "app", "stand-in-api@anchor/stand-in#reader",
|
||||
mirror["node-b.app"] = handedWith("node-b", "app", "stand-in-api@anchor/stand-in#reader",
|
||||
"stand-in-api@anchor/stand-in#writer")
|
||||
if o := pass(t, v, standIn); o.Outcome != PairRemade {
|
||||
t.Fatalf("a second local name binding: %+v", o)
|
||||
}
|
||||
if o := pass(t, v, "provision/novox/gitea-read"); o.Outcome != PairPerHolder {
|
||||
if o := pass(t, v, "provision/node-c/gitea-read"); o.Outcome != PairPerHolder {
|
||||
t.Fatalf("a credential per holder: %+v", o)
|
||||
}
|
||||
if _, held, _ := v.Store.Read("provision/novox/gitea-read"); held {
|
||||
if _, held, _ := v.Store.Read("provision/node-c/gitea-read"); held {
|
||||
t.Fatal("a shared value was made for a credential issued per holder")
|
||||
}
|
||||
}
|
||||
@@ -231,7 +231,7 @@ func TestAnAcceptedOrIssuedValueIsNotRemadeOnABind(t *testing.T) {
|
||||
t.Run(origin, func(t *testing.T) {
|
||||
mirror := assignments{
|
||||
"anchor.stand-in": handedWith("anchor", "stand-in"),
|
||||
"g14.app": handedWith("g14", "app", "stand-in-api@anchor/stand-in"),
|
||||
"node-b.app": handedWith("node-b", "app", "stand-in-api@anchor/stand-in"),
|
||||
}
|
||||
v := pairVault(t, mirror)
|
||||
if _, err := v.keep(standIn, "given-by-a-person", "person.jochen", ControllerCaller, origin); err != nil {
|
||||
@@ -240,11 +240,11 @@ func TestAnAcceptedOrIssuedValueIsNotRemadeOnABind(t *testing.T) {
|
||||
if o := pass(t, v, standIn); o.Outcome != PairRecorded || o.Generation != 1 {
|
||||
t.Fatalf("the first pass: %+v", o)
|
||||
}
|
||||
mirror["ace.other"] = handedWith("ace", "other", "stand-in-api@anchor/stand-in")
|
||||
mirror["node-a.other"] = handedWith("node-a", "other", "stand-in-api@anchor/stand-in")
|
||||
if o := pass(t, v, standIn); o.Outcome != PairRecorded || o.Generation != 1 {
|
||||
t.Fatalf("a bind to a value %s: %+v", origin, o)
|
||||
}
|
||||
for _, c := range []string{"provisioner.anchor", "provisioner.g14", "provisioner.ace"} {
|
||||
for _, c := range []string{"provisioner.anchor", "provisioner.node-b", "provisioner.node-a"} {
|
||||
if got, gen, err := asks(v, c, standIn); err != nil || got != "given-by-a-person" || gen != 1 {
|
||||
t.Fatalf("%s got %q generation %d (%v)", c, got, gen, err)
|
||||
}
|
||||
@@ -272,17 +272,17 @@ func movedRow(t *testing.T, v testVault, consumer, module, value, stamp, origin
|
||||
func TestPairRowsSharingAGenerationStampAreAdoptedAndNothingIsRemade(t *testing.T) {
|
||||
mirror := assignments{
|
||||
"anchor.stand-in": handedWith("anchor", "stand-in"),
|
||||
"g14.app": handedWith("g14", "app", "stand-in-api@anchor/stand-in"),
|
||||
"ace.other": handedWith("ace", "other", "stand-in-api@anchor/stand-in"),
|
||||
"node-b.app": handedWith("node-b", "app", "stand-in-api@anchor/stand-in"),
|
||||
"node-a.other": handedWith("node-a", "other", "stand-in-api@anchor/stand-in"),
|
||||
}
|
||||
v := pairVault(t, mirror)
|
||||
movedRow(t, v, "g14", "app", "the-value-they-share", "20260901T000000-1", OriginMade)
|
||||
movedRow(t, v, "ace", "other", "the-value-they-share", "20260901T000000-1", OriginMade)
|
||||
movedRow(t, v, "node-b", "app", "the-value-they-share", "20260901T000000-1", OriginMade)
|
||||
movedRow(t, v, "node-a", "other", "the-value-they-share", "20260901T000000-1", OriginMade)
|
||||
|
||||
if o := pass(t, v, standIn); o.Outcome != PairAdopted || o.Generation != 1 {
|
||||
t.Fatalf("stamped rows: %+v", o)
|
||||
}
|
||||
for _, c := range []string{"provisioner.anchor", "provisioner.g14", "provisioner.ace"} {
|
||||
for _, c := range []string{"provisioner.anchor", "provisioner.node-b", "provisioner.node-a"} {
|
||||
if got, _, err := asks(v, c, standIn); err != nil || got != "the-value-they-share" {
|
||||
t.Fatalf("%s got %q (%v), not the value it had", c, got, err)
|
||||
}
|
||||
@@ -295,7 +295,7 @@ func TestPairRowsSharingAGenerationStampAreAdoptedAndNothingIsRemade(t *testing.
|
||||
if o := pass(t, v, standIn); o.Outcome != PairUnchanged {
|
||||
t.Fatalf("the pass after the adoption: %+v", o)
|
||||
}
|
||||
if got, gen, _ := asks(v, "provisioner.g14", standIn); got != "the-value-they-share" || gen != 1 {
|
||||
if got, gen, _ := asks(v, "provisioner.node-b", standIn); got != "the-value-they-share" || gen != 1 {
|
||||
t.Fatal("the adopted value was remade with no bind or unbind")
|
||||
}
|
||||
}
|
||||
@@ -305,19 +305,19 @@ func TestPairRowsSharingAGenerationStampAreAdoptedAndNothingIsRemade(t *testing.
|
||||
func TestUnstampedPairRowsAreNeverMadeOneSharedValue(t *testing.T) {
|
||||
mirror := assignments{
|
||||
"anchor.stand-in": handedWith("anchor", "stand-in"),
|
||||
"g14.app": handedWith("g14", "app", "stand-in-api@anchor/stand-in"),
|
||||
"ace.other": handedWith("ace", "other", "stand-in-api@anchor/stand-in"),
|
||||
"node-b.app": handedWith("node-b", "app", "stand-in-api@anchor/stand-in"),
|
||||
"node-a.other": handedWith("node-a", "other", "stand-in-api@anchor/stand-in"),
|
||||
}
|
||||
v := pairVault(t, mirror)
|
||||
movedRow(t, v, "g14", "app", "login-of-app", "", OriginMade)
|
||||
movedRow(t, v, "ace", "other", "login-of-other", "", OriginMade)
|
||||
movedRow(t, v, "node-b", "app", "login-of-app", "", OriginMade)
|
||||
movedRow(t, v, "node-a", "other", "login-of-other", "", OriginMade)
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
if o := pass(t, v, standIn); o.Outcome != PairPerPair {
|
||||
t.Fatalf("unstamped rows, pass %d: %+v", i, o)
|
||||
}
|
||||
}
|
||||
mirror["novox.third"] = handedWith("novox", "third", "stand-in-api@anchor/stand-in")
|
||||
mirror["node-c.third"] = handedWith("node-c", "third", "stand-in-api@anchor/stand-in")
|
||||
if o := pass(t, v, standIn); o.Outcome != PairPerPair {
|
||||
t.Fatalf("a bind to a provider with logins of their own: %+v", o)
|
||||
}
|
||||
@@ -336,45 +336,45 @@ func TestUnstampedPairRowsAreNeverMadeOneSharedValue(t *testing.T) {
|
||||
// apart, and each consumer's machine is handed only its own; the provider's machine is handed both.
|
||||
func TestARotationRemakesEachPostgresConsumersOwnLoginApart(t *testing.T) {
|
||||
mirror := assignments{
|
||||
"novox.postgres": handedWith("novox", "postgres"),
|
||||
"g14.gitea": handedWith("g14", "gitea", "postgres-database@novox/postgres"),
|
||||
"ace.umami": handedWith("ace", "umami", "postgres-database@novox/postgres"),
|
||||
"node-c.postgres": handedWith("node-c", "postgres"),
|
||||
"node-b.gitea": handedWith("node-b", "gitea", "postgres-database@node-c/postgres"),
|
||||
"node-a.umami": handedWith("node-a", "umami", "postgres-database@node-c/postgres"),
|
||||
}
|
||||
v := pairVault(t, mirror)
|
||||
gitea, umami := "pair/novox/postgres-database/g14/gitea", "pair/novox/postgres-database/ace/umami"
|
||||
gitea, umami := "pair/node-c/postgres-database/node-b/gitea", "pair/node-c/postgres-database/node-a/umami"
|
||||
for name, value := range map[string]string{gitea: "login-of-gitea", umami: "login-of-umami"} {
|
||||
if _, err := v.keepWith(name, value, "node.novox", "", OriginMade, keepOptions{neverReplace: true,
|
||||
movedFrom: &MovedFrom{By: "node.novox"}}); err != nil {
|
||||
if _, err := v.keepWith(name, value, "node.node-c", "", OriginMade, keepOptions{neverReplace: true,
|
||||
movedFrom: &MovedFrom{By: "node.node-c"}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
changed, err := v.Rotate(ControllerCaller, "provision/novox/postgres-database", "person.jochen", "yearly")
|
||||
changed, err := v.Rotate(ControllerCaller, "provision/node-c/postgres-database", "person.jochen", "yearly")
|
||||
if err != nil || len(changed.Each) != 2 {
|
||||
t.Fatalf("the rotation: %+v (%v)", changed, err)
|
||||
}
|
||||
ofGitea, _, err := asks(v, "provisioner.g14", gitea)
|
||||
ofGitea, _, err := asks(v, "provisioner.node-b", gitea)
|
||||
if err != nil {
|
||||
t.Fatalf("gitea's machine was refused its own login: %v", err)
|
||||
}
|
||||
ofUmami, _, err := asks(v, "provisioner.ace", umami)
|
||||
ofUmami, _, err := asks(v, "provisioner.node-a", umami)
|
||||
if err != nil {
|
||||
t.Fatalf("umami's machine was refused its own login: %v", err)
|
||||
}
|
||||
if ofGitea == ofUmami || ofGitea == "login-of-gitea" || ofUmami == "login-of-umami" {
|
||||
t.Fatalf("after the rotation gitea holds %q and umami %q: not two new values of their own", ofGitea, ofUmami)
|
||||
}
|
||||
if _, _, err := asks(v, "provisioner.g14", umami); err == nil {
|
||||
if _, _, err := asks(v, "provisioner.node-b", umami); err == nil {
|
||||
t.Fatal("gitea's machine was handed umami's login")
|
||||
}
|
||||
if _, _, err := asks(v, "provisioner.ace", gitea); err == nil {
|
||||
if _, _, err := asks(v, "provisioner.node-a", gitea); err == nil {
|
||||
t.Fatal("umami's machine was handed gitea's login")
|
||||
}
|
||||
for name, want := range map[string]string{gitea: ofGitea, umami: ofUmami} {
|
||||
if got, _, err := asks(v, "provisioner.novox", name); err != nil || got != want {
|
||||
if got, _, err := asks(v, "provisioner.node-c", name); err != nil || got != want {
|
||||
t.Fatalf("the provider's machine got %q for %s (%v)", got, name, err)
|
||||
}
|
||||
}
|
||||
if _, held, _ := v.Store.Read("provision/novox/postgres-database"); held {
|
||||
if _, held, _ := v.Store.Read("provision/node-c/postgres-database"); held {
|
||||
t.Fatal("a shared value was made for postgres-database")
|
||||
}
|
||||
}
|
||||
@@ -384,7 +384,7 @@ func TestARotationRemakesEachPostgresConsumersOwnLoginApart(t *testing.T) {
|
||||
func TestAProviderTheVaultHoldsNoRowOfIsNotGuessed(t *testing.T) {
|
||||
mirror := assignments{
|
||||
"anchor.stand-in": handedWith("anchor", "stand-in"),
|
||||
"g14.app": handedWith("g14", "app", "stand-in-api@anchor/stand-in"),
|
||||
"node-b.app": handedWith("node-b", "app", "stand-in-api@anchor/stand-in"),
|
||||
}
|
||||
v := pairVault(t, mirror)
|
||||
if o := pass(t, v, standIn); o.Outcome != PairHeld || !strings.Contains(o.Why, "no plan says whether") {
|
||||
@@ -405,7 +405,7 @@ func TestNoValueIsMadeThatAHolderFilledTheOldWayWouldNeverGet(t *testing.T) {
|
||||
t.Run("an unmoved provider", func(t *testing.T) {
|
||||
mirror := assignments{
|
||||
"anchor.stand-in": unmoved("anchor", "stand-in"),
|
||||
"g14.app": handedWith("g14", "app", "stand-in-api@anchor/stand-in"),
|
||||
"node-b.app": handedWith("node-b", "app", "stand-in-api@anchor/stand-in"),
|
||||
}
|
||||
v := pairVault(t, mirror)
|
||||
if o := pass(t, v, standIn); o.Outcome != PairHeld || !strings.Contains(o.Why, "stand-in on anchor") {
|
||||
@@ -421,29 +421,29 @@ func TestNoValueIsMadeThatAHolderFilledTheOldWayWouldNeverGet(t *testing.T) {
|
||||
t.Run("an unmoved consumer", func(t *testing.T) {
|
||||
mirror := assignments{
|
||||
"anchor.stand-in": handedWith("anchor", "stand-in"),
|
||||
"g14.app": handedWith("g14", "app", "stand-in-api@anchor/stand-in"),
|
||||
"ace.old": unmoved("ace", "old"),
|
||||
"node-b.app": handedWith("node-b", "app", "stand-in-api@anchor/stand-in"),
|
||||
"node-a.old": unmoved("node-a", "old"),
|
||||
}
|
||||
v := pairVault(t, mirror)
|
||||
movedRow(t, v, "g14", "app", "shared", "stamp-1", OriginMade)
|
||||
movedRow(t, v, "ace", "old", "shared", "stamp-1", OriginMade)
|
||||
movedRow(t, v, "node-b", "app", "shared", "stamp-1", OriginMade)
|
||||
movedRow(t, v, "node-a", "old", "shared", "stamp-1", OriginMade)
|
||||
if o := pass(t, v, standIn); o.Outcome != PairAdopted {
|
||||
t.Fatalf("adopting changes no value, so it is not held back: %+v", o)
|
||||
}
|
||||
mirror["novox.new"] = handedWith("novox", "new", "stand-in-api@anchor/stand-in")
|
||||
mirror["node-c.new"] = handedWith("node-c", "new", "stand-in-api@anchor/stand-in")
|
||||
o := pass(t, v, standIn)
|
||||
if o.Outcome != PairHeld || !strings.Contains(o.Why, "old on ace") {
|
||||
if o.Outcome != PairHeld || !strings.Contains(o.Why, "old on node-a") {
|
||||
t.Fatalf("a bind while a consumer is filled the old way: %+v", o)
|
||||
}
|
||||
if got, gen, _ := asks(v, "provisioner.g14", standIn); got != "shared" || gen != 1 {
|
||||
if got, gen, _ := asks(v, "provisioner.node-b", standIn); got != "shared" || gen != 1 {
|
||||
t.Fatal("the value was remade while a consumer filled the old way holds it")
|
||||
}
|
||||
if _, err := v.Rotate(ControllerCaller, standIn, "person.jochen", "why not"); err == nil ||
|
||||
!strings.Contains(err.Error(), "old on ace") {
|
||||
!strings.Contains(err.Error(), "old on node-a") {
|
||||
t.Fatalf("a rotation while a consumer is filled the old way: %v", err)
|
||||
}
|
||||
// Once it moves, the remake the bind asked for follows.
|
||||
mirror["ace.old"] = handedWith("ace", "old", "stand-in-api@anchor/stand-in")
|
||||
mirror["node-a.old"] = handedWith("node-a", "old", "stand-in-api@anchor/stand-in")
|
||||
if o := pass(t, v, standIn); o.Outcome != PairRemade {
|
||||
t.Fatalf("after the consumer moved: %+v", o)
|
||||
}
|
||||
@@ -454,12 +454,12 @@ func TestNoValueIsMadeThatAHolderFilledTheOldWayWouldNeverGet(t *testing.T) {
|
||||
func TestAPassThatCannotReadTheAssignmentsMakesNothing(t *testing.T) {
|
||||
mirror := assignments{
|
||||
"anchor.stand-in": handedWith("anchor", "stand-in"),
|
||||
"g14.app": handedWith("g14", "app", "stand-in-api@anchor/stand-in"),
|
||||
"node-b.app": handedWith("node-b", "app", "stand-in-api@anchor/stand-in"),
|
||||
}
|
||||
v := pairVault(t, mirror)
|
||||
movedRow(t, v, "g14", "app", "shared", "stamp-1", OriginMade)
|
||||
movedRow(t, v, "node-b", "app", "shared", "stamp-1", OriginMade)
|
||||
pass(t, v, standIn)
|
||||
mirror["g14.broken"] = "not json"
|
||||
mirror["node-b.broken"] = "not json"
|
||||
if _, err := v.PairPass(); err == nil {
|
||||
t.Fatal("a pass over an unreadable assignment said nothing")
|
||||
}
|
||||
@@ -467,7 +467,7 @@ func TestAPassThatCannotReadTheAssignmentsMakesNothing(t *testing.T) {
|
||||
if _, err := v.PairPass(); err == nil {
|
||||
t.Fatal("a pass that could not list the assignments said nothing")
|
||||
}
|
||||
if _, gen, _ := asks(v, "provisioner.g14", standIn); gen != 1 {
|
||||
if _, gen, _ := asks(v, "provisioner.node-b", standIn); gen != 1 {
|
||||
t.Fatal("a pass that could not read the assignments remade the value")
|
||||
}
|
||||
}
|
||||
@@ -477,17 +477,17 @@ func TestAPassThatCannotReadTheAssignmentsMakesNothing(t *testing.T) {
|
||||
func TestAPulledInConsumerBindsLikeAnAssignedOne(t *testing.T) {
|
||||
mirror := assignments{
|
||||
"anchor.stand-in": handedWith("anchor", "stand-in"),
|
||||
"g14.app": handedWith("g14", "app", "stand-in-api@anchor/stand-in"),
|
||||
"node-b.app": handedWith("node-b", "app", "stand-in-api@anchor/stand-in"),
|
||||
}
|
||||
v := pairVault(t, mirror)
|
||||
movedRow(t, v, "g14", "app", "shared", "stamp-1", OriginMade)
|
||||
movedRow(t, v, "node-b", "app", "shared", "stamp-1", OriginMade)
|
||||
pass(t, v, standIn)
|
||||
mirror["ace.helper"] = strings.Replace(handedWith("ace", "helper", "stand-in-api@anchor/stand-in"),
|
||||
mirror["node-a.helper"] = strings.Replace(handedWith("node-a", "helper", "stand-in-api@anchor/stand-in"),
|
||||
`"module":"helper",`, `"module":"helper","required-by":"desk",`, 1)
|
||||
if o := pass(t, v, standIn); o.Outcome != PairRemade {
|
||||
t.Fatalf("a pulled-in consumer binding: %+v", o)
|
||||
}
|
||||
if _, _, err := asks(v, "provisioner.ace", standIn); err != nil {
|
||||
if _, _, err := asks(v, "provisioner.node-a", standIn); err != nil {
|
||||
t.Fatalf("the pulled-in consumer's machine was refused: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,13 +30,13 @@ func untoken(token string) string { return strings.ReplaceAll(token, "_", ".") }
|
||||
const busConf = `listen: 127.0.0.1:-1
|
||||
authorization {
|
||||
users = [
|
||||
{ user: "novox.mesh-vault", password: "pw", permissions: {
|
||||
publish: { allow: ["mesh.mod.gitea.call.gitea_agent_token.novox.novox_mesh-vault"] }
|
||||
subscribe: { allow: ["mesh.seat.mesh-vault.call.get.*", "_INBOX.novox.mesh-vault.>"] }
|
||||
{ user: "node-c.mesh-vault", password: "pw", permissions: {
|
||||
publish: { allow: ["mesh.mod.gitea.call.gitea_agent_token.node-c.node-c_mesh-vault"] }
|
||||
subscribe: { allow: ["mesh.seat.mesh-vault.call.get.*", "_INBOX.node-c.mesh-vault.>"] }
|
||||
allow_responses: true } }
|
||||
{ user: "novox.node-tools", password: "pw", permissions: {
|
||||
{ user: "node-c.node-tools", password: "pw", permissions: {
|
||||
publish: { deny: [">"] }
|
||||
subscribe: { allow: ["mesh.mod.gitea.call.gitea_agent_token.novox.*"] }
|
||||
subscribe: { allow: ["mesh.mod.gitea.call.gitea_agent_token.node-c.*"] }
|
||||
allow_responses: true } }
|
||||
{ user: "provisioner.anchor", password: "pw", permissions: {
|
||||
publish: { allow: ["mesh.seat.mesh-vault.call.get.provisioner_anchor"] }
|
||||
@@ -104,8 +104,8 @@ func forgeOnABus(t *testing.T, rule Askers) (ask func(user, name string) (string
|
||||
|
||||
// The forge's machine's runtime serves the stand-in issuing tool, naming the caller the subject proves.
|
||||
tool := perHolderProvider(agentTokens, nil)
|
||||
runtime := connectAs(t, url, "novox.node-tools")
|
||||
if _, err := runtime.Subscribe("mesh.mod.gitea.call.gitea_agent_token.novox.*", func(m *nats.Msg) {
|
||||
runtime := connectAs(t, url, "node-c.node-tools")
|
||||
if _, err := runtime.Subscribe("mesh.mod.gitea.call.gitea_agent_token.node-c.*", func(m *nats.Msg) {
|
||||
parts := strings.Split(m.Subject, ".")
|
||||
var args map[string]any
|
||||
_ = json.Unmarshal(m.Data, &args)
|
||||
@@ -127,13 +127,13 @@ func forgeOnABus(t *testing.T, rule Askers) (ask func(user, name string) (string
|
||||
// the subject proves. Its rule for who may ask would let both clients ask for both values: its rule for a
|
||||
// holder's value is what keeps each to its own.
|
||||
v := newVault(t, rule)
|
||||
vaultConn := connectAs(t, url, "novox.mesh-vault")
|
||||
vaultConn := connectAs(t, url, "node-c.mesh-vault")
|
||||
v.Ask = func(key string, body any) (json.RawMessage, error) {
|
||||
call, node, _ := strings.Cut(key, "@")
|
||||
module, tl, _ := strings.Cut(call, ".")
|
||||
raw, _ := json.Marshal(body)
|
||||
msg, err := vaultConn.Request(fmt.Sprintf("mesh.mod.%s.call.%s.%s.%s", module, tl, node,
|
||||
callerToken("novox.mesh-vault")), raw, 5*time.Second)
|
||||
callerToken("node-c.mesh-vault")), raw, 5*time.Second)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -168,7 +168,7 @@ func forgeOnABus(t *testing.T, rule Askers) (ask func(user, name string) (string
|
||||
_ = vaultConn.Flush()
|
||||
|
||||
// The controller's `secret issued` for the forge's read tokens, naming both holders.
|
||||
issued, err := v.TakeIssuedFor(ControllerCaller, "gitea-read", "novox", "gitea", "gitea_agent_token", twoAgents)
|
||||
issued, err := v.TakeIssuedFor(ControllerCaller, "gitea-read", "node-c", "gitea", "gitea_agent_token", twoAgents)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -222,7 +222,7 @@ func forgeOnABus(t *testing.T, rule Askers) (ask func(user, name string) (string
|
||||
// noneInTheClear fails when an issued value, or its base64, crossed the bus.
|
||||
func noneInTheClear(t *testing.T, all string) {
|
||||
t.Helper()
|
||||
if !strings.Contains(all, "mesh.mod.gitea.call.gitea_agent_token.novox.novox_mesh-vault") ||
|
||||
if !strings.Contains(all, "mesh.mod.gitea.call.gitea_agent_token.node-c.node-c_mesh-vault") ||
|
||||
!strings.Contains(all, "mesh.seat.mesh-vault.call.get.provisioner_anchor") {
|
||||
t.Fatalf("the listener heard nothing of the calls:\n%s", all)
|
||||
}
|
||||
@@ -258,7 +258,7 @@ func TestOnARealBusTwoHoldersOnTwoMachinesEachGetOnlyTheirOwnIssuedValueAndNoneC
|
||||
// is refused another machine's and another module's, sealed to its key and never in the clear.
|
||||
func TestOnARealBusTheRunningRuleHandsAClientTheIssuedValueItsModulesPlanNamesAndNoOther(t *testing.T) {
|
||||
plan := `{"templates":{"source":"x","digest":"y"},"files":[],"provisions":[{"name":"gitea-read",` +
|
||||
`"provision":"gitea-read","provider":"novox","path":"/var/lib/claude-code/gitea-read.secret"}]}`
|
||||
`"provision":"gitea-read","provider":"node-c","path":"/var/lib/claude-code/gitea-read.secret"}]}`
|
||||
mirror := assignments{
|
||||
"anchor.claude-code": `{"node":"anchor","module":"claude-code","provisioning":` + plan + `}`,
|
||||
"laptop.claude-code": `{"node":"laptop","module":"claude-code","provisioning":` + plan + `}`,
|
||||
@@ -274,7 +274,7 @@ func TestOnARealBusTheRunningRuleHandsAClientTheIssuedValueItsModulesPlanNamesAn
|
||||
for _, r := range []struct{ user, name, why string }{
|
||||
{"provisioner.anchor", laptopsToken, "another machine's holder value"},
|
||||
{"provisioner.laptop", anchorsToken, "another machine's holder value"},
|
||||
{"provisioner.anchor", "provision/novox/gitea-read/anchor/zsh", "another module's, its plan naming it not"},
|
||||
{"provisioner.anchor", "provision/node-c/gitea-read/anchor/zsh", "another module's, its plan naming it not"},
|
||||
} {
|
||||
if got, err := ask(r.user, r.name); err == nil {
|
||||
t.Errorf("%s was handed %s, %s: %q", r.user, r.name, r.why, got)
|
||||
|
||||
@@ -65,8 +65,8 @@ var twoAgents = []Holder{{Node: "anchor", Module: "claude-code"}, {Node: "laptop
|
||||
|
||||
// The holders' secrets' names in the vault's store.
|
||||
const (
|
||||
anchorsToken = "provision/novox/gitea-read/anchor/claude-code"
|
||||
laptopsToken = "provision/novox/gitea-read/laptop/claude-code"
|
||||
anchorsToken = "provision/node-c/gitea-read/anchor/claude-code"
|
||||
laptopsToken = "provision/node-c/gitea-read/laptop/claude-code"
|
||||
)
|
||||
|
||||
// bothMayAsk is a stand-in rule that would let both machines' clients ask for both values: the vault's own rule
|
||||
@@ -79,7 +79,7 @@ var bothMayAsk = AllowList{
|
||||
|
||||
func forgeMesh(provider func(string, map[string]any) (map[string]any, error)) *aMesh {
|
||||
return &aMesh{tools: map[string]func(string, map[string]any) (map[string]any, error){
|
||||
"gitea.gitea_agent_token@novox": provider}}
|
||||
"gitea.gitea_agent_token@node-c": provider}}
|
||||
}
|
||||
|
||||
// Two holders on two machines: the vault asks the tool naming both, keeps a value for each, and hands each only to
|
||||
@@ -89,7 +89,7 @@ func TestAProviderIssuesAValueForEachHolderAndEachHolderGetsOnlyItsOwn(t *testin
|
||||
var asked []map[string]any
|
||||
mesh := forgeMesh(perHolderProvider(agentTokens, &asked))
|
||||
v.Ask = mesh.ask
|
||||
got, err := v.TakeIssuedFor(ControllerCaller, "gitea-read", "novox", "gitea", "gitea_agent_token", twoAgents)
|
||||
got, err := v.TakeIssuedFor(ControllerCaller, "gitea-read", "node-c", "gitea", "gitea_agent_token", twoAgents)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -100,7 +100,7 @@ func TestAProviderIssuesAValueForEachHolderAndEachHolderGetsOnlyItsOwn(t *testin
|
||||
got.Holders[0].Generation != 1 || got.Unchanged {
|
||||
t.Fatalf("answered %+v", got)
|
||||
}
|
||||
if _, held, _ := v.Store.Read("provision/novox/gitea-read"); held {
|
||||
if _, held, _ := v.Store.Read("provision/node-c/gitea-read"); held {
|
||||
t.Error("a value per holder was also kept as the provision's one credential")
|
||||
}
|
||||
// Each holder gets its own, by its machine's client and by itself under an account of its own.
|
||||
@@ -141,14 +141,14 @@ func TestAProviderIssuesAValueForEachHolderAndEachHolderGetsOnlyItsOwn(t *testin
|
||||
}
|
||||
|
||||
// The same values again write nothing; one holder's new value changes that holder alone (ADR 0305 §5).
|
||||
again, err := v.TakeIssuedFor(ControllerCaller, "gitea-read", "novox", "gitea", "gitea_agent_token", twoAgents)
|
||||
again, err := v.TakeIssuedFor(ControllerCaller, "gitea-read", "node-c", "gitea", "gitea_agent_token", twoAgents)
|
||||
if err != nil || !again.Unchanged || again.Holders[0].Generation != 1 || again.Holders[1].Generation != 1 {
|
||||
t.Errorf("again: %+v %v", again, err)
|
||||
}
|
||||
renewed := map[string]string{"anchor/claude-code": agentTokens["anchor/claude-code"],
|
||||
"laptop/claude-code": "gta_laptop-agent-read-token-0003"}
|
||||
v.Ask = forgeMesh(perHolderProvider(renewed, nil)).ask
|
||||
moved, err := v.TakeIssuedFor(ControllerCaller, "gitea-read", "novox", "gitea", "gitea_agent_token", twoAgents)
|
||||
moved, err := v.TakeIssuedFor(ControllerCaller, "gitea-read", "node-c", "gitea", "gitea_agent_token", twoAgents)
|
||||
if err != nil || moved.Unchanged || !moved.Holders[0].Unchanged || moved.Holders[1].Unchanged ||
|
||||
moved.Holders[1].Generation != 2 {
|
||||
t.Errorf("renewed: %+v %v", moved, err)
|
||||
@@ -179,7 +179,7 @@ func TestAPerHolderAnswerIsTakenWholeOrNotAtAll(t *testing.T) {
|
||||
"a holder not asked about": func(args map[string]any) map[string]any {
|
||||
return map[string]any{"holders": []any{
|
||||
map[string]any{"node": "anchor", "module": "claude-code", "sealed": sealedTo(args, "a")},
|
||||
map[string]any{"node": "ace", "module": "claude-code", "sealed": sealedTo(args, "b")}}}
|
||||
map[string]any{"node": "node-a", "module": "claude-code", "sealed": sealedTo(args, "b")}}}
|
||||
},
|
||||
"a holder twice": func(args map[string]any) map[string]any {
|
||||
return map[string]any{"holders": []any{
|
||||
@@ -205,7 +205,7 @@ func TestAPerHolderAnswerIsTakenWholeOrNotAtAll(t *testing.T) {
|
||||
v := newVault(t, nil)
|
||||
answer := answer
|
||||
v.Ask = forgeMesh(func(_ string, args map[string]any) (map[string]any, error) { return answer(args), nil }).ask
|
||||
if _, err := v.TakeIssuedFor(ControllerCaller, "gitea-read", "novox", "gitea", "gitea_agent_token", twoAgents); err == nil {
|
||||
if _, err := v.TakeIssuedFor(ControllerCaller, "gitea-read", "node-c", "gitea", "gitea_agent_token", twoAgents); err == nil {
|
||||
t.Errorf("%s was taken", what)
|
||||
}
|
||||
if all, _ := v.Store.List(); len(all) != 0 {
|
||||
@@ -221,13 +221,13 @@ func TestAPerHolderAnswerIsTakenWholeOrNotAtAll(t *testing.T) {
|
||||
return map[string]any{"holders": []any{
|
||||
map[string]any{"node": "anchor", "module": "claude-code", "sealed": sealedTo(args, "a")}}}, nil
|
||||
}).ask
|
||||
if _, err := v.TakeIssuedFor(ControllerCaller, "gitea-read", "novox", "gitea", "gitea_agent_token", nil); err == nil {
|
||||
if _, err := v.TakeIssuedFor(ControllerCaller, "gitea-read", "node-c", "gitea", "gitea_agent_token", nil); err == nil {
|
||||
t.Error("values per holder were taken for no holder named")
|
||||
}
|
||||
// A holder the tool did not answer is said and keeps nothing; the others are kept.
|
||||
w := newVault(t, nil)
|
||||
w.Ask = forgeMesh(perHolderProvider(map[string]string{"anchor/claude-code": "only-anchors"}, nil)).ask
|
||||
got, err := w.TakeIssuedFor(ControllerCaller, "gitea-read", "novox", "gitea", "gitea_agent_token", twoAgents)
|
||||
got, err := w.TakeIssuedFor(ControllerCaller, "gitea-read", "node-c", "gitea", "gitea_agent_token", twoAgents)
|
||||
if err != nil || len(got.Holders) != 1 || len(got.NotAnswered) != 1 || got.NotAnswered[0] != twoAgents[1] {
|
||||
t.Errorf("a holder not answered: %+v %v", got, err)
|
||||
}
|
||||
@@ -237,7 +237,7 @@ func TestAPerHolderAnswerIsTakenWholeOrNotAtAll(t *testing.T) {
|
||||
// The holders the controller names are names the mesh writes, each once.
|
||||
for _, bad := range [][]Holder{{{Node: "anchor", Module: ""}}, {{Node: "../x", Module: "claude-code"}},
|
||||
{twoAgents[0], twoAgents[0]}} {
|
||||
if _, err := w.TakeIssuedFor(ControllerCaller, "gitea-read", "novox", "gitea", "gitea_agent_token", bad); err == nil {
|
||||
if _, err := w.TakeIssuedFor(ControllerCaller, "gitea-read", "node-c", "gitea", "gitea_agent_token", bad); err == nil {
|
||||
t.Errorf("holders %v were asked for", bad)
|
||||
}
|
||||
}
|
||||
@@ -266,7 +266,7 @@ func TestTheIssuedVerbTakesTheHolders(t *testing.T) {
|
||||
v.Ask = forgeMesh(perHolderProvider(agentTokens, nil)).ask
|
||||
defs := definitions(&Env{Vault: v.Vault})
|
||||
issued := toolNamed(t, defs, Seat+".issued")
|
||||
answer, err := issued.runAs(ControllerCaller, map[string]any{"provision": "gitea-read", "provider": "novox",
|
||||
answer, err := issued.runAs(ControllerCaller, map[string]any{"provision": "gitea-read", "provider": "node-c",
|
||||
"module": "gitea", "tool": "gitea_agent_token", "holders": []any{
|
||||
map[string]any{"node": "anchor", "module": "claude-code"},
|
||||
map[string]any{"node": "laptop", "module": "claude-code"}}})
|
||||
@@ -274,7 +274,7 @@ func TestTheIssuedVerbTakesTheHolders(t *testing.T) {
|
||||
t.Fatalf("%+v %v", answer, err)
|
||||
}
|
||||
for _, bad := range []any{"anchor", []any{"anchor"}, []any{map[string]any{"node": 1}}} {
|
||||
if _, err := issued.runAs(ControllerCaller, map[string]any{"provision": "gitea-read", "provider": "novox",
|
||||
if _, err := issued.runAs(ControllerCaller, map[string]any{"provision": "gitea-read", "provider": "node-c",
|
||||
"module": "gitea", "tool": "gitea_agent_token", "holders": bad}); err == nil {
|
||||
t.Errorf("holders %v were read", bad)
|
||||
}
|
||||
|
||||
@@ -7,15 +7,15 @@ import "testing"
|
||||
// for such an entry: naming a provider alone widens nothing.
|
||||
func TestARuleHandsOnlyACredentialThePlanNamesNotAProviderAlone(t *testing.T) {
|
||||
plan := `{"templates":{"source":"x","digest":"y"},"files":[],"provisions":[` +
|
||||
`{"provision":"postgres-database","provider":"novox","provider-module":"postgres"},` +
|
||||
`{"name":"gitea-read","provision":"gitea-read","provider":"novox","provider-module":"gitea",` +
|
||||
`{"provision":"postgres-database","provider":"node-c","provider-module":"postgres"},` +
|
||||
`{"name":"gitea-read","provision":"gitea-read","provider":"node-c","provider-module":"gitea",` +
|
||||
`"path":"/s/gitea-read.secret"}]}`
|
||||
mirror := assignments{"anchor.app": `{"node":"anchor","module":"app","provisioning":` + plan + `}`}
|
||||
rule := Declared{Read: mirror.read}
|
||||
if ok, why := rule.May("provisioner.anchor", "provision/novox/gitea-read/anchor/app"); !ok {
|
||||
if ok, why := rule.May("provisioner.anchor", "provision/node-c/gitea-read/anchor/app"); !ok {
|
||||
t.Fatalf("the credential the plan names was refused: %s", why)
|
||||
}
|
||||
if ok, _ := rule.May("provisioner.anchor", "provision/novox/postgres-database/anchor/app"); ok {
|
||||
if ok, _ := rule.May("provisioner.anchor", "provision/node-c/postgres-database/anchor/app"); ok {
|
||||
t.Fatal("a provision the plan names only with its provider was handed out")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,21 +11,21 @@ import (
|
||||
// running rule hands that node's client what the plan names, as for an assigned module, and nothing else.
|
||||
func TestTheRunningVaultAnswersAMachinesClientForWhatAPulledInModulesPlanNames(t *testing.T) {
|
||||
mirror := assignments{
|
||||
"g14.app": `{"node":"g14","module":"app","required-by":"desk","provisioning":` +
|
||||
"node-b.app": `{"node":"node-b","module":"app","required-by":"desk","provisioning":` +
|
||||
`{"templates":{"source":"x","digest":"y"},"files":[],"secrets":[{"name":"token"}],` +
|
||||
`"provisions":[{"name":"gitea-read","provision":"gitea-read","provider":"novox"}]}}`,
|
||||
"g14.kept": `{"node":"g14","module":"kept","required-by":"desk"}`,
|
||||
`"provisions":[{"name":"gitea-read","provision":"gitea-read","provider":"node-c"}]}}`,
|
||||
"node-b.kept": `{"node":"node-b","module":"kept","required-by":"desk"}`,
|
||||
}
|
||||
v := newVault(t, Declared{Read: mirror.read})
|
||||
for _, name := range []string{"own/g14/app/token", "own/g14/app/other", "own/g14/kept/token",
|
||||
"provision/novox/gitea-read/g14/app"} {
|
||||
for _, name := range []string{"own/node-b/app/token", "own/node-b/app/other", "own/node-b/kept/token",
|
||||
"provision/node-c/gitea-read/node-b/app"} {
|
||||
if _, err := v.Put(name, "value-of-"+name, "the test"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
asker, _ := seal.GenerateKeyPair()
|
||||
for _, name := range []string{"own/g14/app/token", "provision/novox/gitea-read/g14/app"} {
|
||||
answer, err := v.Get("provisioner.g14", name, asker.PublicKey)
|
||||
for _, name := range []string{"own/node-b/app/token", "provision/node-c/gitea-read/node-b/app"} {
|
||||
answer, err := v.Get("provisioner.node-b", name, asker.PublicKey)
|
||||
if err != nil {
|
||||
t.Fatalf("the machine's client was refused %s, which its pulled-in module's plan names: %v", name, err)
|
||||
}
|
||||
@@ -34,9 +34,9 @@ func TestTheRunningVaultAnswersAMachinesClientForWhatAPulledInModulesPlanNames(t
|
||||
}
|
||||
}
|
||||
for _, r := range []struct{ caller, name, why string }{
|
||||
{"provisioner.g14", "own/g14/app/other", "a secret the pulled-in module's plan does not name"},
|
||||
{"provisioner.g14", "own/g14/kept/token", "a pulled-in module not handed to the provisioner"},
|
||||
{"provisioner.ace", "own/g14/app/token", "another machine's client"},
|
||||
{"provisioner.node-b", "own/node-b/app/other", "a secret the pulled-in module's plan does not name"},
|
||||
{"provisioner.node-b", "own/node-b/kept/token", "a pulled-in module not handed to the provisioner"},
|
||||
{"provisioner.node-a", "own/node-b/app/token", "another machine's client"},
|
||||
} {
|
||||
if a, err := v.Get(r.caller, r.name, asker.PublicKey); err == nil {
|
||||
t.Errorf("get answered %s (%s asking %s): %+v", r.why, r.caller, r.name, a)
|
||||
|
||||
@@ -30,7 +30,7 @@ func newCustody(t *testing.T) custodyAt {
|
||||
bus := &published{}
|
||||
clock := time.Date(2026, 10, 14, 9, 0, 0, 0, time.UTC)
|
||||
c := custodyAt{bus: bus, receives: filepath.Join(dir, "mesh.json"), dir: dir, clock: &clock}
|
||||
c.Custody = &Custody{Ledger: l, Receives: c.receives, Resource: "secret", Node: "novox", Emit: bus.emit,
|
||||
c.Custody = &Custody{Ledger: l, Receives: c.receives, Resource: "secret", Node: "node-c", Emit: bus.emit,
|
||||
Now: func() time.Time { return *c.clock }, Log: t.Logf}
|
||||
return c
|
||||
}
|
||||
@@ -249,7 +249,7 @@ func TestTheRetirementWordsAreTheControllers(t *testing.T) {
|
||||
if err := dec.Decode(&w); err != nil {
|
||||
t.Fatalf("a retirement word the controller cannot read: %v: %s", err, outer.Body)
|
||||
}
|
||||
if w.ProviderNode != "novox" || len(w.Consumers) == 0 || w.Consumers[0].Consumer == "" {
|
||||
if w.ProviderNode != "node-c" || len(w.Consumers) == 0 || w.Consumers[0].Consumer == "" {
|
||||
t.Errorf("a %s word names no machine or no consumer: %s", w.Change, outer.Body)
|
||||
}
|
||||
seen[w.Change] = true
|
||||
@@ -282,7 +282,7 @@ func TestTheRetirementAnswerIsTheControllers(t *testing.T) {
|
||||
sameWords(t, "the retired answer", raw, "retirement-state.json", "retired.consumer", "retired.retired_at",
|
||||
"retired.why", "retired.access")
|
||||
st := c.Retirement()
|
||||
if st.RetiresBy != "mark-only" || st.Resource != "secret" || st.Node != "novox" || len(st.Retired) != 4 ||
|
||||
if st.RetiresBy != "mark-only" || st.Resource != "secret" || st.Node != "node-c" || len(st.Retired) != 4 ||
|
||||
st.Retired[0].Access != "kept" {
|
||||
t.Errorf("the answer is %+v", st)
|
||||
}
|
||||
|
||||
@@ -9,18 +9,18 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// postgresLogins is a vault holding a login each for gitea on g14 and umami on ace, from postgres on novox.
|
||||
// postgresLogins is a vault holding a login each for gitea on node-b and umami on node-a, from postgres on node-c.
|
||||
func postgresLogins(t *testing.T) (testVault, string, string) {
|
||||
t.Helper()
|
||||
v := pairVault(t, assignments{
|
||||
"novox.postgres": handedWith("novox", "postgres"),
|
||||
"g14.gitea": handedWith("g14", "gitea", "postgres-database@novox/postgres"),
|
||||
"ace.umami": handedWith("ace", "umami", "postgres-database@novox/postgres"),
|
||||
"node-c.postgres": handedWith("node-c", "postgres"),
|
||||
"node-b.gitea": handedWith("node-b", "gitea", "postgres-database@node-c/postgres"),
|
||||
"node-a.umami": handedWith("node-a", "umami", "postgres-database@node-c/postgres"),
|
||||
})
|
||||
gitea, umami := "pair/novox/postgres-database/g14/gitea", "pair/novox/postgres-database/ace/umami"
|
||||
gitea, umami := "pair/node-c/postgres-database/node-b/gitea", "pair/node-c/postgres-database/node-a/umami"
|
||||
for name, value := range map[string]string{gitea: "login-of-gitea", umami: "login-of-umami"} {
|
||||
if _, err := v.keepWith(name, value, "node.novox", "", OriginMade, keepOptions{neverReplace: true,
|
||||
movedFrom: &MovedFrom{By: "node.novox"}}); err != nil {
|
||||
if _, err := v.keepWith(name, value, "node.node-c", "", OriginMade, keepOptions{neverReplace: true,
|
||||
movedFrom: &MovedFrom{By: "node.node-c"}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -28,18 +28,18 @@ func postgresLogins(t *testing.T) (testVault, string, string) {
|
||||
}
|
||||
|
||||
func TestARotationNamingOneConsumerRemakesThatConsumersLoginAlone(t *testing.T) {
|
||||
for _, one := range []Narrow{{Consumer: "g14"}, {ConsumerModule: "gitea"}, {Consumer: "g14", ConsumerModule: "gitea"}} {
|
||||
for _, one := range []Narrow{{Consumer: "node-b"}, {ConsumerModule: "gitea"}, {Consumer: "node-b", ConsumerModule: "gitea"}} {
|
||||
v, gitea, umami := postgresLogins(t)
|
||||
before := changedSaid(t, v, umami, "")
|
||||
changed, err := v.RotateFor(ControllerCaller, "provision/novox/postgres-database", one, "person.jochen",
|
||||
"suspected on g14")
|
||||
changed, err := v.RotateFor(ControllerCaller, "provision/node-c/postgres-database", one, "person.jochen",
|
||||
"suspected on node-b")
|
||||
if err != nil || len(changed.Each) != 1 || changed.Each[0].Name != gitea {
|
||||
t.Fatalf("%+v: the rotation %+v (%v)", one, changed, err)
|
||||
}
|
||||
if got, _, err := asks(v, "provisioner.g14", gitea); err != nil || got == "login-of-gitea" {
|
||||
if got, _, err := asks(v, "provisioner.node-b", gitea); err != nil || got == "login-of-gitea" {
|
||||
t.Fatalf("%+v: gitea's login was not remade: %q (%v)", one, got, err)
|
||||
}
|
||||
if got, gen, err := asks(v, "provisioner.ace", umami); err != nil || got != "login-of-umami" || gen != 1 {
|
||||
if got, gen, err := asks(v, "provisioner.node-a", umami); err != nil || got != "login-of-umami" || gen != 1 {
|
||||
t.Fatalf("%+v: umami's login was touched: %q generation %d (%v)", one, got, gen, err)
|
||||
}
|
||||
if changedSaid(t, v, umami, "") != before {
|
||||
@@ -50,13 +50,13 @@ func TestARotationNamingOneConsumerRemakesThatConsumersLoginAlone(t *testing.T)
|
||||
|
||||
func TestARotationNamingAConsumerThatHoldsNoLoginChangesNothing(t *testing.T) {
|
||||
v, gitea, umami := postgresLogins(t)
|
||||
_, err := v.RotateFor(ControllerCaller, "provision/novox/postgres-database", Narrow{Consumer: "g14",
|
||||
_, err := v.RotateFor(ControllerCaller, "provision/node-c/postgres-database", Narrow{Consumer: "node-b",
|
||||
ConsumerModule: "umami"}, "person.jochen", "suspected")
|
||||
if err == nil || !strings.Contains(err.Error(), "umami on g14") {
|
||||
if err == nil || !strings.Contains(err.Error(), "umami on node-b") {
|
||||
t.Fatalf("a consumer holding no login was not refused by name: %v", err)
|
||||
}
|
||||
for name, want := range map[string]string{gitea: "login-of-gitea", umami: "login-of-umami"} {
|
||||
if got, _, _ := asks(v, "provisioner.novox", name); got != want {
|
||||
if got, _, _ := asks(v, "provisioner.node-c", name); got != want {
|
||||
t.Fatalf("%s changed on a refused rotation", name)
|
||||
}
|
||||
}
|
||||
@@ -67,20 +67,20 @@ func TestARotationNamingAConsumerThatHoldsNoLoginChangesNothing(t *testing.T) {
|
||||
func TestASharedValueIsRotatedForEveryHolderAndNeverForOneConsumer(t *testing.T) {
|
||||
v := pairVault(t, assignments{
|
||||
"anchor.stand-in": handedWith("anchor", "stand-in"),
|
||||
"g14.app": handedWith("g14", "app", "stand-in-api@anchor/stand-in"),
|
||||
"ace.other": handedWith("ace", "other", "stand-in-api@anchor/stand-in"),
|
||||
"node-b.app": handedWith("node-b", "app", "stand-in-api@anchor/stand-in"),
|
||||
"node-a.other": handedWith("node-a", "other", "stand-in-api@anchor/stand-in"),
|
||||
})
|
||||
movedRow(t, v, "g14", "app", "the-value-they-share", "20260901T000000-1", OriginMade)
|
||||
movedRow(t, v, "ace", "other", "the-value-they-share", "20260901T000000-1", OriginMade)
|
||||
movedRow(t, v, "node-b", "app", "the-value-they-share", "20260901T000000-1", OriginMade)
|
||||
movedRow(t, v, "node-a", "other", "the-value-they-share", "20260901T000000-1", OriginMade)
|
||||
if o := pass(t, v, standIn); o.Outcome != PairAdopted {
|
||||
t.Fatalf("stamped rows: %+v", o)
|
||||
}
|
||||
|
||||
if _, err := v.RotateFor(ControllerCaller, standIn, Narrow{Consumer: "g14"}, "person.jochen", "suspected"); err == nil ||
|
||||
if _, err := v.RotateFor(ControllerCaller, standIn, Narrow{Consumer: "node-b"}, "person.jochen", "suspected"); err == nil ||
|
||||
!strings.Contains(err.Error(), "every holder") {
|
||||
t.Fatalf("a shared value was rotated for one consumer: %v", err)
|
||||
}
|
||||
if got, gen, _ := asks(v, "provisioner.g14", standIn); got != "the-value-they-share" || gen != 1 {
|
||||
if got, gen, _ := asks(v, "provisioner.node-b", standIn); got != "the-value-they-share" || gen != 1 {
|
||||
t.Fatal("a refused rotation changed the shared value")
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ func TestASharedValueIsRotatedForEveryHolderAndNeverForOneConsumer(t *testing.T)
|
||||
t.Fatalf("the rotation: %+v (%v)", changed, err)
|
||||
}
|
||||
var seen string
|
||||
for _, c := range []string{"provisioner.anchor", "provisioner.g14", "provisioner.ace"} {
|
||||
for _, c := range []string{"provisioner.anchor", "provisioner.node-b", "provisioner.node-a"} {
|
||||
got, gen, err := asks(v, c, standIn)
|
||||
if err != nil || got == "the-value-they-share" || gen != 2 || (seen != "" && got != seen) {
|
||||
t.Fatalf("%s got %q generation %d (%v): not the one new value", c, got, gen, err)
|
||||
@@ -103,17 +103,17 @@ func TestASharedValueIsRotatedForEveryHolderAndNeverForOneConsumer(t *testing.T)
|
||||
|
||||
func TestARotationOfAModulesOwnSecretNamesNoConsumer(t *testing.T) {
|
||||
v := newVault(t, nil)
|
||||
if _, err := v.RotateFor(ControllerCaller, "own/g14/app/key", Narrow{Consumer: "g14"}, "person.jochen", "x"); err == nil {
|
||||
if _, err := v.RotateFor(ControllerCaller, "own/node-b/app/key", Narrow{Consumer: "node-b"}, "person.jochen", "x"); err == nil {
|
||||
t.Fatal("a module's own secret was rotated with a consumer named")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTheRotateToolTakesTheConsumerAndItsModule(t *testing.T) {
|
||||
n, err := narrowNamed(map[string]any{"consumer": "g14", "consumer_module": "gitea"})
|
||||
if err != nil || n != (Narrow{Consumer: "g14", ConsumerModule: "gitea"}) {
|
||||
n, err := narrowNamed(map[string]any{"consumer": "node-b", "consumer_module": "gitea"})
|
||||
if err != nil || n != (Narrow{Consumer: "node-b", ConsumerModule: "gitea"}) {
|
||||
t.Fatalf("%+v (%v)", n, err)
|
||||
}
|
||||
if _, err := narrowNamed(map[string]any{"consumer": "g14/x"}); err == nil {
|
||||
if _, err := narrowNamed(map[string]any{"consumer": "node-b/x"}); err == nil {
|
||||
t.Fatal("a consumer that is not a name was taken")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"resource": "secret",
|
||||
"node": "novox",
|
||||
"node": "node-c",
|
||||
"held": [
|
||||
"a"
|
||||
],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"provider": "secret",
|
||||
"provider-node": "novox",
|
||||
"provider-node": "node-c",
|
||||
"change": "waiting",
|
||||
"consumers": [
|
||||
{
|
||||
|
||||
@@ -72,7 +72,7 @@ func TestSecretExportAnswersTheMeshsExport(t *testing.T) {
|
||||
if got.(map[string]any)["available"] != false {
|
||||
t.Errorf("no export said %v", got)
|
||||
}
|
||||
doc := `{"export":1,"operator-key":"b3A=","fingerprint":"sha256:f","kept":[{"node":"g14","module":"dunst","name":"token","kind":"own","origin":"made","sealed":"c2VhbGVk","key":"b3A=","made-at":"2026-10-01T00:00:00Z"}],"unrecoverable":[{"node":"ace","module":"x","name":"y"}]}`
|
||||
doc := `{"export":1,"operator-key":"b3A=","fingerprint":"sha256:f","kept":[{"node":"node-b","module":"dunst","name":"token","kind":"own","origin":"made","sealed":"c2VhbGVk","key":"b3A=","made-at":"2026-10-01T00:00:00Z"}],"unrecoverable":[{"node":"node-a","module":"x","name":"y"}]}`
|
||||
_ = os.WriteFile(filepath.Join(root, "export.json"), []byte(doc), 0o600)
|
||||
got, _ = export.Run(map[string]any{})
|
||||
a := got.(map[string]any)
|
||||
@@ -122,17 +122,17 @@ func TestSecretHoldersAndVerifySayFingerprintsOnly(t *testing.T) {
|
||||
// The seat's verb, called as the runtime calls it: the caller is the one the runtime proved, never an
|
||||
// argument.
|
||||
func TestTheGetVerbTakesItsCallerFromTheRuntime(t *testing.T) {
|
||||
v := newVault(t, AllowList{"g14.node-provisioner": {"pair/postgres/gitea"}})
|
||||
v := newVault(t, AllowList{"node-b.node-provisioner": {"pair/postgres/gitea"}})
|
||||
if _, err := v.Put("pair/postgres/gitea", "x", "the test"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
asker, _ := seal.GenerateKeyPair()
|
||||
get := toolNamed(t, definitions(&Env{Vault: v.Vault}), Seat+".get")
|
||||
// No runtime proved a caller here, so the call is unproven whatever the arguments say.
|
||||
if _, err := get.Run(map[string]any{"name": "pair/postgres/gitea", "public_key": asker.PublicKey, "caller": "g14.node-provisioner"}); err == nil {
|
||||
if _, err := get.Run(map[string]any{"name": "pair/postgres/gitea", "public_key": asker.PublicKey, "caller": "node-b.node-provisioner"}); err == nil {
|
||||
t.Error("get answered a caller named in its arguments")
|
||||
}
|
||||
answer, err := get.runAs("g14.node-provisioner", map[string]any{"name": "pair/postgres/gitea", "public_key": asker.PublicKey})
|
||||
answer, err := get.runAs("node-b.node-provisioner", map[string]any{"name": "pair/postgres/gitea", "public_key": asker.PublicKey})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -164,13 +164,13 @@ func TestNoBucketHoldsASecret(t *testing.T) {
|
||||
|
||||
// And what the vault publishes while it holds, changes and hands out secrets carries none of them, in
|
||||
// the clear or sealed.
|
||||
v := newVault(t, AllowList{"g14.node-provisioner": {"pair/postgres/gitea"}})
|
||||
v := newVault(t, AllowList{"node-b.node-provisioner": {"pair/postgres/gitea"}})
|
||||
value := "the-password-nobody-may-see"
|
||||
if _, err := v.Put("pair/postgres/gitea", value, "the test"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
asker, _ := seal.GenerateKeyPair()
|
||||
answer, err := v.Get("g14.node-provisioner", "pair/postgres/gitea", asker.PublicKey)
|
||||
answer, err := v.Get("node-b.node-provisioner", "pair/postgres/gitea", asker.PublicKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -188,7 +188,7 @@ func TestNoBucketHoldsASecret(t *testing.T) {
|
||||
// only with the vault's own key.
|
||||
func TestEverySecretIsEncryptedAtRest(t *testing.T) {
|
||||
v := newVault(t, nil)
|
||||
values := map[string]string{"own/g14/dunst/token": "tok-1-plain", "pair/postgres/gitea": "pw-2-plain"}
|
||||
values := map[string]string{"own/node-b/dunst/token": "tok-1-plain", "pair/postgres/gitea": "pw-2-plain"}
|
||||
for name, value := range values {
|
||||
if _, err := v.Put(name, value, "the test"); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -219,18 +219,18 @@ func TestEverySecretIsEncryptedAtRest(t *testing.T) {
|
||||
// ---- no private key is accepted -------------------------------------------------------------------------
|
||||
|
||||
func TestNoPrivateKeyIsAccepted(t *testing.T) {
|
||||
v := newVault(t, AllowList{"g14.node-provisioner": {"pair/postgres/gitea"}})
|
||||
v := newVault(t, AllowList{"node-b.node-provisioner": {"pair/postgres/gitea"}})
|
||||
keys, _ := seal.GenerateKeyPair()
|
||||
ssh := "-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAA\n-----END OPENSSH PRIVATE KEY-----\n"
|
||||
rsa := "-----BEGIN RSA PRIVATE KEY-----\nMIIBOgIBAAJBAK\n-----END RSA PRIVATE KEY-----\n"
|
||||
|
||||
// Not as a secret to hold: a private key stays where it was made (ADR 0113 §2).
|
||||
for _, value := range []string{keys.PrivateKey, ssh, rsa, "prefix " + keys.PrivateKey} {
|
||||
if _, err := v.Put("own/g14/sshd/host", value, "the test"); err == nil {
|
||||
if _, err := v.Put("own/node-b/sshd/host", value, "the test"); err == nil {
|
||||
t.Errorf("the vault took a private key as a secret: %.40q", value)
|
||||
}
|
||||
}
|
||||
if _, ok, _ := v.Store.Read("own/g14/sshd/host"); ok {
|
||||
if _, ok, _ := v.Store.Read("own/node-b/sshd/host"); ok {
|
||||
t.Error("a refused private key was kept all the same")
|
||||
}
|
||||
|
||||
@@ -240,7 +240,7 @@ func TestNoPrivateKeyIsAccepted(t *testing.T) {
|
||||
}
|
||||
raw32 := base64.StdEncoding.EncodeToString(make([]byte, 32))
|
||||
for _, key := range []string{keys.PrivateKey, ssh, raw32, "", "not a key"} {
|
||||
if _, err := v.Get("g14.node-provisioner", "pair/postgres/gitea", key); err == nil {
|
||||
if _, err := v.Get("node-b.node-provisioner", "pair/postgres/gitea", key); err == nil {
|
||||
t.Errorf("get sealed an answer to %.40q, which is not a PEM public key", key)
|
||||
}
|
||||
}
|
||||
@@ -274,7 +274,7 @@ func TestNoPrivateKeyIsAccepted(t *testing.T) {
|
||||
|
||||
func TestEverySecretHasAnOperatorSealedCopy(t *testing.T) {
|
||||
v := newVault(t, nil)
|
||||
values := map[string]string{"own/g14/dunst/token": "a", "pair/postgres/gitea": "b"}
|
||||
values := map[string]string{"own/node-b/dunst/token": "a", "pair/postgres/gitea": "b"}
|
||||
for name, value := range values {
|
||||
if _, err := v.Put(name, value, "the test"); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -323,7 +323,7 @@ func TestNoOperatorKeyKeepsNothing(t *testing.T) {
|
||||
store, _ := OpenStore(filepath.Join(dir, "store"))
|
||||
own, _ := OwnKey(filepath.Join(dir, "state"), store)
|
||||
v := &Vault{Store: store, Own: own, Emit: (&published{}).emit, Now: time.Now, Log: t.Logf}
|
||||
if _, err := v.Put("own/g14/dunst/token", "x", "the test"); err == nil || !strings.Contains(err.Error(), "operator key") {
|
||||
if _, err := v.Put("own/node-b/dunst/token", "x", "the test"); err == nil || !strings.Contains(err.Error(), "operator key") {
|
||||
t.Fatalf("a secret was kept with no operator key to seal its copy to: %v", err)
|
||||
}
|
||||
if all, _ := store.List(); len(all) != 0 {
|
||||
@@ -334,16 +334,16 @@ func TestNoOperatorKeyKeepsNothing(t *testing.T) {
|
||||
// ---- get answers only an allowed asker, sealed to its key -----------------------------------------------
|
||||
|
||||
func TestGetAnswersOnlyAnAllowedAskerSealedToItsKey(t *testing.T) {
|
||||
v := newVault(t, AllowList{"g14.node-provisioner": {"pair/postgres/gitea"}})
|
||||
v := newVault(t, AllowList{"node-b.node-provisioner": {"pair/postgres/gitea"}})
|
||||
if _, err := v.Put("pair/postgres/gitea", "the-value", "the test"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := v.Put("own/ace/dunst/token", "another", "the test"); err != nil {
|
||||
if _, err := v.Put("own/node-a/dunst/token", "another", "the test"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
asker, _ := seal.GenerateKeyPair()
|
||||
|
||||
answer, err := v.Get("g14.node-provisioner", "pair/postgres/gitea", asker.PublicKey)
|
||||
answer, err := v.Get("node-b.node-provisioner", "pair/postgres/gitea", asker.PublicKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -362,11 +362,11 @@ func TestGetAnswersOnlyAnAllowedAskerSealedToItsKey(t *testing.T) {
|
||||
}
|
||||
|
||||
refusals := []struct{ caller, name, why string }{
|
||||
{"ace.node-provisioner", "pair/postgres/gitea", "an asker not on the list"},
|
||||
{"g14.node-provisioner", "own/ace/dunst/token", "a secret the asker is not allowed"},
|
||||
{"node-a.node-provisioner", "pair/postgres/gitea", "an asker not on the list"},
|
||||
{"node-b.node-provisioner", "own/node-a/dunst/token", "a secret the asker is not allowed"},
|
||||
{stdio.Unproven, "pair/postgres/gitea", "a call whose caller the runtime did not prove"},
|
||||
{"", "pair/postgres/gitea", "no caller at all"},
|
||||
{"g14.node-provisioner", "pair/postgres/nothing", "a secret the vault does not hold"},
|
||||
{"node-b.node-provisioner", "pair/postgres/nothing", "a secret the vault does not hold"},
|
||||
}
|
||||
for _, r := range refusals {
|
||||
if a, err := v.Get(r.caller, r.name, asker.PublicKey); err == nil {
|
||||
@@ -382,7 +382,7 @@ func TestAVaultWithNoRuleAnswersNobody(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
asker, _ := seal.GenerateKeyPair()
|
||||
for _, caller := range []string{"provisioner.g14", "novox.mesh-provisioner", "controller", "person.jochen", "novox.node-tools"} {
|
||||
for _, caller := range []string{"provisioner.node-b", "node-c.mesh-provisioner", "controller", "person.jochen", "node-c.node-tools"} {
|
||||
if _, err := v.Get(caller, "pair/postgres/gitea", asker.PublicKey); err == nil {
|
||||
t.Errorf("a vault with no rule answered %s", caller)
|
||||
}
|
||||
@@ -404,44 +404,44 @@ func (a assignments) read(key string) ([]byte, error) {
|
||||
// refusal is recorded with why.
|
||||
func TestTheRunningVaultAnswersAMachinesClientForWhatItsPlanNames(t *testing.T) {
|
||||
mirror := assignments{
|
||||
"g14.dunst": `{"node":"g14","module":"dunst","assigned":"2026-10-14T20:00:00Z","provisioning":` +
|
||||
"node-b.dunst": `{"node":"node-b","module":"dunst","assigned":"2026-10-14T20:00:00Z","provisioning":` +
|
||||
`{"templates":{"source":"x","digest":"y"},"files":[],"secrets":[{"name":"token"}]}}`,
|
||||
"g14.picom": `{"node":"g14","module":"picom","assigned":"2026-10-14T20:00:00Z"}`,
|
||||
"ace.dunst": `{"node":"ace","module":"dunst","assigned":"2026-10-14T20:00:00Z","provisioning":{"secrets":[{"name":"token"}]}}`,
|
||||
"g14.liar": `{"node":"ace","module":"liar","provisioning":{"secrets":[{"name":"token"}]}}`,
|
||||
"g14.unparsed": `not json`,
|
||||
"node-b.picom": `{"node":"node-b","module":"picom","assigned":"2026-10-14T20:00:00Z"}`,
|
||||
"node-a.dunst": `{"node":"node-a","module":"dunst","assigned":"2026-10-14T20:00:00Z","provisioning":{"secrets":[{"name":"token"}]}}`,
|
||||
"node-b.liar": `{"node":"node-a","module":"liar","provisioning":{"secrets":[{"name":"token"}]}}`,
|
||||
"node-b.unparsed": `not json`,
|
||||
}
|
||||
v := newVault(t, Declared{Read: mirror.read})
|
||||
for _, name := range []string{"own/g14/dunst/token", "own/g14/dunst/other", "own/g14/picom/token",
|
||||
"own/ace/dunst/token", "own/g14/liar/token", "own/g14/unparsed/token", "provision/novox/postgres",
|
||||
"own/g14/absent/token"} {
|
||||
for _, name := range []string{"own/node-b/dunst/token", "own/node-b/dunst/other", "own/node-b/picom/token",
|
||||
"own/node-a/dunst/token", "own/node-b/liar/token", "own/node-b/unparsed/token", "provision/node-c/postgres",
|
||||
"own/node-b/absent/token"} {
|
||||
if _, err := v.Put(name, "value-of-"+name, "the test"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
asker, _ := seal.GenerateKeyPair()
|
||||
|
||||
answer, err := v.Get("provisioner.g14", "own/g14/dunst/token", asker.PublicKey)
|
||||
answer, err := v.Get("provisioner.node-b", "own/node-b/dunst/token", asker.PublicKey)
|
||||
if err != nil {
|
||||
t.Fatalf("the machine's client was refused the secret its module's plan names: %v", err)
|
||||
}
|
||||
if got, err := seal.Open(answer.Sealed, asker.PrivateKey); err != nil || got != "value-of-own/g14/dunst/token" {
|
||||
if got, err := seal.Open(answer.Sealed, asker.PrivateKey); err != nil || got != "value-of-own/node-b/dunst/token" {
|
||||
t.Fatalf("the answer opens to %q (%v)", got, err)
|
||||
}
|
||||
|
||||
refusals := []struct{ caller, name, why string }{
|
||||
{"provisioner.g14", "own/g14/dunst/other", "a secret the plan does not name"},
|
||||
{"provisioner.g14", "own/g14/picom/token", "a module not handed to the provisioner"},
|
||||
{"provisioner.g14", "own/ace/dunst/token", "another machine's secret"},
|
||||
{"provisioner.ace", "own/g14/dunst/token", "another machine's client"},
|
||||
{"provisioner.g14", "own/g14/absent/token", "a module not assigned there"},
|
||||
{"provisioner.g14", "own/g14/liar/token", "an assignment whose value names another machine"},
|
||||
{"provisioner.g14", "own/g14/unparsed/token", "an assignment that is not the controller's"},
|
||||
{"provisioner.g14", "provision/novox/postgres", "a provision's credential the vault made for nobody there"},
|
||||
{"g14.node-tools", "own/g14/dunst/token", "the tool runner's account, which an agent shares"},
|
||||
{"g14.dunst", "own/g14/dunst/token", "a module's own account, until issue 569"},
|
||||
{"controller", "own/g14/dunst/token", "the controller"},
|
||||
{"provisioner.", "own/g14/dunst/token", "a client naming no machine"},
|
||||
{"provisioner.node-b", "own/node-b/dunst/other", "a secret the plan does not name"},
|
||||
{"provisioner.node-b", "own/node-b/picom/token", "a module not handed to the provisioner"},
|
||||
{"provisioner.node-b", "own/node-a/dunst/token", "another machine's secret"},
|
||||
{"provisioner.node-a", "own/node-b/dunst/token", "another machine's client"},
|
||||
{"provisioner.node-b", "own/node-b/absent/token", "a module not assigned there"},
|
||||
{"provisioner.node-b", "own/node-b/liar/token", "an assignment whose value names another machine"},
|
||||
{"provisioner.node-b", "own/node-b/unparsed/token", "an assignment that is not the controller's"},
|
||||
{"provisioner.node-b", "provision/node-c/postgres", "a provision's credential the vault made for nobody there"},
|
||||
{"node-b.node-tools", "own/node-b/dunst/token", "the tool runner's account, which an agent shares"},
|
||||
{"node-b.dunst", "own/node-b/dunst/token", "a module's own account, until issue 569"},
|
||||
{"controller", "own/node-b/dunst/token", "the controller"},
|
||||
{"provisioner.", "own/node-b/dunst/token", "a client naming no machine"},
|
||||
}
|
||||
for _, r := range refusals {
|
||||
if a, err := v.Get(r.caller, r.name, asker.PublicKey); err == nil {
|
||||
@@ -473,7 +473,7 @@ func TestTheRunningVaultAnswersAMachinesClientForWhatItsPlanNames(t *testing.T)
|
||||
// one credential.
|
||||
func TestTheRunningVaultAnswersAMachinesClientForAnIssuedValueItsPlanNames(t *testing.T) {
|
||||
plan := `{"templates":{"source":"x","digest":"y"},"files":[],` +
|
||||
`"provisions":[{"name":"gitea-read","provision":"gitea-read","provider":"novox","path":"/s/gitea-read.secret"}]}`
|
||||
`"provisions":[{"name":"gitea-read","provision":"gitea-read","provider":"node-c","path":"/s/gitea-read.secret"}]}`
|
||||
mirror := assignments{
|
||||
"anchor.claude-code": `{"node":"anchor","module":"claude-code","provisioning":` + plan + `}`,
|
||||
"laptop.claude-code": `{"node":"laptop","module":"claude-code","provisioning":` + plan + `}`,
|
||||
@@ -481,10 +481,10 @@ func TestTheRunningVaultAnswersAMachinesClientForAnIssuedValueItsPlanNames(t *te
|
||||
"anchor.picom": `{"node":"anchor","module":"picom"}`,
|
||||
}
|
||||
v := newVault(t, Declared{Read: mirror.read})
|
||||
for _, name := range []string{anchorsToken, laptopsToken, "provision/novox/gitea-read/anchor/zsh",
|
||||
"provision/novox/gitea-read/anchor/picom", "provision/ace/gitea-read/anchor/claude-code",
|
||||
"provision/novox/other/anchor/claude-code", "provision/novox/gitea-read",
|
||||
"provision/novox/gitea-read/anchor/absent"} {
|
||||
for _, name := range []string{anchorsToken, laptopsToken, "provision/node-c/gitea-read/anchor/zsh",
|
||||
"provision/node-c/gitea-read/anchor/picom", "provision/node-a/gitea-read/anchor/claude-code",
|
||||
"provision/node-c/other/anchor/claude-code", "provision/node-c/gitea-read",
|
||||
"provision/node-c/gitea-read/anchor/absent"} {
|
||||
if _, err := v.Put(name, "value-of-"+name, "the test"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -500,12 +500,12 @@ func TestTheRunningVaultAnswersAMachinesClientForAnIssuedValueItsPlanNames(t *te
|
||||
refusals := []struct{ caller, name, why string }{
|
||||
{"provisioner.anchor", laptopsToken, "another machine's holder value"},
|
||||
{"provisioner.laptop", anchorsToken, "another machine's holder value, the other way"},
|
||||
{"provisioner.anchor", "provision/novox/gitea-read/anchor/zsh", "another module's, whose plan names it not"},
|
||||
{"provisioner.anchor", "provision/novox/gitea-read/anchor/picom", "a module not handed to the provisioner"},
|
||||
{"provisioner.anchor", "provision/novox/gitea-read/anchor/absent", "a module not assigned there"},
|
||||
{"provisioner.anchor", "provision/ace/gitea-read/anchor/claude-code", "another provider's"},
|
||||
{"provisioner.anchor", "provision/novox/other/anchor/claude-code", "a provision the plan names not"},
|
||||
{"provisioner.anchor", "provision/novox/gitea-read", "the provision's one credential"},
|
||||
{"provisioner.anchor", "provision/node-c/gitea-read/anchor/zsh", "another module's, whose plan names it not"},
|
||||
{"provisioner.anchor", "provision/node-c/gitea-read/anchor/picom", "a module not handed to the provisioner"},
|
||||
{"provisioner.anchor", "provision/node-c/gitea-read/anchor/absent", "a module not assigned there"},
|
||||
{"provisioner.anchor", "provision/node-a/gitea-read/anchor/claude-code", "another provider's"},
|
||||
{"provisioner.anchor", "provision/node-c/other/anchor/claude-code", "a provision the plan names not"},
|
||||
{"provisioner.anchor", "provision/node-c/gitea-read", "the provision's one credential"},
|
||||
{"anchor.node-tools", anchorsToken, "the tool runner's account, which an agent shares"},
|
||||
{"anchor.claude-code", anchorsToken, "the module's own account, until issue 569"},
|
||||
}
|
||||
@@ -521,7 +521,7 @@ func TestTheRunningVaultAnswersAMachinesClientForAnIssuedValueItsPlanNames(t *te
|
||||
// SPKI PEM, and opens the answer with its private half as PKCS #8 — no second key on the node, and no NaCl
|
||||
// in the vault's answers. This is the path V4 builds on.
|
||||
func TestANodesSealingKeyOpensTheAnswer(t *testing.T) {
|
||||
v := newVault(t, AllowList{"g14.node-provisioner": {"pair/postgres/gitea"}})
|
||||
v := newVault(t, AllowList{"node-b.node-provisioner": {"pair/postgres/gitea"}})
|
||||
if _, err := v.Put("pair/postgres/gitea", "for-the-node", "the test"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -535,7 +535,7 @@ func TestANodesSealingKeyOpensTheAnswer(t *testing.T) {
|
||||
spki, _ := x509.MarshalPKIXPublicKey(pubKey)
|
||||
askWith := string(pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: spki}))
|
||||
|
||||
answer, err := v.Get("g14.node-provisioner", "pair/postgres/gitea", askWith)
|
||||
answer, err := v.Get("node-b.node-provisioner", "pair/postgres/gitea", askWith)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -552,13 +552,13 @@ func TestANodesSealingKeyOpensTheAnswer(t *testing.T) {
|
||||
|
||||
func TestSecretChangedCarriesNoValue(t *testing.T) {
|
||||
v := newVault(t, nil)
|
||||
if _, err := v.Put("pair/postgres/gitea", "first-value", "g14.mesh-vault-test"); err != nil {
|
||||
if _, err := v.Put("pair/postgres/gitea", "first-value", "node-b.mesh-vault-test"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := v.Put("pair/postgres/gitea", "second-value", "person.jochen"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := v.Mint("own/g14/dunst/token", "the vault"); err != nil {
|
||||
if _, err := v.Mint("own/node-b/dunst/token", "the vault"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var changed []map[string]any
|
||||
@@ -589,7 +589,7 @@ func TestSecretChangedCarriesNoValue(t *testing.T) {
|
||||
if changed[1]["by"] != "person.jochen" || changed[1]["generation"] != 2 {
|
||||
t.Errorf("the second change says %v", changed[1])
|
||||
}
|
||||
minted, _, _ := v.Store.Read("own/g14/dunst/token")
|
||||
minted, _, _ := v.Store.Read("own/node-b/dunst/token")
|
||||
mintedValue, _ := seal.Open(minted.AtRest, v.Own.PrivateKey)
|
||||
for _, said := range v.bus.raw {
|
||||
for _, value := range []string{"first-value", "second-value", mintedValue} {
|
||||
@@ -712,23 +712,23 @@ func TestANewOperatorKeyFailingPartwayIsFinished(t *testing.T) {
|
||||
|
||||
// Who was handed which secret, and when, and who was refused, is in the vault's own store.
|
||||
func TestWhoWasHandedWhatIsRecordedInTheStore(t *testing.T) {
|
||||
v := newVault(t, AllowList{"g14.node-provisioner": {"pair/postgres/gitea"}})
|
||||
v := newVault(t, AllowList{"node-b.node-provisioner": {"pair/postgres/gitea"}})
|
||||
if _, err := v.Put("pair/postgres/gitea", "the-value", "the test"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
asker, _ := seal.GenerateKeyPair()
|
||||
if _, err := v.Get("g14.node-provisioner", "pair/postgres/gitea", asker.PublicKey); err != nil {
|
||||
if _, err := v.Get("node-b.node-provisioner", "pair/postgres/gitea", asker.PublicKey); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, _ = v.Get("ace.node-provisioner", "pair/postgres/gitea", asker.PublicKey)
|
||||
_, _ = v.Get("node-a.node-provisioner", "pair/postgres/gitea", asker.PublicKey)
|
||||
handed, err := v.Store.Handed(10)
|
||||
if err != nil || len(handed) != 2 {
|
||||
t.Fatalf("%v %v", handed, err)
|
||||
}
|
||||
if handed[1].To != "g14.node-provisioner" || handed[1].Outcome != "handed" || handed[1].Generation != 1 || handed[1].At.IsZero() {
|
||||
if handed[1].To != "node-b.node-provisioner" || handed[1].Outcome != "handed" || handed[1].Generation != 1 || handed[1].At.IsZero() {
|
||||
t.Errorf("the hand-out is recorded as %+v", handed[1])
|
||||
}
|
||||
if handed[0].To != "ace.node-provisioner" || handed[0].Outcome != "refused" || handed[0].Why == "" {
|
||||
if handed[0].To != "node-a.node-provisioner" || handed[0].Outcome != "refused" || handed[0].Why == "" {
|
||||
t.Errorf("the refusal is recorded as %+v", handed[0])
|
||||
}
|
||||
raw, _ := os.ReadFile(filepath.Join(v.dir, "store", "handed.jsonl"))
|
||||
@@ -806,7 +806,7 @@ func TestResealFinishesWhatIsPending(t *testing.T) {
|
||||
|
||||
// A secret is not handed out when who was handed it cannot be recorded.
|
||||
func TestNoHandOutWithoutItsRecord(t *testing.T) {
|
||||
v := newVault(t, AllowList{"g14.node-provisioner": {"pair/postgres/gitea"}})
|
||||
v := newVault(t, AllowList{"node-b.node-provisioner": {"pair/postgres/gitea"}})
|
||||
if _, err := v.Put("pair/postgres/gitea", "x", "the test"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -814,7 +814,7 @@ func TestNoHandOutWithoutItsRecord(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
asker, _ := seal.GenerateKeyPair()
|
||||
if a, err := v.Get("g14.node-provisioner", "pair/postgres/gitea", asker.PublicKey); err == nil {
|
||||
if a, err := v.Get("node-b.node-provisioner", "pair/postgres/gitea", asker.PublicKey); err == nil {
|
||||
t.Fatalf("handed out with no record of it: %+v", a)
|
||||
}
|
||||
}
|
||||
@@ -823,7 +823,7 @@ func TestNoHandOutWithoutItsRecord(t *testing.T) {
|
||||
func TestAnArbitraryAskFillsNoRecord(t *testing.T) {
|
||||
v := newVault(t, nil)
|
||||
asker, _ := seal.GenerateKeyPair()
|
||||
for _, ask := range [][2]string{{"g14.x", strings.Repeat("junk ", 1000)}, {"g14.x", "../../etc"},
|
||||
for _, ask := range [][2]string{{"node-b.x", strings.Repeat("junk ", 1000)}, {"node-b.x", "../../etc"},
|
||||
{"not a bus user!", "pair/a"}, {strings.Repeat("a", 300), "pair/a"}} {
|
||||
_, _ = v.Get(ask[0], ask[1], asker.PublicKey)
|
||||
}
|
||||
@@ -838,7 +838,7 @@ func TestTheRecordOfHandOutsIsBounded(t *testing.T) {
|
||||
v.Store.HandedMax = 2000
|
||||
asker, _ := seal.GenerateKeyPair()
|
||||
for i := 0; i < 500; i++ {
|
||||
_, _ = v.Get("g14.flood", "pair/postgres/gitea", asker.PublicKey)
|
||||
_, _ = v.Get("node-b.flood", "pair/postgres/gitea", asker.PublicKey)
|
||||
}
|
||||
var total int64
|
||||
files, _ := filepath.Glob(filepath.Join(v.dir, "store", "handed.jsonl*"))
|
||||
@@ -858,7 +858,7 @@ func TestTheRecordOfHandOutsIsBounded(t *testing.T) {
|
||||
if v.Store.Rolled() == 0 {
|
||||
t.Error("rolling was not counted")
|
||||
}
|
||||
if h, _ := v.Store.Handed(5); len(h) != 5 || h[0].To != "g14.flood" {
|
||||
if h, _ := v.Store.Handed(5); len(h) != 5 || h[0].To != "node-b.flood" {
|
||||
t.Errorf("the newest are not read back: %+v", h)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ func (c *clock) pass(d time.Duration) { c.t = c.t.Add(d) }
|
||||
func watcher(t *testing.T) (*Watcher, *fakeTelegram, *clock) {
|
||||
c := &clock{t: time.Date(2026, 10, 6, 12, 0, 0, 0, time.UTC)}
|
||||
tg := &fakeTelegram{}
|
||||
return NewWatcher(tg, c.now, t.Logf, "ace"), tg, c
|
||||
return NewWatcher(tg, c.now, t.Logf, "node-a"), tg, c
|
||||
}
|
||||
|
||||
// beatEvery has the controller's doctor run, and the bus answer, every five minutes up to d.
|
||||
@@ -204,11 +204,11 @@ func TestNotGivenATokenIsBlind(t *testing.T) {
|
||||
|
||||
func TestOnTheControlNodeItSaysItIsMisplaced(t *testing.T) {
|
||||
w, _, _ := watcher(t)
|
||||
w.Placed([]string{"novox"})
|
||||
if st := w.Status("listening"); st.Verdict != "ok: watching" || st.Control != "novox" {
|
||||
w.Placed([]string{"node-c"})
|
||||
if st := w.Status("listening"); st.Verdict != "ok: watching" || st.Control != "node-c" {
|
||||
t.Fatalf("%+v", st)
|
||||
}
|
||||
w.Placed([]string{"ace"})
|
||||
w.Placed([]string{"node-a"})
|
||||
if st := w.Status("listening"); !strings.HasPrefix(st.Verdict, "MISPLACED") {
|
||||
t.Fatalf("%+v", st)
|
||||
}
|
||||
|
||||
@@ -135,8 +135,8 @@ func TestClosedConnectionsArePerUserAndNameAndSayWhichWereDropped(t *testing.T)
|
||||
const closed = `{"connections":[
|
||||
{"cid":10,"name":"mesh-controller verb conditions","authorized_user":"controller","start":"2026-10-08T14:12:00Z","stop":"2026-10-08T14:12:01Z","reason":"Client Closed"},
|
||||
{"cid":11,"name":"mesh-controller verb conditions","authorized_user":"controller","start":"2026-10-08T14:13:00Z","stop":"2026-10-08T14:13:03Z","reason":"Client Closed"},
|
||||
{"cid":12,"name":"ace.node-tools","authorized_user":"ace.node-tools","start":"2026-10-08T13:00:00Z","stop":"2026-10-08T14:14:00Z","reason":"Stale Connection"},
|
||||
{"cid":13,"name":"ace.node-tools","authorized_user":"ace.node-tools","start":"2026-10-08T14:14:05Z","stop":"2026-10-08T14:15:00Z","reason":"Read Error"},
|
||||
{"cid":12,"name":"node-a.node-tools","authorized_user":"node-a.node-tools","start":"2026-10-08T13:00:00Z","stop":"2026-10-08T14:14:00Z","reason":"Stale Connection"},
|
||||
{"cid":13,"name":"node-a.node-tools","authorized_user":"node-a.node-tools","start":"2026-10-08T14:14:05Z","stop":"2026-10-08T14:15:00Z","reason":"Read Error"},
|
||||
{"cid":5,"name":"old","authorized_user":"controller","start":"2026-10-08T10:00:00Z","stop":"2026-10-08T10:00:01Z","reason":"Client Closed"},
|
||||
{"cid":14,"name":"","start":"2026-10-08T14:16:00Z","stop":"2026-10-08T14:16:00Z","reason":"Authentication Failure"}]}`
|
||||
now, _ := time.Parse(time.RFC3339, "2026-10-08T14:20:00Z")
|
||||
@@ -157,7 +157,7 @@ func TestClosedConnectionsArePerUserAndNameAndSayWhichWereDropped(t *testing.T)
|
||||
c.Names[0].Name != "mesh-controller verb conditions" || c.Names[0].Lived != 3 {
|
||||
t.Errorf("the controller's: %+v", c)
|
||||
}
|
||||
tools := byUser["ace.node-tools"]
|
||||
tools := byUser["node-a.node-tools"]
|
||||
if tools.Closed != 2 || tools.Dropped != 2 || tools.Names[0].Reasons["Stale Connection"] != 1 {
|
||||
t.Errorf("the runtime's: %+v", tools)
|
||||
}
|
||||
@@ -168,7 +168,7 @@ func TestClosedConnectionsArePerUserAndNameAndSayWhichWereDropped(t *testing.T)
|
||||
t.Error("a record older than the window does not reach back the whole window")
|
||||
}
|
||||
narrowed, _ := ClosedConnections([]byte(closed), now, 1, "node-tools")
|
||||
if got := narrowed["users"].([]ClosedUser); len(got) != 1 || got[0].User != "ace.node-tools" {
|
||||
if got := narrowed["users"].([]ClosedUser); len(got) != 1 || got[0].User != "node-a.node-tools" {
|
||||
t.Errorf("narrowed: %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ func rig(wait time.Duration) (*Service, *fakeBus) {
|
||||
return &Service{Asks: b, Now: time.Now, Wait: wait, Poll: time.Millisecond}, b
|
||||
}
|
||||
|
||||
const get, put = "http://novox.internal:20001/b/doc.docx?X-Amz-Signature=a", "http://novox.internal:20001/b/doc.pdf?X-Amz-Signature=b"
|
||||
const get, put = "http://node-c.internal:20001/b/doc.docx?X-Amz-Signature=a", "http://node-c.internal:20001/b/doc.pdf?X-Amz-Signature=b"
|
||||
|
||||
// 1595/AC1: one ask with those fields is published on convert-asks, and the verb answers what the converter wrote under
|
||||
// the ask's id.
|
||||
|
||||
@@ -68,7 +68,7 @@ func TestTheToolSideAndTheConverterConvertOverARealBus(t *testing.T) {
|
||||
Retention: jetstream.WorkQueuePolicy, Discard: jetstream.DiscardNew, MaxAge: 24 * time.Hour, MaxBytes: 8 << 20}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
account := process.Account{URL: s.ClientURL(), Node: "novox", Module: "only-office", User: "novox.only-office.converter", Password: "pw"}
|
||||
account := process.Account{URL: s.ClientURL(), Node: "node-c", Module: "only-office", User: "node-c.only-office.converter", Password: "pw"}
|
||||
if _, err := js.CreateConsumer(ctx, AsksStream, jetstream.ConsumerConfig{Durable: account.QueueConsumerName(),
|
||||
AckPolicy: jetstream.AckExplicitPolicy, AckWait: 30 * time.Second, MaxDeliver: 3}); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -85,7 +85,7 @@ func TestTheToolSideAndTheConverterConvertOverARealBus(t *testing.T) {
|
||||
go runConverter(runCtx, account, w)
|
||||
|
||||
svc := &Service{Asks: natsBus{js: js, kv: kv}, Now: time.Now, Wait: 20 * time.Second, Poll: 50 * time.Millisecond}
|
||||
got, err := svc.Convert(ctx, "http://novox.internal:20001/b/in.docx?X-Amz-Signature=a", "docx", tgt.srv.URL+"/b/in.pdf?sig=1", "")
|
||||
got, err := svc.Convert(ctx, "http://node-c.internal:20001/b/in.docx?X-Amz-Signature=a", "docx", tgt.srv.URL+"/b/in.pdf?sig=1", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ func worker(d *fakeDocs) (*converterWorker, *answers) {
|
||||
|
||||
func askBody(t *testing.T, target string, sha string) []byte {
|
||||
raw, err := json.Marshal(Ask{ID: "ask1", Op: OpConvert, At: time.Now(), Deadline: time.Now().Add(time.Minute),
|
||||
Source: "http://novox.internal:20001/b/in.docx?X-Amz-Signature=a", Type: "docx", Target: target, SHA256: sha})
|
||||
Source: "http://node-c.internal:20001/b/in.docx?X-Amz-Signature=a", Type: "docx", Target: target, SHA256: sha})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -142,7 +142,7 @@ func TestTheConverterPostsTheRequestAndPutsThePDF(t *testing.T) {
|
||||
t.Fatalf("%d conversion requests", len(d.requests))
|
||||
}
|
||||
r := d.requests[0]
|
||||
if r["url"] != "http://novox.internal:20001/b/in.docx?X-Amz-Signature=a" || r["filetype"] != "docx" || r["outputtype"] != "pdf" ||
|
||||
if r["url"] != "http://node-c.internal:20001/b/in.docx?X-Amz-Signature=a" || r["filetype"] != "docx" || r["outputtype"] != "pdf" ||
|
||||
r["async"] != false || r["key"] != strings.Repeat("ab", 32) || d.accept != "application/json" {
|
||||
t.Fatalf("request %v accept %q", r, d.accept)
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ and a fake link reader in the tests.
|
||||
|
||||
## What changes when it is assigned
|
||||
|
||||
| | g14 | shanks |
|
||||
| | node-b | node-d |
|
||||
|---|---|---|
|
||||
| package | none: `slack-desktop` 4.51.191-1, AUR, installed explicitly | the same |
|
||||
| start | i3's configuration gains the module's start. **Until the predecessor's `~/.config/autostart/slack.desktop` is deleted, the next login starts Slack twice.** The second start hands over to the first and exits, because Slack is single-instance. Running now: started by dex from that entry at the login of 2026-10-04 16:26 | the same entry, the same until deleted. Running now: since 2026-10-04 17:05, **with its stdout on a pipe nobody reads** (that session predates the `i3` module's login entry), so `slack_check` names it until `slack_restart` or the next login |
|
||||
@@ -135,7 +135,7 @@ and a fake link reader in the tests.
|
||||
off. Leave the setting off: ticking it makes the entry again, and `slack_check` names it.
|
||||
2. **Delete `~/.config/i3/config.d/50-slack.conf`.** It is the operator's own file, and its rules are
|
||||
now the module's.
|
||||
3. **shanks only:** run `slack_restart`, or log out and in, so that Slack's output is read.
|
||||
3. **node-d only:** run `slack_restart`, or log out and in, so that Slack's output is read.
|
||||
|
||||
`slack_check` then answers `ok`. Deleting both files before the module is assigned would leave Slack
|
||||
without a start and without its rules until it is.
|
||||
|
||||
Reference in New Issue
Block a user