The grants part follows the assignment half, reads each node's reading and the runner its node-engine says running, and where the runner does not state approval-mark/1 or the reading is not the running runner's, composes the subscribe deny of every marked tool and verb the runtime carries, named in grants_user_list's withheld and answered by the seat's verb withheld. Builds: design 46 7 §6 (the deny's subjects, its cases, withheld in grants_user_list and the verb withheld), design 47 G §195, §309, lead answer A-203 (the assignment half followed by revision and assignment-revision; withheld's forms, keyed by node in the user list's state as the lead confirmed) Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
253 lines
9.7 KiB
Go
253 lines
9.7 KiB
Go
package grants
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"slices"
|
|
"sync"
|
|
|
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
|
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshwantsclient"
|
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
|
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshdelivery"
|
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshwants"
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
"git.novox.be/novox/mesh-sdk/go/views"
|
|
|
|
"mesh-grants/internal/buscompose"
|
|
)
|
|
|
|
// **The grants part reads the catalog half** (design 47 G §614, §444, §684, A-5, A-52): through meshcatalogclient's branch follower
|
|
// (Branches), adopted on the part's views.ProcessReader, which holds the part's finding writer, so the part's `view-waits` is the
|
|
// reader's. The manifest of the build a want or a have names is the catalog graph's entry of that fingerprint (Resolve), never a
|
|
// builder's build.
|
|
|
|
// catalogHalfVerbs are the verbs every part that reads the catalog half names in its `invokes` (design 47 G §614): the revision of a
|
|
// branch, the registrations, the gate's source and the entry by fingerprint. (The gate's source is named in the short form: the
|
|
// long form with its field `branches`, §558, is not read by mesh-sdk's manifest reader yet.)
|
|
func catalogHalfVerbs() []string {
|
|
return []string{"seat:" + meshcatalog.Seat + "." + meshcatalog.VerbRevision, "seat:" + meshcatalog.Seat + "." + meshcatalog.VerbRegistrations,
|
|
meshcatalogclient.SourceVerb, meshcatalogclient.EntryVerb,
|
|
// And the assignment half's revision (design 46 approval mark §6, design 47 G §195).
|
|
meshwantsclient.RevisionVerb}
|
|
}
|
|
|
|
// catalogHalfEvents are the events every such part names in its `consumes`, as `<seat>.<event>`: the catalog's revision and
|
|
// registration-changed, and the gate's source-moved.
|
|
func catalogHalfEvents() []string {
|
|
return []string{meshcatalog.Seat + "." + meshcatalog.EventCatalogRevision, meshcatalog.Seat + "." + meshcatalog.EventRegistrationChanged,
|
|
meshdelivery.Seat + "." + meshdelivery.EventSourceMoved, meshwants.Seat + "." + meshwants.EventAssignmentRevision}
|
|
}
|
|
|
|
// CatalogHalf is what the part reads of the catalog half: the entry by its fingerprint, the branches whose first view is not in,
|
|
// and what a tool says of both.
|
|
type CatalogHalf interface {
|
|
// Resolve reads the entry of fingerprint: from a followed branch's half, else asked of `entry` once and kept.
|
|
Resolve(fingerprint string) (meshcatalog.CatalogEntry, meshcatalogclient.EntryState)
|
|
// Waiting says why the part has not every first view of the catalog half, or nil when it has them all: the branches
|
|
// followed whose first view is not in, or that none is followed yet.
|
|
Waiting() []string
|
|
// Statuses are the first view of each followed branch, in the order followed.
|
|
Statuses() []views.Status
|
|
// Entries are the entries kept by fingerprint, with their state.
|
|
Entries() []meshcatalogclient.EntryRow
|
|
}
|
|
|
|
// CatalogOf is the part's catalog half over the branch follower. Context is the process's life the entry asks run in; Wake
|
|
// starts a pass, and is called when a followed branch's first view comes in, which may change nothing a view's own change says.
|
|
type CatalogOf struct {
|
|
Branches *meshcatalogclient.Branches
|
|
Context context.Context
|
|
Wake func()
|
|
|
|
mu sync.Mutex
|
|
watched map[string]bool
|
|
}
|
|
|
|
// Resolve reads fingerprint's entry (meshcatalogclient.Branches.Resolve).
|
|
func (c *CatalogOf) Resolve(fingerprint string) (meshcatalog.CatalogEntry, meshcatalogclient.EntryState) {
|
|
return c.Branches.Resolve(c.Context, fingerprint)
|
|
}
|
|
|
|
// Waiting says which first views of the catalog half are not in.
|
|
func (c *CatalogOf) Waiting() []string {
|
|
followed := c.Branches.Followed()
|
|
if len(followed) == 0 {
|
|
return []string{"no branch is followed yet (the registrations and the gate's source are not answered)"}
|
|
}
|
|
var out []string
|
|
for _, b := range followed {
|
|
if cat, ok := c.Branches.Catalog(b); ok {
|
|
select {
|
|
case <-cat.Ready():
|
|
default:
|
|
out = append(out, "the catalog half of the branch "+b)
|
|
c.wakeAt(b, cat)
|
|
}
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// wakeAt starts a pass when branch's first view comes in, once per branch.
|
|
func (c *CatalogOf) wakeAt(branch string, cat *meshcatalogclient.Catalog) {
|
|
c.mu.Lock()
|
|
if c.watched == nil {
|
|
c.watched = map[string]bool{}
|
|
}
|
|
done := c.watched[branch]
|
|
c.watched[branch] = true
|
|
c.mu.Unlock()
|
|
if done || c.Wake == nil {
|
|
return
|
|
}
|
|
go func() {
|
|
select {
|
|
case <-cat.Ready():
|
|
c.Wake()
|
|
case <-c.Context.Done():
|
|
}
|
|
}()
|
|
}
|
|
|
|
// Statuses are the followed branches' first views.
|
|
func (c *CatalogOf) Statuses() []views.Status {
|
|
var out []views.Status
|
|
for _, b := range c.Branches.Followed() {
|
|
if cat, ok := c.Branches.Catalog(b); ok {
|
|
out = append(out, cat.Follower.Status())
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// Entries are the entries kept by fingerprint.
|
|
func (c *CatalogOf) Entries() []meshcatalogclient.EntryRow { return c.Branches.Entries.Rows() }
|
|
|
|
// catalogReader is what a Reader that holds the catalog half answers (Views does): ReadInputs reads the want's entry through it.
|
|
type catalogReader interface {
|
|
Resolve(fingerprint string) (meshcatalog.CatalogEntry, meshcatalogclient.EntryState)
|
|
Waiting() []string
|
|
}
|
|
|
|
// ErrNoCatalog is an entry asked of a reader that holds no catalog half (a tool's).
|
|
var ErrNoCatalog = fmt.Errorf("this reader holds no catalog half")
|
|
|
|
// Resolve reads an entry by its fingerprint through the part's catalog half; none held answers asked.
|
|
func (v *Views) Resolve(fingerprint string) (meshcatalog.CatalogEntry, meshcatalogclient.EntryState) {
|
|
if v == nil || v.Catalog == nil {
|
|
return meshcatalog.CatalogEntry{}, ""
|
|
}
|
|
return v.Catalog.Resolve(fingerprint)
|
|
}
|
|
|
|
// Waiting says which first views of the catalog half are not in; nil where none is held (a tool's reader waits for none).
|
|
func (v *Views) Waiting() []string {
|
|
if v == nil || v.Catalog == nil {
|
|
return nil
|
|
}
|
|
return v.Catalog.Waiting()
|
|
}
|
|
|
|
// entryManifest is the manifest of the entry of fingerprint in the catalog half, kept by fingerprint. state is the entry's
|
|
// state as the half answers it ("" where the reader holds no catalog half); a manifest is answered only with an `answered` entry.
|
|
func (c *Manifests) entryManifest(r Reader, fingerprint string) (catalogue.Manifest, meshcatalogclient.EntryState, error) {
|
|
cr, ok := r.(catalogReader)
|
|
if !ok {
|
|
return catalogue.Manifest{}, "", ErrNoCatalog
|
|
}
|
|
c.mu.Lock()
|
|
m, ok := c.byFp[fingerprint]
|
|
c.mu.Unlock()
|
|
if ok {
|
|
return m, meshcatalogclient.EntryAnswered, nil
|
|
}
|
|
entry, state := cr.Resolve(fingerprint)
|
|
if state != meshcatalogclient.EntryAnswered {
|
|
return catalogue.Manifest{}, state, fmt.Errorf("the catalog half's entry of %s is %s", fingerprint, orNone(string(state)))
|
|
}
|
|
raw, err := json.Marshal(entry.Manifest)
|
|
if err != nil || len(entry.Manifest) == 0 {
|
|
return catalogue.Manifest{}, state, fmt.Errorf("the entry of %s holds no manifest", fingerprint)
|
|
}
|
|
// Decoded, never judged, as a manifest by digest is (Manifests.get).
|
|
m, err = catalogue.Decode(raw)
|
|
if err != nil {
|
|
return catalogue.Manifest{}, state, fmt.Errorf("the manifest of the entry of %s does not decode: %w", fingerprint, err)
|
|
}
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
if c.byFp == nil {
|
|
c.byFp = map[string]catalogue.Manifest{}
|
|
}
|
|
c.byFp[fingerprint] = m
|
|
return m, state, nil
|
|
}
|
|
|
|
func orNone(s string) string {
|
|
if s == "" {
|
|
return "not held"
|
|
}
|
|
return s
|
|
}
|
|
|
|
// catalogWaiting is why this pass composes before the catalog half's first views (design 47 G §684): the part keeps the user list it
|
|
// last wrote and adds what the entries of the builds wanted give.
|
|
func catalogWaiting(r Reader) string {
|
|
cr, ok := r.(catalogReader)
|
|
if !ok {
|
|
return ""
|
|
}
|
|
w := cr.Waiting()
|
|
if len(w) == 0 {
|
|
return ""
|
|
}
|
|
slices.Sort(w)
|
|
return fmt.Sprintf("the first views of the catalog half are not in (%v), so each account is composed from its want's entry by fingerprint and the list in force is kept", w)
|
|
}
|
|
|
|
// **The grants part reads the assignment half** (design 46 approval mark §6, design 47 G §195, lead answer A-203): the view of
|
|
// meshwantsclient's Assignment, a first view by mesh-wants' `revision`, then its `assignment-revision`, adopted on the part's
|
|
// views reader; from it each node's capability reading and the runner that announced it.
|
|
|
|
// AssignmentHalf is what the part reads of the assignment half: the revision it holds, false while it holds none.
|
|
type AssignmentHalf interface {
|
|
Revision() (meshwants.AssignmentRevision, bool)
|
|
}
|
|
|
|
// readingsReader is what a Reader that holds the assignment half answers (Views does).
|
|
type readingsReader interface {
|
|
Readings() (map[string]buscompose.RunnerReading, bool)
|
|
}
|
|
|
|
// Readings are each node's capability reading of the assignment half the part holds, by node; false where it holds none,
|
|
// and true with none where the reader follows no assignment half (a tool's reads nothing of it).
|
|
func (v *Views) Readings() (map[string]buscompose.RunnerReading, bool) {
|
|
if v == nil || v.Assignment == nil {
|
|
return nil, true
|
|
}
|
|
rev, ok := v.Assignment.Revision()
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
out := map[string]buscompose.RunnerReading{}
|
|
for _, c := range rev.Capabilities {
|
|
out[c.Node] = buscompose.RunnerReading{Capabilities: c.Readings, Fingerprint: c.Fingerprint}
|
|
}
|
|
return out, true
|
|
}
|
|
|
|
// readingsOf is each node's reading through r, and why the pass composes before the assignment half's first view.
|
|
func readingsOf(r Reader) (map[string]buscompose.RunnerReading, string) {
|
|
rr, ok := r.(readingsReader)
|
|
if !ok {
|
|
return nil, ""
|
|
}
|
|
readings, held := rr.Readings()
|
|
if !held {
|
|
return nil, "the first view of the assignment half is not in, so no node's capability reading is known and the list in force is kept"
|
|
}
|
|
return readings, ""
|
|
}
|