Files
mesh-delivery/modules/mesh-grants/internal/buscompose/objects.go
T
jschoubben 0b03b9c7c5 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
2026-11-02 18:56:19 +01:00

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
}