The grants part writes its list as an object of its own bucket, sealed to the bus node's key, and names it in user-list.sealed; the node-engine of the node holding mesh-broker is granted that one key and an object by name, the grants part its bucket (the controller's copy carries the same grants, byte for byte, golden regenerated there). A state may name its readers (announce, NamedReaders): mesh-vault.bus-accounts is read by mesh-grants alone.
112 lines
4.9 KiB
Go
112 lines
4.9 KiB
Go
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 `<owner>.<name>`.
|
|
var NamedReaders = map[string][]string{VaultBusAccounts: {GrantsModule}}
|
|
|
|
// ReaderMay says whether module may read the state read (`<owner>.<name>`) 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
|
|
}
|