Serve node-engine.compose-candidate from the node's shadow (H4)

ADR 0593 §7: a check's composition is the checker's, made from each
placed node's answer, since only a node can compose with its own sealed
values. The node composes the indexer's candidate in shadow on a copy of
its shadow's inputs, the candidate's modules laid over the indexer's
with their manifest verdicts, and answers per module composed (changed
or not), refused or held, and its own composition's verdict, within the
answer bound; it applies, writes and hands nothing. With input it also
answers the composition input, for the sandbox path.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
2026-11-02 18:33:58 +01:00
parent 4f51393b72
commit 3fa41dfe1a
7 changed files with 468 additions and 0 deletions
+6
View File
@@ -7,6 +7,7 @@ import (
"sort"
"strings"
"sync"
"sync/atomic"
"time"
"git.novox.be/novox/mesh-sdk/go/delivery"
@@ -54,6 +55,8 @@ type Shadow struct {
// after a bound (laterAgain), whether or not an input moves (issue 1126, 2026-10-28: ace kept "the vault's own: not
// yet ... no responders" in its answer to seed-7).
owed bool
// ready says this run read every input once: the verb compose-candidate composes only then (ComposeCandidate).
ready atomic.Bool
}
// answeredAt is what one proposal's answer was composed at: its keys' revisions, by node, and the changes of each input.
@@ -146,6 +149,8 @@ func (s *Shadow) Run(ctx context.Context) error {
s.retry = map[string]bool{}
s.c = New(s.c.env)
s.mu.Unlock()
s.ready.Store(false)
defer s.ready.Store(false)
c := s.c
node := c.env.Node
// In inputWords' order.
@@ -238,6 +243,7 @@ func (s *Shadow) Run(ctx context.Context) error {
if ready = all(caught); !ready {
continue
}
s.ready.Store(true)
dirty = true
}
if dirty {