An act says the unmet seat dependencies of the node it acted on, not the mesh's (hq ADR 0207)

assign and unassign say only what they changed on their node; push <node>
lists that node's, push to many counts each and points at status. The
once-per-change log is the serving controller's alone: a one-shot command
starts with no memory, so it logged every node on every call.
This commit is contained in:
jochen
2026-10-04 12:50:25 +02:00
parent 3ee32970ef
commit e2622fd031
4 changed files with 232 additions and 17 deletions
+32 -12
View File
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"github.com/novox/mesh-controller/internal/broker"
"slices"
"sort"
"strings"
@@ -56,7 +57,7 @@ func assign(ctx context.Context, open *stores, node string, modules ...string) (
// — the service manager, the package manager and the runtime before anything that installs,
// runs or contains — is the mesh's to keep. Several modules in one act are judged together, so
// holders that depend on each other go on in one command.
said, err := seatDependenciesOnAssign(ctx, open, node, modules)
shelf, before, err := seatDependenciesOnAssign(ctx, open, node, modules)
if err != nil {
return "", err
}
@@ -91,8 +92,11 @@ func assign(ctx context.Context, open *stores, node string, modules ...string) (
for _, line := range settled {
answer += "\n " + line
}
for _, line := range said {
answer += "\n but " + line
// What this act changed about this node's unmet seat dependencies, and nothing else (novox/hq
// ADR 0207): a dependency of a module just assigned, or one this assignment met. The rest of the
// node's list, and every other node's, is `status`'s.
for _, line := range unheldChange(shelf, node, before, append(append([]string(nil), before...), added...)) {
answer += "\n " + line
}
// Its bus credential, in the same act (novox/hq issue 203): an assignment pushed before its
// credential exists delivers a process that cannot authenticate and crash-loops until somebody
@@ -128,17 +132,18 @@ func assign(ctx context.Context, open *stores, node string, modules ...string) (
blockedElsewhere(ctx, open, node), nil
}
// seatDependenciesOnAssign is the refusal ADR 0207 makes at assignment, or the lines an assignment
// says beside itself when a dependency has no holder in the catalogue to name. Modules already
// assigned are not new and are not judged again.
func seatDependenciesOnAssign(ctx context.Context, open *stores, node string, modules []string) ([]string, error) {
// seatDependenciesOnAssign is the refusal ADR 0207 makes at assignment, or nothing, with the
// catalogue and the node's assignments it was judged against. Modules already assigned are not new
// and are not judged again.
func seatDependenciesOnAssign(ctx context.Context, open *stores, node string, modules []string) (
map[string]catalogue.Manifest, []string, error) {
shelf, err := open.inventory.Catalogue(ctx)
if err != nil {
return nil, err
return nil, nil, err
}
assigned, err := open.inventory.Assigned(ctx, node)
if err != nil {
return nil, err
return nil, nil, err
}
already := map[string]bool{}
for _, a := range assigned {
@@ -150,7 +155,12 @@ func seatDependenciesOnAssign(ctx context.Context, open *stores, node string, mo
adding = append(adding, m)
}
}
return catalogue.AssignRefusal(shelf, node, assigned, adding)
// The lines AssignRefusal says beside an assignment it lets through are said by unheldChange
// with everything else this act changed, so they are not said twice.
if _, err := catalogue.AssignRefusal(shelf, node, assigned, adding); err != nil {
return nil, nil, err
}
return shelf, assigned, nil
}
// unassign takes modules off a node. What they leave behind is the host's business: a directory
@@ -198,8 +208,18 @@ func unassign(ctx context.Context, open *stores, node string, modules ...string)
return "", err
}
}
return fmt.Sprintf("%s no longer runs %s — run `push %s` to make it so",
node, strings.Join(modules, ", "), node) + blockedElsewhere(ctx, open, node), nil
answer := fmt.Sprintf("%s no longer runs %s — run `push %s` to make it so",
node, strings.Join(modules, ", "), node)
var left []string
for _, a := range assigned {
if !slices.Contains(modules, a) {
left = append(left, a)
}
}
for _, line := range unheldChange(shelf, node, assigned, left) {
answer += "\n " + line
}
return answer + blockedElsewhere(ctx, open, node), nil
}
// splitModules is a surface's one `module` field as the modules it names: several, comma-separated,