`assign ace postgres` made the control plane's own store unresolvable: postgres's manifest claims mesh-store, nobody was ever recorded as its holder, and with two eligible assignments and nothing on record both claimed and both were refused — novox's included. ADR 0110 says the assignment holds, by a deliberate act; ADR 0131 gave the record its force but left a seat nobody handed over held by whichever assignment happened to be alone. Before acting on an assignment the controller now writes the derived answer down: every mesh-scoped seat the store knows, resolved to exactly one holder with nothing on record, gets that holder recorded — the same record `seat <name> --to <node>/<module>` makes by hand. The next assignment able to hold the seat then stands beside the holder, eligible and silent. A seat with two derived claimants is left for a person; a seat on record is never rewritten; seats the store does not list stay held by derivation as before. And the refusal, when it still happens, names the handover that records the holder. Verified: catalogue tests against the real catalogue; the cmd suite against a store (the only failure, TestConvergingPreviewsThenChanges…, fails identically on main).
93 lines
3.2 KiB
Go
93 lines
3.2 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sort"
|
|
|
|
"github.com/novox/mesh-controller/internal/catalogue"
|
|
)
|
|
|
|
// recordDerivedHolders writes down who holds each mesh-scoped seat that nobody was ever recorded
|
|
// as holding.
|
|
//
|
|
// **A seat held by derivation is a seat held by accident of being alone** (novox/hq
|
|
// 04-ISSUES/170). ADR 0131 lets a holder on record settle a seat, and lets any other assignment
|
|
// whose module could hold it stand beside the holder, eligible and silent. But a seat nobody
|
|
// ever handed over has no record, so its holder is whichever assignment happened to be the sole
|
|
// claimant — and the day a second one is assigned, both claim, both are refused, and the first
|
|
// one's whole machine stops resolving. That is what assigning a second postgres did to the
|
|
// control plane's own store.
|
|
//
|
|
// So the mesh writes the derived answer down before it acts on an assignment: for every
|
|
// mesh-scoped seat with exactly one resolved holder and nothing on record, that holder is
|
|
// recorded as the standing one — the same record `seat <name> --to <node>/<module>` makes by
|
|
// hand, made from what the mesh already resolved. A seat with two derived claimants is left
|
|
// alone: that is the ambiguity a person settles, and recording either would be guessing.
|
|
//
|
|
// Node-scoped seats are untouched: a record is one holder per seat, and a node-scoped seat has
|
|
// one holder per machine (ADR 0121), so there is nothing for a record to settle there.
|
|
func recordDerivedHolders(ctx context.Context, open *stores) ([]string, error) {
|
|
inv := open.inventory
|
|
shelf, err := inv.Catalogue(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// exclude nobody: every node's claims, resolved with the holdings on record.
|
|
world, err := theRestOfTheMesh(ctx, inv, shelf, "")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
recorded, err := inv.Holdings(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// A record is a row against a seat the store knows. A seat it does not — a mesh whose seats
|
|
// were never seeded, a seat a module declares for itself — stays held by derivation, as it
|
|
// always was; a missing row is not a reason an assignment fails.
|
|
known, err := inv.Seats(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
recordable := map[string]bool{}
|
|
for _, s := range known {
|
|
recordable[s.Name] = true
|
|
}
|
|
onRecord := map[string]bool{}
|
|
for _, h := range recorded {
|
|
if s, ok := catalogue.SeatNamed(h.Claim); ok {
|
|
onRecord[s.Name] = true
|
|
}
|
|
}
|
|
holders := map[string][]catalogue.Held{}
|
|
for _, h := range world.Held {
|
|
if h.Scope != catalogue.ScopeMesh {
|
|
continue
|
|
}
|
|
s, ok := catalogue.SeatNamed(h.Claim)
|
|
if !ok || onRecord[s.Name] || !recordable[s.Name] {
|
|
continue
|
|
}
|
|
holders[s.Name] = append(holders[s.Name], h)
|
|
}
|
|
names := make([]string, 0, len(holders))
|
|
for name := range holders {
|
|
names = append(names, name)
|
|
}
|
|
sort.Strings(names)
|
|
var said []string
|
|
for _, name := range names {
|
|
if len(holders[name]) != 1 {
|
|
continue
|
|
}
|
|
h := holders[name][0]
|
|
if err := inv.HoldSeat(ctx, name, catalogue.ScopeMesh, h.Node, h.Module); err != nil {
|
|
return said, err
|
|
}
|
|
said = append(said, fmt.Sprintf(
|
|
"recorded %s on %s as the standing holder of %s, which it held only by being alone",
|
|
h.Module, h.Node, name))
|
|
}
|
|
return said, nil
|
|
}
|