B1 (enrolment): the enrolment's own module is the one the runtime serves it as
enrolment.Module is gone: the finding writer, the reader, the warrant record, the log lines and the ask's bound act name the served-as module (ownModule, the holder's Module). Builds: design 47 G A-296, §428 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
@@ -17,10 +17,9 @@ import (
|
||||
// vault mints the token and the node's account and signs (ADR 0587 §3). What it keeps is one key per open enrolment in its
|
||||
// state `enrolments` (§2), and what it says is its five events.
|
||||
|
||||
// Module and Seat are the module's name and the seat it holds.
|
||||
// Seat is the seat the module holds; the module's own name is the runtime's (ownModule).
|
||||
const (
|
||||
Module = "mesh-enrolment"
|
||||
Seat = meshnodeenroller.Seat
|
||||
Seat = meshnodeenroller.Seat
|
||||
// State is the holder's bucket of open enrolments; its keys are node.<node> and address.<address> (ADR 0587 §2).
|
||||
State = enrolmentsState
|
||||
)
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
package main
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"testing"
|
||||
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
)
|
||||
|
||||
// The enrolment's findings are written through the held finding writer of a stdio module (design 47 G §639, A-37): it is made
|
||||
// over the state findings with the node the runtime sets, and refused without one.
|
||||
// over the state findings with the module and node the runtime sets, and refused without one.
|
||||
func TestFindingWriter(t *testing.T) {
|
||||
t.Setenv("MESH_NODE", "n1")
|
||||
t.Setenv(stdio.ServedAs, "acme-enrolment") // the module it is served as names its writer
|
||||
if w, err := newFindingWriter(); err != nil || w == nil {
|
||||
t.Fatalf("writer = %v, %v; want one", w, err)
|
||||
}
|
||||
|
||||
@@ -50,10 +50,12 @@ type Asker interface {
|
||||
|
||||
// Holder is the seat's holder: the state it keeps, and every other part it reaches through a port.
|
||||
type Holder struct {
|
||||
Store Store
|
||||
Read Reader
|
||||
Vault Vault
|
||||
Asker Asker
|
||||
// Module is the holder's own module, the runtime's: the module its asks name (design 47 G A-296).
|
||||
Module string
|
||||
Store Store
|
||||
Read Reader
|
||||
Vault Vault
|
||||
Asker Asker
|
||||
// Say publishes a seat event under a message id the bus keeps one of.
|
||||
Say func(event string, body any, id string) error
|
||||
// AddNode asks mesh-module-assigner to add the node, or to take a node that never confirmed off again (ADR 0587 §7).
|
||||
|
||||
@@ -232,7 +232,7 @@ func TestMeshEnrolmentWholeOnARealBus(t *testing.T) {
|
||||
}
|
||||
return kv
|
||||
}
|
||||
enrolments := mk(Module+"_"+enrolmentsState, true) // the runtime's bucket of the bed's credential's module
|
||||
enrolments := mk(testModule+"_"+enrolmentsState, true) // the runtime's bucket of the bed's credential's module
|
||||
// No controller lease bucket at all: the controller ended by its unassignment (issue 1126).
|
||||
logins, assignments := mk("mesh-vault_node-logins", false), mk("mesh-delivery-assigner_assignments", false)
|
||||
have := mk("node-engine_have", false)
|
||||
@@ -263,7 +263,7 @@ func TestMeshEnrolmentWholeOnARealBus(t *testing.T) {
|
||||
store := &busStore{ctx: ctx, js: js, kv: enrolments}
|
||||
vault := &standInVault{ctx: ctx, logins: logins, tokens: map[string]meshnodeenrollerclient.Token{}}
|
||||
asker := &fakeAsker{}
|
||||
h := &Holder{Store: store, Read: busReader{ctx, js}, Vault: vault, Asker: asker, AskLasts: time.Hour,
|
||||
h := &Holder{Module: testModule, Store: store, Read: busReader{ctx, js}, Vault: vault, Asker: asker, AskLasts: time.Hour,
|
||||
Say: func(event string, body any, id string) error {
|
||||
raw, _ := json.Marshal(body)
|
||||
_, err := js.Publish(ctx, "mesh.seat.mesh-enrolment.event."+event, raw, jetstream.WithMsgID(id))
|
||||
@@ -462,3 +462,6 @@ func TestMeshEnrolmentWholeOnARealBus(t *testing.T) {
|
||||
return dropped && len(keys) == 0
|
||||
})
|
||||
}
|
||||
|
||||
// testModule is the module the bed's runtime serves the enrolment as; its bucket is named from it (design 47 G A-296).
|
||||
const testModule = "mesh-enrolment"
|
||||
|
||||
@@ -163,7 +163,7 @@ func (h *Holder) release(e Enrolment) error {
|
||||
// AskOf is the ask of an enrolment, in the words the operator reads: that a machine of this name may join until the
|
||||
// expiry at this address. Its yes binds the act (name, address, expiry).
|
||||
func (h *Holder) AskOf(e Enrolment) (oc.Ask, error) {
|
||||
act := occ.Act{"verb": Seat + ".issue", "module": Module, "node": e.Node, "address": e.Address,
|
||||
act := occ.Act{"verb": Seat + ".issue", "module": h.Module, "node": e.Node, "address": e.Address,
|
||||
"expires": e.Expires.UTC().Format(time.RFC3339)}
|
||||
bind, err := occ.ActDigest(act)
|
||||
if err != nil {
|
||||
|
||||
@@ -90,6 +90,10 @@ func twoIssuesAtOnceHoldTwoAddressesAndAYesMakesTheToken(t *testing.T) {
|
||||
if e.Stage != StageAsked || e.Caller != "person.operator" || e.Ask == "" || len(r.asker.asked) != 2 {
|
||||
t.Fatalf("%+v, asked %v", e, r.asker.asked)
|
||||
}
|
||||
// The ask's yes binds an act naming the holder's own module, the runtime's (design 47 G A-296).
|
||||
if want, _ := occ.ActDigest(occ.Act{"verb": Seat + ".issue", "module": "acme-enrolment", "node": "ace", "address": e.Address, "expires": e.Expires.UTC().Format(time.RFC3339)}); !slices.Contains(r.asker.binds, want) {
|
||||
t.Errorf("the ask binds %v, want the act of the module acme-enrolment (%s)", r.asker.binds, want)
|
||||
}
|
||||
ask, _ := r.h.AskOf(e)
|
||||
if ask.Options[0].Level != occ.Approve || ask.Options[0].Binds == "" || occ.Check(ask, r.now) != nil {
|
||||
t.Errorf("the ask does not authorise the act: %+v, %v", ask.Options[0], occ.Check(ask, r.now))
|
||||
|
||||
@@ -161,6 +161,7 @@ func (v *fakeVault) Signer() (string, error) { return "vault-signer", nil }
|
||||
type fakeAsker struct {
|
||||
mu sync.Mutex
|
||||
asked, cancelled []string
|
||||
binds []string // the digest each ask's yes binds
|
||||
err error
|
||||
answers map[string]string // the router's record: the option each ask was answered with
|
||||
}
|
||||
@@ -191,6 +192,11 @@ func (a *fakeAsker) Ask(k oc.Ask) error {
|
||||
return a.err
|
||||
}
|
||||
a.asked = append(a.asked, k.ID)
|
||||
for _, o := range k.Options {
|
||||
if o.Binds != "" {
|
||||
a.binds = append(a.binds, o.Binds)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (a *fakeAsker) Cancel(id string) error {
|
||||
@@ -237,7 +243,7 @@ func newRig(t *testing.T) *rig {
|
||||
"mesh-delivery-assigner.assignments": {"hub.node": hubAssignments},
|
||||
haveRead: {"hub.node": hubRecord},
|
||||
}
|
||||
r.h = &Holder{Store: r.st, Read: r.rd, Vault: r.vault, Asker: r.asker, AskLasts: time.Hour,
|
||||
r.h = &Holder{Module: "acme-enrolment", Store: r.st, Read: r.rd, Vault: r.vault, Asker: r.asker, AskLasts: time.Hour,
|
||||
Say: func(event string, body any, id string) error {
|
||||
raw, _ := json.Marshal(body)
|
||||
r.mu.Lock()
|
||||
|
||||
@@ -149,13 +149,16 @@ const (
|
||||
AssignmentsTool = assignerModule + ".assigner_read_assignments"
|
||||
)
|
||||
|
||||
// ownModule is the module the runtime serves the enrolment as, never a constant (design 47 G A-296, §428).
|
||||
func ownModule() string { return os.Getenv(stdio.ServedAs) }
|
||||
|
||||
// heldWriter is the enrolment's finding writer.
|
||||
type heldWriter = views.HeldWriter[mcseat.Finding, mcseat.FindingCleared, mcseat.FindingsStated, mcseat.FindingsAskedEvent]
|
||||
|
||||
// newFindingWriter makes the enrolment's held finding writer over its state findings, its node from MESH_NODE (design 47 G §639, A-37).
|
||||
func newFindingWriter() (*heldWriter, error) {
|
||||
return views.NewStdioHeldWriter(views.WriterConfig[mcseat.Finding, mcseat.FindingCleared, mcseat.FindingsStated, mcseat.FindingsAskedEvent]{
|
||||
Module: Module, State: stdio.State(mcseat.StateFindings), Asked: mcseat.SubjectFindingsAsked, Logf: logf})
|
||||
Module: ownModule(), State: stdio.State(mcseat.StateFindings), Asked: mcseat.SubjectFindingsAsked, Logf: logf})
|
||||
}
|
||||
|
||||
// followed is the enrolment's pictures of the assigner's assignments, started at once: its first view asked through
|
||||
@@ -166,14 +169,14 @@ func followed() *views.Reader {
|
||||
var findings views.WireWriter
|
||||
w, err := newFindingWriter()
|
||||
if err != nil {
|
||||
logf("[%s] the finding writer cannot be made, so no finding is written: %v", Module, err)
|
||||
logf("[%s] the finding writer cannot be made, so no finding is written: %v", ownModule(), err)
|
||||
} else {
|
||||
if err := w.Start(context.Background()); err != nil {
|
||||
logf("[%s] the finding writer cannot start, its findings are kept in the state alone: %v", Module, err)
|
||||
logf("[%s] the finding writer cannot start, its findings are kept in the state alone: %v", ownModule(), err)
|
||||
}
|
||||
findings = w
|
||||
}
|
||||
v := &views.Reader{Module: Module, Logf: func(f string, a ...any) { logf("["+Module+"] "+f, a...) }, Findings: findings,
|
||||
v := &views.Reader{Module: ownModule(), Logf: func(f string, a ...any) { logf("["+ownModule()+"] "+f, a...) }, Findings: findings,
|
||||
Acts: meshhandactsclient.Recorder{Ask: stdio.Ask}}
|
||||
v.Follow(assignerModule, assignmentsState, AssignmentsTool)
|
||||
v.Start(context.Background())
|
||||
@@ -190,20 +193,21 @@ func newHolder(v *views.Reader) *Holder {
|
||||
rd := followedReader{pics: v, rest: runtimeReader{call: call}}
|
||||
client := occ.New("asked")
|
||||
return &Holder{
|
||||
Store: runtimeStore{}, Read: rd, Vault: vaultOver{call: call, read: rd}, Asker: client,
|
||||
Module: ownModule(),
|
||||
Store: runtimeStore{}, Read: rd, Vault: vaultOver{call: call, read: rd}, Asker: client,
|
||||
Say: func(event string, body any, id string) error {
|
||||
return stdio.SeatPublish(SeatEventSubject(event), body, id)
|
||||
},
|
||||
AddNode: addNodeOver(call),
|
||||
WarrantOf: warrantOf(client),
|
||||
Warrant: func(ask string) error { _, err := rec.RecordWarrant(Module, ask); return err },
|
||||
Warrant: func(ask string) error { _, err := rec.RecordWarrant(ownModule(), ask); return err },
|
||||
Act: func(what, why, cause string) error {
|
||||
_, err := rec.Record(meshhandactsclient.Request{What: what, Why: why, Cause: cause})
|
||||
return err
|
||||
},
|
||||
Loaded: loadedVia(rd),
|
||||
Now: time.Now,
|
||||
Logf: func(f string, a ...any) { logf("["+Module+"] "+f, a...) },
|
||||
Logf: func(f string, a ...any) { logf("["+ownModule()+"] "+f, a...) },
|
||||
AskLasts: 55 * time.Minute,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ func theManifestSaysWhatTheCodeDoes(t *testing.T) {
|
||||
if err := json.Unmarshal(raw, &m); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if m.Module != Module || m.Runs != Module || len(m.Claims) != 1 || m.Claims[0].Name != Seat || m.Claims[0].Scope != "mesh" ||
|
||||
if m.Runs != m.Module || len(m.Claims) != 1 || m.Claims[0].Name != Seat || m.Claims[0].Scope != "mesh" ||
|
||||
!slices.Equal(m.Claims[0].Serves, Verbs) || len(m.Emits) != 0 {
|
||||
t.Errorf("module %q runs as %q, claims %+v, emits %v", m.Module, m.Runs, m.Claims, m.Emits)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user