The sandbox composes a node from the input its engine took in, through one shape
mesh/delivery merged: source.main moved to novox/mesh-host@798d73eb11e9
mesh/check checking df13a83a9117 on 798d73eb11e9

The second sandbox fault of the same kind on mesh-host #200 (postgres's secret
reader handed, the module not): the engine stripped the whole catalogue from
the input it answers, so the sandbox composed the node's own modules from the
indexer's source manifests while the bus reader composes them taken in from
their builds (artifacts, templates archive texts, what is handed). Both sides
now go through composer.SentInput: the node's own taken-in manifests and kept
builds stay in the input, and the sandbox composes them over the cases'. An
input past what one bus answer carries is refused in words. A conformance row
composes one node through the bus reader and through the sandbox and requires
the same declaration.

Issue 1126.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
2026-11-03 01:55:08 +01:00
parent 61a3bd996d
commit df13a83a91
3 changed files with 95 additions and 6 deletions
+14 -6
View File
@@ -15,6 +15,7 @@ import (
"github.com/novox/mesh-host/internal/agent"
"github.com/novox/mesh-host/internal/compose/frombus"
"github.com/novox/mesh-host/internal/composer"
)
// **A check's candidate, composed in shadow on a call** (novox/hq ADR 0593 §7): the node seat's verb compose-candidate.
@@ -37,6 +38,10 @@ var errNotReady = errors.New("this node's shadow has not read every input yet")
// nothing at all. A node not done by then answers unready, naming what it waits on.
var candidateWithin = delivery.ComposeAnswerBound - 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.
const maxSentInput = 900 << 10
// warmAtOnce is how many of the indexer's manifests the shadow reads at once before it composes a candidate.
const warmAtOnce = 8
@@ -184,15 +189,18 @@ func (s *Shadow) composeCandidate(ctx context.Context, c *Composer, ask delivery
if err != nil {
return
}
// The request's input carries no catalogue (composer.Request.Input): the cases give the manifests.
in.Catalogue = nil
for i := range in.Nodes {
in.Nodes[i].Kept = nil
}
if body, err := json.Marshal(in); err == nil {
// 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.
if body, err := json.Marshal(composer.SentInput(in, laid.env.Node)); err == nil {
a.Input = body
}
})
if len(a.Input) > maxSentInput {
// Past what the bus carries in one answer, the checker would hear nothing: refused, saying so.
size := len(a.Input)
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))
}
if ctx.Err() != nil {
_, on := run.said(c.now())
return unready(fmt.Sprintf("the node did not compose the candidate within %s: it waits on %s", candidateWithin, on))