mesh-knowledge: review every proposal in a judgement of its own, and serve no review verb (hq issue 425, ADR 0290 as updated)
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 superseded: a newer head of the same pull request

The mesh proves which machine called, not which agent on it (hq issue 365), so a review verb could not tell a
reviewer from the author. Each version proposed is now judged by claude-code.claude_code_judge in a separate
call that sees only the record, the records it links and the rules: approve accepts (or asks the operator for a
needs-operator record), changes keeps it proposed with the findings for its author, and a judgement that cannot
be had or read leaves it proposed, says so, and never counts as approval.
This commit is contained in:
2026-10-11 12:06:18 +02:00
parent 300406e0dd
commit ebba63d71f
14 changed files with 743 additions and 315 deletions
+25 -98
View File
@@ -1,9 +1,9 @@
package main
// The knowledge base's writes (novox/hq ADR 0290 rules 4 and 5, to-be 55 §4, §5 and §6): a number reserved from
// the issue tracker, a record proposed and judged by the 29 rules, reviewed by someone who did not write it, and
// accepted — at once, or on the operator's proven warrant when it needs the operator — with every clause it
// changes or supersedes moved accordingly and the whole record set judged after.
// the issue tracker, a record proposed and judged by the 29 rules, reviewed by the knowledge base's own judgement
// (review.go), and accepted — at once, or on the operator's proven warrant when it needs the operator — with every
// clause it changes or supersedes moved accordingly and the whole record set judged after.
import (
"context"
@@ -83,6 +83,10 @@ type KB struct {
// mu serialises the writes: one instance holds the seat, and a write reads, judges and writes as one.
mu sync.Mutex
// kick wakes the background work when a proposal waits for its review; nil where nothing runs in the
// background (the tests take each step themselves).
kick chan struct{}
refMu sync.Mutex
ref *Reference
refAt time.Time
@@ -246,9 +250,10 @@ var numberedClause = regexp.MustCompile(`^(\d+)\.\s+`)
// MostRecordBytes is the most one record may hold.
const MostRecordBytes = 512 << 10
// Propose writes a record whole as proposed, or refuses it naming every rule it breaks. A proposal is revised
// as new versions until it is reviewed; an accepted record is changed only by a progressive insight (Insight),
// which waits for its own review while the accepted version stays the one served.
// Propose writes a record whole as proposed, or refuses it naming every rule it breaks. Every version proposed
// waits for the knowledge base's own review (review.go), which the background work takes at once. A proposal is
// revised as new versions by its author until it is accepted; an accepted record is changed only by a progressive
// insight (Insight), which is reviewed like a proposal while the accepted version stays the one served.
func (k *KB) Propose(caller string, a ProposeAsk) (*Proposed, error) {
ctx := context.Background()
k.mu.Lock()
@@ -345,6 +350,9 @@ func (k *KB) Propose(caller string, a ProposeAsk) (*Proposed, error) {
if !contains(r.Writers, caller) {
r.Writers = append(r.Writers, caller)
}
// Every version proposed is reviewed by the knowledge base itself, once; its earlier reviews stay with the
// versions they reviewed.
r.Judging = judgingOf(r)
if !current && a.Kind == KindResearch && r.Effort == "" {
r.Effort = held.Current.Status
}
@@ -365,106 +373,25 @@ func (k *KB) Propose(caller string, a ProposeAsk) (*Proposed, error) {
}
n := held.Versions + 1
k.emit(EventProposed, r, caller, why, n)
says := fmt.Sprintf("%s is proposed as version %d. Nothing of it is in force until a review by someone who did not write it approves it", key, n)
k.wake()
if r.Judging.State == JudgingUnproven {
return &Proposed{Record: key.String(), Version: n, Status: StatusProposed,
Says: fmt.Sprintf("%s is proposed as version %d, and %s. Nothing of it is in force.", key, n, r.Judging.Reason)}, nil
}
says := fmt.Sprintf("%s is proposed as version %d. The knowledge base reviews it now, in a judgement of its own; nothing of it "+
"is in force until that judgement approves it", key, n)
if r.NeedsOperator {
says += ", and then the operator's Approve"
}
if !current {
says = fmt.Sprintf("A progressive insight to %s is proposed as version %d; the accepted version is served until a review approves it", key, n)
says = fmt.Sprintf("A progressive insight to %s is proposed as version %d. The knowledge base reviews it now; the accepted "+
"version is served until that review approves it", key, n)
}
says += ". get shows the review's verdict and findings once it is made; on changes, you revise it by proposing it again"
return &Proposed{Record: key.String(), Version: n, Status: StatusProposed, Says: says + "."}, nil
}
// ---- review and acceptance ---------------------------------------------------------------------------------
// ReviewAsk is one review.
type ReviewAsk struct {
Kind string
Number int
Verdict string
Findings string
}
// Reviewed is the answer to review.
type Reviewed struct {
Record string `json:"record"`
Verdict string `json:"verdict"`
Status string `json:"status"`
Says string `json:"says"`
}
// Review takes a review of the version proposed, by someone who did not write it. On approve the record is
// accepted, or, when it needs the operator, asked of the operator; on changes it stays proposed, the findings
// kept with it.
func (k *KB) Review(caller string, a ReviewAsk) (*Reviewed, error) {
ctx := context.Background()
k.mu.Lock()
defer k.mu.Unlock()
key := Key{a.Kind, a.Number}
if a.Verdict != VerdictApprove && a.Verdict != VerdictChanges {
return nil, fmt.Errorf("verdict is approve or changes, not %q; nothing was reviewed", a.Verdict)
}
if a.Verdict == VerdictChanges && strings.TrimSpace(a.Findings) == "" {
return nil, errors.New("a review that asks for changes says them in findings; nothing was reviewed")
}
held, err := k.Store.Get(ctx, key)
if err != nil {
return nil, fmt.Errorf("the knowledge base cannot be read (%v); nothing was reviewed", err)
}
if held == nil || held.Working().Status != StatusProposed {
status := "not held"
if held != nil {
status = held.Working().Status
}
return nil, fmt.Errorf("%s is %s: only a proposed record is reviewed", key, status)
}
if !proven(caller) {
return nil, fmt.Errorf("the runtime does not prove who calls (hq issue 365), so a review cannot be told from one by %s's "+
"author, and none is taken until it does. Nothing was reviewed", key)
}
w := held.Working().Clone()
// The record's writers; an imported record's are hq's, reviewed there, and none of them is named here.
var writers []string
for _, by := range append([]string{w.Author}, w.Writers...) {
if by != "" {
writers = append(writers, by)
}
}
for _, by := range writers {
if !proven(by) {
return nil, fmt.Errorf("%s has a version written while the runtime proved no caller, so who wrote it cannot be told from "+
"who reviews it: its author is to propose it again under a new number. Nothing was reviewed", key)
}
}
if contains(writers, caller) {
return nil, fmt.Errorf("%s has a version %s wrote, who called this review: a record is reviewed by someone who wrote none "+
"of it. Nothing was reviewed", key, caller)
}
reviewed := held.CurrentVersion
if held.Pending != nil {
reviewed = held.PendingVersion
}
w.Reviews = append(w.Reviews, Review{By: caller, At: k.now(), Verdict: a.Verdict, Findings: strings.TrimSpace(a.Findings), Version: reviewed})
if a.Verdict == VerdictChanges {
if err := k.Store.Save(ctx, Version{By: caller, At: k.now(), Why: "reviewed: changes asked", Record: w, Insight: held.Pending != nil},
held.Pending == nil, false, held.Revision); err != nil {
return nil, fmt.Errorf("the review could not be kept (%v)", err)
}
k.emit(EventReviewed, w, caller, "changes asked", held.Versions+1)
return &Reviewed{Record: key.String(), Verdict: a.Verdict, Status: StatusProposed,
Says: fmt.Sprintf("%s stays proposed; the findings are kept with version %d for its author to answer with a revision.", key, reviewed)}, nil
}
if w.NeedsOperator {
return k.askOperator(ctx, held, w, caller)
}
k.emit(EventReviewed, w, caller, "approved", held.Versions+1)
r, err := k.accept(ctx, held, w, caller, "accepted after a review by "+caller, "")
if err != nil {
return nil, err
}
return &Reviewed{Record: key.String(), Verdict: a.Verdict, Status: r.Status,
Says: fmt.Sprintf("%s is %s and its clauses are in force.", key, r.Status)}, nil
}
// ---- acceptance ------------------------------------------------------------------------------------------
// accept makes a reviewed version the one served: accepted, its clauses in force, every clause and record it
// changes or supersedes moved; then the whole record set is judged. by is who acted; warrant, the operator's word