Put a version of a module on a node with one tool call: the assigner's put, release and puts (ADR 0546) #49
@@ -30,9 +30,9 @@ import (
|
||||
|
||||
func main() {
|
||||
if os.Getenv("MESH_BROKER_FILE") == "" {
|
||||
// Started by the tool runner, with no credential of its own: the seat's verbs (ADR 0546), served as every seat a module
|
||||
// holds is, since a process never serves (ADR 0436). The process the manifest runs names its credential.
|
||||
if err := serveSeat(); err != nil {
|
||||
// Started by the tool runner, with no credential of its own: the put's tools (ADR 0546), served through the runtime,
|
||||
// since a process never serves (ADR 0436). The process the manifest runs names its credential.
|
||||
if err := serveTools(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "mesh-delivery-assigner:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ func (m *memKV) Update(_ context.Context, key string, value []byte, revision uin
|
||||
return uint64(len(value)), nil
|
||||
}
|
||||
|
||||
// memStore is the runtime's states for the seat's verbs, over the same memKVs a pass writes.
|
||||
// memStore is the runtime's states for the put's tools, over the same memKVs a pass writes.
|
||||
type memStore map[string]*memKV
|
||||
|
||||
func (s memStore) get(state, key string) ([]byte, uint64, bool, error) {
|
||||
@@ -167,8 +167,8 @@ func (b *fakeBus) Emit(eventType string, body any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ADR 0546, the first put (2026-10-25, want.novox.nats): the verb writes the put; a pass asks the controller's compose verb;
|
||||
// once the composition and grant are there it asks the operator with a yes that approves; only on that warrant does it have
|
||||
// ADR 0546, the first put (2026-10-25, want.novox.nats): the tool writes the put; a pass waits for the composition and grant
|
||||
// the controller's take-in writes; once they are there it asks the operator with a yes that approves; only on that warrant does it have
|
||||
// the streams snapshotted, then writes the want held and records the warrant; the have running it healthy holds it; a release
|
||||
// gives the want back to the rules. Each step is said as the event put.
|
||||
func putOfTheBus(t *testing.T) {
|
||||
@@ -213,7 +213,7 @@ func putOfTheBus(t *testing.T) {
|
||||
builds.values[bkey] = built
|
||||
modules := mem()
|
||||
modules.values["nats"] = mod
|
||||
s := newSeat(memStore{putsState: puts, delivery.BuilderModule + ".builds": builds, delivery.IndexerModule + ".modules": modules})
|
||||
s := newPutTools(memStore{putsState: puts, delivery.BuilderModule + ".builds": builds, delivery.IndexerModule + ".modules": modules})
|
||||
s.now = func() time.Time { return now }
|
||||
was := answerWithin
|
||||
answerWithin = 0
|
||||
@@ -245,13 +245,14 @@ func putOfTheBus(t *testing.T) {
|
||||
}
|
||||
return got
|
||||
}
|
||||
if got := step(); got.Step != delivery.PutCompose {
|
||||
if got := step(); got.Step != delivery.PutCompose || !strings.Contains(got.Words, "no composition") {
|
||||
t.Fatalf("the first pass left the put at %s: %s", got.Step, got.Words)
|
||||
}
|
||||
if call := <-bus.calls; !strings.HasPrefix(call, "seat:mesh-controller.compose") || !strings.Contains(call, fp) {
|
||||
t.Errorf("the controller was asked %q", call)
|
||||
select {
|
||||
case call := <-bus.calls:
|
||||
t.Errorf("a put asked %q: it reads the compositions and asks nobody for one", call)
|
||||
default:
|
||||
}
|
||||
<-woke
|
||||
ckey, _ := delivery.ComposedKey("novox", "nats", fp)
|
||||
cd, _ := delivery.EncodeComposedDigest(delivery.ComposedDigest{Digest: digest, Judgement: delivery.JudgedCannot, Words: "the have names no composition"})
|
||||
world(delivery.ControllerComposedBucket, ckey, cd)
|
||||
|
||||
@@ -16,12 +16,12 @@ import (
|
||||
"mesh-delivery-assigner/internal/assign"
|
||||
)
|
||||
|
||||
// The puts (novox/hq ADR 0546): the seat's verb `put` writes the key `<node>.<module>` of the assigner's puts, and each pass
|
||||
// takes every put that has not ended one step further (assign.StepPut), doing the acts the step asks for: the controller's
|
||||
// compose verb, the put's ask, the bus's snapshot, the warranted record. Each act's outcome is heard back as a change of the
|
||||
// world (the composition, the grant, the answer) or, for the calls, as a wake once they answer; nothing polls but the
|
||||
// snapshot's read-back of the backup holder, as the controller's bus step reads it (ADR 0235), bounded. Every step is said as
|
||||
// the event mesh-delivery-assigner.put, so a caller waits on it.
|
||||
// The puts (novox/hq ADR 0546): the tool `assigner_put` writes the key `<node>.<module>` of the assigner's puts, and each pass
|
||||
// takes every put that has not ended one step further (assign.StepPut), doing the acts the step asks for: the put's ask, the
|
||||
// bus's snapshot, the warranted record. Each act's outcome is heard back as a change of the world (the composition, the grant,
|
||||
// the answer) or, for the calls, as a wake once they answer; nothing polls but the snapshot's read-back of the backup holder,
|
||||
// as the controller's bus step reads it (ADR 0235), bounded. Every step is said as the event mesh-delivery-assigner.put, so a
|
||||
// caller waits on it.
|
||||
|
||||
// PutEvent is the event a put's every step is said as: mesh.mod.mesh-delivery-assigner.event.put.
|
||||
const PutEvent = "put"
|
||||
@@ -35,14 +35,12 @@ type caller interface {
|
||||
}
|
||||
|
||||
// putActs keeps, by put (its key and when it was asked), what the acts of a put came to while this process runs: a restart
|
||||
// asks the controller and the snapshot again, which compose and back up again without harm.
|
||||
// takes the snapshot again, without harm.
|
||||
type putActs struct {
|
||||
mu sync.Mutex
|
||||
bus caller
|
||||
wake func()
|
||||
logf func(string, ...any)
|
||||
composing map[string]time.Time
|
||||
composeNo map[string]string
|
||||
snapping map[string]bool
|
||||
snapped map[string]string
|
||||
snapNo map[string]string
|
||||
@@ -53,8 +51,7 @@ type putActs struct {
|
||||
}
|
||||
|
||||
func newPutActs(bus caller, wake func(), logf func(string, ...any)) *putActs {
|
||||
a := &putActs{bus: bus, wake: wake, logf: logf, composing: map[string]time.Time{}, composeNo: map[string]string{},
|
||||
snapping: map[string]bool{}, snapped: map[string]string{}, snapNo: map[string]string{}, warranted: map[string]bool{},
|
||||
a := &putActs{bus: bus, wake: wake, logf: logf, snapping: map[string]bool{}, snapped: map[string]string{}, snapNo: map[string]string{}, warranted: map[string]bool{},
|
||||
said: map[string]string{}}
|
||||
a.snapshot = a.snapshotTheBus
|
||||
return a
|
||||
@@ -118,12 +115,12 @@ func (p *passer) puts(ctx context.Context) {
|
||||
key, _ := delivery.PutKey(node, module)
|
||||
id := identity(key, put)
|
||||
world := p.putWorld(node, module, put, manifest, assigned[node])
|
||||
world.ComposeAsked, world.ComposeFailed, world.Snapshot, world.Snapshotting, world.SnapshotFailed = p.acts.state(id)
|
||||
world.Snapshot, world.Snapshotting, world.SnapshotFailed = p.acts.state(id)
|
||||
next := assign.StepPut(world)
|
||||
// A held bus whose user list the rules moved on (ADR 0539 §7): the put follows the composition its want names.
|
||||
if w, ok := wanted[module][node]; ok && w.Put && w.FP == next.Put.FP && w.Composed != "" && w.Composed != next.Put.Composed &&
|
||||
(next.Put.Step == delivery.PutWanted || next.Put.Step == delivery.PutHolding) {
|
||||
if d, there := p.w.composed(node, module, w.FP); there && d.UserListOnly && d.Digest == w.Composed {
|
||||
if d, _ := p.composedFor(node, module, w.FP); d != nil && d.UserListOnly && d.Digest == w.Composed {
|
||||
next.Put = next.Put.Said(world.Now, next.Put.Step, fmt.Sprintf("the bus's user list changed: the want names composition %s", shortHex(w.Composed)))
|
||||
next.Put.Composed = w.Composed
|
||||
}
|
||||
@@ -167,8 +164,8 @@ func (p *passer) putWorld(node, module string, put delivery.Put, manifest catalo
|
||||
}
|
||||
}
|
||||
}
|
||||
if d, ok := p.w.composed(node, module, put.FP); ok {
|
||||
w.Composed = &d
|
||||
w.Composed, w.Granted = p.composedFor(node, module, put.FP)
|
||||
if d := w.Composed; d != nil {
|
||||
policy := d.Policy
|
||||
if policy == "" {
|
||||
policy, _, _ = catalogue.DerivedUpgrade(manifest)
|
||||
@@ -183,13 +180,6 @@ func (p *passer) putWorld(node, module string, put delivery.Put, manifest catalo
|
||||
}
|
||||
}
|
||||
}
|
||||
if gkey, err := delivery.ControllerGrantKey(module, put.FP); err == nil {
|
||||
if raw := p.w.value(delivery.ControllerGrantsBucket, gkey); raw != nil {
|
||||
if g, err := delivery.DecodeControllerGrant(raw); err == nil {
|
||||
w.Granted = g.NamesNode(node, module)
|
||||
}
|
||||
}
|
||||
}
|
||||
if hkey, err := delivery.HaveKey(node, module); err == nil {
|
||||
if raw := p.w.value(delivery.HaveBucket, hkey); raw != nil {
|
||||
if h, err := delivery.DecodeHave(raw); err == nil {
|
||||
@@ -200,11 +190,28 @@ func (p *passer) putWorld(node, module string, put delivery.Put, manifest catalo
|
||||
return w
|
||||
}
|
||||
|
||||
// composedFor is the composition of module for node at fp and whether the grant at fp names the node's accounts: the one
|
||||
// place a put reads them (ADR 0546 clause 4). Today their writer is the controller (mesh-controller_composed and
|
||||
// mesh-controller_grants); when composing and grants move out of the controller into delivery's own parts, this reads them
|
||||
// from their new owner and nothing else changes.
|
||||
func (p *passer) composedFor(node, module, fp string) (*delivery.ComposedDigest, bool) {
|
||||
var out *delivery.ComposedDigest
|
||||
if d, ok := p.w.composed(node, module, fp); ok {
|
||||
out = &d
|
||||
}
|
||||
granted := false
|
||||
if gkey, err := delivery.ControllerGrantKey(module, fp); err == nil {
|
||||
if raw := p.w.value(delivery.ControllerGrantsBucket, gkey); raw != nil {
|
||||
if g, err := delivery.DecodeControllerGrant(raw); err == nil {
|
||||
granted = g.NamesNode(node, module)
|
||||
}
|
||||
}
|
||||
}
|
||||
return out, granted
|
||||
}
|
||||
|
||||
// actOn does the acts a step asks for.
|
||||
func (p *passer) actOn(ctx context.Context, node, module, id string, w assign.PutWorld, next assign.PutNext) {
|
||||
if next.Compose {
|
||||
p.acts.compose(ctx, id, node, module, next.Put.FP)
|
||||
}
|
||||
if next.Ask && w.Composed != nil {
|
||||
akey := next.Put.Ask
|
||||
if !p.w.has(delivery.AssignerAsksBucket, akey) {
|
||||
@@ -229,7 +236,7 @@ func samePut(a, b delivery.Put) bool {
|
||||
return a.Step == b.Step && a.Words == b.Words && a.Composed == b.Composed && a.Ask == b.Ask && a.Snapshot == b.Snapshot
|
||||
}
|
||||
|
||||
// writePut writes a put's next value over the one the pass read, by compare-and-set, unless the verb has put again or
|
||||
// writePut writes a put's next value over the one the pass read, by compare-and-set, unless the tool has put again or
|
||||
// released it since; and says it as the event put.
|
||||
func (p *passer) writePut(ctx context.Context, key string, read, next delivery.Put) {
|
||||
kv := p.kvs[delivery.AssignerPutsBucket]
|
||||
@@ -262,55 +269,10 @@ func (p *passer) writePut(ctx context.Context, key string, read, next delivery.P
|
||||
}
|
||||
|
||||
// state is what this process knows of a put's acts.
|
||||
func (a *putActs) state(id string) (asked time.Time, composeNo, snapshot string, snapping bool, snapNo string) {
|
||||
func (a *putActs) state(id string) (snapshot string, snapping bool, snapNo string) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
return a.composing[id], a.composeNo[id], a.snapped[id], a.snapping[id], a.snapNo[id]
|
||||
}
|
||||
|
||||
// composeAnswer is what the controller's compose verb answers inside its seat's answer ({output, ok, answer}).
|
||||
type composeAnswer struct {
|
||||
OK bool `json:"ok"`
|
||||
Output string `json:"output"`
|
||||
Answer *struct {
|
||||
Digest string `json:"digest"`
|
||||
Granted bool `json:"granted"`
|
||||
Grant string `json:"grant"`
|
||||
} `json:"answer"`
|
||||
}
|
||||
|
||||
// compose asks the controller's compose verb once for a put. Its answer wakes a pass; the composition and the grant are
|
||||
// read from their buckets, and a refusal is kept as the put's words.
|
||||
func (a *putActs) compose(ctx context.Context, id, node, module, fp string) {
|
||||
a.mu.Lock()
|
||||
if _, asked := a.composing[id]; asked {
|
||||
a.mu.Unlock()
|
||||
return
|
||||
}
|
||||
a.composing[id] = time.Now()
|
||||
a.mu.Unlock()
|
||||
go func() {
|
||||
defer a.wake()
|
||||
raw, err := a.bus.Call("seat:"+catalogue.ControllerSeatName+".compose", map[string]any{"node": node, "module": module, "fp": fp})
|
||||
why := ""
|
||||
var ans composeAnswer
|
||||
switch {
|
||||
case err != nil:
|
||||
why = err.Error()
|
||||
case json.Unmarshal(raw, &ans) != nil:
|
||||
// An answer of another shape: the controller still running it after its answer window. The buckets say the rest.
|
||||
case !ans.OK && strings.TrimSpace(ans.Output) != "":
|
||||
why = strings.TrimSpace(ans.Output)
|
||||
case ans.Answer != nil && ans.Answer.Digest != "" && !ans.Answer.Granted:
|
||||
why = ans.Answer.Grant
|
||||
}
|
||||
if why != "" {
|
||||
a.mu.Lock()
|
||||
a.composeNo[id] = why
|
||||
a.mu.Unlock()
|
||||
a.logf("the controller's compose of %s on %s at %s: %s", module, node, fp, why)
|
||||
}
|
||||
}()
|
||||
return a.snapped[id], a.snapping[id], a.snapNo[id]
|
||||
}
|
||||
|
||||
// snap has the bus's snapshot taken once for a put, in the background.
|
||||
|
||||
+70
-35
@@ -6,35 +6,36 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// The assigner's seat (novox/hq ADR 0546), served through the tool runner as every seat a module holds is: a process never
|
||||
// serves (ADR 0436, mesh-sdk process). The verbs write and read the puts; the assigner's process takes each put further
|
||||
// (puts.go) and says every step as the event put.
|
||||
// The assigner's tools (novox/hq ADR 0546), served through the tool runner from its bundle: a process never serves (ADR
|
||||
// 0436, mesh-sdk process). They write and read the puts; the assigner's process takes each put further (puts.go) and says
|
||||
// every step as the event put. They are the module's own tools, not a seat's verbs: a new seat needs a change of the
|
||||
// controller, which takes none; they become the verbs of a seat once composing leaves the controller.
|
||||
|
||||
// putsState is the assigner's state of puts, as the runtime names a module's own state.
|
||||
const putsState = "puts"
|
||||
|
||||
// answerWithin is how long `put` waits for the assigner's process to take the put up before it answers what stands.
|
||||
// answerWithin is how long assigner_put waits for the assigner's process to take the put up before it answers what stands.
|
||||
var answerWithin = 20 * time.Second
|
||||
|
||||
// store is what the verbs need of the states: the runtime's (stdio.State), or a test's.
|
||||
// store is what the tools need of the states: the runtime's (stdio.State), or a test's.
|
||||
type store interface {
|
||||
get(state, key string) ([]byte, uint64, bool, error)
|
||||
put(state, key string, value []byte) error
|
||||
keys(state string) ([]string, error)
|
||||
}
|
||||
|
||||
// seat serves the verbs over a store, and keeps the newest value of every put it heard, to wait on.
|
||||
type seat struct {
|
||||
// putTools serves the tools over a store, and keeps the newest value of every put it heard, to wait on.
|
||||
type putTools struct {
|
||||
st store
|
||||
now func() time.Time
|
||||
mu sync.Mutex
|
||||
@@ -43,12 +44,12 @@ type seat struct {
|
||||
changed chan struct{}
|
||||
}
|
||||
|
||||
func newSeat(st store) *seat {
|
||||
return &seat{st: st, now: time.Now, heard: map[string][]byte{}, changed: make(chan struct{})}
|
||||
func newPutTools(st store) *putTools {
|
||||
return &putTools{st: st, now: time.Now, heard: map[string][]byte{}, changed: make(chan struct{})}
|
||||
}
|
||||
|
||||
// hear takes a put's change in from the watch.
|
||||
func (s *seat) hear(key string, value []byte) {
|
||||
func (s *putTools) hear(key string, value []byte) {
|
||||
s.mu.Lock()
|
||||
s.heard[key] = value
|
||||
close(s.changed)
|
||||
@@ -64,7 +65,7 @@ var (
|
||||
|
||||
// version reads the version a put names: newest (the indexer's fingerprint at the trunk), a whole fingerprint, or the start
|
||||
// of one the builder built, which must name one build alone.
|
||||
func (s *seat) version(module, version string) (string, error) {
|
||||
func (s *putTools) version(module, version string) (string, error) {
|
||||
v := strings.ToLower(strings.TrimSpace(version))
|
||||
switch {
|
||||
case v == "newest":
|
||||
@@ -110,7 +111,7 @@ func (s *seat) version(module, version string) (string, error) {
|
||||
return "", fmt.Errorf("%q is no version: newest, a fingerprint (src2: and 64 hex digits) or at least its first 12 hex digits", version)
|
||||
}
|
||||
|
||||
// putAnswer is what `put` and `puts` answer of one put.
|
||||
// putAnswer is what assigner_put and assigner_puts answer of one put.
|
||||
type putAnswer struct {
|
||||
Node string `json:"node"`
|
||||
Module string `json:"module"`
|
||||
@@ -118,8 +119,8 @@ type putAnswer struct {
|
||||
Follow string `json:"follow,omitempty"`
|
||||
}
|
||||
|
||||
// doPut is the verb put.
|
||||
func (s *seat) doPut(args map[string]any, by string) (any, error) {
|
||||
// doPut is the tool assigner_put.
|
||||
func (s *putTools) doPut(args map[string]any, by string) (any, error) {
|
||||
node, module, why := arg(args, "node"), arg(args, "module"), arg(args, "why")
|
||||
key, err := delivery.PutKey(node, module)
|
||||
if err != nil {
|
||||
@@ -165,7 +166,7 @@ func (s *seat) doPut(args map[string]any, by string) (any, error) {
|
||||
}
|
||||
|
||||
// await is the put at key once the assigner's process took it up past its asking, or as it stands after answerWithin.
|
||||
func (s *seat) await(key string, p delivery.Put) delivery.Put {
|
||||
func (s *putTools) await(key string, p delivery.Put) delivery.Put {
|
||||
deadline := time.After(answerWithin)
|
||||
for {
|
||||
s.mu.Lock()
|
||||
@@ -188,11 +189,11 @@ func (s *seat) await(key string, p delivery.Put) delivery.Put {
|
||||
// followPut says how a caller follows a put after the answer.
|
||||
func followPut(node, module string) string {
|
||||
return fmt.Sprintf("the put goes on: mesh_wait on mesh-delivery-assigner.put with filter {\"node\": %q, \"module\": %q}, then "+
|
||||
"mesh-delivery-assigner.puts for where it stands", node, module)
|
||||
"mesh-delivery-assigner.assigner_puts for where it stands", node, module)
|
||||
}
|
||||
|
||||
// doRelease is the verb release.
|
||||
func (s *seat) doRelease(args map[string]any, by string) (any, error) {
|
||||
// doRelease is the tool assigner_release.
|
||||
func (s *putTools) doRelease(args map[string]any, by string) (any, error) {
|
||||
node, module, why := arg(args, "node"), arg(args, "module"), arg(args, "why")
|
||||
key, err := delivery.PutKey(node, module)
|
||||
if err != nil {
|
||||
@@ -227,8 +228,8 @@ func (s *seat) doRelease(args map[string]any, by string) (any, error) {
|
||||
return putAnswer{Node: node, Module: module, Put: p}, nil
|
||||
}
|
||||
|
||||
// doPuts is the verb puts.
|
||||
func (s *seat) doPuts(args map[string]any) (any, error) {
|
||||
// doPuts is the tool assigner_puts.
|
||||
func (s *putTools) doPuts(args map[string]any) (any, error) {
|
||||
keys, err := s.st.keys(putsState)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the puts cannot be listed: %w", err)
|
||||
@@ -263,21 +264,55 @@ func arg(args map[string]any, name string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// tools are the seat's verbs as the runtime serves them.
|
||||
func (s *seat) tools() []stdio.Tool {
|
||||
verbs := map[string]catalogue.Verb{}
|
||||
if seat, ok := catalogue.SeatNamed(catalogue.AssignerSeat); ok {
|
||||
for _, v := range seat.Serves {
|
||||
verbs[v.Name] = v
|
||||
// The tools' names, as the manifest lists them.
|
||||
const (
|
||||
toolPut = "assigner_put"
|
||||
toolRelease = "assigner_release"
|
||||
toolPuts = "assigner_puts"
|
||||
)
|
||||
|
||||
// schema is a tool's arguments, each a string: required ones first named, and switches true or false.
|
||||
func schema(properties map[string]string, required []string, switches ...string) map[string]any {
|
||||
props := map[string]any{}
|
||||
for name, description := range properties {
|
||||
p := map[string]any{"type": "string", "description": description}
|
||||
if slices.Contains(switches, name) {
|
||||
p["enum"] = []string{"true", "false"}
|
||||
}
|
||||
props[name] = p
|
||||
}
|
||||
tool := func(name string, run func(map[string]any) (any, error)) stdio.Tool {
|
||||
return stdio.Tool{Name: catalogue.AssignerSeat + "." + name, Description: verbs[name].Description, Input: verbs[name].Input, Run: run}
|
||||
out := map[string]any{"type": "object", "properties": props}
|
||||
if len(required) > 0 {
|
||||
out["required"] = required
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// tools are the assigner's tools as the runtime serves them (ADR 0546 clause 1).
|
||||
func (s *putTools) tools() []stdio.Tool {
|
||||
return []stdio.Tool{
|
||||
tool("put", func(a map[string]any) (any, error) { return s.doPut(a, stdio.CallerOf(a)) }),
|
||||
tool("release", func(a map[string]any) (any, error) { return s.doRelease(a, stdio.CallerOf(a)) }),
|
||||
tool("puts", s.doPuts),
|
||||
{Name: toolPut, Description: "Put one version of a module on one machine (novox/hq ADR 0546): the build at that fingerprint, " +
|
||||
"the composition and grant the controller wrote for that machine, then the machine's want naming it, held there until " +
|
||||
"released when hold is true. A module whose moves a person takes (the bus, a module of policy record) is asked of the " +
|
||||
"operator first, and the bus has its streams snapshotted after the yes. Answers what was done or what it waits on; the " +
|
||||
"put goes on after the answer, each step said as the event mesh-delivery-assigner.put, and assigner_puts says where it stands.",
|
||||
Input: schema(map[string]string{
|
||||
"node": "the machine, where the module is assigned",
|
||||
"module": "the module",
|
||||
"version": "newest, a fingerprint (src2: and 64 hex digits), or at least its first 12 hex digits",
|
||||
"hold": "\"true\": the want stays at this version until released; \"false\" (the default): the rules move it again once it runs",
|
||||
"why": "why, kept with the put and said in any ask",
|
||||
}, []string{"node", "module", "version", "why"}, "hold"),
|
||||
Run: func(a map[string]any) (any, error) { return s.doPut(a, stdio.CallerOf(a)) }},
|
||||
{Name: toolRelease, Description: "End a put on one machine (novox/hq ADR 0546): its hold is taken away and the assigner's " +
|
||||
"rules write the machine's want again from their next pass. Nothing changes on the machine until they move it.",
|
||||
Input: schema(map[string]string{"node": "the machine", "module": "the module", "why": "why, kept with the put"},
|
||||
[]string{"node", "module", "why"}),
|
||||
Run: func(a map[string]any) (any, error) { return s.doRelease(a, stdio.CallerOf(a)) }},
|
||||
{Name: toolPuts, Description: "Every put and where it stands (novox/hq ADR 0546): its version, hold, why, each step with " +
|
||||
"its time and words. Narrowed by machine or module. Read-only.",
|
||||
Input: schema(map[string]string{"node": "one machine", "module": "one module"}, nil),
|
||||
Run: s.doPuts},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,9 +334,9 @@ func (runtimeStore) put(state, key string, value []byte) error {
|
||||
|
||||
func (runtimeStore) keys(state string) ([]string, error) { return stdio.State(state).Keys() }
|
||||
|
||||
// serveSeat serves the seat's verbs to the runtime until it stops, hearing every put to answer `put` with its first steps.
|
||||
func serveSeat() error {
|
||||
s := newSeat(runtimeStore{})
|
||||
// serveTools serves the tools to the runtime until it stops, hearing every put to answer assigner_put with its first steps.
|
||||
func serveTools() error {
|
||||
s := newPutTools(runtimeStore{})
|
||||
go func() {
|
||||
<-stdio.Served()
|
||||
for wait := 2 * time.Second; ; wait = min(wait*2, time.Minute) {
|
||||
@@ -3,7 +3,7 @@ module mesh-delivery-assigner
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261025195051-20a6dc4920f8
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261025200810-d4ae4b6790b8
|
||||
github.com/nats-io/nats-server/v2 v2.11.17
|
||||
github.com/nats-io/nats.go v1.54.0
|
||||
)
|
||||
|
||||
@@ -2,6 +2,8 @@ git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024210118-748c564cdf7c h1:pY2YpHSS
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024210118-748c564cdf7c/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261025195051-20a6dc4920f8 h1:jA7V7hlfJAigVUvJDpKwfgd9ylQ9bqppg3rK9XgLWoA=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261025195051-20a6dc4920f8/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261025200810-d4ae4b6790b8 h1:vhYk/iv43u2WGSTrFyQGDvGBxLaRITgMAiiI9DjuA10=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261025200810-d4ae4b6790b8/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
||||
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||
|
||||
@@ -8,11 +8,12 @@ import (
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// A put (novox/hq ADR 0546): one version of a module put on one node by the assigner seat's verb. The verb writes the put
|
||||
// A put (novox/hq ADR 0546): one version of a module put on one node by the assigner's tool assigner_put. The tool writes the put
|
||||
// key; each pass takes it one step further from what the world holds (StepPut), and the rules pass over the node's want for
|
||||
// the module from its asking until it ends (Module.Put).
|
||||
// the module from its asking until it ends (Module.Puts).
|
||||
|
||||
// ComposeWithin is how long a put waits for the controller's composition after it asked for it before it is refused.
|
||||
// ComposeWithin is how long a put waits for the composition and grant it needs (a take-in of a build just done writes them)
|
||||
// before it is refused.
|
||||
const ComposeWithin = 10 * time.Minute
|
||||
|
||||
// PutWorld is what one step of a put reads.
|
||||
@@ -27,14 +28,10 @@ type PutWorld struct {
|
||||
Build, BuildWords string
|
||||
// Newest is the module's newest fingerprint at the trunk, which the builder builds by itself.
|
||||
Newest string
|
||||
// Composed is the controller's composition of the module for the node at the fingerprint; nil when there is none.
|
||||
// Composed is the composition of the module for the node at the fingerprint, as its writer holds it; nil when there is
|
||||
// none. Granted says the grant at the fingerprint names the node's accounts. Both are read, never asked for (clause 4).
|
||||
Composed *delivery.ComposedDigest
|
||||
// Granted says the controller's grant at the fingerprint names the node's accounts.
|
||||
Granted bool
|
||||
// ComposeAsked is when this put asked the controller's compose verb, zero when it has not; ComposeFailed the words
|
||||
// of its answer when it refused.
|
||||
ComposeAsked time.Time
|
||||
ComposeFailed string
|
||||
Granted bool
|
||||
// Answer is the option the operator chose on the put's ask for the composition, "" when none.
|
||||
Answer string
|
||||
// Snapshot says where the bus's streams were snapshotted for this put, once they were; Snapshotting that it was
|
||||
@@ -50,8 +47,6 @@ type PutWorld struct {
|
||||
// PutNext is what a step decides: the put as it stands after it, and the acts the pass does for it.
|
||||
type PutNext struct {
|
||||
Put delivery.Put
|
||||
// Compose asks the controller's compose verb for the composition at the fingerprint.
|
||||
Compose bool
|
||||
// Ask writes the put's ask about the move (PutAsk).
|
||||
Ask bool
|
||||
// Snapshot has the bus's node back the bus up now, its dump the streams' snapshot (ADR 0235).
|
||||
@@ -88,20 +83,19 @@ func StepPut(w PutWorld) PutNext {
|
||||
return say(delivery.PutBuild, "waits for the builder's build of %s at %s, which is %s", w.Module, fp, w.Build)
|
||||
}
|
||||
if w.Composed == nil || !w.Granted {
|
||||
switch {
|
||||
case w.ComposeFailed != "":
|
||||
return say(delivery.PutRefused, "the controller did not compose %s for %s at %s: %s", w.Module, w.Node, fp, w.ComposeFailed)
|
||||
case w.ComposeAsked.IsZero():
|
||||
next := say(delivery.PutCompose, "asked the controller to issue the accounts of %s on %s, write its grant and compose it at %s", w.Module, w.Node, fp)
|
||||
next.Compose = true
|
||||
return next
|
||||
case w.Now.Sub(w.ComposeAsked) > ComposeWithin:
|
||||
return say(delivery.PutRefused, "the controller wrote no composition of %s for %s at %s with a grant naming %s within %s of the ask", w.Module, w.Node, fp, w.Node, ComposeWithin)
|
||||
case w.Composed != nil:
|
||||
return say(delivery.PutCompose, "waits for the controller's grant of %s at %s to name %s's accounts", w.Module, fp, w.Node)
|
||||
default:
|
||||
return say(delivery.PutCompose, "waits for the controller's composition of %s for %s at %s", w.Module, w.Node, fp)
|
||||
missing := fmt.Sprintf("no composition of %s for %s at %s is written", w.Module, w.Node, fp)
|
||||
if w.Composed != nil {
|
||||
missing = fmt.Sprintf("the grant of %s at %s does not name %s's accounts", w.Module, fp, w.Node)
|
||||
}
|
||||
since := w.Now
|
||||
if p.Step == delivery.PutCompose && len(p.Steps) > 0 {
|
||||
since = p.Steps[len(p.Steps)-1].At
|
||||
}
|
||||
if w.Now.Sub(since) > ComposeWithin {
|
||||
return say(delivery.PutRefused, "%s after %s: only a composition written by the take-in of its build (or one of the five kept) is put; "+
|
||||
"for another the hand path is mesh-hand-compose (ADR 0539 §10)", missing, ComposeWithin)
|
||||
}
|
||||
return say(delivery.PutCompose, "waits up to %s: %s yet", ComposeWithin, missing)
|
||||
}
|
||||
d := *w.Composed
|
||||
if p.Step == delivery.PutWanted || p.Step == delivery.PutHolding {
|
||||
|
||||
@@ -492,8 +492,8 @@ func (m Module) userListRevision(node string, before delivery.Want, had bool, n
|
||||
return w, true
|
||||
}
|
||||
|
||||
// GrantNamesNode says whether a grant of module names its accounts on node (mesh-sdk's ControllerGrant.NamesNode, which the
|
||||
// controller's compose verb answers by too).
|
||||
// GrantNamesNode says whether a grant of module names its accounts on node (mesh-sdk's ControllerGrant.NamesNode, which a
|
||||
// put reads by too).
|
||||
func GrantNamesNode(g delivery.ControllerGrant, node, module string) bool {
|
||||
return g.NamesNode(node, module)
|
||||
}
|
||||
|
||||
@@ -438,49 +438,44 @@ func stepsOfAPut(t *testing.T) {
|
||||
Composed: &d, Granted: true, Have: &running, Now: now}
|
||||
}
|
||||
cases := []struct {
|
||||
name string
|
||||
world func() PutWorld
|
||||
step string
|
||||
says string
|
||||
compose, ask, snap bool
|
||||
warranted bool
|
||||
name string
|
||||
world func() PutWorld
|
||||
step string
|
||||
says string
|
||||
ask, snap bool
|
||||
warranted bool
|
||||
}{
|
||||
{"not assigned", func() PutWorld { w := bus(); w.Assigned = false; return w }, delivery.PutRefused, "not assigned", false, false, false, false},
|
||||
{"no build yet of the newest", func() PutWorld { w := bus(); w.Build = ""; return w }, delivery.PutBuild, "builds by itself", false, false, false, false},
|
||||
{"no build of an older one", func() PutWorld { w := bus(); w.Build, w.Newest = "", fpOf('c'); return w }, delivery.PutRefused, "holds no build", false, false, false, false},
|
||||
{"a failed build", func() PutWorld { w := bus(); w.Build, w.BuildWords = delivery.BuildFailed, "go vet"; return w }, delivery.PutRefused, "go vet", false, false, false, false},
|
||||
{"no composition: asks the controller", func() PutWorld { w := bus(); w.Composed = nil; return w }, delivery.PutCompose, "asked the controller", true, false, false, false},
|
||||
{"composing", func() PutWorld { w := bus(); w.Composed, w.ComposeAsked = nil, now.Add(-time.Minute); return w }, delivery.PutCompose, "waits for the controller's composition", false, false, false, false},
|
||||
{"composed, grant not naming the node", func() PutWorld { w := bus(); w.Granted, w.ComposeAsked = false, now.Add(-time.Minute); return w }, delivery.PutCompose, "grant", false, false, false, false},
|
||||
{"the controller refused", func() PutWorld {
|
||||
{"not assigned", func() PutWorld { w := bus(); w.Assigned = false; return w }, delivery.PutRefused, "not assigned", false, false, false},
|
||||
{"no build yet of the newest", func() PutWorld { w := bus(); w.Build = ""; return w }, delivery.PutBuild, "builds by itself", false, false, false},
|
||||
{"no build of an older one", func() PutWorld { w := bus(); w.Build, w.Newest = "", fpOf('c'); return w }, delivery.PutRefused, "holds no build", false, false, false},
|
||||
{"a failed build", func() PutWorld { w := bus(); w.Build, w.BuildWords = delivery.BuildFailed, "go vet"; return w }, delivery.PutRefused, "go vet", false, false, false},
|
||||
{"no composition yet: waits", func() PutWorld { w := bus(); w.Composed = nil; return w }, delivery.PutCompose, "no composition of nats for novox", false, false, false},
|
||||
{"composed, grant not naming the node", func() PutWorld { w := bus(); w.Granted = false; return w }, delivery.PutCompose, "does not name novox", false, false, false},
|
||||
{"none within the bound: refused", func() PutWorld {
|
||||
w := bus()
|
||||
w.Composed, w.ComposeAsked, w.ComposeFailed = nil, now, "no lease"
|
||||
w.Composed = nil
|
||||
w.Put = w.Put.Said(now.Add(-ComposeWithin-time.Second), delivery.PutCompose, "waits")
|
||||
return w
|
||||
}, delivery.PutRefused, "no lease", false, false, false, false},
|
||||
{"the controller silent too long", func() PutWorld {
|
||||
w := bus()
|
||||
w.Composed, w.ComposeAsked = nil, now.Add(-ComposeWithin-time.Second)
|
||||
return w
|
||||
}, delivery.PutRefused, "within", false, false, false, false},
|
||||
{"the bus asks the operator", bus, delivery.PutPerson, "waits for the operator's yes", false, true, false, false},
|
||||
{"the operator said no", func() PutWorld { w := bus(); w.Answer = OptionNo; return w }, delivery.PutRefused, "answered no", false, false, false, false},
|
||||
{"yes: the snapshot is asked", func() PutWorld { w := bus(); w.Answer = OptionYes; return w }, delivery.PutSnapshot, "asked novox's backup holder", false, false, true, false},
|
||||
{"the snapshot is taken", func() PutWorld { w := bus(); w.Answer, w.Snapshotting = OptionYes, true; return w }, delivery.PutSnapshot, "waits for novox's backup holder", false, false, false, false},
|
||||
{"the snapshot failed", func() PutWorld { w := bus(); w.Answer, w.SnapshotFailed = OptionYes, "no restic"; return w }, delivery.PutRefused, "no restic", false, false, false, false},
|
||||
}, delivery.PutRefused, "mesh-hand-compose", false, false, false},
|
||||
{"the bus asks the operator", bus, delivery.PutPerson, "waits for the operator's yes", true, false, false},
|
||||
{"the operator said no", func() PutWorld { w := bus(); w.Answer = OptionNo; return w }, delivery.PutRefused, "answered no", false, false, false},
|
||||
{"yes: the snapshot is asked", func() PutWorld { w := bus(); w.Answer = OptionYes; return w }, delivery.PutSnapshot, "asked novox's backup holder", false, true, false},
|
||||
{"the snapshot is taken", func() PutWorld { w := bus(); w.Answer, w.Snapshotting = OptionYes, true; return w }, delivery.PutSnapshot, "waits for novox's backup holder", false, false, false},
|
||||
{"the snapshot failed", func() PutWorld { w := bus(); w.Answer, w.SnapshotFailed = OptionYes, "no restic"; return w }, delivery.PutRefused, "no restic", false, false, false},
|
||||
{"snapshotted: the want, warranted", func() PutWorld {
|
||||
w := bus()
|
||||
w.Answer, w.Snapshot = OptionYes, "novox's restore point"
|
||||
w.Put.Ask, _ = delivery.RecordAskKey("novox", "nats", d.Digest)
|
||||
return w
|
||||
}, delivery.PutWanted, "held until released; the streams were snapshotted: novox's restore point", false, false, false, true},
|
||||
}, delivery.PutWanted, "held until released; the streams were snapshotted: novox's restore point", false, false, true},
|
||||
{"a user-list revision of the bus needs nobody", func() PutWorld {
|
||||
w := bus()
|
||||
ul := d
|
||||
ul.UserListOnly = true
|
||||
w.Composed, w.Have = &ul, &delivery.Have{FP: fNew, Composed: dig(fOld), Phase: delivery.PhaseRunning, Health: delivery.HealthOK}
|
||||
return w
|
||||
}, delivery.PutWanted, "held until released", false, false, false, false},
|
||||
{"a module not recorded needs nobody", func() PutWorld { w := bus(); w.Bus = false; return w }, delivery.PutWanted, "names", false, false, false, false},
|
||||
}, delivery.PutWanted, "held until released", false, false, false},
|
||||
{"a module not recorded needs nobody", func() PutWorld { w := bus(); w.Bus = false; return w }, delivery.PutWanted, "names", false, false, false},
|
||||
{"a recorded module judged to recreate asks", func() PutWorld {
|
||||
w := bus()
|
||||
w.Bus, w.Recorded = false, true
|
||||
@@ -488,14 +483,14 @@ func stepsOfAPut(t *testing.T) {
|
||||
r.Judgement = delivery.JudgedRecreates
|
||||
w.Composed = &r
|
||||
return w
|
||||
}, delivery.PutPerson, "waits for the operator's yes", false, true, false, false},
|
||||
}, delivery.PutPerson, "waits for the operator's yes", true, false, false},
|
||||
{"wanted, the node runs it: holding", func() PutWorld {
|
||||
w := bus()
|
||||
w.Put = w.Put.Said(now, delivery.PutWanted, "wanted")
|
||||
w.Put.Composed = d.Digest
|
||||
w.Have = &delivery.Have{FP: fNew, Composed: d.Digest, Phase: delivery.PhaseRunning, Health: delivery.HealthOK}
|
||||
return w
|
||||
}, delivery.PutHolding, "held there until released", false, false, false, false},
|
||||
}, delivery.PutHolding, "held there until released", false, false, false},
|
||||
{"wanted without hold: done", func() PutWorld {
|
||||
w := bus()
|
||||
w.Put.Hold = false
|
||||
@@ -503,17 +498,17 @@ func stepsOfAPut(t *testing.T) {
|
||||
w.Put.Composed = d.Digest
|
||||
w.Have = &delivery.Have{FP: fNew, Composed: d.Digest, Phase: delivery.PhaseRunning, Health: delivery.HealthOK}
|
||||
return w
|
||||
}, delivery.PutDone, "the rules write its want again", false, false, false, false},
|
||||
{"released: nothing more", func() PutWorld { w := bus(); w.Put = w.Put.Said(now, delivery.PutReleased, "released"); return w }, delivery.PutReleased, "released", false, false, false, false},
|
||||
}, delivery.PutDone, "the rules write its want again", false, false, false},
|
||||
{"released: nothing more", func() PutWorld { w := bus(); w.Put = w.Put.Said(now, delivery.PutReleased, "released"); return w }, delivery.PutReleased, "released", false, false, false},
|
||||
}
|
||||
for _, c := range cases {
|
||||
next := StepPut(c.world())
|
||||
if next.Put.Step != c.step || !strings.Contains(next.Put.Words, c.says) {
|
||||
t.Errorf("put, %s: at %s, %q; want %s saying %q", c.name, next.Put.Step, next.Put.Words, c.step, c.says)
|
||||
}
|
||||
if next.Compose != c.compose || next.Ask != c.ask || next.Snapshot != c.snap || next.Warranted != c.warranted {
|
||||
t.Errorf("put, %s: acts compose %v ask %v snapshot %v warranted %v; want %v %v %v %v", c.name, next.Compose, next.Ask,
|
||||
next.Snapshot, next.Warranted, c.compose, c.ask, c.snap, c.warranted)
|
||||
if next.Ask != c.ask || next.Snapshot != c.snap || next.Warranted != c.warranted {
|
||||
t.Errorf("put, %s: acts ask %v snapshot %v warranted %v; want %v %v %v", c.name, next.Ask, next.Snapshot, next.Warranted,
|
||||
c.ask, c.snap, c.warranted)
|
||||
}
|
||||
if next.Ask && next.Put.Ask == "" {
|
||||
t.Errorf("put, %s: an ask without its key", c.name)
|
||||
|
||||
@@ -56,18 +56,13 @@ func TestManifest(t *testing.T) {
|
||||
if !slices.Equal(m.Uses, []string{"node-build-go", "operator-channel"}) {
|
||||
t.Errorf("uses = %v, want exactly node-build-go and operator-channel", m.Uses)
|
||||
}
|
||||
// ADR 0546: it holds its seat (put, release, puts), served by its bundle through the tool runner (the artifact loads the
|
||||
// module), says each put's step as the event put, and calls the controller's compose and warranted and the bus node's
|
||||
// backup holder.
|
||||
if len(m.Claims) != 1 || m.Claims[0].Name != catalogue.AssignerSeat || len(m.Tools) != 0 {
|
||||
t.Errorf("the assigner claims %v and serves tools %v, want its own seat alone", m.Claims, m.Tools)
|
||||
// ADR 0546: it serves its own tools (put, release, puts) from its bundle through the tool runner (the artifact loads the
|
||||
// module), claims no seat (a new seat needs a change of the controller), says each put's step as the event put, and
|
||||
// calls the controller's warranted and the bus node's backup holder.
|
||||
if len(m.Claims) != 0 || !slices.Equal(m.Tools, []string{"assigner_put", "assigner_release", "assigner_puts"}) {
|
||||
t.Errorf("the assigner claims %v and serves tools %v, want no claim and its three put tools", m.Claims, m.Tools)
|
||||
}
|
||||
if seat, ok := catalogue.SeatNamed(catalogue.AssignerSeat); !ok {
|
||||
t.Errorf("the sdk defines no seat %s", catalogue.AssignerSeat)
|
||||
} else if err := catalogue.CanHold(m, seat); err != nil {
|
||||
t.Errorf("the assigner cannot hold its seat: %v", err)
|
||||
}
|
||||
if want := []string{"seat:mesh-controller.compose", "seat:mesh-controller.warranted", "seat:node-backup.backed-up", "seat:node-backup.now"}; !slices.Equal(slices.Sorted(slices.Values(m.Invokes)), want) {
|
||||
if want := []string{"seat:mesh-controller.warranted", "seat:node-backup.backed-up", "seat:node-backup.now"}; !slices.Equal(slices.Sorted(slices.Values(m.Invokes)), want) {
|
||||
t.Errorf("invokes = %v, want %v", m.Invokes, want)
|
||||
}
|
||||
if !slices.Equal(m.Emits, []string{"put"}) {
|
||||
|
||||
@@ -2,17 +2,6 @@
|
||||
"module": "mesh-delivery-assigner",
|
||||
"version": "1",
|
||||
"slug": "assigner",
|
||||
"claims": [
|
||||
{
|
||||
"name": "mesh-delivery-assigner",
|
||||
"scope": "mesh",
|
||||
"serves": [
|
||||
"put",
|
||||
"release",
|
||||
"puts"
|
||||
]
|
||||
}
|
||||
],
|
||||
"reads": [
|
||||
"mesh-build-go.steps",
|
||||
"mesh-controller.assignments",
|
||||
@@ -32,7 +21,6 @@
|
||||
"operator-channel"
|
||||
],
|
||||
"invokes": [
|
||||
"seat:mesh-controller.compose",
|
||||
"seat:mesh-controller.warranted",
|
||||
"seat:node-backup.now",
|
||||
"seat:node-backup.backed-up"
|
||||
@@ -40,6 +28,11 @@
|
||||
"emits": [
|
||||
"put"
|
||||
],
|
||||
"tools": [
|
||||
"assigner_put",
|
||||
"assigner_release",
|
||||
"assigner_puts"
|
||||
],
|
||||
"state": [
|
||||
{ "name": "asks", "history": 5 },
|
||||
{ "name": "compositions", "history": 5 },
|
||||
|
||||
@@ -3,7 +3,7 @@ module mesh-delivery-teller
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261025195051-20a6dc4920f8
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261025200810-d4ae4b6790b8
|
||||
github.com/nats-io/nats-server/v2 v2.11.17
|
||||
github.com/nats-io/nats.go v1.54.0
|
||||
)
|
||||
|
||||
@@ -2,6 +2,8 @@ git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261023150321-a2c813a6b468 h1:UcMee+IV
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261023150321-a2c813a6b468/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261025195051-20a6dc4920f8 h1:jA7V7hlfJAigVUvJDpKwfgd9ylQ9bqppg3rK9XgLWoA=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261025195051-20a6dc4920f8/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261025200810-d4ae4b6790b8 h1:vhYk/iv43u2WGSTrFyQGDvGBxLaRITgMAiiI9DjuA10=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261025200810-d4ae4b6790b8/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
||||
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||
|
||||
Reference in New Issue
Block a user