From 8e06b6af65666ddaaf90ae89dc9b8a6eb583eb4c Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 12 Nov 2026 23:39:54 +0100 Subject: [PATCH] B1 (enrolment): the enrolment's own module is the one the runtime serves it as MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../cmd/mesh-enrolment/enrolment.go | 5 ++--- .../cmd/mesh-enrolment/findings_test.go | 9 +++++++-- .../cmd/mesh-enrolment/holder.go | 10 ++++++---- .../cmd/mesh-enrolment/integration_test.go | 7 +++++-- .../mesh-enrolment/cmd/mesh-enrolment/issue.go | 2 +- .../cmd/mesh-enrolment/issue_test.go | 4 ++++ .../cmd/mesh-enrolment/kit_test.go | 8 +++++++- .../mesh-enrolment/cmd/mesh-enrolment/main.go | 18 +++++++++++------- .../cmd/mesh-enrolment/tokens_test.go | 2 +- 9 files changed, 44 insertions(+), 21 deletions(-) diff --git a/modules/mesh-enrolment/cmd/mesh-enrolment/enrolment.go b/modules/mesh-enrolment/cmd/mesh-enrolment/enrolment.go index 06d342b8..b3328eb4 100644 --- a/modules/mesh-enrolment/cmd/mesh-enrolment/enrolment.go +++ b/modules/mesh-enrolment/cmd/mesh-enrolment/enrolment.go @@ -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. and address.
(ADR 0587 §2). State = enrolmentsState ) diff --git a/modules/mesh-enrolment/cmd/mesh-enrolment/findings_test.go b/modules/mesh-enrolment/cmd/mesh-enrolment/findings_test.go index e178f12d..93bdfa2d 100644 --- a/modules/mesh-enrolment/cmd/mesh-enrolment/findings_test.go +++ b/modules/mesh-enrolment/cmd/mesh-enrolment/findings_test.go @@ -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) } diff --git a/modules/mesh-enrolment/cmd/mesh-enrolment/holder.go b/modules/mesh-enrolment/cmd/mesh-enrolment/holder.go index af6d9f49..9ce6f35d 100644 --- a/modules/mesh-enrolment/cmd/mesh-enrolment/holder.go +++ b/modules/mesh-enrolment/cmd/mesh-enrolment/holder.go @@ -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). diff --git a/modules/mesh-enrolment/cmd/mesh-enrolment/integration_test.go b/modules/mesh-enrolment/cmd/mesh-enrolment/integration_test.go index de273669..d4cea539 100644 --- a/modules/mesh-enrolment/cmd/mesh-enrolment/integration_test.go +++ b/modules/mesh-enrolment/cmd/mesh-enrolment/integration_test.go @@ -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" diff --git a/modules/mesh-enrolment/cmd/mesh-enrolment/issue.go b/modules/mesh-enrolment/cmd/mesh-enrolment/issue.go index 4410b626..1d029be1 100644 --- a/modules/mesh-enrolment/cmd/mesh-enrolment/issue.go +++ b/modules/mesh-enrolment/cmd/mesh-enrolment/issue.go @@ -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 { diff --git a/modules/mesh-enrolment/cmd/mesh-enrolment/issue_test.go b/modules/mesh-enrolment/cmd/mesh-enrolment/issue_test.go index 512e4cd0..6f630a8f 100644 --- a/modules/mesh-enrolment/cmd/mesh-enrolment/issue_test.go +++ b/modules/mesh-enrolment/cmd/mesh-enrolment/issue_test.go @@ -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)) diff --git a/modules/mesh-enrolment/cmd/mesh-enrolment/kit_test.go b/modules/mesh-enrolment/cmd/mesh-enrolment/kit_test.go index 723ca658..ad246cb5 100644 --- a/modules/mesh-enrolment/cmd/mesh-enrolment/kit_test.go +++ b/modules/mesh-enrolment/cmd/mesh-enrolment/kit_test.go @@ -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() diff --git a/modules/mesh-enrolment/cmd/mesh-enrolment/main.go b/modules/mesh-enrolment/cmd/mesh-enrolment/main.go index 34ddd403..b1678729 100644 --- a/modules/mesh-enrolment/cmd/mesh-enrolment/main.go +++ b/modules/mesh-enrolment/cmd/mesh-enrolment/main.go @@ -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, } } diff --git a/modules/mesh-enrolment/cmd/mesh-enrolment/tokens_test.go b/modules/mesh-enrolment/cmd/mesh-enrolment/tokens_test.go index fbfd654f..10078f98 100644 --- a/modules/mesh-enrolment/cmd/mesh-enrolment/tokens_test.go +++ b/modules/mesh-enrolment/cmd/mesh-enrolment/tokens_test.go @@ -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) }