A seat is handed over as one act, and the holder is on record
`seat <name> --to <node>/<module>` makes one assignment the holder of a seat in the same write that removes the previous one. The row is new (migration 0039); without one, the resolver derives the holder as it always did — the sole eligible assignment, two refused — so nothing changes for a mesh that never hands a seat over. With one, the recorded assignment holds and any other whose module could hold the seat is eligible and silent: not refused, not holding. That is what lets the next holder run beside the current one until the switch (hq design 26, design 28 task 5.3, ADR 0131). Why: the controller finds its own bus through a seat, and the day that seat was left with nobody in it — because two eligible holders could not coexist and the old one's claim was taken away — the control plane looped for two hours while every service stayed up. A handover that is never empty in between is the fix, not a workaround for it. `CanHold` is the one judgement of whether a module may hold a seat — claims it at its scope, provides what it delivers, against the store's row — shared by registration and the handover so they cannot drift apart. The holding belongs to the assignment and goes when it does, so a seat never points at nothing running. Tests: the resolver with and without a record, on the same and another machine, under a former name; the store's row replaced not added, refused for an unassigned target, removed with its assignment; CanHold's four answers and that they follow the store. Full suite green against a real NATS and store.
This commit is contained in:
@@ -41,6 +41,11 @@ type Node struct {
|
||||
type World struct {
|
||||
// Held is the claims already taken, for the scopes wider than one node.
|
||||
Held []Held
|
||||
// Holdings is every seat whose holder is **on record** (novox/hq ADR 0131): the one assignment
|
||||
// that holds it, chosen by a handover. A seat absent here is held by derivation — the sole
|
||||
// eligible assignment — as it always was. Present, it decides, and any other assignment whose
|
||||
// module could hold the seat is eligible and silent rather than refused.
|
||||
Holdings []Held
|
||||
// Offered is what other nodes provide at mesh scope, and everything needed to use it.
|
||||
Offered map[string][]Provider
|
||||
// Pinned is which node this machine was told to get a provision from, by name. Only consulted
|
||||
@@ -557,7 +562,7 @@ func Resolve(catalogue map[string]Manifest, assigned []string, node Node, world
|
||||
}
|
||||
|
||||
problems = append(problems, checkCapabilities(resolution.Modules, node)...)
|
||||
claims, claimProblems := checkClaims(resolution.Modules, node, elsewhere)
|
||||
claims, claimProblems := checkClaims(resolution.Modules, node, elsewhere, world.Holdings)
|
||||
problems = append(problems, claimProblems...)
|
||||
problems = append(problems, checkResources(resolution.Modules)...)
|
||||
resolution.Claims = claims
|
||||
@@ -650,14 +655,35 @@ func checkCapabilities(modules []Manifest, node Node) []string {
|
||||
//
|
||||
// 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 []Manifest, node Node, elsewhere []Held) ([]Held, []string) {
|
||||
func checkClaims(modules []Manifest, node Node, elsewhere []Held, holdings []Held) ([]Held, []string) {
|
||||
var problems []string
|
||||
var held []Held
|
||||
|
||||
// onRecord is the recorded holder of a seat, if a handover ever named one.
|
||||
onRecord := func(claim, scope string) (Held, bool) {
|
||||
for _, h := range holdings {
|
||||
hs, ok := SeatNamed(h.Claim)
|
||||
cs, cok := SeatNamed(claim)
|
||||
if ok && cok && hs.Name == cs.Name && h.Scope == scope {
|
||||
return h, true
|
||||
}
|
||||
}
|
||||
return Held{}, false
|
||||
}
|
||||
|
||||
byScope := map[string]map[string]string{} // scope → claim → module
|
||||
for _, m := range modules {
|
||||
for _, c := range m.Claims {
|
||||
scope := c.At()
|
||||
// **A recorded holder settles it before any counting.** An assignment that could hold
|
||||
// the seat but is not the one on record is eligible, and that is all: it is not a second
|
||||
// holder, so it is not refused, and it does not hold (novox/hq ADR 0131). This is what
|
||||
// lets the next holder stand beside the current one until the seat is handed over.
|
||||
if rec, recorded := onRecord(c.Name, scope); recorded {
|
||||
if rec.Node != node.Name || rec.Module != m.Module {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if byScope[scope] == nil {
|
||||
byScope[scope] = map[string]string{}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user