close: read the became links the effort already carries, check the record size, drop empty table cases (issue 784, ADR 0630)
mesh/check being carried to e9ba05bd9a3c on d2f326dff40e | from 79c2fc7fe1d5
mesh/delivery merged: source.main moved to novox/mesh-catalog@d2f326dff40e
mesh/check being carried to e9ba05bd9a3c on d2f326dff40e | from 79c2fc7fe1d5
mesh/delivery merged: source.main moved to novox/mesh-catalog@d2f326dff40e
Claude-Session: https://claude.ai/code/session_01JG73KG7AfSdh6FP8VrBqCn
This commit is contained in:
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
@@ -131,7 +132,12 @@ func (k *KB) Close(caller string, a CloseAsk) (*Closed, error) {
|
||||
look := k.lookup(ctx)
|
||||
probe := &Record{Kind: KindResearch, Number: a.Number, Title: "the closing", Date: day, Effort: a.To, Status: StatusProposed,
|
||||
Links: added, Sections: []Section{{Name: "00-overview.md", Text: note}, {Name: name, Text: note}}}
|
||||
closing := JudgeRecordOn(probe, e, look, ref, day)
|
||||
var closing []Refusal
|
||||
for _, x := range JudgeRecordOn(probe, e, look, ref, day) {
|
||||
if x.Rule != "became" { // the probe holds only the added links; the whole record's became is checked below
|
||||
closing = append(closing, x)
|
||||
}
|
||||
}
|
||||
if a.To == StatusGraduated {
|
||||
became := false
|
||||
for _, l := range r.Links {
|
||||
@@ -141,7 +147,10 @@ func (k *KB) Close(caller string, a CloseAsk) (*Closed, error) {
|
||||
refusals = append(refusals, Refusal{Rule: "became", Says: "a graduated research effort names what it became, as a became link"})
|
||||
}
|
||||
}
|
||||
for _, l := range added {
|
||||
for _, l := range r.Links {
|
||||
if l.Kind != LinkBecame {
|
||||
continue
|
||||
}
|
||||
if target, ok := l.To.Record(); ok && a.To == StatusGraduated && target.Kind == KindDecision {
|
||||
if t := look(target); t != nil && t.Status != StatusAccepted {
|
||||
refusals = append(refusals, Refusal{Rule: "link-target", Says: fmt.Sprintf("a graduated effort's became names accepted records: %s is %s", target, t.Status)})
|
||||
@@ -149,6 +158,9 @@ func (k *KB) Close(caller string, a CloseAsk) (*Closed, error) {
|
||||
}
|
||||
}
|
||||
refusals = append(refusals, closing...)
|
||||
if raw, _ := json.Marshal(r); len(raw) > MostRecordBytes {
|
||||
refusals = append(refusals, Refusal{Rule: "size", Says: fmt.Sprintf("the record is %d bytes; a record holds at most %d", len(raw), MostRecordBytes)})
|
||||
}
|
||||
if len(refusals) > 0 {
|
||||
lines := make([]string, len(refusals))
|
||||
for i, x := range refusals {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -59,12 +60,9 @@ func TestACloseAddsOneSectionAndJudgesOnlyTheClosing(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// ADR 0630 §4: the moves it allows, a refused move naming the allowed ones; the active limit never fires.
|
||||
// ADR 0630 §4: the moves it allows, a refused move naming the allowed ones.
|
||||
func TestCloseAllowsOnlyItsMoves(t *testing.T) {
|
||||
w := imported(t, newMemStore())
|
||||
for i := 0; i < 3; i++ {
|
||||
w.effort(t, StatusActive) // the limit is reached: close must not meet it
|
||||
}
|
||||
becameTo := []LinkAsk{{Kind: LinkBecame, To: "0290"}}
|
||||
citesTo := []LinkAsk{{Kind: LinkCites, To: "0290"}}
|
||||
for _, c := range []struct {
|
||||
@@ -75,20 +73,17 @@ func TestCloseAllowsOnlyItsMoves(t *testing.T) {
|
||||
note string
|
||||
wantErr string
|
||||
}{
|
||||
{"an idea to abandoned, at the limit", StatusIdea, StatusAbandoned, citesTo, closeNote("0290"), ""},
|
||||
{"an idea to abandoned", StatusIdea, StatusAbandoned, citesTo, closeNote("0290"), ""},
|
||||
{"an idea to graduated", StatusIdea, StatusGraduated, becameTo, closeNote("0290"), "from idea it allows abandoned"},
|
||||
{"an idea to an idea", StatusIdea, StatusIdea, nil, closeNote("0290"), "from idea it allows abandoned"},
|
||||
{"an active to an idea, at the limit", StatusActive, StatusIdea, nil, closeNote("0290"), ""},
|
||||
{"an active to an idea", StatusActive, StatusIdea, nil, closeNote("0290"), ""},
|
||||
{"graduated needs a became", StatusActive, StatusGraduated, nil, closeNote("0290"), "became"},
|
||||
{"a wrong link kind", StatusActive, StatusAbandoned, becameTo, closeNote("0290"), "cites"},
|
||||
{"no note", StatusActive, StatusIdea, nil, " ", "note"},
|
||||
} {
|
||||
n := w.effort(t, StatusIdea)
|
||||
if c.from == StatusActive {
|
||||
// make room under the limit for the effort under test, then take it up
|
||||
w.k.Settings = func() (Settings, error) { return Settings{ResearchActiveAtMost: 9}, nil }
|
||||
w.accepted(t, "author@g14", effortAsk(n, StatusActive, Section{Name: "00-overview.md", Text: "Taken up."}))
|
||||
w.k.Settings = nil
|
||||
}
|
||||
_, err := w.k.Close("lead@shanks", CloseAsk{Number: n, To: c.to, Links: c.links, Note: c.note})
|
||||
if (err == nil) != (c.wantErr == "") || (err != nil && !strings.Contains(err.Error(), c.wantErr)) {
|
||||
@@ -136,3 +131,41 @@ func TestACloseEndsTheWaitingVersionAndItsAsk(t *testing.T) {
|
||||
t.Errorf("after the review: %s, the ended version's finding served: %q", got.Status, got.Section("01-findings.md"))
|
||||
}
|
||||
}
|
||||
|
||||
// ADR 0630 §2: a graduated close counts the became links the effort already carries, and holds every one of them
|
||||
// to naming an accepted record.
|
||||
func TestAGraduatingCloseReadsTheBecameLinksTheEffortCarries(t *testing.T) {
|
||||
w := imported(t, newMemStore())
|
||||
dec := w.reserved(t, "author@test")
|
||||
for _, c := range []struct {
|
||||
name, became, wantErr string
|
||||
}{
|
||||
{"an old became to an accepted decision", "0290", ""},
|
||||
{"an old became to a decision not accepted", Key{KindDecision, dec}.String(), "accepted"},
|
||||
} {
|
||||
r, _ := w.k.ReserveNumber("author@g14", KindResearch, "an effort")
|
||||
a := effortAsk(r.Number, StatusActive, Section{Name: "00-overview.md", Text: "What it was."})
|
||||
a.Links = append(a.Links, LinkAsk{Kind: LinkBecame, To: "0290"})
|
||||
w.accepted(t, "author@g14", a)
|
||||
if c.became != "0290" {
|
||||
if _, err := w.k.Propose("author@test", valid(dec)); err != nil { // proposed, never reviewed: not accepted
|
||||
t.Fatal(err)
|
||||
}
|
||||
// the old link goes to a record not accepted: written into the held record, as an import would
|
||||
e := mustGet(t, w.k.Store, Key{KindResearch, r.Number})
|
||||
cur := e.Current.Clone()
|
||||
for i := range cur.Links {
|
||||
if cur.Links[i].Kind == LinkBecame {
|
||||
cur.Links[i].To = Ref{Kind: KindDecision, Number: dec}
|
||||
}
|
||||
}
|
||||
if err := w.k.Store.Save(context.Background(), Version{By: "import@test", At: time.Now(), Why: "seeded", Record: cur}, true, false, e.Revision); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
_, err := w.k.Close("lead@shanks", CloseAsk{Number: r.Number, To: StatusGraduated, Note: closeNote("0290")})
|
||||
if (err == nil) != (c.wantErr == "") || (err != nil && !strings.Contains(err.Error(), c.wantErr)) {
|
||||
t.Errorf("%s: %v, want %q", c.name, err, c.wantErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1135,7 +1135,7 @@ func Tools(k *KB, caller func(map[string]any) string) []stdio.Tool {
|
||||
"and one more section (Graduated, Abandoned or Parked as an idea) holding your note of two or three sentences. Only the " +
|
||||
"closing is judged: the rules run on the added links and the note, and the knowledge base's review sees only the closing, " +
|
||||
"so an old text's findings never ask changes; they come back once in the answer as notes. A version of the effort still " +
|
||||
"waiting for its review, and the operator's ask for it, end with the closing, named in the answer. Allowed: an active effort " +
|
||||
"waiting for its review, whoever its author is, and the operator's ask for it, end with the closing, named in the answer. Allowed: an active effort " +
|
||||
"to graduated, abandoned or idea; an idea to abandoned; anything else is refused naming the move it allows. An idea is taken up " +
|
||||
"by propose at active.",
|
||||
Input: schema([]string{"number", "to", "note"}, map[string]any{"number": integer("the research effort's number"),
|
||||
|
||||
Reference in New Issue
Block a user