From 9c1f52a902b0a39c0922de4263b6e6a169744a18 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 5 Nov 2026 18:43:21 +0100 Subject: [PATCH 1/2] The wanted accounts' layout leaves mesh-sdk for the grants part's package grantsstate (ADR 0634 section 1, ADR 0635 section 4, issue 1966) Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i --- go/buscompose/accounts.go | 162 +----------------------------------- go/buscompose/names_test.go | 6 +- 2 files changed, 4 insertions(+), 164 deletions(-) diff --git a/go/buscompose/accounts.go b/go/buscompose/accounts.go index 499fbe4..1d3c1eb 100644 --- a/go/buscompose/accounts.go +++ b/go/buscompose/accounts.go @@ -1,126 +1,11 @@ package buscompose -import ( - "encoding/json" - "fmt" - "slices" - "strings" - "time" -) +import "time" -// AccountsState is the grants part's state of wanted accounts, by bus user. -const AccountsState = "accounts" - -// GrantsAccounts is that state as a reader names it (`.`). -const GrantsAccounts = GrantsModule + "." + AccountsState - -// AccountIssuedEvent is what the vault says when it minted an account: AccountIssued, never a value or a hash. +// AccountIssuedEvent is what the vault says when it minted an account: AccountIssued, never a value or a hash. The state of +// wanted accounts it mints from is the grants part's (grantsstate.AccountsState; novox/hq ADR 0634 §1). const AccountIssuedEvent = "account-issued" -// ClaimedSeat is one seat a module claims, as its runtime needs it (the controller's seatClaimed): the name, the scope (a -// node-scoped seat's verb carries the machine) and the verbs the runtime serves for it. -type ClaimedSeat struct { - Seat string `json:"seat"` - Scope string `json:"scope"` - Serves []string `json:"serves,omitempty"` -} - -// WantedAccount is one key of the grants part's state `accounts`: an account the mesh needs, without its password. -type WantedAccount struct { - User string `json:"user"` - // Kind is KindModule (KindNodeTools for the runtime, named as a module is), KindProcess, KindProvisioner or - // KindLedger. - Kind Kind `json:"kind"` - Node string `json:"node"` - Module string `json:"module"` - // Process is a KindProcess account's process id. - Process string `json:"process,omitempty"` - // URL is the bus's address as that node reaches it, `nats://:`; Fingerprint the bus certificate's. - URL string `json:"url"` - Fingerprint string `json:"fingerprint,omitempty"` - // Claims are a module's claims with the verbs each seat promises: none for a process or a provisioner client. - Claims []ClaimedSeat `json:"claims,omitempty"` - // At is when the grants part wrote the key: not compared (Same). - At time.Time `json:"at"` -} - -// LedgersSecret is the tool runner's own secret holding its machine's ledger accounts, a map of module to credential -// (ADR 0597 §9). -const LedgersSecret = "ledgers" - -// mintedKinds are the kinds of account the vault mints (ADR 0561 §1). -var mintedKinds = []Kind{KindModule, KindNodeTools, KindProcess, KindProvisioner, KindLedger} - -// principal is the account as the composer names it. -func (w WantedAccount) principal() Principal { - return Principal{Kind: w.Kind, Node: w.Node, Module: w.Module, Process: w.Process} -} - -// Valid says why w is not an account the vault may mint, or nil: a kind it mints, every name a subject token, the user -// the one the composer derives from them, and an address to reach the bus at. -func (w WantedAccount) Valid() error { - if !slices.Contains(mintedKinds, w.Kind) { - return fmt.Errorf("%s is a %s account, which the vault never mints (ADR 0561 §4)", w.User, w.Kind) - } - for what, v := range map[string]string{"node": w.Node, "module": w.Module} { - if !safeSubject.MatchString(v) { - return fmt.Errorf("%s: the %s %q is not a name the mesh writes", w.User, what, v) - } - } - if w.Kind == KindProcess && !safeSubject.MatchString(w.Process) { - return fmt.Errorf("%s: the process %q is not a name the mesh writes", w.User, w.Process) - } - if w.Kind != KindProcess && w.Process != "" { - return fmt.Errorf("%s: only a process's account names a process", w.User) - } - if want := w.principal().Username(); w.User != want { - return fmt.Errorf("%s is not the user of a %s account of %s on %s (that is %s)", w.User, w.Kind, w.Module, w.Node, want) - } - if !strings.HasPrefix(w.URL, "nats://") || len(w.URL) == len("nats://") { - return fmt.Errorf("%s names no bus address (url, nats://:)", w.User) - } - if len(w.Claims) > 0 && w.Kind != KindModule && w.Kind != KindNodeTools { - return fmt.Errorf("%s: a %s account holds no seat, so it carries no claims", w.User, w.Kind) - } - return nil -} - -// Same says whether two wanted accounts want the same account: every field but when it was written. -func (w WantedAccount) Same(o WantedAccount) bool { - a, b := w, o - a.At, b.At = time.Time{}, time.Time{} - ra, _ := json.Marshal(a) - rb, _ := json.Marshal(b) - return string(ra) == string(rb) -} - -// OwnSecret is the module's own secret the account is held in: `broker`, `broker-` or `provisioner-broker` -// (the controller's names, ADR 0436 §2, ADR 0349 §11); a ledger account in the tool runner's `ledgers`, which keeps every -// ledger account of its machine, by module (ADR 0597 §9). -func (w WantedAccount) OwnSecret() string { - switch w.Kind { - case KindLedger: - return LedgersSecret - case KindProcess: - return "broker-" + w.Process - case KindProvisioner: - return "provisioner-broker" - } - return "broker" -} - -// ReadWantedAccount reads the key user of the state: refused when it is not a valid wanted account of that user. -func ReadWantedAccount(user string, raw []byte) (WantedAccount, error) { - var w WantedAccount - if err := json.Unmarshal(raw, &w); err != nil { - return WantedAccount{}, fmt.Errorf("%s %s is not a wanted account: %w", GrantsAccounts, user, err) - } - if w.User != user { - return WantedAccount{}, fmt.Errorf("%s %s names the user %q", GrantsAccounts, user, w.User) - } - return w, w.Valid() -} - // AccountIssued is the vault's account-issued: which account it minted, and its generation. Never a value or a hash. type AccountIssued struct { User string `json:"user"` @@ -131,44 +16,3 @@ type AccountIssued struct { Generation int `json:"generation"` At time.Time `json:"at"` } - -// HeldCredential is the value the account's holder reads, exactly as the controller builds it today (issueWith, -// sealProcess and sealProvisionerClient): a module's `{url, fingerprint, node, module, user, password, claims}`, a -// process's without claims, a provisioner client's without module or claims; an empty fingerprint or claims left out. -func HeldCredential(w WantedAccount, password string) ([]byte, error) { - if err := w.Valid(); err != nil { - return nil, err - } - if password == "" { - return nil, fmt.Errorf("%s: an account is held with its password", w.User) - } - switch w.Kind { - case KindProcess: - return json.Marshal(struct { - URL string `json:"url"` - Fingerprint string `json:"fingerprint,omitempty"` - Node string `json:"node"` - Module string `json:"module"` - User string `json:"user"` - Password string `json:"password"` - }{URL: w.URL, Fingerprint: w.Fingerprint, Node: w.Node, Module: w.Module, User: w.User, Password: password}) - case KindProvisioner: - return json.Marshal(struct { - URL string `json:"url"` - Fingerprint string `json:"fingerprint,omitempty"` - Node string `json:"node"` - User string `json:"user"` - Password string `json:"password"` - }{URL: w.URL, Fingerprint: w.Fingerprint, Node: w.Node, User: w.User, Password: password}) - } - return json.Marshal(struct { - URL string `json:"url"` - Fingerprint string `json:"fingerprint,omitempty"` - Node string `json:"node"` - Module string `json:"module"` - User string `json:"user"` - Password string `json:"password"` - Claims []ClaimedSeat `json:"claims,omitempty"` - }{URL: w.URL, Fingerprint: w.Fingerprint, Node: w.Node, Module: w.Module, User: w.User, Password: password, - Claims: w.Claims}) -} diff --git a/go/buscompose/names_test.go b/go/buscompose/names_test.go index 6a31678..3284dff 100644 --- a/go/buscompose/names_test.go +++ b/go/buscompose/names_test.go @@ -25,14 +25,10 @@ func TestTheBusNamesAgree(t *testing.T) { sn != "mesh.ledger.m.records.snapshot" { t.Fatalf("a ledger's subjects are %q and %q", h, sn) } - // 1966/AC23: a wanted account of kind ledger for m on a names the user ledger.a.m and the own secret ledgers. - ledger := WantedAccount{User: "ledger.a.m", Kind: KindLedger, Node: "a", Module: "m", URL: "nats://10.0.0.1:4222"} + // 1966/AC23: a ledger account for m on a is named ledger.a.m. if got := (Principal{Kind: KindLedger, Node: "a", Module: "m"}).Username(); got != "ledger.a.m" { t.Fatalf("a ledger account is named %q", got) } - if err := ledger.Valid(); err != nil || ledger.OwnSecret() != "ledgers" { - t.Fatalf("a wanted ledger account: %v, held in %q", err, ledger.OwnSecret()) - } // A seat's family is the shared events stream filtered to its subjects: a definition says the stream's age. for _, st := range MeshStreams() { if st.Name == EventsStream && int64(st.MaxAge) != seatdef.SharedEventsMaxAgeDays*24*60*60 { From 7cdcc34a6fa5643a09767a4a93cf02cbc45c875c Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 5 Nov 2026 18:45:50 +0100 Subject: [PATCH 2/2] RemovedDeliveryVerbs leaves mesh-sdk for the seat mesh-delivery's client (ADR 0634 section 2, issue 1966) Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i --- go/catalogue/delivery_seat.go | 14 -------------- go/catalogue/verbs_r6_test.go | 6 +++--- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/go/catalogue/delivery_seat.go b/go/catalogue/delivery_seat.go index 6521ff7..f5f8912 100644 --- a/go/catalogue/delivery_seat.go +++ b/go/catalogue/delivery_seat.go @@ -1,19 +1,5 @@ package catalogue -// DeliverySeat is the seat mesh-delivery holds. - -// RemovedDeliveryVerbs are the delivery seat's verbs release 6 removed (novox/hq ADR 0539 clause 17): with no plan the -// gate keeps the merge's states and derives nothing after the merge. Each stays in deliveryVerbs as an optional row -// with no input, so the holder of before and the holder of release 6 both hold the seat and a caller is answered by -// name with its replacement, given here. -var RemovedDeliveryVerbs = map[string]string{ - "times": "the delivery view (nox-delivery): each machine's want and have, and since when", - "what-if": "the merge check's composition, which composes every machine a change touches", - "release": "the assigner's asks (hold, restore, backup, record), answered on the phone or in the delivery view", - "stop": "a bad fingerprint is held and its canary set back by the assigner (ADR 0429 §6, ADR 0539 clause 18)", - "retire-history": "nothing to retire: the gate keeps no delivery after its merge", -} - // SeatSays is whether a consumed `.` names one of the seat's own events: the seat emits it, // exactly or by one of its patterns (`log.*`). A consumed name whose emitter is a seat and whose event the // seat does not emit is the event of the module of that name (novox/hq ADR 0239): mesh-delivery is a seat diff --git a/go/catalogue/verbs_r6_test.go b/go/catalogue/verbs_r6_test.go index 546d8fe..31254e1 100644 --- a/go/catalogue/verbs_r6_test.go +++ b/go/catalogue/verbs_r6_test.go @@ -19,9 +19,9 @@ func TestTheDeliverySeatHasNoPlanVerbs(t *testing.T) { } for _, name := range []string{"times", "what-if", "release", "stop", "retire-history"} { v, ok := rows[name] - replacement, removed := RemovedDeliveryVerbs[name] - if !ok || !removed || replacement == "" { - t.Errorf("%s: in the table %v, in the removed list %v", name, ok, removed) + replacement := strings.TrimPrefix(v.Description, "Removed in R6 (ADR 0539): refused by name, naming its replacement: ") + if !ok || replacement == "" || replacement == v.Description { + t.Errorf("%s: in the table %v, replacement %q", name, ok, replacement) continue } if !strings.HasPrefix(v.Description, "Removed in R6 (ADR 0539): refused by name") || !strings.Contains(v.Description, replacement) || !v.Optional {