Rename mesh-control -> mesh-controller, substrate -> foundation
One name per thing, per the HQ glossary: the module/container/image/binary/repo becomes mesh-controller, the seat the-controller, and the store+broker pair the foundation (embedded base bundles, default template and example lock renamed with their go:embed directives). No behaviour change — a pure vocabulary rename. Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/novox/mesh-controller/internal/catalogue"
|
||||
)
|
||||
|
||||
// The things the mesh can be asked to do, separated from how it was asked.
|
||||
//
|
||||
// **A surface is an adapter with no decisions in it** (novox/hq ADR 0035). The command line and
|
||||
// the command API call the same functions here, so an assignment refused at one is refused at the
|
||||
// other for the same reason and in the same words. The moment a surface can accept something
|
||||
// another would reject, the mesh has two answers to one question and people learn which to trust.
|
||||
//
|
||||
// Each returns what happened as text a person can read and a caller can pass on. Neither surface
|
||||
// composes its own explanation, because two explanations of one refusal drift.
|
||||
|
||||
// assign puts a module on a node, and says at once what in the mesh no longer works out.
|
||||
//
|
||||
// The assignment is kept even when the node does not resolve: it is what a person meant, and
|
||||
// assignment is not an ordering. A consumer assigned before its provider does not resolve for as
|
||||
// long as it takes to assign the provider, and refusing the first half of a pair would make the
|
||||
// order somebody types two commands in part of the mesh's rules.
|
||||
//
|
||||
// **What is checked is the mesh, not the one machine** — because that is what the assignment
|
||||
// changes. novox/hq 04-ISSUES/017 names the shape: an action can succeed into a state its own
|
||||
// verify rejects, and it happens when the action's test is not the test the verify uses. Here the
|
||||
// action tested one node and the verify is resolution over all of them, so an assignment could be
|
||||
// reported as fine while it took a provision away from every other machine — a module offering a
|
||||
// mesh-scoped provision stops offering it the moment its own node stops resolving, and every
|
||||
// consumer elsewhere is then told *nothing in this mesh provides it*, with a remedy that names a
|
||||
// module already assigned. That was found on a four-node raise and read as a version bump breaking
|
||||
// provider recognition; it was neither the version nor the provider.
|
||||
//
|
||||
// It costs a resolution per machine. Assignment is a person typing a command, and being told which
|
||||
// machines this just blocked is worth more than the milliseconds.
|
||||
func assign(ctx context.Context, open *stores, node, module string) (string, error) {
|
||||
if err := open.inventory.Assign(ctx, node, module); err != nil {
|
||||
return "", err
|
||||
}
|
||||
said := fmt.Sprintf("%s is assigned %s", node, module)
|
||||
plan, _, err := planFor(ctx, open, node)
|
||||
if err != nil {
|
||||
// Kept, and still refused. Both halves are the answer, and the rest of the mesh is still
|
||||
// worth reporting: this machine's refusal is rarely the only consequence.
|
||||
return said + blockedElsewhere(ctx, open, node), 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) +
|
||||
blockedElsewhere(ctx, open, node), nil
|
||||
}
|
||||
|
||||
// unassign takes a module off a node. What it leaves behind is the host's business: a directory
|
||||
// holding anything the mesh did not put there is kept (novox/hq ADR 0030).
|
||||
//
|
||||
// It reports the rest of the mesh for the same reason assign does, and more sharply: taking a
|
||||
// module off one machine is the ordinary way to stop providing something to another, and nothing
|
||||
// about the command's own output would ever have said so.
|
||||
func unassign(ctx context.Context, open *stores, node, module string) (string, error) {
|
||||
if err := open.inventory.Unassign(ctx, node, module); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%s no longer runs %s — run `push %s` to make it so",
|
||||
node, module, node) + blockedElsewhere(ctx, open, node), nil
|
||||
}
|
||||
|
||||
// blockedElsewhere is every OTHER machine that cannot be worked out as things now stand.
|
||||
//
|
||||
// **The state, not the cause.** Saying "this assignment broke laptop" would mean resolving the
|
||||
// whole mesh twice and would still be a guess about which of several changes did it; saying
|
||||
// "laptop cannot be worked out, and here is what it says" is true, is what somebody has to fix,
|
||||
// and cannot mislead. The machine that was just changed is left out because its own refusal is
|
||||
// already the answer beside this one.
|
||||
//
|
||||
// Nothing here can fail the act it reports on. A mesh that cannot be read is worth saying and is
|
||||
// not a reason to claim the assignment did not happen — it did.
|
||||
func blockedElsewhere(ctx context.Context, open *stores, except string) string {
|
||||
nodes, err := open.inventory.Nodes(ctx)
|
||||
if err != nil {
|
||||
return "\n\nThe rest of the mesh could not be checked: " + err.Error()
|
||||
}
|
||||
blocked := map[string]string{}
|
||||
for _, n := range nodes {
|
||||
if n.Name == except {
|
||||
continue
|
||||
}
|
||||
if _, _, err := planFor(ctx, open, n.Name); err != nil {
|
||||
blocked[n.Name] = err.Error()
|
||||
}
|
||||
}
|
||||
if len(blocked) == 0 {
|
||||
return ""
|
||||
}
|
||||
names := make([]string, 0, len(blocked))
|
||||
for name := range blocked {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
|
||||
var out strings.Builder
|
||||
fmt.Fprintf(&out, "\n\nAND %d other machine(s) cannot be worked out as things stand, so "+
|
||||
"nothing will be sent to them:\n", len(names))
|
||||
for _, name := range names {
|
||||
fmt.Fprintf(&out, " %s\n", name)
|
||||
for _, line := range strings.Split(strings.TrimRight(blocked[name], "\n"), "\n") {
|
||||
fmt.Fprintf(&out, " %s\n", strings.TrimSpace(line))
|
||||
}
|
||||
}
|
||||
out.WriteString("\nThis may or may not be what just changed — it is what is true now.")
|
||||
return out.String()
|
||||
}
|
||||
Reference in New Issue
Block a user