A module's bus account not issued yet is foreseen in a node's answer, so a new module can be assigned; a refusal leads with each held module's first reason (issue 774) #141

Merged
mesh-tools merged 2 commits from fix/kept-read-and-foreseen-account into main 2026-10-30 19:49:13 +00:00
3 changed files with 39 additions and 1 deletions
Showing only changes of commit 4e9621edd6 - Show all commits
+18 -1
View File
@@ -335,7 +335,7 @@ func composeEach(in compose.Input, node string, fps map[string]string, applied b
if err != nil {
module, leave := culprit(in, node, fps, mode, failed, err)
if module == "" {
return nil, nil, nil, err
return nil, nil, nil, firstReason(err, failed)
}
hold(module, err.Error(), leave)
continue
@@ -373,6 +373,23 @@ func composeEach(in compose.Input, node string, fps map[string]string, applied b
return out, skipped, stood, nil
}
// firstReason is err, a refusal no culprit is found for, led by the first reason of each held module it names: a held
// module's stand-in refused later never replaces why it was held (issue 774, 2026-10-30: mesh-self-check on ace held for
// "has no bus credential", then refused as "runs as its own account and declares no own secret broker", its bare stub's
// words, which were all the proposal's refusal said).
func firstReason(err error, failed map[string]string) error {
var named []string
for _, m := range sortedKeys(failed) {
if strings.Contains(err.Error(), m) {
named = append(named, m+" is held: "+failed[m])
}
}
if len(named) == 0 {
return err
}
return fmt.Errorf("%s; its stand-in is then refused: %w", strings.Join(named, "; "), err)
}
// blames says the composition with m's stub (again) makes m the culprit of err: it composes, or fails otherwise. The
// runtime's stub is never a runtime (no build is taken in, so it carries no bundle) and always fails otherwise: it is
// the culprit only where its stub composes (issue 774, 2026-10-30 19:30: node-tools held for any error naming it).
+1
View File
@@ -142,6 +142,7 @@ func TestInput(t *testing.T) {
t.Run("a node refused whole says what it held", aNodeRefusedWholeSaysWhatItHeld)
t.Run("the runtime is blamed only where its stub composes", theRuntimeIsBlamedOnlyWhereItsStubComposes)
t.Run("a running module waits for its build", aRunningModuleWaitsForItsBuild)
t.Run("a held module's first reason leads", aHeldModulesFirstReasonLeads)
t.Run("another node not resolved is named", otherNodeNotResolvedIsNamed)
ctx := context.Background()
xterm, err := os.ReadFile("../../catalogue/testdata/catalogue-835bf22b-xterm.module.json")
+20
View File
@@ -131,3 +131,23 @@ func rawStrings(rs []json.RawMessage) []string {
}
return out
}
// Issue 774: a refusal of a held module's stand-in leads with why the module was held, its first reason, as mesh-self-check's
// on ace should have; a refusal naming no held module is as it was.
func aHeldModulesFirstReasonLeads(t *testing.T) {
held := map[string]string{"mesh-self-check": "mesh-self-check on ace has no bus credential: nothing was issued for ace.mesh-self-check"}
for _, row := range []struct {
err, want string
}{
{"mesh-self-check runs as its own account and declares no own secret broker",
"mesh-self-check is held: mesh-self-check on ace has no bus credential: nothing was issued for ace.mesh-self-check; " +
"its stand-in is then refused: mesh-self-check runs as its own account and declares no own secret broker"},
{"ace is the hub and has no endpoint", "ace is the hub and has no endpoint"},
} {
err := errors.New(row.err)
got := firstReason(err, held)
if got.Error() != row.want || !errors.Is(got, err) {
t.Errorf("refused %q, want %q", got, row.want)
}
}
}