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
@@ -24,7 +24,7 @@ func imported487(t *testing.T, s Store) *world {
if err != nil {
t.Fatal(err)
}
if out.Refused != 0 || out.Created != 13 {
if out.Refused != 0 || out.Created != 15 {
t.Fatalf("%s %+v", out.Said, out.Rows)
}
return w
@@ -107,7 +107,7 @@ func TestTheImportHoldsEveryFieldOfARecord(t *testing.T) {
func TestClausesComeOnlyFromTheDecisionSectionsOwnList(t *testing.T) {
eachStore(t, func(t *testing.T, s Store) {
imported487(t, s)
for _, n := range []int{259, 272, 273, 278, 288, 40} {
for _, n := range []int{259, 272, 273, 278, 288, 40, 114, 148} {
r := mustGet(t, s, Key{KindDecision, n}).Current
if len(r.Clauses) != 0 || r.Drafting == nil || r.Drafting.State != DraftWaiting {
t.Errorf("ADR %04d: %d clause(s) %v, drafting %+v", n, len(r.Clauses), r.Clauses, r.Drafting)
@@ -254,12 +254,11 @@ func TestARefreshRepairsOnlyRecordsNobodyChangedSinceTheImportAndChangesNothingT
t.Fatal(err)
}
}
// ADR 0085 was drafted since its import: someone changed it, and the refresh never touches it.
// ADR 0085 was corrected by someone since its import, and the refresh never touches it.
e85 := mustGet(t, s, Key{KindDecision, 85})
d := e85.Current.Clone()
d.Clauses = []Clause{{N: 1, Text: "**Secret-holding is a module.** It is.", Status: ClauseInForce, Source: SourceDrafted}}
d.Drafting = &Drafting{State: DraftDrafted}
if err := s.Save(ctx, Version{By: "the agent on test (sonnet)", At: at, Why: "1 clauses drafted", Record: d}, true, false, e85.Revision); err != nil {
d.Title = "A secret is a provision, corrected"
if err := s.Save(ctx, Version{By: "someone@g14", At: at, Why: "a correction by hand", Record: d}, true, false, e85.Revision); err != nil {
t.Fatal(err)
}
before := countVersions(t, s)
@@ -269,7 +268,7 @@ func TestARefreshRepairsOnlyRecordsNobodyChangedSinceTheImportAndChangesNothingT
if err != nil {
t.Fatal(err)
}
if countVersions(t, s) != before || dry.Refreshed != 12 || dry.ChangedSince != 1 || dry.Created != 0 {
if countVersions(t, s) != before || dry.Refreshed != 14 || dry.ChangedSince != 1 || dry.Created != 0 {
t.Fatalf("dry run: %s", dry.Said)
}
says := map[string]ImportRow{}
@@ -290,7 +289,7 @@ func TestARefreshRepairsOnlyRecordsNobodyChangedSinceTheImportAndChangesNothingT
if err != nil {
t.Fatal(err)
}
if out.Refreshed != 12 || out.ChangedSince != 1 || countVersions(t, s) != before+12 {
if out.Refreshed != 14 || out.ChangedSince != 1 || countVersions(t, s) != before+14 {
t.Fatalf("refresh: %s", out.Said)
}
e := mustGet(t, s, Key{KindDecision, 259})
@@ -308,7 +307,7 @@ func TestARefreshRepairsOnlyRecordsNobodyChangedSinceTheImportAndChangesNothingT
if r := mustGet(t, s, Key{KindResearch, 21}).Current; len(r.Touches) != 4 {
t.Fatalf("research 021 not repaired: %v", r.Touches)
}
if e := mustGet(t, s, Key{KindDecision, 85}); e.Versions != 2 || len(e.Current.Clauses) != 1 {
if e := mustGet(t, s, Key{KindDecision, 85}); e.Versions != 2 || e.Current.Title != "A secret is a provision, corrected" {
t.Fatalf("0085, changed since its import, was touched: %d versions", e.Versions)
}
// Run again: nothing changes, and it says so.
@@ -317,22 +316,81 @@ func TestARefreshRepairsOnlyRecordsNobodyChangedSinceTheImportAndChangesNothingT
if err != nil {
t.Fatal(err)
}
if again.Refreshed != 0 || again.Unchanged != 12 || countVersions(t, s) != after || !strings.Contains(again.Said, "Nothing changed") {
if again.Refreshed != 0 || again.Unchanged != 14 || countVersions(t, s) != after || !strings.Contains(again.Said, "Nothing changed") {
t.Fatalf("a second refresh: %s", again.Said)
}
// Once drafted, a repaired record is someone's: a later refresh leaves it alone.
draftUntil(t, w, Key{KindDecision, 259}, DraftDone)
// Drafted and confirmed since, by the knowledge base itself: still the import's side. A later refresh keeps
// the drafted clauses, since the Decision section they come from is unchanged, and changes nothing.
r259 := draftUntil(t, w, Key{KindDecision, 259}, DraftDone)
third, _ := w.k.Import("import@test", ImportAsk{Refresh: true}, at487())
changed := false
for _, row := range third.Rows {
changed = changed || (row.Record == "ADR 0259" && row.Action == ImportChangedSince)
if row.Record == "ADR 0259" {
t.Fatalf("0259 in a third refresh: %+v", row)
}
}
if third.Refreshed != 0 || !changed || mustGet(t, s, Key{KindDecision, 259}).Current.Drafting.State != DraftDone {
if got := mustGet(t, s, Key{KindDecision, 259}).Current; third.Refreshed != 0 || renderJSON(got.Clauses) != renderJSON(r259.Clauses) {
t.Fatalf("a third refresh: %s", third.Said)
}
})
}
// Live, ADR 0002 has versions 2 and 3 drafted and confirmed by the knowledge base after its import. Those are the
// import's side: a refresh repairs it, keeping its clauses while the Decision section they were drafted from is
// what hq says, and queuing it to be drafted again when it is not.
func TestARefreshRepairsARecordTheKnowledgeBaseDraftedAndKeepsItsClauses(t *testing.T) {
eachStore(t, func(t *testing.T, s Store) {
ctx := context.Background()
w := newWorld(t, s, at487())
commit, _ := at487().Standing()
p2 := DecisionsFolder + "/0002-nodes-communicate-over-a-broker.md"
p4 := DecisionsFolder + "/0004-a-node-and-how-it-joins.md"
r2 := legacy(t, p2, commit)
// 0004 has no Decision section; the import before #487 queued nothing, so a hand-made drafting stands in for
// one made from a text that is no longer what hq says.
r4 := legacy(t, p4, commit)
r4.Drafting = &Drafting{State: DraftWaiting}
r4.Sections = append(r4.Sections, Section{Name: "Decision", Text: "Nodes join by a token. That is decided here."})
for _, c := range []struct {
path string
r *Record
}{{p2, r2}, {p4, r4}} {
if err := s.Create(ctx, Version{By: Unproven, Why: "imported from hq at " + short(commit) + ": " + c.path, Record: c.r}); err != nil {
t.Fatal(err)
}
}
drafted2 := draftUntil(t, w, Key{KindDecision, 2}, DraftDone)
draftUntil(t, w, Key{KindDecision, 4}, DraftDone)
if mustGet(t, s, Key{KindDecision, 2}).Versions != 3 {
t.Fatal("0002 was not drafted and confirmed as live")
}
out, err := w.k.Import("import@test", ImportAsk{Refresh: true}, at487())
if err != nil {
t.Fatal(err)
}
if out.ChangedSince != 0 {
t.Fatalf("drafting counted as a change: %s %+v", out.Said, out.Rows)
}
e2 := mustGet(t, s, Key{KindDecision, 2})
if e2.Versions != 4 || e2.Current.Reconstructed == nil || !*e2.Current.Reconstructed ||
renderJSON(e2.Current.Clauses) != renderJSON(drafted2.Clauses) || e2.Current.Drafting == nil || e2.Current.Drafting.State != DraftDone {
t.Fatalf("0002 after the refresh: %d versions, %+v, drafting %+v", e2.Versions, e2.Current.Clauses, e2.Current.Drafting)
}
// 0004's clauses were drafted from a text hq does not hold: taken back, and drafted again from its sections.
e4 := mustGet(t, s, Key{KindDecision, 4}).Current
if len(e4.Clauses) != 0 || e4.Drafting == nil || e4.Drafting.State != DraftWaiting || !strings.Contains(e4.Drafting.Reason, "no Decision section") {
t.Fatalf("0004 after the refresh: %+v %+v", e4.Clauses, e4.Drafting)
}
// A record someone returned is someone's change: left alone.
if _, err := w.k.Return("operator@shanks", Key{KindDecision, 2}, "clause 2 says more than the record"); err != nil {
t.Fatal(err)
}
again, _ := w.k.Import("import@test", ImportAsk{Refresh: true}, at487())
if again.ChangedSince != 1 {
t.Fatalf("a returned record refreshed: %s", again.Said)
}
})
}
// A refresh without a commit's records refuses, and an import without refresh never rewrites what it holds.
func TestAnImportWithoutRefreshLeavesAHeldRecordAsItIs(t *testing.T) {
w := newWorld(t, newMemStore(), at487())