mesh-knowledge: the review is shown every link kind propose accepts and a research effort's status apart from its state (hq issue 492)
mesh/merge-gate pass: builds mesh-knowledge → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery delivered

The first live review sent research 046 back for a touches link the rules never named and a status proposed
that is the record's state, not the effort's. The rules now list LinkKinds, the one list propose holds a link
to, each with its meaning.
This commit is contained in:
2026-10-11 13:07:29 +02:00
parent 73367192c1
commit a6e2881448
4 changed files with 86 additions and 1 deletions
+3 -1
View File
@@ -96,7 +96,9 @@ as text. `get` serves each, and any other field a record of hq wrote under `fiel
3. Revise it by proposing again; every version is kept.
4. The knowledge base reviews every version proposed itself. It asks the agent module's judge
(`claude-code.claude_code_judge` on this node) one question, in a separate call with no tools, that holds only
the record, the records it links and the rules, and answers `approve` or `changes` with findings. The background
the record, the records it links and the rules, and answers `approve` or `changes` with findings. The rules it sees
name every kind of link `propose` accepts, from the one list `propose` holds a link to, and the record it sees
shows its state in the knowledge base apart from a research effort's own status (hq issue 492). The background
work asks it at once, before the next step of any drafting, one judgement at a time.
- On `changes` the record stays proposed, the findings kept with the version they judged (`get` shows them).
Its author revises it by proposing it again, and that version is reviewed again.
@@ -73,6 +73,18 @@ const (
// LinkKinds are the kinds a link may be.
var LinkKinds = []string{LinkSupersedes, LinkChanges, LinkExtends, LinkBecame, LinkCites, LinkRestsOn, LinkTouches}
// 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.
var LinkMeanings = map[string]string{
LinkSupersedes: "a record it replaces whole",
LinkChanges: "a clause of another decision it replaces part of, named as 0276 §4",
LinkExtends: "a record of its kind it builds on",
LinkBecame: "from a research effort, the decision record or design it became",
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",
}
// 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"
@@ -87,6 +87,10 @@ func ReviewQuestion(r *Record, linked []*Record) string {
for _, rule := range PerRecordRules {
fmt.Fprintf(&b, "- %s\n", rule.Holds)
}
b.WriteString("\nThe kinds of link a record may have, each going out from the record or one of its clauses:\n")
for _, kind := range LinkKinds {
fmt.Fprintf(&b, "- `%s`: %s\n", kind, LinkMeanings[kind])
}
b.WriteString("\nThe record:\n\n")
b.WriteString(Render(reviewed(r)))
if len(linked) > 0 {
@@ -112,9 +116,16 @@ func ReviewQuestion(r *Record, linked []*Record) string {
}
// reviewed is the record as its judgement sees it: what it says, without where its review stands.
// Its state in the knowledge base (proposed, while it is reviewed) and a research effort's own status (active,
// graduated or abandoned, which it takes once accepted) are shown apart, each named for what it is (hq issue 492).
func reviewed(r *Record) *Record {
c := r.Clone()
c.Reviews, c.Ask, c.Judging, c.Drafting, c.Writers, c.Author = nil, nil, nil, nil, nil, ""
c.Extra = addExtra(c.Extra, "state in the knowledge base", c.Status+" (every record under review is; it is not the record's own status)")
if c.Kind == KindResearch && c.Effort != "" {
c.Extra = addExtra(c.Extra, "research status", c.Effort)
}
c.Status, c.Effort = "", ""
return c
}
@@ -0,0 +1,60 @@
package main
import (
"strings"
"testing"
)
// hq issue 492: the first live review (research 046) sent a valid proposal back because the rules it was shown
// named no link kind propose accepts beyond its words, and the record it was shown carried the record's state
// `proposed` where a research effort's status belongs.
// Every link kind propose accepts is named in the rules the judgement sees: one list, so the two cannot drift.
func TestTheReviewIsShownEveryLinkKindProposeAccepts(t *testing.T) {
q := ReviewQuestion(&Record{Kind: KindDecision, Number: 300, Title: "x", Status: StatusProposed}, nil)
for _, kind := range LinkKinds {
if !strings.Contains(q, "- `"+kind+"`: ") {
t.Errorf("the review is not shown the link kind %s", kind)
}
}
for _, kind := range LinkKinds {
if strings.TrimSpace(LinkMeanings[kind]) == "" {
t.Errorf("the link kind %s says nothing of what it means", kind)
}
}
if len(LinkMeanings) != len(LinkKinds) {
t.Errorf("%d meanings for %d link kinds", len(LinkMeanings), len(LinkKinds))
}
}
// A research effort's own status is shown apart from its state in the knowledge base.
func TestTheReviewIsShownAResearchEffortsStatusApartFromItsState(t *testing.T) {
w := imported(t, newMemStore())
r, err := w.k.ReserveNumber("author@g14", KindResearch, "a vendor-neutral inference layer")
if err != nil {
t.Fatal(err)
}
if _, err := w.k.Propose("author@g14", ProposeAsk{Kind: KindResearch, Number: r.Number, Title: "A vendor-neutral inference layer",
Date: "2026-10-11", Deciders: []string{"jochen"}, Status: StatusActive,
Sections: []Section{{Name: "00-overview.md", Text: "What an inference layer the mesh controls would be."}},
Links: []LinkAsk{{Kind: LinkTouches, To: "0290"}, {Kind: LinkTouches, To: "issue 365"}}}); err != nil {
t.Fatal(err)
}
w.reviewAll(t)
if len(w.judge.reviews) != 1 {
t.Fatalf("%d reviews", len(w.judge.reviews))
}
q := w.judge.reviews[0]
record := q[strings.Index(q, "The record:"):]
if !strings.Contains(record, "research status: active") || !strings.Contains(record, "state in the knowledge base: proposed") {
t.Fatalf("the record as the review sees it:\n%.600s", record)
}
for _, never := range []string{"\nstatus: proposed", "\neffort: "} {
if strings.Contains(record, never) {
t.Errorf("the review is shown %q", strings.TrimSpace(never))
}
}
if !strings.Contains(q, "- `touches`: ") {
t.Error("the review is not told what touches is")
}
}