The controller judged the operator's account for approving or deleting a retirement, rotating a secret and exporting sealed copies; it is not placed, and these verbs are reached through the mesh MCP server on each machine. Each now answers the operator's account (person.<name>), or a caller naming its own recorded hand act for that verb and subject, and nothing else (ADR 0590 §8, issue 1126). Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
73 lines
4.0 KiB
Go
73 lines
4.0 KiB
Go
package main
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// ADR 0590 §8 (issue 1126): approving or deleting a retirement, rotating a secret and exporting sealed copies each
|
|
// answer the operator's account, or a caller naming the hand act it recorded for that verb and that subject, and
|
|
// nothing else: the controller that judged for them is not placed.
|
|
func TestEachSensitiveVerbJudgesItsOwnCaller(t *testing.T) {
|
|
now := time.Date(2026, 11, 2, 12, 0, 0, 0, time.UTC)
|
|
acts := map[string]HandActSeen{
|
|
"mine": {ID: "mine", By: "novox.mesh-healers", At: now.Add(-time.Hour), Verb: "mesh-vault.rotate", Args: []string{"own/g14/app/key"}},
|
|
"old": {ID: "old", By: "novox.mesh-healers", At: now.Add(-25 * time.Hour), Verb: "rotate", Args: []string{"own/g14/app/key"}},
|
|
"other": {ID: "other", By: "novox.mesh-config", At: now.Add(-time.Hour), Verb: "rotate", Args: []string{"own/g14/app/key"}},
|
|
"del": {ID: "del", By: "novox.mesh-healers", At: now.Add(-time.Hour), Verb: "provisioner_delete", Args: []string{"d-app"}},
|
|
}
|
|
read := func(id string) (*HandActSeen, error) {
|
|
if a, ok := acts[id]; ok {
|
|
return &a, nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
clock := func() time.Time { return now }
|
|
for _, row := range []struct {
|
|
name, caller, verb, act string
|
|
about []string
|
|
want string // empty: allowed
|
|
}{
|
|
{"the operator's account", "person.jochen", SensitiveRotate, "", []string{"own/g14/app/key"}, ""},
|
|
{"the operator's account exports", "person.jochen", SensitiveExport, "", nil, ""},
|
|
{"unproven", "unproven", SensitiveVaultExport, "", nil, "proved none"},
|
|
{"a bare person prefix", "person.", SensitiveExport, "", nil, "named none"},
|
|
{"a module with no act", "novox.mesh-healers", SensitiveRotate, "", []string{"own/g14/app/key"}, "named none"},
|
|
{"a module's own act", "novox.mesh-healers", SensitiveRotate, "mine", []string{"own/g14/app/key"}, ""},
|
|
{"an act of another subject", "novox.mesh-healers", SensitiveRotate, "mine", []string{"own/ace/app/key"}, "not own/ace/app/key"},
|
|
{"an act of another verb", "novox.mesh-healers", SensitiveRotate, "del", []string{"own/g14/app/key"}, "warrants no rotate"},
|
|
{"an act of another caller", "novox.mesh-healers", SensitiveRotate, "other", []string{"own/g14/app/key"}, "recorded by novox.mesh-config"},
|
|
{"an act too old", "novox.mesh-healers", SensitiveRotate, "old", []string{"own/g14/app/key"}, "older than"},
|
|
{"an act not recorded", "novox.mesh-healers", SensitiveDelete, "none", []string{"d-app"}, "holds no hand act"},
|
|
{"a delete's own act", "novox.mesh-healers", SensitiveDelete, "del", []string{"d-app"}, ""},
|
|
} {
|
|
err := JudgeSensitive(read, clock, row.caller, row.verb, row.act, row.about...)
|
|
if (row.want == "") != (err == nil) || (err != nil && !strings.Contains(err.Error(), row.want)) {
|
|
t.Errorf("%s: %v, want %q", row.name, err, row.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The tools themselves judge: a module's call with no act is refused before anything is read or changed, and the
|
|
// operator's account goes through.
|
|
func TestTheSensitiveToolsRefuseACallerTheyDoNotJudgeTheirs(t *testing.T) {
|
|
env := &Env{Root: t.TempDir()}
|
|
defs := definitions(env)
|
|
for _, name := range []string{Seat + ".rotate", "provisioner_retire_approve", "provisioner_delete", "secret_export",
|
|
"secret_vault_export"} {
|
|
if _, err := toolNamed(t, defs, name).runAs("novox.some-module", map[string]any{"why": "x", "consumer": "c",
|
|
"confirm": "c", "consumers": []any{"c"}, "node": "g14", "module": "app", "name": "key"}); err == nil ||
|
|
!strings.Contains(err.Error(), "ADR 0590") {
|
|
t.Errorf("%s answered a module with no act: %v", name, err)
|
|
}
|
|
}
|
|
if _, err := toolNamed(t, defs, "secret_export").runAs("person.jochen", map[string]any{}); err != nil {
|
|
t.Errorf("the operator's export was refused: %v", err)
|
|
}
|
|
// Listing holders alone is no sealed copy: any proven caller reads it.
|
|
if _, err := toolNamed(t, defs, "secret_export").runAs("novox.some-module", map[string]any{"sealed": false}); err != nil {
|
|
t.Errorf("a listing without sealed copies was refused: %v", err)
|
|
}
|
|
}
|