The node-engine speaks operator-channel, mesh-vault and node-engine through their contracts and clients; it serves no pull-waits
ADR 0627 §1, §4: mesh-sdk keeps no asks, asker, vault answers or compose-candidate checks, so the node-engine takes them from mesh-contracts: operator-channel's contract and client (asks, warrants, the asker's client), mesh-vault's sign verb and answer, and node-engine's own contract and client for compose-candidate's ask, answer, verdicts and bound. The lead's ruling on pull-waits (no record gives the seat that verb): the verb and its handler go; the row that read the waits now holds the seat refusing the verb, and failed before the change. The go.mod requires of mesh-contracts' modules come with the group's pin. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
@@ -12,6 +12,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
nec "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
|
||||
"github.com/novox/mesh-host/internal/agent"
|
||||
@@ -33,11 +35,11 @@ import (
|
||||
var errNotReady = errors.New("this node's shadow has not read every input yet")
|
||||
|
||||
// candidateWithin is the node's own bound on an answer to compose-candidate: shorter than the checker's
|
||||
// (delivery.ComposeAnswerBound), because the bus lets the engine answer an ask only within a minute of it (the grants
|
||||
// (nec.AnswerBound), because the bus lets the engine answer an ask only within a minute of it (the grants
|
||||
// part's allow_responses ttl, mesh-grants ResponseTTL): an answer at the checker's 60 s bound was refused by the bus
|
||||
// (issue 1126, 2026-11-03 01:17:04 on shanks, "Permissions Violation for Publish to _INBOX..."), so the checker heard
|
||||
// nothing at all. A node not done by then answers unready, naming what it waits on.
|
||||
var candidateWithin = delivery.ComposeAnswerBound - 10*time.Second
|
||||
var candidateWithin = nec.AnswerBound - 10*time.Second
|
||||
|
||||
// maxSentInput is the most composition input an answer carries: the bus carries 1 MiB in one message (its max_payload),
|
||||
// and the answer's own fields and envelope come beside it.
|
||||
@@ -92,7 +94,7 @@ func (r *candidateRun) said(now time.Time) (waits string, on string) {
|
||||
// ComposeCandidate is the node's answer to ask, within candidateWithin (or ctx's deadline, if sooner). Each answer is
|
||||
// said in one line, with what it waited on and for how long (issue 1126: ace, g14 and shanks answered nothing for a
|
||||
// minute while nothing said why). An answer not composed within the bound is unready, naming what the node waits on.
|
||||
func (s *Shadow) ComposeCandidate(ctx context.Context, ask delivery.ComposeCandidateAsk) delivery.ComposeCandidateAnswer {
|
||||
func (s *Shadow) ComposeCandidate(ctx context.Context, ask nodeengine.ComposeCandidateInput) nodeengine.ComposeCandidateAnswer {
|
||||
s.mu.Lock()
|
||||
c := s.c
|
||||
s.mu.Unlock()
|
||||
@@ -100,14 +102,14 @@ func (s *Shadow) ComposeCandidate(ctx context.Context, ask delivery.ComposeCandi
|
||||
ctx, cancel := context.WithTimeout(ctx, candidateWithin)
|
||||
defer cancel()
|
||||
run := &candidateRun{}
|
||||
done := make(chan delivery.ComposeCandidateAnswer, 1)
|
||||
done := make(chan nodeengine.ComposeCandidateAnswer, 1)
|
||||
go func() { done <- s.composeCandidate(ctx, c, ask, began, run) }()
|
||||
var a delivery.ComposeCandidateAnswer
|
||||
var a nodeengine.ComposeCandidateAnswer
|
||||
select {
|
||||
case a = <-done:
|
||||
case <-ctx.Done():
|
||||
_, on := run.said(c.now())
|
||||
a = delivery.ComposeCandidateAnswer{Node: c.env.Node, Candidate: ask.Candidate, Verdict: delivery.ComposeCandidateUnready,
|
||||
a = nodeengine.ComposeCandidateAnswer{Node: c.env.Node, Candidate: ask.Candidate, Verdict: nec.Unready,
|
||||
Refused: fmt.Sprintf("the node did not compose the candidate within %s: it waits on %s", candidateWithin, on),
|
||||
Took: c.now().Sub(began).Milliseconds()}
|
||||
}
|
||||
@@ -122,19 +124,19 @@ func (s *Shadow) ComposeCandidate(ctx context.Context, ask delivery.ComposeCandi
|
||||
}
|
||||
|
||||
// composeCandidate composes ask on scratch copies of c, each added to run as it starts.
|
||||
func (s *Shadow) composeCandidate(ctx context.Context, c *Composer, ask delivery.ComposeCandidateAsk, began time.Time, run *candidateRun) delivery.ComposeCandidateAnswer {
|
||||
func (s *Shadow) composeCandidate(ctx context.Context, c *Composer, ask nodeengine.ComposeCandidateInput, began time.Time, run *candidateRun) nodeengine.ComposeCandidateAnswer {
|
||||
node := c.env.Node
|
||||
a := delivery.ComposeCandidateAnswer{Node: node, Candidate: ask.Candidate}
|
||||
refuse := func(why string) delivery.ComposeCandidateAnswer {
|
||||
a.Verdict, a.Refused, a.Modules, a.Own = delivery.ComposeCandidateRefused, why, nil, delivery.ModuleComposed{}
|
||||
a := nodeengine.ComposeCandidateAnswer{Node: node, Candidate: ask.Candidate}
|
||||
refuse := func(why string) nodeengine.ComposeCandidateAnswer {
|
||||
a.Verdict, a.Refused, a.Modules, a.Own = nec.Refused, why, nil, nodeengine.Module{}
|
||||
a.Took = c.now().Sub(began).Milliseconds()
|
||||
return a
|
||||
}
|
||||
// A node that cannot compose now (its shadow not ready, a read the bus did not answer, the bound passed) answers
|
||||
// unready: the checker takes it as no answer (ADR 0593 §9) by the verdict, the words only saying why.
|
||||
unready := func(why string) delivery.ComposeCandidateAnswer {
|
||||
unready := func(why string) nodeengine.ComposeCandidateAnswer {
|
||||
a = refuse(why)
|
||||
a.Verdict = delivery.ComposeCandidateUnready
|
||||
a.Verdict = nec.Unready
|
||||
return a
|
||||
}
|
||||
group, heads, err := delivery.ParseCompositionKey(ask.Candidate)
|
||||
@@ -223,8 +225,8 @@ func (s *Shadow) composeCandidate(ctx context.Context, c *Composer, ask delivery
|
||||
return unready("a read on the bus did not answer: " + laidErr.Error())
|
||||
case laidErr != nil && baseErr != nil:
|
||||
// The node composes none of its own without the candidate either: the candidate changes nothing of that.
|
||||
a.Verdict, a.Modules = delivery.ComposeCandidateComposed, []delivery.ModuleComposed{}
|
||||
a.Own = delivery.ModuleComposed{Module: delivery.HaveNodeWord, State: delivery.ModuleHeldState, Words: laidErr.Error()}
|
||||
a.Verdict, a.Modules = nec.Composed, []nodeengine.Module{}
|
||||
a.Own = nodeengine.Module{Module: delivery.HaveNodeWord, State: nec.ModuleHeld, Words: laidErr.Error()}
|
||||
a.Took = c.now().Sub(began).Milliseconds()
|
||||
return a
|
||||
case laidErr != nil:
|
||||
@@ -238,15 +240,15 @@ func (s *Shadow) composeCandidate(ctx context.Context, c *Composer, ask delivery
|
||||
return out
|
||||
}
|
||||
was, now := byModule(baseComps), byModule(laidComps)
|
||||
one := func(module string) (delivery.ModuleComposed, bool) {
|
||||
m := delivery.ModuleComposed{Module: module}
|
||||
one := func(module string) (nodeengine.Module, bool) {
|
||||
m := nodeengine.Module{Module: module}
|
||||
if module == "" {
|
||||
m.Module = delivery.HaveNodeWord
|
||||
}
|
||||
if why, held := laidHeld[module]; held {
|
||||
m.State, m.Words = delivery.ModuleRefusedState, why
|
||||
m.State, m.Words = nec.ModuleRefused, why
|
||||
if _, before := baseHeld[module]; before || baseErr != nil {
|
||||
m.State = delivery.ModuleHeldState
|
||||
m.State = nec.ModuleHeld
|
||||
}
|
||||
return m, true
|
||||
}
|
||||
@@ -256,10 +258,10 @@ func (s *Shadow) composeCandidate(ctx context.Context, c *Composer, ask delivery
|
||||
}
|
||||
dig, err := delivery.ComposedDigestOf(comp)
|
||||
if err != nil {
|
||||
m.State, m.Words = delivery.ModuleRefusedState, err.Error()
|
||||
m.State, m.Words = nec.ModuleRefused, err.Error()
|
||||
return m, true
|
||||
}
|
||||
m.State, m.Fp, m.Digest = delivery.ModuleComposedState, comp.FP, dig
|
||||
m.State, m.Fp, m.Digest = nec.ModuleComposed, comp.FP, dig
|
||||
if before, ok := was[module]; !ok {
|
||||
m.Changed = true
|
||||
} else if d, err := delivery.ComposedDigestOf(before); err != nil || d != dig {
|
||||
@@ -281,24 +283,24 @@ func (s *Shadow) composeCandidate(ctx context.Context, c *Composer, ask delivery
|
||||
}
|
||||
}
|
||||
sort.Strings(names)
|
||||
a.Modules = []delivery.ModuleComposed{}
|
||||
a.Modules = []nodeengine.Module{}
|
||||
refused := false
|
||||
for _, m := range names {
|
||||
if mc, ok := one(m); ok {
|
||||
a.Modules = append(a.Modules, mc)
|
||||
refused = refused || mc.State == delivery.ModuleRefusedState
|
||||
refused = refused || mc.State == nec.ModuleRefused
|
||||
}
|
||||
}
|
||||
if own, ok := one(""); ok {
|
||||
a.Own = own
|
||||
} else {
|
||||
a.Own = delivery.ModuleComposed{Module: delivery.HaveNodeWord, State: delivery.ModuleHeldState,
|
||||
a.Own = nodeengine.Module{Module: delivery.HaveNodeWord, State: nec.ModuleHeld,
|
||||
Words: "the node composes no composition of its own"}
|
||||
}
|
||||
refused = refused || a.Own.State == delivery.ModuleRefusedState
|
||||
a.Verdict = delivery.ComposeCandidateComposed
|
||||
refused = refused || a.Own.State == nec.ModuleRefused
|
||||
a.Verdict = nec.Composed
|
||||
if refused {
|
||||
a.Verdict = delivery.ComposeCandidateRefused
|
||||
a.Verdict = nec.Refused
|
||||
}
|
||||
a.Took = c.now().Sub(began).Milliseconds()
|
||||
return a
|
||||
|
||||
Reference in New Issue
Block a user