mesh-grants: create a module's object buckets and history streams, grant their readers read-only, and warn at 75 percent
ADR 0592 §5, §6, §9, §25 (the grants part's share by ADR 0593 §10): an object bucket and a stream that keeps its history are created create-only with their caps and no age limit, refusing new data when full; the owner alone writes its bucket and a module its grant names reads it; the part raises mesh-grants.history-full once any passes 75 percent. Each engine also reads the indexer's manifest verdicts by one key for compose-candidate. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
package buscompose
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
)
|
||||
|
||||
// A module's own object bucket (novox/hq ADR 0592 §5, §9). The grants part creates it once, create-only, kept with no
|
||||
// age limit and refusing a new object when full; only its module writes it; another module reads it only through a
|
||||
// `reads` entry naming it and the owner's trusted config value of the bucket's grant naming that module, read-only, as a
|
||||
// restricted stream is heard (ADR 0363 §4, §5). The condition when it passes 75 percent of its cap is the grants part's
|
||||
// (grants.FullCondition).
|
||||
|
||||
// ObjectBucket is one object bucket a module owns, as the bus holds it.
|
||||
type ObjectBucket struct {
|
||||
Module string
|
||||
Name string
|
||||
// MaxMiB is its cap, catalogue.ObjectsLeastMiB to catalogue.ObjectsMostMiB.
|
||||
MaxMiB int
|
||||
// Restricted says it names a grant, so a module the owner's trusted config value names reads it.
|
||||
Restricted bool
|
||||
}
|
||||
|
||||
// Bucket is its name on the bus, `<module>_<name>` (catalogue.ObjectsBucket).
|
||||
func (o ObjectBucket) Bucket() string { return catalogue.ObjectsBucket(o.Module, o.Name) }
|
||||
|
||||
// Stream is the stream the bus keeps it in.
|
||||
func (o ObjectBucket) Stream() string { return "OBJ_" + o.Bucket() }
|
||||
|
||||
// ObjectsStreamConfig is a module's object bucket as the stream the client library's object store makes of it, with
|
||||
// the mesh's rule: no age limit, its own cap, and a new object refused when full rather than an old one dropped (ADR
|
||||
// 0592 §5); file storage and one replica, as a state's.
|
||||
func ObjectsStreamConfig(o ObjectBucket) jetstream.StreamConfig {
|
||||
b := o.Bucket()
|
||||
return jetstream.StreamConfig{
|
||||
Name: o.Stream(),
|
||||
Description: fmt.Sprintf("%s's object bucket %q (novox/hq ADR 0592 §5): written by %s alone, read by the modules its "+
|
||||
"grant names, kept with no age limit and at most %d MiB, a new object refused when full", o.Module, o.Name, o.Module, o.MaxMiB),
|
||||
Subjects: []string{"$O." + b + ".C.>", "$O." + b + ".M.>"},
|
||||
MaxBytes: int64(o.MaxMiB) * 1024 * 1024,
|
||||
Storage: jetstream.FileStorage,
|
||||
Replicas: 1,
|
||||
Discard: jetstream.DiscardNew,
|
||||
AllowRollup: true,
|
||||
AllowDirect: true,
|
||||
MaxMsgs: -1,
|
||||
MaxConsumers: -1,
|
||||
}
|
||||
}
|
||||
|
||||
// objectReadGrants is what reading one object bucket needs and nothing more: binding it, a direct get of an object's
|
||||
// metadata, an ordered consumer on its chunks or its names (a list or a watch), that consumer's deletion and flow
|
||||
// control. No publish under `$O.`, so no write; no purge.
|
||||
func objectReadGrants(bucket string) []string {
|
||||
stream := "OBJ_" + bucket
|
||||
return []string{
|
||||
"$JS.API.STREAM.INFO." + stream,
|
||||
"$JS.API.DIRECT.GET." + stream + ".>",
|
||||
"$JS.API.CONSUMER.CREATE." + stream + ".>",
|
||||
"$JS.API.CONSUMER.DELETE." + stream + ".>",
|
||||
"$JS.FC." + stream + ".>",
|
||||
}
|
||||
}
|
||||
|
||||
// objectGrants is what a principal publishes for its module's own object buckets (written, and a replaced or deleted
|
||||
// object's chunks purged) and for those it reads (read-only), never their creation, which is the grants part's.
|
||||
func objectGrants(own, reads []string) []string {
|
||||
var out []string
|
||||
for _, b := range own {
|
||||
if !safeSubject.MatchString(b) {
|
||||
continue
|
||||
}
|
||||
out = append(out, "$O."+b+".>", "$JS.API.STREAM.PURGE.OBJ_"+b)
|
||||
out = append(out, objectReadGrants(b)...)
|
||||
}
|
||||
for _, b := range reads {
|
||||
if safeSubject.MatchString(b) {
|
||||
out = append(out, objectReadGrants(b)...)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// objectNames is the bus names of a module's own object buckets.
|
||||
func objectNames(objects []ObjectBucket) []string {
|
||||
out := make([]string, 0, len(objects))
|
||||
for _, o := range objects {
|
||||
out = append(out, o.Bucket())
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// writesOwnObjects is a module writing one of its own object buckets (ADR 0592 §5), itself or through the runtime that
|
||||
// carries it, and nothing of another's.
|
||||
func writesOwnObjects(p Principal, pattern string) bool {
|
||||
switch p.Kind {
|
||||
case KindModule:
|
||||
for _, b := range p.Objects {
|
||||
if pattern == "$O."+b+".>" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case KindNodeTools:
|
||||
for _, d := range p.Carries {
|
||||
for _, b := range objectNames(d.Objects) {
|
||||
if pattern == "$O."+b+".>" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user