The node-engine answers compose-candidate in its seat's contract: the input as a JSON object, a module's fingerprint Fp

mesh-sdk's ComposeCandidateAnswer and ModuleComposed are now the generated nodeengine types (ADR 0613 §3, §5), so the
input sent for the checker's sandbox is decoded once into the object the contract carries, its numbers kept as written
(json.Number), and its size still counted from the bytes it was marshalled to.

Claude-Session: https://claude.ai/code/session_01JG73KG7AfSdh6FP8VrBqCn
This commit is contained in:
2026-11-04 12:40:34 +01:00
parent 0128b962ab
commit c6ae0fc405
3 changed files with 16 additions and 7 deletions
+1 -1
View File
@@ -603,7 +603,7 @@ func h8candidate(t *testing.T, b *itBed, fp, running string) {
t.Fatalf("the answer %s: %v", raw, err)
}
if a.Verdict != delivery.ComposeCandidateComposed || len(a.Modules) != 1 || a.Modules[0].Module != "web" ||
a.Modules[0].FP != fpIt("c") || !a.Modules[0].Changed || a.Took > delivery.ComposeAnswerBound.Milliseconds() {
a.Modules[0].Fp != fpIt("c") || !a.Modules[0].Changed || a.Took > delivery.ComposeAnswerBound.Milliseconds() {
t.Errorf("the node's answer to a candidate changing web: %+v", a)
}
after, _ := b.composed.Status()
+12 -4
View File
@@ -1,6 +1,7 @@
package selfcompose
import (
"bytes"
"context"
"encoding/json"
"errors"
@@ -181,6 +182,7 @@ func (s *Shadow) composeCandidate(ctx context.Context, c *Composer, ask delivery
if err != nil {
return unready(err.Error())
}
sentSize := 0
laidComps, laidHeld, laidErr := laid.composeShadow(ctx, running, touched, func(scope frombus.Scope, fps map[string]string) {
if !ask.Input {
return
@@ -196,13 +198,19 @@ func (s *Shadow) composeCandidate(ctx context.Context, c *Composer, ask delivery
in.ComposedHow.HeldBefore = maps.Clone(baseHeld)
// As the sandbox reads it (composer.SentInput, the one shape): this node's own modules as taken in from their
// builds, every other module's manifest left to the cases.
// The seat's contract carries it as a JSON object; its numbers are kept as written (json.Number).
if body, err := json.Marshal(composer.SentInput(in, laid.env.Node)); err == nil {
a.Input = body
d := json.NewDecoder(bytes.NewReader(body))
d.UseNumber()
var obj map[string]any
if d.Decode(&obj) == nil {
a.Input, sentSize = obj, len(body)
}
}
})
if len(a.Input) > maxSentInput {
if sentSize > maxSentInput {
// Past what the bus carries in one answer, the checker would hear nothing: refused, saying so.
size := len(a.Input)
size := sentSize
a.Input = nil
return refuse(fmt.Sprintf("the composition input this node composed from is %d bytes, more than the %d an answer on the bus carries", size, maxSentInput))
}
@@ -251,7 +259,7 @@ func (s *Shadow) composeCandidate(ctx context.Context, c *Composer, ask delivery
m.State, m.Words = delivery.ModuleRefusedState, err.Error()
return m, true
}
m.State, m.FP, m.Digest = delivery.ModuleComposedState, comp.FP, dig
m.State, m.Fp, m.Digest = delivery.ModuleComposedState, comp.FP, dig
if before, ok := was[module]; !ok {
m.Changed = true
} else if d, err := delivery.ComposedDigestOf(before); err != nil || d != dig {
+3 -2
View File
@@ -103,7 +103,7 @@ func TestTheNodeComposesACandidateOnAsk(t *testing.T) {
t.Fatalf("the answer does not fit the seat's shape: %v (%+v)", err, a)
}
if a.Verdict != delivery.ComposeCandidateComposed || len(a.Modules) != 1 || a.Modules[0].Module != "lemurs" ||
a.Modules[0].State != delivery.ModuleComposedState || a.Modules[0].FP != fpNew || !a.Modules[0].Changed ||
a.Modules[0].State != delivery.ModuleComposedState || a.Modules[0].Fp != fpNew || !a.Modules[0].Changed ||
a.Own.Module != delivery.HaveNodeWord || a.Took > delivery.ComposeAnswerBound.Milliseconds() {
t.Errorf("an accepted candidate answered %+v", a)
}
@@ -111,7 +111,8 @@ func TestTheNodeComposesACandidateOnAsk(t *testing.T) {
// through the same path.
sent := s.ComposeCandidate(ctx, delivery.ComposeCandidateAsk{Candidate: accepted, Input: true})
var in compose.Input
if err := json.Unmarshal(sent.Input, &in); err != nil || in.ComposedHow == nil || in.ComposedHow.Node != "n1" ||
raw, _ := json.Marshal(sent.Input)
if err := json.Unmarshal(raw, &in); err != nil || in.ComposedHow == nil || in.ComposedHow.Node != "n1" ||
in.ComposedHow.Builds["lemurs"] != fpNew {
t.Errorf("the input sent for the sandbox does not say how the node composes: %+v, %v", in.ComposedHow, err)
}