package catalogue import ( "fmt" "slices" "strings" ) // A consumer of a provision that keeps its data stays bound where its data is (novox/hq ADR 0232). // // **What a resolution chooses is not where a consumer's data is.** Resolving answers "which provider // would I pick now", from the seats' holders, the pins and what is assigned where, and every one of // those can change under a consumer without anybody meaning to move it. For a resolver that is the // point: any provider answers alike. For a database it is the consumer's whole state: on 2026-10-05 // one change to how a seat's holder answers (issue 258) re-bound five database consumers on one // machine to the store on another, each was given a fresh, empty database there, and nothing warned // for twenty hours (issue 273). Nothing was lost only because the old provider kept everything. // // So the mesh records where each such consumer was last sent (the store's `binding` table), and a // resolution that would answer it from anywhere else keeps the recorded provider instead and says so. // **Only a pin moves it**, because a pin is a person: the one act that says "answer this machine's // consumers from there", taken knowing the data must go first. // KeptBinding is one consumer this resolution would have moved, and did not. type KeptBinding struct { // Machine is where the consumer runs, and Consumer the module there that is bound. Machine string Consumer string // Provision is what it is bound for. Provision string // Bound is the provider it was recorded at, and still is; Would is the one the resolution chose. Bound Chosen Would Chosen } // String is the condition's sentence: the move, where the data is, and the act that confirms it. func (k KeptBinding) String() string { return fmt.Sprintf("would move %s's %s from %s to %s — its data is on %s; kept there. "+ "`pin %s %s %s %s` to confirm a move (and move the data first)", k.Consumer, k.Provision, k.Bound, k.Would, k.Bound, k.Machine, k.Provision, k.Would.Node, k.Would.Module) } // keepBound holds every need for a provision that keeps its consumers' data at the provider its // consumer was bound to, where the resolution chose another. // // A need with no record is a binding being made, and is left as resolved: it is recorded when it is // first sent. A pin naming the provider the resolution chose is a person moving it, and is left too. // Otherwise the recorded provider answers, if it still provides the provision — beside the consumer, // or offered from elsewhere — and the move is returned as kept. **One that no longer does is refused, // never answered by the provider chosen**: answering it there is exactly the silent move this exists // to stop, and the consumer's data is still wherever it was. func keepBound(needs []Needed, catalogue map[string]Manifest, node Node, world World, keeps map[string]bool, here func(string) bool) ([]Needed, []KeptBinding, []string) { var kept []KeptBinding var problems []string refused := map[[2]string]bool{} out := make([]Needed, 0, len(needs)) for _, n := range needs { if n.ByRecord || !keeps[n.Name] { out = append(out, n) continue } n.KeepsData = true bound, recorded := world.Bound[n.For][n.Name] chose := Chosen{Node: n.From, Module: n.Module} if !recorded || sameProvider(bound, chose) { out = append(out, n) continue } if pin, pinned := world.Pinned[n.Name]; pinned && pin.matches(Provider{Node: chose.Node, Module: chose.Module}) { out = append(out, n) continue } held, ok, why := boundNeed(n, bound, catalogue, node, world, here) if !ok { if key := [2]string{n.For, n.Name}; !refused[key] { refused[key] = true problems = append(problems, fmt.Sprintf( "%s on %s is bound to %s for %q, which keeps its data, and %s — it would move to %s, "+ "which holds none of it. Move its data and say so with `pin %s %s %s %s`, or give "+ "%s back what it provided", n.For, node.Name, bound, n.Name, why, chose, node.Name, n.Name, chose.Node, chose.Module, bound.Node)) } continue } out = append(out, held) kept = append(kept, KeptBinding{Machine: node.Name, Consumer: n.For, Provision: n.Name, Bound: bound, Would: chose}) } return out, kept, problems } // sameProvider is whether a recorded provider is the one chosen. A record naming no module (none // is written without one, but a person's hand might) matches any module on its node. func sameProvider(bound, chose Chosen) bool { return bound.Node == chose.Node && (bound.Module == "" || bound.Module == chose.Module) } // boundNeed is the need answered by the recorded provider, or why it cannot be. func boundNeed(n Needed, bound Chosen, catalogue map[string]Manifest, node Node, world World, here func(string) bool) (Needed, bool, string) { held := Needed{Name: n.Name, For: n.For, Local: n.Local, KeepsData: true} if bound.Node == node.Name { m, known := catalogue[bound.Module] if !known || !here(bound.Module) || !providesAt(m, n.Name, ScopeMesh) { return Needed{}, false, fmt.Sprintf("%s no longer runs on %s", bound.Module, node.Name) } at := node.At if at == "" { at = "127.0.0.1" } held.From, held.At, held.Module = node.Name, at, m.Module held.Serves, held.SharedOwn, held.Identity = servedByOne(m, n.Name), sharedByOne(m, n.Name), m.IdentityBoundOf(n.Name) return held, true, "" } matching := bound.among(world.Offered[n.Name]) if len(matching) != 1 { return Needed{}, false, fmt.Sprintf("%s no longer provides it", bound) } p := matching[0] if node.At == "" || p.At == "" { return Needed{}, false, fmt.Sprintf("%s and %s are not both on the private network", node.Name, p.Node) } identity := DefaultIdentityBound if pm, known := catalogue[p.Module]; known { held.SharedOwn, _ = pm.SharedCredentialOf(n.Name) identity = pm.IdentityBoundOf(n.Name) } held.From, held.At, held.Module, held.Serves, held.Identity = p.Node, p.At, p.Module, p.Serves, identity return held, true, "" } // Unbound is one consumer that still asks for a provision and whose own resolution binds it to // another provider than the one a pair credential on record was made with (novox/hq issue 274). // // **A provider is granted exactly the consumers bound to it.** The credential from the old provider // stays on record — it is the key to a login that provider keeps, disabled, with the consumer's data, // until a person deletes it with `cleanup delete` (ADR 0230), and a pin back must find it — but it is // no longer granted, so the provider stops being asked for it and retires it. Said on every plan and // push of the provider, so a credential the mesh keeps and does not use is never kept silently. type Unbound struct { // Provision is what was required, Provider the machine the credential on record is from. Provision string `json:"provision"` Provider string `json:"provider"` // Consumer is the machine, Module the module on it that requires it, Local the credential's // name inside it where it keeps several (ADR 0094). Consumer string `json:"consumer"` Module string `json:"module"` Local string `json:"local,omitempty"` // BoundTo is every provider the consumer's resolution binds this credential to now; empty when // it binds it nowhere — the module asks for the provision under other local names. BoundTo []string `json:"bound_to,omitempty"` } func (u Unbound) String() string { who := u.Module if u.Local != "" { who += " (as " + u.Local + ")" } now := "is bound to no provider under that name" if len(u.BoundTo) > 0 { now = "is bound to " + strings.Join(u.BoundTo, ", ") } return fmt.Sprintf("%s on %s %s for %s, not to %s — %s no longer grants it, so it retires that "+ "login and keeps its data until `cleanup delete` (ADR 0230); the credential from %s stays on "+ "record while that login does", who, u.Consumer, now, u.Provision, u.Provider, u.Provider, u.Provider) } // BindsFrom is the providers this resolution binds a pair credential's need to — the provision, the // module that requires it and the credential's local name — answered by a machine rather than by a // record. None means this machine states no such binding. func (r Resolution) BindsFrom(provision, module, local string) []string { var from []string for _, n := range r.Needs { if n.ByRecord || n.Name != provision || n.For != module || n.Local != local { continue } if !slices.Contains(from, n.From) { from = append(from, n.From) } } return from }