mesh-knowledge: refresh repairs records the knowledge base drafted, keeping their clauses while their source is unchanged; ADR 0114 and 0148 as fixtures (review of #213)
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 covered: a later merge that contains it was delivered: novox/mesh-catalog@1b7f79f76177 (merged as 73367192 into main, walk plan-17917161183…

This commit is contained in:
2026-10-11 12:44:08 +02:00
parent 1cc0ab369f
commit 156fda4b33
5 changed files with 592 additions and 25 deletions
@@ -314,21 +314,79 @@ func (k *KB) Import(caller string, a ImportAsk, records Records) (*Imported, err
return out, nil
}
// importedOnly says whether every version of a record was written by the import or a refresh of it: nobody has
// changed it since it was read from hq.
// draftedHere is the why of a version the knowledge base wrote itself while drafting and confirming clauses, or
// judging which clause a change of hq's names (draft.go): the import's side of a record, not anyone's change.
var draftedHere = regexp.MustCompile(`^(?:\d+ clauses drafted by |\d+ of \d+ drafted clauses confirmed by |drafting failed: |` +
`confirming failed: |no Decision section|which clause of .+ it changes drafted: |that it changes .+ (?:was not confirmed: |confirmed by )|` +
`§\d+ changed by .+ \(as hq said, its clause judged and confirmed\)$)`)
// importedOnly says whether every version of a record was written by the import, a refresh of it, or the
// knowledge base's own drafting and confirming of its clauses: nobody has changed it since it was read from hq. A
// return, a correction, an acceptance over it or an insight is someone's change.
func importedOnly(vs []Version) bool {
if len(vs) == 0 {
return false
}
for _, v := range vs {
if v.Insight || v.Record == nil || !strings.HasPrefix(v.Record.Source, "hq@") ||
!(strings.HasPrefix(v.Why, "imported from hq at ") || strings.HasPrefix(v.Why, "refreshed from hq at ")) {
if v.Insight || v.Record == nil || !strings.HasPrefix(v.Record.Source, "hq@") {
return false
}
imported := strings.HasPrefix(v.Why, "imported from hq at ") || strings.HasPrefix(v.Why, "refreshed from hq at ")
drafted := draftedHere.MatchString(v.Why) && (v.By == "the knowledge base" || strings.HasPrefix(v.By, "the agent on "))
if !imported && !drafted {
return false
}
}
return true
}
// carry keeps in a record read again (now) what the knowledge base made of the one held: its clauses with their
// statuses when hq writes the same ones; its drafted or confirmed clauses and their drafting while the text they
// were drafted from is what hq says (else they are drafted again); and the clause a change of hq's names, once
// judged.
func carry(held, now *Record) {
switch {
case len(now.Clauses) > 0:
if len(held.Clauses) == len(now.Clauses) {
same := true
for i := range now.Clauses {
same = same && held.Clauses[i].N == now.Clauses[i].N && held.Clauses[i].Text == now.Clauses[i].Text
}
if same {
now.Clauses = held.Clauses
}
}
case now.Drafting != nil && held.Drafting != nil && (len(held.Clauses) > 0 || held.Drafting.State != DraftWaiting):
a, _ := draftSource(held)
b, _ := draftSource(now)
if a == b {
now.Clauses, now.Drafting = held.Clauses, held.Drafting
}
}
judged := map[Ref][]Link{}
for _, l := range held.Links {
if l.Kind == LinkChanges && l.Source != LinkPending {
if to, ok := l.To.Record(); ok {
judged[Ref{Kind: to.Kind, Number: to.Number}] = append(judged[Ref{Kind: to.Kind, Number: to.Number}], l)
}
}
}
var links []Link
for _, l := range now.Links {
target := Ref{Kind: l.To.Kind, Number: l.To.Number}
if l.Kind == LinkChanges && l.Source == LinkPending && len(judged[target]) > 0 {
for _, j := range judged[target] {
j.Unresolved = l.Unresolved
links = append(links, j)
}
delete(judged, target)
continue
}
links = append(links, l)
}
now.Links = links
}
// refresh reads a held record again (r, as hq has it at the commit) and, when only the import ever wrote it and
// what it holds differs, writes r as its new version naming what changed and why. A record changed here since
// its import is never touched; a second refresh finds the same and changes nothing.
@@ -349,6 +407,7 @@ func (k *KB) refresh(ctx context.Context, a ImportAsk, e *Entry, r *Record, path
out.ChangedSince++
return
}
carry(e.Current, r)
diffs := differences(e.Current, r)
if len(diffs) == 0 {
row.Action = ImportHeld