package buscompose import ( "slices" "strings" "git.novox.be/novox/mesh-sdk/go/delivery" ) // **The grants part's user list, sealed to the bus node** (novox/hq ADR 0547 clause 7, issue 1945). The grants part writes // the list it composed as one object of its own object bucket, sealed to the key of the node holding mesh-broker, and // names it in one key of its own state `user-list`. The node-engine of that node follows the key when the switch says // so (clause 8), opens the object and writes the bus's user-list file. // // Who may do what, and nothing more: // // - the grants part writes its object bucket (create, put, purge of an object's old chunks, delete) and its own state // as before; // - the node-engine of the node holding mesh-broker reads that one key of the state, watched, and an object of the // bucket by its name: never the bucket's list of names, never a write, never another key of the grants part's state; // - no other principal touches either. // GrantsUserListBucket, GrantsUserListKey, GrantsUserListObjects and GrantsUserListObjectStream are delivery's names, // here where the grants are composed. const ( GrantsUserListBucket = delivery.GrantsUserListBucket GrantsUserListKey = delivery.GrantsUserListKey GrantsUserListObjects = delivery.GrantsUserListObjects GrantsUserListObjectStream = delivery.GrantsUserListObjectStream ) // GrantsUserListFollows is what the node-engine of the node holding mesh-broker may publish to follow the grants part's // list: the one key `user-list.sealed` read and watched (its own ordered consumer, filtered on that key alone), and an // object of the bucket by its name, as a node reads its declaration's files (ADR 0486 ยง2: never STREAM.INFO of the object // stream, which lists every name, and never STREAM.MSG.GET). func GrantsUserListFollows() []string { kv := "KV_" + GrantsUserListBucket key := "$KV." + GrantsUserListBucket + "." + GrantsUserListKey obj, s := GrantsUserListObjects, GrantsUserListObjectStream return []string{ "$JS.API.DIRECT.GET." + kv + "." + key, "$JS.API.CONSUMER.CREATE." + kv + ".*." + key, "$JS.API.CONSUMER.DELETE." + kv + ".*", "$JS.API.STREAM.INFO." + kv, "$JS.FC." + kv + ".>", "$JS.API.DIRECT.GET." + s + ".$O." + obj + ".M.*", "$JS.API.CONSUMER.CREATE." + s + ".*.$O." + obj + ".C.*", "$JS.API.CONSUMER.DELETE." + s + ".*", "$JS.FC." + s + ".>", } } // GrantsUserListWrites is what the grants part may publish to keep its object bucket: create it and read its info back, // put an object (its chunks and its metadata, the old chunks of a name purged), read an object's metadata, and delete // one. Its own state `user-list` it writes already, as every module writes its own. func GrantsUserListWrites() []string { obj, s := GrantsUserListObjects, GrantsUserListObjectStream return []string{ "$JS.API.STREAM.CREATE." + s, "$JS.API.STREAM.INFO." + s, "$JS.API.STREAM.PURGE." + s, "$JS.API.DIRECT.GET." + s + ".>", "$JS.API.STREAM.MSG.GET." + s, "$O." + obj + ".>", } } // grantsOwnObjects is the grants part writing its own object bucket, and nothing of another. func grantsOwnObjects(p Principal, pattern string) bool { return p.Kind == KindModule && p.Module == GrantsModule && (pattern == "$O."+GrantsUserListObjects+".>" || pattern == "$JS.API.STREAM.CREATE."+GrantsUserListObjectStream) } // **Named readers** (novox/hq ADR 0547 clause 9, progressive insight of 2026-10-26; issue 1945). A state named here is // read by the modules named and by no other, whatever another module's `reads` says: the vault's `bus-accounts` holds // every bus account's bcrypt hash, and from take-over the grants part alone may read it. A module's announcement may name // readers too (announce.State.Readers); both are honoured. const VaultBusAccounts = "mesh-vault.bus-accounts" // NamedReaders are the states only the modules named may read, by `.`. var NamedReaders = map[string][]string{VaultBusAccounts: {GrantsModule}} // ReaderMay says whether module may read the state read (`.`) by the named readers: always, for a state // that names none. func ReaderMay(module, read string) bool { readers, named := NamedReaders[read] return !named || slices.Contains(readers, module) } // readsNamed splits a module's reads by the named readers: those it may read, and those named for others. func readsNamed(module string, reads []string) (granted, refused []string) { for _, r := range reads { if ReaderMay(module, strings.TrimSpace(r)) { granted = append(granted, r) } else { refused = append(refused, r) } } return granted, refused } // withNamedReaders is d with the reads named for other modules moved to its refused reads. func withNamedReaders(d Declared) Declared { granted, refused := readsNamed(d.Module, d.Reads) if len(refused) == 0 { return d } d.Reads = granted d.ReadsRefused = append(append([]string(nil), d.ReadsRefused...), refused...) return d }