Merge pull request 'Forge rights read as the user sees them; write-only grant for mesh-tools; progress answers in minimal detail (issues 1945, 1947)' (#509) from fix/progress-and-unwritable into main

This commit was merged in pull request #509.
This commit is contained in:
2026-10-26 20:08:05 +00:00
5 changed files with 221 additions and 32 deletions
@@ -168,6 +168,8 @@ func (k *KB) Progress(a ProgressAsk) (*Progress, error) {
if err != nil {
return nil, err
}
// A cut takes the warnings from the last: the calls that failed are said before the answers that did not parse.
callsFirst(p.Warnings)
fitProgress(p, boundOf(a.Bound))
return p, nil
}
@@ -835,8 +837,21 @@ func movedSay(t *time.Time) string {
return " · moved " + when(t)
}
// callsFirst puts the warnings of calls that failed before those of answers that did not parse, in place.
func callsFirst(w []string) {
sort.SliceStable(w, func(i, j int) bool {
return !strings.Contains(w[i], " answered something else") && strings.Contains(w[j], " answered something else")
})
}
// fitProgress keeps an answer within its bound: the items go first, then the tree's last lines, saying what was cut.
// A minimal answer never carries the items: its tree says the same, one line per milestone, so leaving them out is how
// the minimal form is made (the items of design 47 alone passed 8192 bytes) and no cut. A detailed answer's items, and
// everything else that goes, are said in cut.
func fitProgress(p *Progress, bound int) {
if p.Detail == DetailMinimal && p.Tree != "" {
p.Items = nil
}
if size(p) <= bound {
return
}
@@ -306,12 +306,23 @@ func TestProgressOfADesignMinimalAndDetailed(t *testing.T) {
if len(raw) > DefaultBound || p.Detail != DetailMinimal || !strings.Contains(p.Source, "the issue tracker's milestone records of design 47") {
t.Fatalf("%d bytes, %s, %s", len(raw), p.Detail, p.Source)
}
if p.Cut != "" || len(p.Items) != 0 {
t.Errorf("a minimal answer leaves the items to its tree and is not a cut: cut %q, %d items", p.Cut, len(p.Items))
}
warns := []string{"mesh-delivery.show a answered something else: x", "issue-tracker.get 1: refused", "the tracker did not name 2", "issue-tracker.get 3 answered something else: y"}
callsFirst(warns)
if strings.Join(warns, "|") != "issue-tracker.get 1: refused|the tracker did not name 2|mesh-delivery.show a answered something else: x|issue-tracker.get 3 answered something else: y" {
t.Errorf("call failures do not come first: %q", warns)
}
if seats.asked["seat:issue-tracker.list"] != 1 || seats.asked["seat:mesh-delivery.deliveries"] != 0 || seats.asked["seat:issue-tracker.get"] != 0 {
t.Fatalf("minimal asked %v", seats.asked)
}
if w := strings.Join(p.Warnings, "\n"); !strings.Contains(w, "the tracker neither answered nor named missing") || !strings.Contains(w, "973, 525") {
t.Errorf("a row the tracker did not answer is not said: %v", p.Warnings)
}
if big, _ := k.Progress(ProgressAsk{Of: "design 47", Bound: 8192}); big.Cut != "" || big.Tree != p.Tree {
t.Errorf("a minimal answer at bound 8192 is cut: %q", big.Cut)
}
lines := strings.Split(p.Tree, "\n")
if !strings.HasPrefix(lines[0], "✗ design 47 · Delivery: from a commit to delivered — ") {
t.Fatalf("first line %q", lines[0])