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
117 lines
4.1 KiB
Go
117 lines
4.1 KiB
Go
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
|
|
}
|