Open every grants condition's explanation as the operator-channel requires (issue 1966, recovery)
The channel refuses a raised condition whose explanation does not open with "Needs you: " and its needs, so seven of grants' conditions, among them first-view-unanswered, never reached the operator. One helper now writes both, and every condition the unit tests say passes the channel's own check. Claude-Session: https://claude.ai/code/session_01NcbgfFfQ4Z9UE5jK32PmE5
This commit is contained in:
@@ -801,6 +801,7 @@ func TestGrants(t *testing.T) {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
p.Say = channelTakes(t, p.Say)
|
||||
for _, s := range b.streams {
|
||||
if s["name"] == "OBJ_mesh-knowledge_sections" {
|
||||
s["bytes"] = int64(3500 << 20)
|
||||
@@ -1664,6 +1665,7 @@ func TestGrants(t *testing.T) {
|
||||
|
||||
var said []oc.Condition
|
||||
p.Say = func(c oc.Condition) error { said = append(said, c); return nil }
|
||||
p.Say = channelTakes(t, p.Say)
|
||||
// settled is the assignments of the lists agreeing, every hash held, and the loaded list as edit leaves it.
|
||||
settled := func(edit func(modX *grants.User)) func() {
|
||||
return func() {
|
||||
@@ -1768,6 +1770,7 @@ func TestGrants(t *testing.T) {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
fp.Say = channelTakes(t, fp.Say)
|
||||
fp.Compose()
|
||||
if len(fresh) == 0 || fresh[0].Key != grants.HeldCondition || fresh[0].State != occ.StateCleared {
|
||||
t.Errorf("a fresh part's first pass does not say the held warning is cleared: %+v", fresh)
|
||||
@@ -2442,6 +2445,7 @@ func TestGrants(t *testing.T) {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
p.Say = channelTakes(t, p.Say)
|
||||
// With no lease key (1126/A1).
|
||||
// The private network the mesh provides is placed with no fingerprint by construction: never a missing grant.
|
||||
b.assign("b", "mesh-wireguard")
|
||||
@@ -3074,6 +3078,7 @@ func TestGrants(t *testing.T) {
|
||||
sayMu.Unlock()
|
||||
return nil
|
||||
}
|
||||
say = channelTakes(t, say)
|
||||
fv := grants.NewViews(owner, say, func() {}, nil)
|
||||
fv.Rest = b
|
||||
var minutes atomic.Int64
|
||||
@@ -3467,6 +3472,7 @@ func TestGrants(t *testing.T) {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
p.Say = channelTakes(t, p.Say)
|
||||
made["max_deliver"] = 9
|
||||
res = p.Compose()
|
||||
if len(res.Consumers.Differ) != 1 || !strings.Contains(res.Consumers.Differ[0], "EVENTS.b_mod-c: max deliver 9") ||
|
||||
@@ -3538,6 +3544,7 @@ func TestGrants(t *testing.T) {
|
||||
p.Objects = &fakeObjects{}
|
||||
var said []oc.Condition
|
||||
p.Say = func(c oc.Condition) error { said = append(said, c); return nil }
|
||||
p.Say = channelTakes(t, p.Say)
|
||||
p.Compose()
|
||||
inForce, _, _ := p.Sealed()
|
||||
if inForce.Digest == "" {
|
||||
@@ -3708,6 +3715,7 @@ func TestGrants(t *testing.T) {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
p.Say = channelTakes(t, p.Say)
|
||||
p.Compose()
|
||||
inForce, _, _ := p.Sealed()
|
||||
if inForce.Digest == "" {
|
||||
@@ -3793,6 +3801,7 @@ func TestGrants(t *testing.T) {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
p.Say = channelTakes(t, p.Say)
|
||||
p.Compose()
|
||||
inForce, _, _ := p.Sealed()
|
||||
before, _, ok, _ := b.ownGet(grants.StateUserList, grants.SealedUserKey(inForce.Digest, "b.mod-r.mod-r-tools"))
|
||||
@@ -4084,3 +4093,15 @@ func TestPassComposesMembershipFromTheHaveBuild(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// channelTakes is say, after the operator-channel's own check of each condition: one the channel would refuse (its
|
||||
// explanation not opening with "Needs you: " and what it needs) fails the test, since the operator would never see it.
|
||||
func channelTakes(t *testing.T, say func(oc.Condition) error) func(oc.Condition) error {
|
||||
t.Helper()
|
||||
return func(c oc.Condition) error {
|
||||
if _, err := occ.EncodeCondition(c); err != nil {
|
||||
t.Errorf("the channel refuses the condition %s: %v", c.Key, err)
|
||||
}
|
||||
return say(c)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,9 +224,9 @@ func (p *Part) sayDiffer(st *ConsumersState) {
|
||||
} else {
|
||||
c.Severity, c.State = occ.SeverityWarning, occ.StateRaised
|
||||
c.Summary = fmt.Sprintf("%d durable consumers differ from their composed configuration", len(st.Differ))
|
||||
c.Headline, c.Needs = "A module's consumer is not as composed", "a look, and grants_consumer_match for each"
|
||||
c.Explanation = occ.NeedsYou + "a look. The bus holds these consumers otherwise than the bus composer composes them, " +
|
||||
"and the grants part never changes a held one by itself (its delivery position would be lost): " + why
|
||||
c.Headline = "A module's consumer is not as composed"
|
||||
needsYou(&c, "a look, and grants_consumer_match for each", "The bus holds these consumers otherwise than the bus composer "+
|
||||
"composes them, and the grants part never changes a held one by itself (its delivery position would be lost): "+why)
|
||||
c.Resolved = "every account consumer matches, by grants_consumer_match or otherwise"
|
||||
}
|
||||
if p.Say(c) == nil {
|
||||
@@ -321,9 +321,9 @@ func (p *Part) sayBehind(st *ConsumersState) {
|
||||
} else {
|
||||
c.Severity, c.State = occ.SeverityWarning, occ.StateRaised
|
||||
c.Summary = fmt.Sprintf("%d durable consumers are more than %d messages behind their stream's head", len(st.Behind), BehindBound)
|
||||
c.Headline, c.Needs = "Messages pile up for a listener", "a look at the module that reads them"
|
||||
c.Explanation = occ.NeedsYou + "a look. These consumers stayed far behind their stream's head at two passes " +
|
||||
"running, so what is sent to their modules waits unread (how far each is, in the grants part's last pass): " + why
|
||||
c.Headline = "Messages pile up for a listener"
|
||||
needsYou(&c, "a look at the module that reads them", "These consumers stayed far behind their stream's head at two passes "+
|
||||
"running, so what is sent to their modules waits unread (how far each is, in the grants part's last pass): "+why)
|
||||
c.Resolved = "every account consumer is back within the bound"
|
||||
}
|
||||
if p.Say(c) == nil {
|
||||
|
||||
@@ -354,9 +354,9 @@ func unansweredCondition(fv FirstView, s views.Status, raised bool) oc.Condition
|
||||
c.Summary = fmt.Sprintf("the grants part has no first view of %s's %s: %s has not answered since %s (%s)", fv.Owner, fv.State,
|
||||
fv.Tool, s.Asked.Format(time.RFC3339), strings.TrimSpace(s.Error))
|
||||
c.Headline = "The grants part waits for " + fv.Owner
|
||||
c.Explanation = "The grants part reads " + fv.Owner + "'s " + fv.State + " only through " + fv.Tool + ", and keeps asking it; " +
|
||||
"it never reads the state itself, and keeps what it composed meanwhile (novox/hq ADR 0615 §6). The answer clears this."
|
||||
c.Needs = "a look at " + fv.Owner + "'s runtime of its own: is it running and connected?"
|
||||
needsYou(&c, "a look at "+fv.Owner+"'s runtime of its own: is it running and connected?", "The grants part reads "+fv.Owner+
|
||||
"'s "+fv.State+" only through "+fv.Tool+", and keeps asking it; it never reads the state itself, and keeps what it "+
|
||||
"composed meanwhile (novox/hq ADR 0615 §6). The answer clears this.")
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -376,10 +376,10 @@ func emptyCondition(fv FirstView, raised bool) oc.Condition {
|
||||
c.State, c.Severity = occ.StateRaised, occ.SeverityWarning
|
||||
c.Summary = fmt.Sprintf("%s answered %s's %s empty at revision 0, so the grants part keeps the user list in force", fv.Tool, fv.Owner, fv.State)
|
||||
c.Headline = fv.Owner + "'s " + fv.State + " is empty"
|
||||
c.Explanation = occ.NeedsYou + "a step. The owner holds nothing of " + fv.State + " (its state rebuilt by a recreate that could " +
|
||||
"not keep the data, or a fresh mesh), so the grants part drops no login and keeps what it holds; a login revoked meanwhile " +
|
||||
"stays until the owner's first change (novox/hq design 25 § 12.2)."
|
||||
c.Needs = "let the owner write its state again from its sources (" + step + "), then grants_first_view_again"
|
||||
needsYou(&c, "let the owner write its state again from its sources ("+step+"), then grants_first_view_again", "The owner holds "+
|
||||
"nothing of "+fv.State+" (its state rebuilt by a recreate that could not keep the data, or a fresh mesh), so the grants "+
|
||||
"part drops no login and keeps what it holds; a login revoked meanwhile stays until the owner's first change "+
|
||||
"(novox/hq design 25 § 12.2).")
|
||||
return c
|
||||
}
|
||||
|
||||
|
||||
@@ -339,9 +339,9 @@ func (p *Part) sayMissing(missing []string) {
|
||||
} else {
|
||||
c.Severity, c.State = occ.SeverityWarning, occ.StateRaised
|
||||
c.Summary = fmt.Sprintf("%d placed module(s) have no grant, so no want can name them", len(missing))
|
||||
c.Headline, c.Needs = "A placed module has no bus grant", "a look at why each has none"
|
||||
c.Explanation = occ.NeedsYou + "a look. The grants part wrote no grant for these modules, so the assigner's " +
|
||||
"credential rule gives them no want: " + why
|
||||
c.Headline = "A placed module has no bus grant"
|
||||
needsYou(&c, "a look at why each has none", "The grants part wrote no grant for these modules, so the assigner's "+
|
||||
"credential rule gives them no want: "+why)
|
||||
c.Resolved = "every placed module has a grant at the fingerprint the indexer names"
|
||||
}
|
||||
if p.Say(c) == nil {
|
||||
|
||||
@@ -143,9 +143,10 @@ func (p *Part) sayHeldModules(held map[string]string, kept map[string]User) {
|
||||
} else {
|
||||
c.Severity, c.State = occ.SeverityWarning, occ.StateRaised
|
||||
c.Summary = fmt.Sprintf("%d bus user(s) of a module whose manifest is held are kept in the list: %s", len(users), why)
|
||||
c.Headline, c.Needs = "A module's manifest is held, its bus users are kept", "a manifest the indexer accepts, or a judge that knows its fields"
|
||||
c.Explanation = occ.NeedsYou + "a manifest the indexer accepts (a refused one mended, or a waiting one judged by an indexer that knows its fields). " +
|
||||
"The rest of the user list is written as ever; a held module's users stay as the list in force holds them: " + why
|
||||
c.Headline = "A module's manifest is held, its bus users are kept"
|
||||
needsYou(&c, "a manifest the indexer accepts, or a judge that knows its fields", "A refused one mended, or a waiting one "+
|
||||
"judged by an indexer that knows its fields. The rest of the user list is written as ever; a held module's users stay "+
|
||||
"as the list in force holds them: "+why)
|
||||
c.Resolved = "every module's manifest is accepted"
|
||||
}
|
||||
if p.Say(c) == nil {
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package grants
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
|
||||
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
|
||||
)
|
||||
|
||||
// needsYou sets what a raised condition needs of the operator and its explanation: the channel takes a condition whose
|
||||
// explanation opens with "Needs you: " and those very words (operatorchannelclient.EncodeCondition, novox/hq ADR 0253 §1),
|
||||
// and refuses any other, so the operator would never see it. rest is the explanation after them.
|
||||
func needsYou(c *oc.Condition, needs, rest string) {
|
||||
c.Needs = needs
|
||||
sep := ". "
|
||||
if strings.HasSuffix(needs, "?") || strings.HasSuffix(needs, ".") {
|
||||
sep = " "
|
||||
}
|
||||
c.Explanation = occ.NeedsYou + needs + sep + rest
|
||||
}
|
||||
@@ -127,9 +127,10 @@ func (p *Part) sayPrincipalsRefused(refused map[string]string, kept map[string]U
|
||||
} else {
|
||||
c.Severity, c.State = occ.SeverityWarning, occ.StateRaised
|
||||
c.Summary = fmt.Sprintf("%d principal(s) refused their composed grants: %s", len(refused), why)
|
||||
c.Headline, c.Needs = "A principal's bus grants are refused", "a fix of the manifest or the writers table"
|
||||
c.Explanation = occ.NeedsYou + "a fix of what makes it a second writer. The rest of the user list is written as ever; " +
|
||||
"each refused principal keeps the grants the list in force gives it, or stays out where it holds none: " + why
|
||||
c.Headline = "A principal's bus grants are refused"
|
||||
needsYou(&c, "a fix of the manifest or the writers table, of what makes it a second writer",
|
||||
"The rest of the user list is written as ever; each refused principal keeps the grants the list in force gives it, "+
|
||||
"or stays out where it holds none: "+why)
|
||||
c.Resolved = "every principal's grants compose again"
|
||||
}
|
||||
if p.Say(c) == nil {
|
||||
|
||||
@@ -180,8 +180,8 @@ func (p *Part) sayHeld(why string) {
|
||||
} else {
|
||||
reason := strings.TrimPrefix(why, "held: ")
|
||||
c.Severity, c.State = occ.SeverityWarning, occ.StateRaised
|
||||
c.Summary, c.Headline, c.Needs = "the user list is held: "+reason, "The user list in force is held back", "a look at why"
|
||||
c.Explanation = occ.NeedsYou + "a look at why. The bus node keeps following the list written before, not the one composed now: " + reason
|
||||
c.Summary, c.Headline = "the user list is held: "+reason, "The user list in force is held back"
|
||||
needsYou(&c, "a look at why", "The bus node keeps following the list written before, not the one composed now: "+reason)
|
||||
c.Resolved = "a pass finds nothing to hold back and the list advances"
|
||||
}
|
||||
if p.Say(c) == nil {
|
||||
|
||||
Reference in New Issue
Block a user