A read the bus did not answer is tried again after a bound, never kept as an answer: the shadow's answer, the node's composition and the own-secret step (issue 1126)

Met twice on 2026-10-28: shanks kept 'want.shanks.brother could not be read ... no
responders' as the own-secret step's last word, and ace kept 'the vault's own: not yet
... no responders' as its answer to seed-7, each until another input moved. Every bus
read of the composer (manifests, builds, archives, served values, the vault's own and
credentials, the have of what runs) is noted (BusFailed); an answer or a pass that met
one is made again after a growing bound (5 s to 5 min), and a pass is not handed when
the have of its sealed values did not answer. The own-secret step tells a read or an
ask that did not answer (Retry, taken again) from a key absent or the vault's word.
This commit is contained in:
2026-10-28 12:11:08 +01:00
parent 4182f8fbfb
commit cd1d2ff447
8 changed files with 306 additions and 66 deletions
+14 -9
View File
@@ -75,8 +75,12 @@ func (h sessionHaves) Read(ctx context.Context, module string) (agent.Have, bool
// ReadErr is Read telling a have absent from one the bus could not read (agent.HavesReadErr).
func (h sessionHaves) ReadErr(ctx context.Context, module string) (agent.Have, bool, error) {
if module == agent.NodeModule {
got, ok := h.Read(ctx, module)
return got, ok, nil
// The node's own key, its read the bus did not answer told as such (issue 1126, 2026-10-28).
got, ok, err := h.session.ReadNodeHaveErr(ctx)
if !ok || err != nil || got.Composed == "" {
return agent.Have{}, false, err
}
return agent.Have{Module: module, Composed: got.Composed, Sealed: got.SealedAt}, true, nil
}
got, ok, err := h.session.ReadHaveErr(ctx, module)
if !ok || err != nil {
@@ -535,16 +539,17 @@ func nodeAgent(ctx context.Context, opts options, signer ed25519.PublicKey, sess
},
unseal: sealOpener(opts.state),
// And what each module's have says it runs sealed: after a restart the agent has applied nothing yet.
held: func(module string) (delivery.Seals, []string) {
// A have the bus did not answer for is said as such, never as one holding no seals (issue 1126, 2026-10-28).
held: func(module string) (delivery.Seals, []string, error) {
if module == agent.NodeModule {
got, _ := session.ReadNodeHave(linked)
return got.SealedAt, got.Sealed
got, _, err := session.ReadNodeHaveErr(linked)
return got.SealedAt, got.Sealed, err
}
got, ok := session.ReadHave(linked, module)
got, ok, err := session.ReadHaveErr(linked, module)
if !ok {
return nil, nil
return nil, nil, err
}
return got.SealedAt, got.Sealed
return got.SealedAt, got.Sealed, nil
},
refused: func(why string) {
if now := own.get(); now != nil && run.Err() == nil {
@@ -879,7 +884,7 @@ type composerHooks struct {
running func() []delivery.Composed
unseal func(string) ([]byte, error)
refused func(string)
held func(module string) (delivery.Seals, []string)
held func(module string) (delivery.Seals, []string, error)
}
func selfComposer(ctx context.Context, session *link.Session, node string, say func(string), hooks composerHooks) (*selfcompose.Composer, string) {
+36 -3
View File
@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"sync"
"time"
"git.novox.be/novox/mesh-sdk/go/delivery"
@@ -30,6 +31,10 @@ type resyncStep struct {
one sync.Mutex
mu sync.Mutex
last map[string]delivery.CandidateAnswer
// tries and pending are the step taken again after a bound when a read or an ask the bus did not answer stopped it
// (Outcome.Retry, issue 1126, 2026-10-28), one wait at a time.
tries int
pending bool
}
func ownResync(session *link.Session, node string, say func(string), env selfcompose.Env, composes *composesNow) *resyncStep {
@@ -48,14 +53,42 @@ func (s *resyncStep) take(ctx context.Context, answers map[string]delivery.Candi
s.again(ctx)
}
// again takes the last answers.
// again takes the last answers, and once more after a bound (selfcompose.LaterWait) while the bus did not answer for one.
func (s *resyncStep) again(ctx context.Context) {
s.one.Lock()
defer s.one.Unlock()
s.mu.Lock()
last := s.last
s.mu.Unlock()
if last != nil {
s.r.Take(ctx, last)
if last == nil {
return
}
retry := false
for _, o := range s.r.Take(ctx, last) {
retry = retry || o.Retry
}
s.mu.Lock()
defer s.mu.Unlock()
if !retry {
s.tries = 0
return
}
if s.pending || ctx.Err() != nil {
return
}
wait := selfcompose.LaterWait(s.tries)
s.tries++
s.pending = true
go func() {
select {
case <-ctx.Done():
case <-time.After(wait):
}
s.mu.Lock()
s.pending = false
s.mu.Unlock()
if ctx.Err() == nil {
s.again(ctx)
}
}()
}