sdk/forms could not import the reference package: the registry client's module requires the delivery sdk (parts). builds already requires the client and both seats, and both users of forms (builder, assigner) require builds. Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
115 lines
4.2 KiB
Go
115 lines
4.2 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/nats-io/nats.go/jetstream"
|
|
|
|
sweep "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshartifactsweep"
|
|
|
|
"mesh-delivery-builder/internal/build"
|
|
)
|
|
|
|
// eventsStream is the stream every module's and role's events are on, where the grants part makes the builder's durable
|
|
// consumer of artifacts-gone (mesh-sdk buscompose EventsStream).
|
|
const eventsStream = "EVENTS"
|
|
|
|
// SeatHeld is the seat mesh-artifact-sweep's verb held (ADR 0571 §2).
|
|
const SeatHeld = "seat:" + sweep.Seat + ".held"
|
|
|
|
// toolCaller calls a tool or a seat verb as the builder's own account (mesh-sdk process.Conn.Call).
|
|
type toolCaller func(key string, body any) (json.RawMessage, error)
|
|
|
|
// storedVia is build.Builder's Stored: it asks the seat mesh-artifact-sweep's `held` (novox/hq ADR 0571 §2, issue 1937)
|
|
// and answers the references the store no longer holds. One it could not ask about, with none gone, is an error: the key
|
|
// is then left done, and asked again at the next module value or artifacts-gone.
|
|
func storedVia(call toolCaller) func(ctx context.Context, references []string) ([]string, error) {
|
|
return func(_ context.Context, references []string) ([]string, error) {
|
|
body := map[string]any{"references": references}
|
|
raw, err := call(SeatHeld, body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("asking held: %w", err)
|
|
}
|
|
a, err := decodeHeld(raw, references)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(a.Gone) == 0 && len(a.Unknown) > 0 {
|
|
var why []string
|
|
for r, w := range a.Unknown {
|
|
why = append(why, r+": "+w)
|
|
}
|
|
sort.Strings(why)
|
|
return nil, fmt.Errorf("the artifact store could not be asked about %s", strings.Join(why, "; "))
|
|
}
|
|
return a.Gone, nil
|
|
}
|
|
}
|
|
|
|
// heardGone takes one artifacts-gone event in: acknowledged, then handed to the builder, which asks the store and builds
|
|
// the key again. A body that is not one is said and acknowledged, so it is not handed over again.
|
|
func heardGone(ctx context.Context, b *build.Builder, m jetstream.Msg, logf func(string, ...any)) {
|
|
e, err := decodeGone(m.Data())
|
|
if ackErr := m.Ack(); ackErr != nil {
|
|
logf("acknowledging %s: %v", m.Subject(), ackErr)
|
|
}
|
|
if err != nil {
|
|
logf("%s holds what is not %s, left: %v", m.Subject(), sweep.EventArtifactsGone, err)
|
|
return
|
|
}
|
|
b.ArtifactsGone(ctx, e)
|
|
}
|
|
|
|
// decodeHeld reads the seat's answer to held: the verb answers {output, ok, answer}, the answer being the seat's
|
|
// HeldAnswer. A reference asked that the answer names in none of held, gone and unknown is an error: an answer that
|
|
// leaves one out would let a key with a gone blob be reused.
|
|
func decodeHeld(raw []byte, asked []string) (sweep.HeldAnswer, error) {
|
|
var wrapped struct {
|
|
Output string `json:"output"`
|
|
OK bool `json:"ok"`
|
|
Answer json.RawMessage `json:"answer"`
|
|
}
|
|
if err := json.Unmarshal(raw, &wrapped); err != nil {
|
|
return sweep.HeldAnswer{}, fmt.Errorf("the answer of %s is not {output, ok, answer}: %w", SeatHeld, err)
|
|
}
|
|
if !wrapped.OK || len(wrapped.Answer) == 0 {
|
|
return sweep.HeldAnswer{}, fmt.Errorf("%s did not answer: %s", SeatHeld, wrapped.Output)
|
|
}
|
|
var a sweep.HeldAnswer
|
|
if err := json.Unmarshal(wrapped.Answer, &a); err != nil {
|
|
return sweep.HeldAnswer{}, fmt.Errorf("the answer of %s is not {held, gone}: %w", SeatHeld, err)
|
|
}
|
|
named := map[string]bool{}
|
|
for _, r := range a.Held {
|
|
named[r] = true
|
|
}
|
|
for _, r := range a.Gone {
|
|
named[r] = true
|
|
}
|
|
for r := range a.Unknown {
|
|
named[r] = true
|
|
}
|
|
for _, r := range asked {
|
|
if !named[r] {
|
|
return sweep.HeldAnswer{}, fmt.Errorf("the answer of %s names nothing of %s", SeatHeld, r)
|
|
}
|
|
}
|
|
return a, nil
|
|
}
|
|
|
|
// decodeGone reads the event's body, refusing one that names no module, fingerprint or reference.
|
|
func decodeGone(raw []byte) (sweep.ArtifactsGone, error) {
|
|
var e sweep.ArtifactsGone
|
|
if err := json.Unmarshal(raw, &e); err != nil {
|
|
return sweep.ArtifactsGone{}, fmt.Errorf("%s is not one: %w", sweep.EventArtifactsGone, err)
|
|
}
|
|
if e.Event != sweep.EventArtifactsGone || e.Module == "" || e.Fingerprint == "" || len(e.Gone) == 0 {
|
|
return sweep.ArtifactsGone{}, fmt.Errorf("%s names no module, fingerprint or gone reference: %s", sweep.EventArtifactsGone, raw)
|
|
}
|
|
return e, nil
|
|
}
|