The composition verb says what holds each held module and the build it would be composed at (issue 1921, milestone 47/G H4)
mesh/delivery closed: a newer head of the same pull request
mesh/delivery-group group feat/registry-milestone checking: 6 of 7 member(s) ready
mesh/check failed (4): unit: git.novox.be/novox/mesh-catalog/checks/gitignored.TestNoProgramABuildLeavesIsC…; and 3 more | 5b75e866d079 on e73aae3686a6

The node's own composition named a held module's why only; it now also says whether its manifest's verdict or this
node-engine's decoder holds it, and at which build, as the input a check's composer reads says it. Said in the held
words, so the node-engine seat's answer keeps its shape.

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
2026-11-07 02:38:18 +01:00
parent d0fb384f00
commit 5b75e866d0
3 changed files with 51 additions and 4 deletions
+14
View File
@@ -171,6 +171,10 @@ type Scope struct {
// on the node that its manifest's verdict or its decoder holds, into compose.Input.Holds, never into what the node composes: the
// check's composer, judging the change's manifest itself, composes it at that build (issue 1921).
takeInHeld bool
// Holds is told, after Compose read the input, what holds each module it holds by its manifest's verdict or its
// decoder (compose.Input.Holds), with the build it would compose it at, so the node says why it holds it (issue 1921,
// the composition verb). Nil: said nowhere.
Holds func(map[string]compose.Hold)
}
// InOwn is, of the modules p places on its node, those whose resources the node's own composition it runs
@@ -590,6 +594,16 @@ func Compose(ctx context.Context, r Reader, mesh Mesh, s Scope, fps map[string]s
if err != nil {
return nil, nil, held, err
}
if s.Holds != nil {
holds := map[string]compose.Hold{}
for name := range held {
if h, said := in.Holds[name]; said {
h.Build = fps[name]
holds[name] = h
}
}
s.Holds(holds)
}
// The pair credentials its composition reads, asked of the vault sealed to it (ADR 0550 §5).
asConsumer, asProvider, err := compose.CredentialsNeeded(in, s.Node)
if err != nil {
+31 -4
View File
@@ -235,6 +235,9 @@ type Pass struct {
Refused string `json:"refused,omitempty"`
// Held is each module refused alone, with why.
Held map[string]string `json:"held,omitempty"`
// holds is what holds each module Held names by its manifest's verdict or its decoder, with the build it would be
// composed at (frombus.Scope.Holds), for LastHeld's words.
holds map[string]compose.Hold
// Handed is how many compositions the pass handed the agent that differ from what it handed before.
Handed int `json:"handed"`
// Again is when the node composes again on its own, after a seat or a read that did not answer; zero when it does
@@ -247,11 +250,34 @@ type Pass struct {
Queued string `json:"queued,omitempty"`
}
// LastHeld is what the last pass held, each module with why; nil before a pass or when it held none.
// LastHeld is what the last pass held, each module with why: for one its manifest's verdict or its decoder holds, also
// that and the build it would be composed at, as the input a check's composer reads says it (compose.Input.Holds,
// issue 1921); nil before a pass or when it held none.
func (c *Composer) LastHeld() map[string]string {
c.mu.Lock()
defer c.mu.Unlock()
return maps.Clone(c.last.Held)
out := maps.Clone(c.last.Held)
for name, h := range c.last.holds {
if why, held := out[name]; held {
out[name] = why + " (" + holdWords(h) + ")"
}
}
return out
}
// holdWords is what holds a module and the build it would be composed at, in words.
func holdWords(h compose.Hold) string {
by := "held by " + h.By
switch h.By {
case compose.HeldByVerdict:
by = "held by its manifest's verdict"
case compose.HeldByDecoder:
by = "held by this node-engine's decoder"
}
if h.Build == "" {
return by + ", at no build"
}
return by + ", at the build " + h.Build
}
// askedWithin is how recent a pass answers an ask of the verb compose without a new one: the first wait after a seat
@@ -919,6 +945,7 @@ func (c *Composer) compose(ctx context.Context) []agent.Update {
// module's have, from which the watchman raises its condition.
keptRuns := map[string][]string{}
var keptMu sync.Mutex
var holds map[string]compose.Hold
scope := frombus.Scope{Node: c.env.Node, KeptAt: kept, InOwn: inOwn, Running: running, Kept: func(module, words string) {
keptMu.Lock()
defer keptMu.Unlock()
@@ -926,7 +953,7 @@ func (c *Composer) compose(ctx context.Context) []agent.Update {
keptRuns[module] = append(keptRuns[module], words)
}
c.sayOnceEach(words)
}}
}, Holds: func(h map[string]compose.Hold) { holds = h }}
c.mu.Lock()
c.dataScope = frombus.Scope{Node: c.env.Node, KeptAt: kept, InOwn: inOwn} // the data items are worked out as composed
c.mu.Unlock()
@@ -985,7 +1012,7 @@ func (c *Composer) compose(ctx context.Context) []agent.Update {
return nil
}
c.paid()
pass.Held = held
pass.Held, pass.holds = held, holds
// The provisioner client's account the node runs with, dropped by this pass, is said: every filled write is refused
// under a declaration that names none (filling.JudgeAccount), and no module is handed to it (issue 774, ace,
// 2026-11-04 01:29). A placed client keeps its account while it is held (compose.Node.RunningProvisioner).
+6
View File
@@ -508,6 +508,12 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) {
if len(*told4) != 1 || !strings.HasPrefix((*told4)[0], "held power: ") || !strings.Contains((*told4)[0], `"shows" is none of them`) {
t.Errorf("the agent was told %q, want power held alone, naming its item", *told4)
}
// Issue 1921: the composition verb's held says what holds power and the build it would be composed at, as the input a
// check's composer reads says it.
if why := c4.LastHeld()["power"]; !strings.Contains(why, `"shows" is none of them`) ||
!strings.Contains(why, "held by its manifest's verdict, at the build "+powerFP) {
t.Errorf("the last pass held power for %q, want its verdict and its build named", why)
}
// Review of issue 774 (the kept read): mesh-wireguard, which the mesh provides and which never has a fingerprint, is
// folded in the node's own and composed from the manifest mesh-sdk provides (overlay.ProvidedManifest), not from
// what runs nor the kept declaration: with nothing running (the kept read retired, Running empty) the node's own