A claim problem or a refused credential holds its module, not the whole pass (issue 1966, S1 and S5)
The node-engine refused the whole pass when two modules claimed a seat held once, or when a consumer's credential was refused. Now the composer leaves that module out of the declaration and composes the rest: the module is returned held (Resolved.Held, resolution.ResolveHolding), reaches the declaration as left out through the filter and the declaration #218 already keep (Rendering.NotPlaced), and Declared.LeftOut names it. The second claimant of a seat is the one held; its claim is not this node's holding. The rest-of-mesh view and the network use the same, so a node with a held module stays on the network. Resolve stays strict for callers that cannot leave a module out. Quick fix: hold only the credential refusal (resolve.go planForNow), about 20 minutes. Thorough fix, chosen: every claim problem is attributed to its module (claimsOf) and both kinds go through the one left-out path of ADR 0163 rule 6, in the declaration, the filter and the network, so no per-kind refusal of a pass remains in the composer. Left for the next step: a declared-and-not-made own secret (NotMadeError) and the Foreseeing-mode refusal still refuse the pass; the frombus candidate path holds those by stub already. Claude-Session: https://claude.ai/code/session_01NcbgfFfQ4Z9UE5jK32PmE5
This commit is contained in:
@@ -1253,6 +1253,11 @@ func Rules(r mcat.Resolution, with mcat.Rendering) ([]mcat.Rule, error) {
|
||||
// cannot be read for it, its listens open no wider than the mesh, never at the manifest's
|
||||
// default, which could reopen a port an operator had narrowed.
|
||||
left := LeftOut(r, with.Settings, with.Adopted)
|
||||
for module, why := range with.NotPlaced {
|
||||
if _, already := left[module]; !already {
|
||||
left[module] = why
|
||||
}
|
||||
}
|
||||
exposure := map[string]map[int]string{}
|
||||
for _, m := range r.Modules {
|
||||
e, err := exposureOf(m, with.Settings[m.Module])
|
||||
|
||||
@@ -37,6 +37,11 @@ func TestWithNoHolderOnRecordTheSoleClaimantHoldsAndTwoAreRefused(t *testing.T)
|
||||
if len(problems) != 1 || !strings.Contains(problems[0], "both claim") {
|
||||
t.Fatalf("two claimants with nothing on record were not refused: %v", problems)
|
||||
}
|
||||
// Held for the module that cannot hold, which then holds nothing; the other still does (issue 1966).
|
||||
held, holding := holdClaims([]mcat.Manifest{oldBroker(), newBroker()}, node, nil, nil)
|
||||
if len(holding) != 1 || !strings.Contains(holding["new-broker"], "both claim") || len(held) != 1 || held[0].Module != "old-broker" {
|
||||
t.Fatalf("the second claimant is not the one held: held=%v holding=%v", held, holding)
|
||||
}
|
||||
}
|
||||
|
||||
// With a holder on record, the other eligible assignment is silent: not refused, and not holding.
|
||||
|
||||
@@ -32,6 +32,20 @@ func (r *Refusal) Error() string {
|
||||
// What comes back is the closure — assigned modules plus everything they require — or a refusal
|
||||
// naming every reason it could not be closed.
|
||||
func Resolve(catalogue map[string]mcat.Manifest, assigned []string, node mcat.Node, world mcat.World) (mcat.Resolution, error) {
|
||||
resolution, _, err := resolve(catalogue, assigned, node, world, true)
|
||||
return resolution, err
|
||||
}
|
||||
|
||||
// ResolveHolding is Resolve where a claim problem costs the module that has it, not the machine (issue 1966, ADR 0636 §3: a
|
||||
// refused claim blocks that claimant only). The modules that cannot hold what they claim come back with why, each still in
|
||||
// the resolution's modules and none of them in its claims; the caller leaves them out of what it declares (LeftOut), so the
|
||||
// machine composes everything else. Every other refusal is Resolve's.
|
||||
func ResolveHolding(catalogue map[string]mcat.Manifest, assigned []string, node mcat.Node, world mcat.World) (mcat.Resolution, map[string]string, error) {
|
||||
return resolve(catalogue, assigned, node, world, false)
|
||||
}
|
||||
|
||||
// resolve is Resolve; with strict, a claim problem is a refusal beside every other, and without it a module held.
|
||||
func resolve(catalogue map[string]mcat.Manifest, assigned []string, node mcat.Node, world mcat.World, strict bool) (mcat.Resolution, map[string]string, error) {
|
||||
elsewhere := world.Held
|
||||
var problems []string
|
||||
|
||||
@@ -593,8 +607,13 @@ func Resolve(catalogue map[string]mcat.Manifest, assigned []string, node mcat.No
|
||||
}
|
||||
|
||||
problems = append(problems, checkCapabilities(resolution.Modules, node)...)
|
||||
claims, claimProblems := checkClaims(resolution.Modules, node, elsewhere, world.Holdings)
|
||||
problems = append(problems, claimProblems...)
|
||||
claims, held := holdClaims(resolution.Modules, node, elsewhere, world.Holdings)
|
||||
if strict {
|
||||
for _, why := range held {
|
||||
problems = append(problems, why)
|
||||
}
|
||||
held = nil
|
||||
}
|
||||
problems = append(problems, checkResources(resolution.Modules)...)
|
||||
resolution.Claims = claims
|
||||
|
||||
@@ -616,9 +635,9 @@ func Resolve(catalogue map[string]mcat.Manifest, assigned []string, node mcat.No
|
||||
|
||||
if len(problems) > 0 {
|
||||
sort.Strings(problems)
|
||||
return mcat.Resolution{}, &Refusal{Problems: problems}
|
||||
return mcat.Resolution{}, nil, &Refusal{Problems: problems}
|
||||
}
|
||||
return resolution, nil
|
||||
return resolution, held, nil
|
||||
}
|
||||
|
||||
// isModule reports whether a name is a module in its own right rather than only something
|
||||
@@ -677,7 +696,37 @@ func checkCapabilities(modules []mcat.Manifest, node mcat.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 []mcat.Manifest, node mcat.Node, elsewhere []mcat.Held, holdings []mcat.Held) ([]mcat.Held, []string) {
|
||||
var problems []string
|
||||
held, problems := claimsOf(modules, node, elsewhere, holdings)
|
||||
var out []string
|
||||
for _, p := range problems {
|
||||
out = append(out, p.why)
|
||||
}
|
||||
return held, out
|
||||
}
|
||||
|
||||
// holdClaims is checkClaims with each problem put on the module that has it: the claims that hold, and for each module that
|
||||
// cannot hold what it claims, why (every reason, joined).
|
||||
func holdClaims(modules []mcat.Manifest, node mcat.Node, elsewhere []mcat.Held, holdings []mcat.Held) ([]mcat.Held, map[string]string) {
|
||||
held, problems := claimsOf(modules, node, elsewhere, holdings)
|
||||
var out map[string]string
|
||||
for _, p := range problems {
|
||||
if out == nil {
|
||||
out = map[string]string{}
|
||||
}
|
||||
if prior, ok := out[p.module]; ok {
|
||||
out[p.module] = prior + "; " + p.why
|
||||
} else {
|
||||
out[p.module] = p.why
|
||||
}
|
||||
}
|
||||
return held, out
|
||||
}
|
||||
|
||||
// claimProblem is a refusal of one module's claim.
|
||||
type claimProblem struct{ module, why string }
|
||||
|
||||
func claimsOf(modules []mcat.Manifest, node mcat.Node, elsewhere []mcat.Held, holdings []mcat.Held) ([]mcat.Held, []claimProblem) {
|
||||
var problems []claimProblem
|
||||
var held []mcat.Held
|
||||
|
||||
// onRecord is every recorded holder of a seat, if a handover ever named one: one for most seats,
|
||||
@@ -717,9 +766,9 @@ func checkClaims(modules []mcat.Manifest, node mcat.Node, elsewhere []mcat.Held,
|
||||
// disagrees with the mesh's definition of the role, and it is refused, named, rather
|
||||
// than letting two machines answer for what the mesh has one of.
|
||||
if s, known := mcat.SeatNamed(c.Name); known && !s.Replicated && len(recs) > 1 {
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
problems = append(problems, claimProblem{m.Module, fmt.Sprintf(
|
||||
"%q is on record as held by %d assignments, and it is held once per %s — "+
|
||||
"`seat %s --to <node>/<module>` records one", c.Name, len(recs), scope, c.Name))
|
||||
"`seat %s --to <node>/<module>` records one", c.Name, len(recs), scope, c.Name)})
|
||||
continue
|
||||
}
|
||||
if !recordedHere(c.Name, scope, m.Module) {
|
||||
@@ -734,9 +783,9 @@ func checkClaims(modules []mcat.Manifest, node mcat.Node, elsewhere []mcat.Held,
|
||||
// one seat, compared by the seat they resolve to and not by how each spelled it.
|
||||
seat := heldKey(c.Name, c.Kind)
|
||||
if other, taken := byScope[scope][seat]; taken {
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
problems = append(problems, claimProblem{m.Module, fmt.Sprintf(
|
||||
"%s and %s both claim %q, and only one thing may hold it per %s",
|
||||
other, m.Module, seat, scope))
|
||||
other, m.Module, seat, scope)})
|
||||
continue
|
||||
}
|
||||
byScope[scope][seat] = m.Module
|
||||
@@ -762,20 +811,34 @@ func checkClaims(modules []mcat.Manifest, node mcat.Node, elsewhere []mcat.Held,
|
||||
// Both claim and nobody is on record, or this refusal could not have happened.
|
||||
// The remedy is the handover that records the holder (novox/hq ADR 0131,
|
||||
// 04-ISSUES/170), so it is named here rather than left to be found.
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
problems = append(problems, claimProblem{h.Module, fmt.Sprintf(
|
||||
"%s on %s claims %q, which %s on %s already holds — one per mesh. Nothing is "+
|
||||
"on record for it; `seat %s --to %s/%s` records the holder, and the other "+
|
||||
"assignment then stands beside it, eligible and silent",
|
||||
h.Module, node.Name, h.Claim, e.Module, e.Node, h.Claim, e.Node, e.Module))
|
||||
h.Module, node.Name, h.Claim, e.Module, e.Node, h.Claim, e.Node, e.Module)})
|
||||
case mcat.ScopeSite:
|
||||
if node.Site != "" && node.Site == e.Site {
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
problems = append(problems, claimProblem{h.Module, fmt.Sprintf(
|
||||
"%s on %s claims %q, which %s on %s already holds at %s — one per site",
|
||||
h.Module, node.Name, h.Claim, e.Module, e.Node, node.Site))
|
||||
h.Module, node.Name, h.Claim, e.Module, e.Node, node.Site)})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// A module refused here holds nothing of what it claims: its claims are not this node's holdings.
|
||||
refused := map[string]bool{}
|
||||
for _, p := range problems {
|
||||
refused[p.module] = true
|
||||
}
|
||||
if len(refused) > 0 {
|
||||
kept := held[:0]
|
||||
for _, h := range held {
|
||||
if !refused[h.Module] {
|
||||
kept = append(kept, h)
|
||||
}
|
||||
}
|
||||
held = kept
|
||||
}
|
||||
return held, problems
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user