diff --git a/modules/mesh-knowledge/cmd/mesh-knowledge/idea_test.go b/modules/mesh-knowledge/cmd/mesh-knowledge/idea_test.go index 7f1b8865..b4bfcb24 100644 --- a/modules/mesh-knowledge/cmd/mesh-knowledge/idea_test.go +++ b/modules/mesh-knowledge/cmd/mesh-knowledge/idea_test.go @@ -16,7 +16,7 @@ func (w *world) effort(t *testing.T, status string) int { return r.Number } -// ADR 0629 §1, §4: at most three efforts are active; a fourth, new or taken up from an idea, is refused and the +// ADR 0629 §3, §4: at most three efforts are active; a fourth, new or taken up from an idea, is refused and the // refusal names the active ones; the limit is the setting's. func TestAtMostThreeResearchEffortsAreActive(t *testing.T) { w := imported(t, newMemStore()) @@ -50,11 +50,15 @@ func TestAtMostThreeResearchEffortsAreActive(t *testing.T) { } } } - // An active effort takes a new version at the limit; the setting raises the limit, and an idea taken up and + // ADR 0629 §4: an active effort takes a new version at the limit (even over it); the setting raises the limit, and an idea taken up and // still waiting for its review counts as active. if _, err := w.k.Propose("author@g14", effortAsk(active[0], StatusActive, Section{Name: "00-overview.md", Text: "More."})); err != nil { t.Fatalf("an active effort refused a new version at the limit: %v", err) } + w.k.Settings = func() (Settings, error) { return Settings{ResearchActiveAtMost: 1}, nil } + if _, err := w.k.Propose("author@g14", effortAsk(active[1], StatusActive, Section{Name: "00-overview.md", Text: "Over the limit."})); err != nil { + t.Fatalf("an active effort refused a new version over the limit: %v", err) + } w.k.Settings = func() (Settings, error) { return Settings{ResearchActiveAtMost: 4}, nil } if _, err := w.k.Propose("author@g14", effortAsk(idea, StatusActive, Section{Name: "00-overview.md", Text: "Taken up."})); err != nil { t.Fatalf("an idea taken up under a limit of 4: %v", err) @@ -65,6 +69,16 @@ func TestAtMostThreeResearchEffortsAreActive(t *testing.T) { } } +// ADR 0629 §2: holds answers nothing from an idea. +func TestHoldsAnswersNothingFromAnIdea(t *testing.T) { + w := imported(t, newMemStore()) + k := Key{KindResearch, w.effort(t, StatusIdea)} + h, err := w.k.Holds("", &k, 0) + if err != nil || len(h.Clauses) != 0 { + t.Fatalf("holds from an idea: %+v, %v", h, err) + } +} + // ADR 0629 §1, §2: list at idea is the roadmap; ask, holds and get never answer an idea as a record in force. func TestAnIdeaIsListedAndNeverServedAsTruth(t *testing.T) { w := imported(t, newMemStore()) diff --git a/modules/mesh-knowledge/cmd/mesh-knowledge/kb.go b/modules/mesh-knowledge/cmd/mesh-knowledge/kb.go index 3e3ad318..149f9d4a 100644 --- a/modules/mesh-knowledge/cmd/mesh-knowledge/kb.go +++ b/modules/mesh-knowledge/cmd/mesh-knowledge/kb.go @@ -397,7 +397,7 @@ func (k *KB) Propose(caller string, a ProposeAsk) (*Proposed, error) { if !current && a.Kind == KindResearch && r.Effort == "" { r.Effort = held.Current.Status } - if a.Kind == KindResearch && r.Effort == StatusActive { + if a.Kind == KindResearch && r.Effort == StatusActive && (held == nil || held.Current.Status != StatusActive) { if why := k.activeLimitRefusal(ctx, key); why != "" { refusals = append(refusals, Refusal{Rule: "research-active-at-most", Says: why}) } @@ -446,7 +446,7 @@ func (k *KB) Propose(caller string, a ProposeAsk) (*Proposed, error) { return &Proposed{Record: key.String(), Version: n, Status: StatusProposed, Says: says + "."}, nil } -// activeLimitRefusal (ADR 0629 §4, a check of the record set beside the one-record rules of ADR 0290 §4) says why a research effort may not be active now: the limit of efforts other than this one +// activeLimitRefusal (ADR 0629 §4 names only a new effort or one taken up from an idea, never a new version of an active one; a check of the record set beside the one-record rules of ADR 0290 §4) says why a research effort may not be active now: the limit of efforts other than this one // already active (accepted at active, or proposed at active and waiting for review) is reached (ADR 0629). Empty // when it may. func (k *KB) activeLimitRefusal(ctx context.Context, key Key) string { diff --git a/modules/mesh-knowledge/cmd/mesh-knowledge/main.go b/modules/mesh-knowledge/cmd/mesh-knowledge/main.go index 82a5b138..79aa4e12 100644 --- a/modules/mesh-knowledge/cmd/mesh-knowledge/main.go +++ b/modules/mesh-knowledge/cmd/mesh-knowledge/main.go @@ -974,7 +974,7 @@ func Tools(k *KB, caller func(map[string]any) string) []stdio.Tool { "links (supersedes, changes \"0276 §4\", extends, became, cites), and whether it needs the operator, with the brief its ask shows. Refused, naming " + "every reason at once and writing nothing, when it breaks one of the 30 rules that judge one record or its prose — a " + "link to nothing, a clause without a bold lead, a superseded record cited alone, an unknown topic, a retired word. A " + - "proposal is revised by proposing it again; an accepted record (an active research effort excepted, which takes new versions) changes only by a progressive insight (insight: what " + + "proposal is revised by proposing it again; an accepted record (an active research effort or an idea excepted, which take new versions; at most three research efforts are active) changes only by a progressive insight (insight: what " + "stood before). The knowledge base then reviews each version itself, in a judgement that sees only the record, the " + "records it links and the rules. A decision that names no home and that no accepted record links is not judged: " + "its review asks changes for the whole-set rule cited, which its acceptance would break. On approve it is accepted (a record that needs the operator waits for the operator's " +