Resolve: one un-hostable assignment no longer refuses the whole node

A module a person assigns to a machine that cannot host it — its declared
capability has no detector there, as fail2ban does on a host with no firewall —
made Resolve refuse the entire node, so a whole-node push refused to send the
healthy modules beside it too. One module on the wrong machine took down every
other module on that node.

Assign already keeps such an assignment on purpose (it is what a person meant,
and acts.go says so), so the fix is on the resolve/push side: a directly-assigned
module the machine cannot host is left out of the closure and reported as
un-applied on the Resolution, rather than refusing the set. The healthy modules
still resolve, declare, and converge. A module that is *required* by something
running here and cannot be hosted still refuses — that set is genuinely
incoherent — so the distinction is who wanted it.

assign, plan and push now name the un-applied module and the missing capability,
via a shared WrongMachine message, so it is neither silently dropped nor fatal.

Reconciled two tests that encoded the old whole-node refusal for directly-assigned
un-hostable modules; added coverage for the healthy-modules-still-converge case
and the required-un-hostable-still-refuses distinction.

Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
This commit is contained in:
2026-09-08 18:37:41 +02:00
parent 474382c141
commit a92c11be12
5 changed files with 190 additions and 25 deletions
+15 -1
View File
@@ -3,6 +3,8 @@ package main
import (
"context"
"fmt"
"github.com/novox/mesh-control/internal/catalogue"
)
// The things the mesh can be asked to do, separated from how it was asked.
@@ -25,10 +27,22 @@ func assign(ctx context.Context, open *stores, node, module string) (string, err
return "", err
}
said := fmt.Sprintf("%s is assigned %s", node, module)
if _, _, err := planFor(ctx, open, node); err != nil {
plan, _, err := planFor(ctx, open, node)
if err != nil {
// Kept, and still refused. Both halves are the answer.
return said, err
}
// Kept, and cannot be hosted here. Said at once rather than discovered at push: a module whose
// capability the machine lacks is on the wrong machine, and the assignment records what a person
// meant while this line says it will not run until it moves. The rest of the node still pushes.
for _, u := range plan.Unhostable {
if u.Module != module {
continue
}
for _, c := range u.Missing {
said += "\n but " + catalogue.WrongMachine(u.Module, c, node)
}
}
return said + fmt.Sprintf("\n run `push %s` to send it", node), nil
}