Research efforts can be ideas, and at most three are active (issue 1970, ADR 0629)
The roadmap lives in the knowledge base: an idea is listed, never served as in force; propose refuses a further active effort. Claude-Session: https://claude.ai/code/session_01JG73KG7AfSdh6FP8VrBqCn
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -53,8 +54,14 @@ type Settings struct {
|
||||
// ReservedFor is how long a reserved number waits for its proposal before it is withdrawn, as a duration
|
||||
// such as 168h; DefaultReservedFor when empty.
|
||||
ReservedFor string `json:"reserved-for,omitempty"`
|
||||
// ResearchActiveAtMost is the most research efforts that may be active at once (ADR 0629); DefaultResearchActiveAtMost
|
||||
// when absent or 0.
|
||||
ResearchActiveAtMost int `json:"research-active-at-most,omitempty"`
|
||||
}
|
||||
|
||||
// DefaultResearchActiveAtMost is how many research efforts may be active at once unless the settings say otherwise.
|
||||
const DefaultResearchActiveAtMost = 3
|
||||
|
||||
// DefaultReservedFor is how long a reservation waits for its proposal unless the settings say otherwise.
|
||||
const DefaultReservedFor = 7 * 24 * time.Hour
|
||||
|
||||
@@ -248,7 +255,7 @@ type ProposeAsk struct {
|
||||
NeedsOperator bool
|
||||
// Brief is what the operator's ask shows (novox/hq issue 639).
|
||||
Brief *Brief
|
||||
// Status is a research effort's: active, graduated or abandoned.
|
||||
// Status is a research effort's: active, graduated, abandoned or idea.
|
||||
Status string
|
||||
// Insight, for an accepted record, is what stood before: the proposal is a progressive insight.
|
||||
Insight string
|
||||
@@ -348,7 +355,7 @@ func (k *KB) Propose(caller string, a ProposeAsk) (*Proposed, error) {
|
||||
}
|
||||
// An active research effort is still being researched: it takes new versions freely, each reviewed as a
|
||||
// proposal while the served version stays (novox/hq issue 844). Every other accepted record needs an insight.
|
||||
revision := a.Kind == KindResearch && held.Current.Status == StatusActive && strings.TrimSpace(a.Insight) == ""
|
||||
revision := a.Kind == KindResearch && (held.Current.Status == StatusActive || held.Current.Status == StatusIdea) && strings.TrimSpace(a.Insight) == ""
|
||||
if strings.TrimSpace(a.Insight) == "" && !revision {
|
||||
return nil, fmt.Errorf("%s is %s: it changes only by a new record, or by a progressive insight, a correction of fact "+
|
||||
"that leaves the decision standing (give insight: what stood before). Nothing was proposed", key, held.Current.Status)
|
||||
@@ -390,6 +397,11 @@ 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 why := k.activeLimitRefusal(ctx, key); why != "" {
|
||||
refusals = append(refusals, Refusal{Rule: "research-active-at-most", Says: why})
|
||||
}
|
||||
}
|
||||
// A new version of an active effort is new text: the dated rules hold it from the day it is proposed, while the
|
||||
// record keeps its own date (novox/hq issue 844).
|
||||
day := r.Date
|
||||
@@ -434,6 +446,36 @@ 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
|
||||
// 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 {
|
||||
limit := k.settings().ResearchActiveAtMost
|
||||
if limit <= 0 {
|
||||
limit = DefaultResearchActiveAtMost
|
||||
}
|
||||
all, err := k.Store.All(ctx)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("the active efforts cannot be counted (%v)", err)
|
||||
}
|
||||
var names []string
|
||||
for _, e := range all {
|
||||
if e.Current == nil || e.Current.Kind != KindResearch || e.Current.Key() == key {
|
||||
continue
|
||||
}
|
||||
w := e.Working()
|
||||
if e.Current.Status == StatusActive || (w != nil && w.Effort == StatusActive && (w.Status == StatusProposed || e.Pending != nil)) {
|
||||
names = append(names, fmt.Sprintf("%s (%s)", e.Current.Key(), e.Current.Title))
|
||||
}
|
||||
}
|
||||
if len(names) < limit {
|
||||
return ""
|
||||
}
|
||||
sort.Strings(names)
|
||||
return fmt.Sprintf("at most %d research efforts are active and %d already are: %s. Park one as an idea first, or propose this one as an idea",
|
||||
limit, len(names), strings.Join(names, "; "))
|
||||
}
|
||||
|
||||
// ---- acceptance ------------------------------------------------------------------------------------------
|
||||
|
||||
// accept makes a reviewed version the one served: accepted, its clauses in force, every clause and record it
|
||||
|
||||
Reference in New Issue
Block a user