Files
mesh-controller/internal/inventory/people_test.go
T
jschoubben 53e8f5bdd8 A person may be issued, listed and revoked
Design 25 §7's first item, which existed as a permission model and as nothing a person
could actually be given. There is a record now, and three commands.

Their authority is a list of tools and nothing else. Not a module: they hold no seat,
nothing is addressed to them, nothing is delivered to them, and they have no consumer to
acknowledge. What they have is permission to ask — which is why there is no scope and no
node in the record.

Stating what somebody may call replaces what was there rather than adding to it: a list
that could only grow is a permission nobody can take back. Forgetting somebody takes
their credential with them, because a person's row gone with their bus user left behind
is a credential that still works and that nothing derives — the worst of both, since it
keeps working and nobody can explain why.

The credential is printed once and the mesh keeps only a hash, the same contract a token
has. And it starts working at the next composition rather than immediately, because the
bus's users are a file — said out loud in both the issue and the revoke messages, since
"revoked" that still works for another minute is worth knowing about.

Four properties held by test, each a way of being wrong that would not announce itself:
a person may publish exactly the tool subjects they were given and nothing on control,
nodes or events; they cannot answer a request; changing the list removes what is no longer
named; and forgetting them revokes them.
2026-09-27 17:07:19 +02:00

121 lines
3.8 KiB
Go

package inventory
import (
"context"
"strings"
"testing"
"github.com/novox/mesh-controller/internal/broker"
)
// Somebody who may call the mesh's tools, and what the bus makes of them.
// A person's authority is a list of tools, and it becomes exactly that on the bus — nothing on
// control, nothing on nodes, nothing they could publish as a module.
func TestAPersonMayCallToolsAndNothingElse(t *testing.T) {
inv := ForTest(t)
ctx := context.Background()
if err := inv.RecordPerson(ctx, Person{Name: "ada",
Invokes: []string{"mesh-catalog.catalog_tools"}}); err != nil {
t.Fatal(err)
}
records, err := inv.BusRecords(ctx)
if err != nil {
t.Fatal(err)
}
if got := records.People["ada"]; len(got) != 1 || got[0] != "mesh-catalog.catalog_tools" {
t.Fatalf("ada may call %v", got)
}
users, err := broker.Users(records)
if err != nil {
t.Fatal(err)
}
var found bool
for _, u := range users {
if u.Username() != "person.ada" {
continue
}
found = true
perms, err := broker.PermissionsFor(u)
if err != nil {
t.Fatal(err)
}
if len(perms.Publish) != 1 || perms.Publish[0] != "mesh.mod.mesh-catalog.tool.catalog_tools" {
t.Errorf("ada may publish %v, which should be the one tool and nothing else", perms.Publish)
}
for _, s := range perms.Publish {
if strings.HasPrefix(s, "mesh.control") || strings.HasPrefix(s, "mesh.node") ||
strings.Contains(s, ".event.") {
t.Errorf("a person may publish %s — an event would let them claim a module said "+
"something, and control is not theirs", s)
}
}
if perms.AllowResponses {
t.Error("a person may answer a request, which is impersonating a module on a bus where " +
"anyone may serve a tool")
}
}
if !found {
t.Fatal("no bus user was derived for a recorded person")
}
}
// Stating what somebody may call replaces what was there. A list that could only grow is a permission
// nobody can take back.
func TestChangingWhatAPersonMayCallRemovesWhatIsNotNamed(t *testing.T) {
inv := ForTest(t)
ctx := context.Background()
if err := inv.RecordPerson(ctx, Person{Name: "ada", Invokes: []string{"a.one", "b.two"}}); err != nil {
t.Fatal(err)
}
if err := inv.RecordPerson(ctx, Person{Name: "ada", Invokes: []string{"a.one"}}); err != nil {
t.Fatal(err)
}
people, err := inv.People(ctx)
if err != nil {
t.Fatal(err)
}
if len(people) != 1 || len(people[0].Invokes) != 1 || people[0].Invokes[0] != "a.one" {
t.Fatalf("ada may call %v; the removed tool is still there", people)
}
}
// Somebody who may call nothing is refused: there is no reason for them to reach the mesh, and an
// empty list is more likely a mistake than an intention.
func TestSomebodyWhoMayCallNothingIsRefused(t *testing.T) {
inv := ForTest(t)
if err := inv.RecordPerson(context.Background(), Person{Name: "ada"}); err == nil {
t.Fatal("somebody who may call nothing was recorded")
}
}
// Forgetting somebody takes their credential with them. **Both, or it is not a revocation**: a
// person's row gone and their bus user left behind is a credential that still works and that nothing
// derives.
func TestForgettingAPersonTakesTheirCredential(t *testing.T) {
inv := ForTest(t)
ctx := context.Background()
if err := inv.RecordPerson(ctx, Person{Name: "ada", Invokes: []string{"a.one"}}); err != nil {
t.Fatal(err)
}
if _, err := inv.MintBusPassword(ctx, BusUser{Username: "person.ada", Kind: BusPerson}); err != nil {
t.Fatal(err)
}
if err := inv.ForgetPerson(ctx, "ada"); err != nil {
t.Fatal(err)
}
if _, known, err := inv.BusUserHash(ctx, "person.ada"); err != nil || known {
t.Fatalf("a forgotten person's credential still works: %v %v", known, err)
}
records, err := inv.BusRecords(ctx)
if err != nil {
t.Fatal(err)
}
if _, still := records.People["ada"]; still {
t.Fatal("a forgotten person is still composed into the bus")
}
}