A decision links the milestone that builds it: link kind built-in (milestone 47/G, W15.2)
ADR 0493 section 4 names the links; the knowledge base took no milestone as a target. Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ (Joined from 5f4e4d29.) Builds: design 47 G §318 (KB1) Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
@@ -75,10 +75,12 @@ const (
|
||||
LinkCites = "cites"
|
||||
LinkRestsOn = "rests-on"
|
||||
LinkTouches = "touches"
|
||||
// LinkBuiltIn is from a decision the milestone that builds it, design/id such as 47/G (ADR 0493 §4, design 47).
|
||||
LinkBuiltIn = "built-in"
|
||||
)
|
||||
|
||||
// LinkKinds are the kinds a link may be.
|
||||
var LinkKinds = []string{LinkSupersedes, LinkChanges, LinkExtends, LinkBecame, LinkCites, LinkRestsOn, LinkTouches}
|
||||
var LinkKinds = []string{LinkSupersedes, LinkChanges, LinkExtends, LinkBecame, LinkCites, LinkRestsOn, LinkTouches, LinkBuiltIn}
|
||||
|
||||
// LinkMeanings says what each kind of link means, as the knowledge base's own review is told (hq issue 492): the
|
||||
// rules it sees list LinkKinds, which propose holds a link to, so the two cannot drift.
|
||||
@@ -90,12 +92,15 @@ var LinkMeanings = map[string]string{
|
||||
LinkCites: "any record, issue or design it names",
|
||||
LinkRestsOn: "from a decision, a decision it stands on, which may not be superseded under it",
|
||||
LinkTouches: "from a research effort, a record, issue or design its effort bears on",
|
||||
LinkBuiltIn: "from a decision, the milestone that builds it, named as milestone 47/G",
|
||||
}
|
||||
|
||||
// What a link may point at besides a record of this knowledge base: an issue in the tracker, a design in hq.
|
||||
const (
|
||||
TargetIssue = "issue"
|
||||
TargetDesign = "design"
|
||||
// TargetMilestone is a milestone of the issue tracker, design/id; its Path holds that.
|
||||
TargetMilestone = "milestone"
|
||||
)
|
||||
|
||||
// Key is a record's key: its kind and number together.
|
||||
@@ -166,6 +171,8 @@ func (r Ref) String() string {
|
||||
return fmt.Sprintf("issue %d", r.Number)
|
||||
case TargetDesign:
|
||||
return "design " + r.Path
|
||||
case TargetMilestone:
|
||||
return "milestone " + r.Path
|
||||
}
|
||||
return r.Kind
|
||||
}
|
||||
@@ -174,6 +181,8 @@ var (
|
||||
refRecord = regexp.MustCompile(`^(?i)(?:(adr|decision|research)\s*)?(\d{1,4})(?:\s*§\s*(\d{1,3}))?$`)
|
||||
refIssue = regexp.MustCompile(`^(?i)issue\s*(\d{1,5})$`)
|
||||
refDesign = regexp.MustCompile(`^(?i)(?:design\s+)?(03-DESIGN/[0-9a-z/-]+\.md)$`)
|
||||
// refMilestone is the tracker's name for a milestone: design/id, such as 47/G.
|
||||
refMilestone = regexp.MustCompile(`^(?i)milestone\s+([0-9]{1,3}/[A-Za-z0-9][A-Za-z0-9_-]{0,31})$`)
|
||||
)
|
||||
|
||||
// ParseRef reads a link's target as an agent writes it: "0276", "ADR 0276 §4", "research 045", "issue 365",
|
||||
@@ -187,6 +196,9 @@ func ParseRef(s string) (Ref, error) {
|
||||
if m := refDesign.FindStringSubmatch(s); m != nil {
|
||||
return Ref{Kind: TargetDesign, Path: m[1]}, nil
|
||||
}
|
||||
if m := refMilestone.FindStringSubmatch(s); m != nil {
|
||||
return Ref{Kind: TargetMilestone, Path: m[1]}, nil
|
||||
}
|
||||
if m := refRecord.FindStringSubmatch(s); m != nil {
|
||||
kind := KindDecision
|
||||
if strings.EqualFold(m[1], "research") {
|
||||
@@ -202,8 +214,8 @@ func ParseRef(s string) (Ref, error) {
|
||||
}
|
||||
return Ref{Kind: kind, Number: n, Clause: c}, nil
|
||||
}
|
||||
return Ref{}, fmt.Errorf("%q is not a record (0276, ADR 0276 §4, research 045), an issue (issue 365) or a design "+
|
||||
"(03-DESIGN/01-to-be/55-the-knowledge-base.md)", s)
|
||||
return Ref{}, fmt.Errorf("%q is not a record (0276, ADR 0276 §4, research 045), an issue (issue 365), a design "+
|
||||
"(03-DESIGN/01-to-be/55-the-knowledge-base.md) or a milestone (milestone 47/G)", s)
|
||||
}
|
||||
|
||||
// Link is one typed link, held on the record it goes out from and read from both ends. From is the clause it
|
||||
|
||||
@@ -248,6 +248,10 @@ func JudgeRecordOn(r *Record, held *Entry, lookup Lookup, ref *Reference, day st
|
||||
if !decision || l.To.Kind != KindDecision || l.To.Clause > 0 {
|
||||
refuse("link-fits", "rests-on goes from a decision to a decision it stands on: not %s", l)
|
||||
}
|
||||
case l.Kind == LinkBuiltIn:
|
||||
if !decision || l.To.Kind != TargetMilestone {
|
||||
refuse("link-fits", "built-in goes from a decision to the milestone that builds it, named as milestone 47/G: not %s", l)
|
||||
}
|
||||
case l.Kind == LinkTouches:
|
||||
if r.Kind != KindResearch || l.To.Clause > 0 {
|
||||
refuse("link-fits", "touches goes from a research effort to a record, an issue or a design: not %s", l)
|
||||
|
||||
@@ -54,6 +54,9 @@ func TestEveryPerRecordRuleRefusesAProposal(t *testing.T) {
|
||||
{"link-fits", func(p *ProposeAsk, w *world, t *testing.T) {
|
||||
p.Links = append(p.Links, LinkAsk{Kind: LinkBecame, To: "0276"})
|
||||
}},
|
||||
{"link-fits", func(p *ProposeAsk, w *world, t *testing.T) {
|
||||
p.Links = append(p.Links, LinkAsk{Kind: LinkBuiltIn, To: "0276"}) // built-in names a milestone, not a record
|
||||
}},
|
||||
{"self-link", func(p *ProposeAsk, w *world, t *testing.T) {
|
||||
p.Links = append(p.Links, LinkAsk{Kind: LinkCites, To: itoa4(p.Number)})
|
||||
}},
|
||||
@@ -165,11 +168,16 @@ func TestTheValidProposalIsTaken(t *testing.T) {
|
||||
eachStore(t, func(t *testing.T, s Store) {
|
||||
w := imported(t, s)
|
||||
n := w.reserved(t, "author@test")
|
||||
got, err := w.k.Propose("author@test", valid(n))
|
||||
p := valid(n)
|
||||
p.Links = append(p.Links, LinkAsk{Kind: LinkBuiltIn, To: "milestone 47/G"}) // a decision links its milestone
|
||||
got, err := w.k.Propose("author@test", p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
e := mustGet(t, s, Key{KindDecision, n})
|
||||
if l := e.Current.Links[len(e.Current.Links)-1]; l.Kind != LinkBuiltIn || l.To != (Ref{Kind: TargetMilestone, Path: "47/G"}) {
|
||||
t.Fatalf("the milestone link was kept as %+v", l)
|
||||
}
|
||||
if got.Status != StatusProposed || e.Current.Status != StatusProposed || len(e.Current.Clauses) != 2 ||
|
||||
e.Current.Clauses[0].Status != ClauseProposed || e.Current.Author != "author@test" {
|
||||
t.Fatalf("%+v / %+v", got, e.Current)
|
||||
|
||||
Reference in New Issue
Block a user