From 4839884d936a099e07c1d082eb34835d4033cdb3 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 11 Nov 2026 14:48:07 +0100 Subject: [PATCH] A decision links the milestone that builds it: link kind built-in (milestone 47/G, W15.2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../cmd/mesh-knowledge/record.go | 18 +++++++++++++++--- .../mesh-knowledge/cmd/mesh-knowledge/rules.go | 4 ++++ .../cmd/mesh-knowledge/rules_test.go | 10 +++++++++- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/modules/mesh-knowledge/cmd/mesh-knowledge/record.go b/modules/mesh-knowledge/cmd/mesh-knowledge/record.go index 1cc60e6e9..c2c48d6b0 100644 --- a/modules/mesh-knowledge/cmd/mesh-knowledge/record.go +++ b/modules/mesh-knowledge/cmd/mesh-knowledge/record.go @@ -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 diff --git a/modules/mesh-knowledge/cmd/mesh-knowledge/rules.go b/modules/mesh-knowledge/cmd/mesh-knowledge/rules.go index 0d93290cc..a893c47c2 100644 --- a/modules/mesh-knowledge/cmd/mesh-knowledge/rules.go +++ b/modules/mesh-knowledge/cmd/mesh-knowledge/rules.go @@ -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) diff --git a/modules/mesh-knowledge/cmd/mesh-knowledge/rules_test.go b/modules/mesh-knowledge/cmd/mesh-knowledge/rules_test.go index 5d39d5964..2e724cf38 100644 --- a/modules/mesh-knowledge/cmd/mesh-knowledge/rules_test.go +++ b/modules/mesh-knowledge/cmd/mesh-knowledge/rules_test.go @@ -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)