The controller ended by its unassignment and its lease is never handed, so the engine's lease gate kept trusting the controller's key and the bus node kept the controller's list as a fallback nobody writes (issue 1126, A6). The engine now trusts no controller signature, its hand-over line names the node seat's hand-over, and the user-list follower writes only the grants part's list: a key unreadable or deleted keeps the list loaded, and with no key heard nothing is written. Compositions in the node's bucket name the file's place alone. The guard no longer asks for the controller's user. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
179 lines
7.6 KiB
Go
179 lines
7.6 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/novox/mesh-host/internal/apply"
|
|
"github.com/novox/mesh-host/internal/link"
|
|
"github.com/novox/mesh-host/internal/store"
|
|
)
|
|
|
|
// Every directory used as found is its module's resource of kind directory, unhealthy, naming its owner, on
|
|
// every statement (novox/hq issue 339): the controller raises that as the module's condition. The way out it
|
|
// names is the node seat's hand-over, with this node's name (novox/hq ADR 0563 §5).
|
|
func TestAFoundDirectoryIsStatedUnhealthy(t *testing.T) {
|
|
dir := t.TempDir()
|
|
known := store.State{Resources: []store.Applied{
|
|
{ID: "notes.data", Type: "directory", Target: dir, AsFound: true, Declared: "root, mode 755", AppliedAt: time.Now()},
|
|
{ID: "notes.cache", Type: "directory", Target: dir},
|
|
}}
|
|
foundNode.Store("laptop")
|
|
t.Cleanup(func() { foundNode.Store("") })
|
|
h := withFoundDirectories(&link.Health{}, foundDirectories(known))
|
|
if len(h.Resources) != 1 {
|
|
t.Fatalf("stated: %+v", h.Resources)
|
|
}
|
|
r := h.Resources[0]
|
|
if r.Module != "notes" || r.Kind != link.KindDirectory || r.State != link.StateUnhealthy || r.Target != dir ||
|
|
!strings.HasPrefix(r.Reason, link.ReasonUsedAsFound) || !strings.Contains(r.Reason, "owned by ") ||
|
|
!strings.Contains(r.Reason, "root, mode 755") {
|
|
t.Fatalf("stated as: %+v", r)
|
|
}
|
|
// The path is the target, kept as evidence, and never in the reason: the operator's channel withholds a path
|
|
// (novox/hq ADR 0234 §6). The way out is the node seat's hand-over (ADR 0563 §5), never the controller's terminal line,
|
|
// which nothing answers since the controller ended (issue 1126).
|
|
if !strings.Contains(r.Reason, "`laptop/node-engine.hand-over`") || strings.Contains(r.Reason, "nox node hand-over") ||
|
|
strings.Contains(r.Reason, "mesh-host") || strings.Contains(r.Reason, dir) {
|
|
t.Fatalf("the way out is not the node seat's hand-over, or carries the path: %q", r.Reason)
|
|
}
|
|
foundNode.Store("")
|
|
if r := withFoundDirectories(&link.Health{}, foundDirectories(known)).Resources[0]; !strings.Contains(r.Reason,
|
|
"`<node>/node-engine.hand-over`") {
|
|
t.Fatalf("before enrolment the line names a placeholder: %q", r.Reason)
|
|
}
|
|
}
|
|
|
|
// realDir is a temporary directory with every link in its path resolved, so a judgement of links is about the
|
|
// links the test makes.
|
|
func realDir(t *testing.T) string {
|
|
t.Helper()
|
|
dir, err := filepath.EvalSymlinks(t.TempDir())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return dir
|
|
}
|
|
|
|
// A hand-over records a directory used as found, and refuses one that is not — through the engine's own
|
|
// subcommand and through the controller's ask alike, which are one judgement.
|
|
func TestHandOverRecordsOnlyAFoundDirectory(t *testing.T) {
|
|
dir := realDir(t)
|
|
statePath := filepath.Join(dir, "state.json")
|
|
found := filepath.Join(dir, "found")
|
|
if err := os.Mkdir(found, 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.Mkdir(filepath.Join(dir, "mine"), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := store.Save(statePath, store.State{Resources: []store.Applied{
|
|
{ID: "notes.data", Type: "directory", Target: found, AsFound: true, Declared: "root, mode 755"},
|
|
{ID: "notes.mine", Type: "directory", Target: filepath.Join(dir, "mine")},
|
|
}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var out bytes.Buffer
|
|
if err := handOverCommand(options{state: statePath, file: filepath.Join(dir, "mine"), out: &out}); err == nil {
|
|
t.Fatal("a directory the mesh made was handed over")
|
|
}
|
|
if err := handOverCommand(options{state: statePath, file: found + "/", out: &out}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
all, err := store.LoadHandedOver(statePath)
|
|
if err != nil || len(all) != 1 || all[0].Path != found || !strings.HasSuffix(all[0].By, " at the machine") {
|
|
t.Fatalf("recorded: %+v %v", all, err)
|
|
}
|
|
handedOverFrom(statePath)
|
|
if !apply.HandedOver()[apply.HandOverKey("notes.data", found)] {
|
|
t.Fatal("the apply does not see the hand-over")
|
|
}
|
|
if !strings.Contains(out.String(), "root, mode 755") {
|
|
t.Fatalf("said: %s", out.String())
|
|
}
|
|
|
|
// The controller's ask is the same judgement, recording who asked in the controller's words.
|
|
_ = os.Remove(store.HandedOverPath(statePath))
|
|
said, err := handOverFrom(statePath)(found, "jo through mesh-cli on anchor")
|
|
if err != nil || !strings.Contains(said, "by jo through mesh-cli on anchor") || !strings.Contains(said, "root, mode 755") {
|
|
t.Fatalf("the ask answered %q, %v", said, err)
|
|
}
|
|
all, _ = store.LoadHandedOver(statePath)
|
|
if len(all) != 1 || all[0].By != "jo through mesh-cli on anchor" || all[0].ID != "notes.data" {
|
|
t.Fatalf("recorded: %+v", all)
|
|
}
|
|
}
|
|
|
|
// Every value of a hand-over is judged before anything is recorded, and each refusal records nothing: a relative
|
|
// path, a path that is not the engine's statement of the directory (`..`, a doubled or trailing separator), one
|
|
// not used as found, a link at the path, a link in the path, a file, a directory that is gone.
|
|
func TestAHandOverIsRefusedForEveryPathThatIsNotTheFoundDirectory(t *testing.T) {
|
|
dir := realDir(t)
|
|
statePath := filepath.Join(dir, "state.json")
|
|
found := filepath.Join(dir, "found")
|
|
if err := os.Mkdir(found, 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// A link at the path of a second found directory, and a found directory below a linked directory.
|
|
linked := filepath.Join(dir, "linked")
|
|
if err := os.Symlink(found, linked); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
through := filepath.Join(dir, "via", "data")
|
|
if err := os.Symlink(dir, filepath.Join(dir, "via")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.Mkdir(filepath.Join(dir, "data"), 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
file := filepath.Join(dir, "file")
|
|
if err := os.WriteFile(file, nil, 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
gone := filepath.Join(dir, "gone")
|
|
if err := store.Save(statePath, store.State{Resources: []store.Applied{
|
|
{ID: "notes.data", Type: "directory", Target: found, AsFound: true},
|
|
{ID: "notes.linked", Type: "directory", Target: linked, AsFound: true},
|
|
{ID: "notes.through", Type: "directory", Target: through, AsFound: true},
|
|
{ID: "notes.file", Type: "directory", Target: file, AsFound: true},
|
|
{ID: "notes.gone", Type: "directory", Target: gone, AsFound: true},
|
|
}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Each refused by its own check, which the reason names.
|
|
refused := map[string][2]string{
|
|
"a relative path": {"found", "not one"},
|
|
"a path with ..": {dir + "/x/../found", "as the engine states it"},
|
|
"a doubled separator": {dir + "//found", "as the engine states it"},
|
|
"a trailing separator": {found + "/", "as the engine states it"},
|
|
"a directory not used as found": {filepath.Join(dir, "other"), "not a directory this machine uses as found"},
|
|
"a link at the path": {linked, "is a link"},
|
|
"a link in the path": {through, "reached through a link"},
|
|
"a file": {file, "is not a directory on this machine"},
|
|
"a directory that is gone": {gone, "cannot be read on this machine"},
|
|
}
|
|
for name, c := range refused {
|
|
said, err := handOverFound(statePath, c[0], "jo")
|
|
if err == nil || said != "" {
|
|
t.Fatalf("%s (%s) was handed over: %q", name, c[0], said)
|
|
}
|
|
if !strings.Contains(err.Error(), "nothing was handed over") || !strings.Contains(err.Error(), c[1]) {
|
|
t.Fatalf("%s's refusal does not say %q and that nothing was handed over: %v", name, c[1], err)
|
|
}
|
|
}
|
|
if all, err := store.LoadHandedOver(statePath); err != nil || len(all) != 0 {
|
|
t.Fatalf("a refusal recorded something: %+v %v", all, err)
|
|
}
|
|
// The found directory itself, stated exactly, is taken.
|
|
if _, err := handOverFound(statePath, found, "jo"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if all, _ := store.LoadHandedOver(statePath); len(all) != 1 || all[0].Path != found {
|
|
t.Fatalf("recorded: %+v", all)
|
|
}
|
|
}
|