The node-engine's live path holds a module whose claim or credential fails (issue 1966, review round 1)
mesh/delivery merged: source.main moved to novox/mesh-host@6e461bd24aa5
mesh/check being carried to ef41fa2e2796 on 6e461bd24aa5 | from 7c483da93a03

compose.CompositionsHolding returns the modules the declaration leaves out for a claim that cannot be held or a refused
credential; frombus composeEach holds each with those words, so the node-engine keeps what it handed of the module (no
stop of a running module), the candidate answer and the check's sandbox name it refused, and the rest composes.
CredentialsNeeded asks nothing for a held module. Resolve and checkClaims move to a test helper (production uses
ResolveHolding and holdClaims); a pass refused anyway names a held module's reason among the others.

Quick fix: Declared.Held read in the composer only. Thorough, chosen: the same held words reach every reader of the
composition (selfcompose, ComposeSent, the candidate answer) through composeEach's one hold path.

Claude-Session: https://claude.ai/code/session_01NcbgfFfQ4Z9UE5jK32PmE5
This commit is contained in:
2026-11-09 13:04:55 +01:00
parent 868ee6beb0
commit ef41fa2e27
7 changed files with 125 additions and 57 deletions
+12 -36
View File
@@ -26,26 +26,17 @@ func (r *Refusal) Error() string {
return "these assignments cannot be applied:\n - " + strings.Join(r.Problems, "\n - ")
}
// Resolve works out everything a node runs, from what was assigned to it.
//
// The catalogue is every module the mesh knows about; assigned is what a person put on this node.
// What comes back is the closure — assigned modules plus everything they require — or a refusal
// naming every reason it could not be closed.
func Resolve(catalogue map[string]mcat.Manifest, assigned []string, node mcat.Node, world mcat.World) (mcat.Resolution, error) {
resolution, _, err := resolve(catalogue, assigned, node, world, true)
return resolution, err
}
// ResolveHolding is Resolve where a claim problem costs the module that has it, not the machine (issue 1966, ADR 0636 §3: a
// refused claim blocks that claimant only). The modules that cannot hold what they claim come back with why, each still in
// ResolveHolding works out everything a node runs, from what was assigned to it. The catalogue is every module the mesh knows
// about; assigned is what a person put on this node. What comes back is the closure -- assigned modules plus everything they
// require -- or a refusal naming every reason it could not be closed. A claim problem costs the module that has it, not the
// machine (issue 1966, ADR 0636 §3: a refused claim blocks that claimant only). The modules that cannot hold what they claim come back with why, each still in
// the resolution's modules and none of them in its claims; the caller leaves them out of what it declares (LeftOut), so the
// machine composes everything else. Every other refusal is Resolve's.
func ResolveHolding(catalogue map[string]mcat.Manifest, assigned []string, node mcat.Node, world mcat.World) (mcat.Resolution, map[string]string, error) {
return resolve(catalogue, assigned, node, world, false)
return resolve(catalogue, assigned, node, world)
}
// resolve is Resolve; with strict, a claim problem is a refusal beside every other, and without it a module held.
func resolve(catalogue map[string]mcat.Manifest, assigned []string, node mcat.Node, world mcat.World, strict bool) (mcat.Resolution, map[string]string, error) {
func resolve(catalogue map[string]mcat.Manifest, assigned []string, node mcat.Node, world mcat.World) (mcat.Resolution, map[string]string, error) {
elsewhere := world.Held
var problems []string
@@ -608,12 +599,6 @@ func resolve(catalogue map[string]mcat.Manifest, assigned []string, node mcat.No
problems = append(problems, checkCapabilities(resolution.Modules, node)...)
claims, held := holdClaims(resolution.Modules, node, elsewhere, world.Holdings)
if strict {
for _, why := range held {
problems = append(problems, why)
}
held = nil
}
problems = append(problems, checkResources(resolution.Modules)...)
resolution.Claims = claims
@@ -634,6 +619,10 @@ func resolve(catalogue map[string]mcat.Manifest, assigned []string, node mcat.No
}
if len(problems) > 0 {
// A pass refused anyway says every reason at once, a held module's among them.
for _, why := range held {
problems = append(problems, why)
}
sort.Strings(problems)
return mcat.Resolution{}, nil, &Refusal{Problems: problems}
}
@@ -691,21 +680,8 @@ func checkCapabilities(modules []mcat.Manifest, node mcat.Node) []string {
return problems
}
// checkClaims refuses two modules holding one singular thing.
//
// Within this node's own set, and against what is already held elsewhere for the wider scopes. A
// claim at mesh scope is the same idea as the mesh's one hub, said once instead of hard-coded.
func checkClaims(modules []mcat.Manifest, node mcat.Node, elsewhere []mcat.Held, holdings []mcat.Held) ([]mcat.Held, []string) {
held, problems := claimsOf(modules, node, elsewhere, holdings)
var out []string
for _, p := range problems {
out = append(out, p.why)
}
return held, out
}
// holdClaims is checkClaims with each problem put on the module that has it: the claims that hold, and for each module that
// cannot hold what it claims, why (every reason, joined).
// holdClaims refuses two modules holding one singular thing, within this node's own set and against what is held elsewhere for
// the wider scopes: the claims that hold, and for each module that cannot hold what it claims, why (every reason, joined).
func holdClaims(modules []mcat.Manifest, node mcat.Node, elsewhere []mcat.Held, holdings []mcat.Held) ([]mcat.Held, map[string]string) {
held, problems := claimsOf(modules, node, elsewhere, holdings)
var out map[string]string
@@ -0,0 +1,35 @@
package resolution
import (
"sort"
mcat "git.novox.be/novox/mesh-sdk/go/catalogue"
)
// Resolve is ResolveHolding for a test that wants a claim problem as a refusal of the whole set, as before issue 1966.
func Resolve(catalogue map[string]mcat.Manifest, assigned []string, node mcat.Node, world mcat.World) (mcat.Resolution, error) {
got, held, err := ResolveHolding(catalogue, assigned, node, world)
if err != nil {
return mcat.Resolution{}, err
}
if len(held) > 0 {
var problems []string
for _, why := range held {
problems = append(problems, why)
}
sort.Strings(problems)
return mcat.Resolution{}, &Refusal{Problems: problems}
}
return got, nil
}
// checkClaims is holdClaims with the problems as a list.
func checkClaims(modules []mcat.Manifest, node mcat.Node, elsewhere []mcat.Held, holdings []mcat.Held) ([]mcat.Held, []string) {
held, why := holdClaims(modules, node, elsewhere, holdings)
var problems []string
for _, w := range why {
problems = append(problems, w)
}
sort.Strings(problems)
return held, problems
}