// Package approval is the tool runner's side of mesh-sdk's approval gate (go/approval; design 47 G lead answers A-224, // A-228): it translates what the gate reads beyond itself from the generated contracts, the gate's findings into the // contract's findings about the runner on its machine, the catalog's entries into the gate's, and the assigner's view of // the seats' holders into its holder function. The gate itself is mesh-sdk's; none is written here. package approval import ( "context" "encoding/json" "fmt" "time" assignerseat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" condseat "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions" catalogseat "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog" gate "git.novox.be/novox/mesh-sdk/go/approval" "git.novox.be/novox/mesh-sdk/go/views" ) // HeldWriter is the runner's held finding writer (views.HeldWriter over the contract's Finding). type HeldWriter interface { Write(ctx context.Context, finding condseat.Finding) error Clear(ctx context.Context, scope, id, kind, why string) error } // scopeMachine is the scope of the gate's findings on the tool runner: its machine (lead answer A-154). const scopeMachine = "machine" // Findings is the gate's finding writer over the runner's held writer: each finding about the runner's module on node, at // scope machine, and its pending named in the writer's `findings-stated` where the writer keeps one (design 47 G §384). func Findings(w HeldWriter, node, runner string, now func() time.Time) gate.FindingWriter { if w == nil { return nil } if now == nil { now = time.Now } return findingWriter{w: w, node: node, runner: runner, now: now} } type findingWriter struct { w HeldWriter node, runner string now func() time.Time } func (f findingWriter) Raise(ctx context.Context, kind, id, summary, headline string) error { return f.w.Write(ctx, condseat.Finding{Kind: kind, Scope: scopeMachine, ID: id, Severity: condseat.FindingSeverityWarning, Source: "approval", Observer: f.runner, Subject: condseat.Subject{Scope: "module", ID: f.runner, Machine: f.node}, Summary: summary, Headline: headline, At: f.now().UTC()}) } func (f findingWriter) Clear(ctx context.Context, kind, id, why string) error { return f.w.Clear(ctx, scopeMachine, id, kind, why) } // pender is the held writer's pending (views.HeldWriter's Pend and Judged). type pender interface { Pend(views.PendingKey) Judged(key string) } func (f findingWriter) Pend(kind, id string, since time.Time, bound time.Duration) { if p, ok := f.w.(pender); ok { p.Pend(views.PendingKey{Key: views.FindingKey(scopeMachine, id, kind), Since: since, Bound: bound.String()}) } } func (f findingWriter) Judged(kind, id string) { if p, ok := f.w.(pender); ok { p.Judged(views.FindingKey(scopeMachine, id, kind)) } } // Entry is a catalog entry as the gate reads its marks. func Entry(e catalogseat.CatalogEntry) gate.Entry { manifest, _ := json.Marshal(e.Manifest) return gate.Entry{Module: e.Module, Fingerprint: e.Fp, Serves: e.Serves, Manifest: manifest} } // HoldersIn is the gate's holder function over the assigner's view of the seats' holders as the runner holds it (§19): // view answers the view's values by key, an error while its first view is not answered; a seat is held when its key names // a holder. func HoldersIn(view func() (map[string][]byte, error)) func(seat string) (bool, error) { return func(seat string) (bool, error) { values, err := view() if err != nil { return false, err } raw, ok := values[assignerseat.SeatHoldersBySeatKey(seat)] if !ok { return false, nil } var h assignerseat.SeatHolders if err := json.Unmarshal(raw, &h); err != nil { return false, fmt.Errorf("the holders of %s cannot be read: %w", seat, err) } return len(h.Holders) > 0, nil } }