catalogue.Verb gains NeedsApproval, copied by SeatsFromDefinitions from the definition's verb, so the manifest validator and every reader of the seat list read a verb's mark from one place. Compiled only, never read from a manifest's own verb. Builds: design 46 7-approval-mark §2, §4; lead answer A-177 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
119 lines
5.4 KiB
Go
119 lines
5.4 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/seatdef"
|
|
)
|
|
|
|
// A module declares the state it keeps and the state it reads (novox/hq ADR 0201), a bucket by its
|
|
// bare name or with the owner's options.
|
|
func TestAManifestMaySayWhatStateItKeepsAndReads(t *testing.T) {
|
|
m, err := decodeForTest([]byte(`{"module":"claude-code","version":"1",` +
|
|
`"state":["servers",{"name":"seen","history":5,"ttl-seconds":3600}],` +
|
|
`"reads":["licence-manager.bindings"]}`))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(m.State) != 2 || m.State[0].Name != "servers" || m.State[1].History != 5 || m.State[1].TTLSeconds != 3600 {
|
|
t.Fatalf("state not read: %+v", m.State)
|
|
}
|
|
if len(m.Reads) != 1 || m.Reads[0] != "licence-manager.bindings" {
|
|
t.Fatalf("reads not read: %v", m.Reads)
|
|
}
|
|
// Written back as it came in: the short form where nothing else is said.
|
|
out, _ := json.Marshal(m.State)
|
|
if string(out) != `["servers",{"name":"seen","history":5,"ttl-seconds":3600}]` {
|
|
t.Fatalf("written back as %s", out)
|
|
}
|
|
}
|
|
|
|
// 1154/AC1: a state entry may say key-ttl; it is read, written back, and a bare name or an entry without it reads as before.
|
|
// G-sdk-contracts 4: read-only likewise, and given twice it is refused.
|
|
func TestAStateMayHaveKeysThatExpireOneByOne(t *testing.T) {
|
|
m, err := decodeForTest([]byte(`{"module":"mesh-delivery-watchman","version":"1",` +
|
|
`"state":[{"name":"pulse","key-ttl":true},"stuck",{"name":"seen","history":5},{"name":"spent","read-only":true}]}`))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !m.State[0].KeyTTL || m.State[1].KeyTTL || m.State[2].KeyTTL {
|
|
t.Fatalf("key-ttl not read as said: %+v", m.State)
|
|
}
|
|
if !m.State[3].ReadOnly || m.State[0].ReadOnly || m.State[1].ReadOnly {
|
|
t.Fatalf("read-only not read as said: %+v", m.State)
|
|
}
|
|
out, _ := json.Marshal(m.State)
|
|
if string(out) != `[{"name":"pulse","key-ttl":true},"stuck",{"name":"seen","history":5},{"name":"spent","read-only":true}]` {
|
|
t.Fatalf("written back as %s", out)
|
|
}
|
|
// G §469, §695: an old state entry takes max-mib, written back, bounded as an objects section's.
|
|
capped, err := decodeForTest([]byte(`{"module":"x","version":"1","state":[{"name":"a","max-mib":256},"b"]}`))
|
|
if err != nil || capped.State[0].MaxMiB != 256 || capped.State[1].MaxMiB != 0 {
|
|
t.Fatalf("max-mib read as %+v, %v", capped.State, err)
|
|
}
|
|
if out, _ := json.Marshal(capped.State); string(out) != `[{"name":"a","max-mib":256},"b"]` {
|
|
t.Fatalf("max-mib written back as %s", out)
|
|
}
|
|
for _, bad := range []string{"0", "-1", "8193"} {
|
|
if _, err := decodeForTest([]byte(`{"module":"x","version":"1","state":[{"name":"a","max-mib":` + bad + `}]}`)); err == nil {
|
|
t.Errorf("max-mib %s was read", bad)
|
|
}
|
|
}
|
|
if _, err := decodeForTest([]byte(`{"module":"x","version":"1","state":[{"name":"a","max-mib":8192}]}`)); err != nil {
|
|
t.Errorf("max-mib 8192 refused: %v", err)
|
|
}
|
|
if _, err := decodeForTest([]byte(`{"module":"nats","version":"1","state":[{"name":"spent","read-only":true,"read-only":false}]}`)); err == nil ||
|
|
!strings.Contains(err.Error(), "the field state[0].read-only is given twice") {
|
|
t.Fatalf("read-only given twice: %v", err)
|
|
}
|
|
}
|
|
|
|
// A read of a seat holder's state is its own kind, `seat:<seat>.<state>` (design 47 G A-128), and a seat's compiled form
|
|
// carries the names of the states its definition declares.
|
|
func TestSeatRead(t *testing.T) {
|
|
for _, row := range []struct {
|
|
read, seat, local string
|
|
ok, bad bool
|
|
}{
|
|
{"seat:mesh-provisioner.plans", "mesh-provisioner", "plans", true, false},
|
|
{"mesh-provisioner.plans", "", "", false, false},
|
|
{"seat:mesh-provisioner", "", "", true, true},
|
|
{"seat:.plans", "", "", true, true},
|
|
{"seat:Mesh.plans", "", "", true, true},
|
|
} {
|
|
seat, local, ok, err := SeatRead(row.read)
|
|
if seat != row.seat || local != row.local || ok != row.ok || (err != nil) != row.bad {
|
|
t.Errorf("SeatRead(%q) = %q, %q, %v, %v", row.read, seat, local, ok, err)
|
|
}
|
|
}
|
|
for _, row := range []struct {
|
|
consume, seat, state string
|
|
ok, bad bool
|
|
}{
|
|
{"seat:mesh-provisioner.views.plans", "mesh-provisioner", "plans", true, false},
|
|
{"mesh-provisioner.views.plans", "", "", false, false},
|
|
{"seat:mesh-provisioner.plans", "", "", true, true},
|
|
{"seat:.views.plans", "", "", true, true},
|
|
} {
|
|
seat, state, ok, err := SeatViewConsume(row.consume)
|
|
if seat != row.seat || state != row.state || ok != row.ok || (err != nil) != row.bad {
|
|
t.Errorf("SeatViewConsume(%q) = %q, %q, %v, %v", row.consume, seat, state, ok, err)
|
|
}
|
|
}
|
|
s := SeatFromDefinition(seatdef.Definition{Seat: "mesh-provisioner", Scope: "mesh",
|
|
States: []seatdef.State{{Name: "plans", View: true}, {Name: "provisions"}},
|
|
Accepts: []seatdef.Message{{Name: "step", Stream: "check"}, {Name: "step"}}})
|
|
if len(s.States) != 2 || s.States[0] != "plans" || s.States[1] != "provisions" || len(s.Views) != 1 || s.Views[0] != "plans" ||
|
|
len(s.AcceptedOn) != 1 || s.AcceptedOn[0] != "check.step" {
|
|
t.Errorf("the seat's states = %v, views = %v, accepted on = %v", s.States, s.Views, s.AcceptedOn)
|
|
}
|
|
// A-177 (design 46 approval mark §2, §4): a compiled verb carries its definition's approval mark.
|
|
marked := SeatFromDefinition(seatdef.Definition{Seat: "node-login-shell",
|
|
Verbs: []seatdef.Verb{{Name: "execute", NeedsApproval: true}, {Name: "read"}}})
|
|
if len(marked.Serves) != 2 || !marked.Serves[0].NeedsApproval || marked.Serves[1].NeedsApproval {
|
|
t.Errorf("the compiled verbs' marks = %+v", marked.Serves)
|
|
}
|
|
}
|