diff --git a/modules/mesh-knowledge/cmd/mesh-knowledge/active_effort_test.go b/modules/mesh-knowledge/cmd/mesh-knowledge/active_effort_test.go index 6a87a83b..ead658a6 100644 --- a/modules/mesh-knowledge/cmd/mesh-knowledge/active_effort_test.go +++ b/modules/mesh-knowledge/cmd/mesh-knowledge/active_effort_test.go @@ -3,6 +3,7 @@ package main import ( "strings" "testing" + "time" ) // effortAsk is a research effort proposal with the given documents. @@ -76,3 +77,49 @@ func TestAGraduatedEffortAndADecisionStillTakeOnlyAnInsight(t *testing.T) { t.Fatalf("a decision took a plain version: %v", err) } } + +// An old active effort's new text is held to the rules as of the day it is proposed, not the day the effort began. +func TestANewVersionOfAnOldActiveEffortIsHeldToTodaysRules(t *testing.T) { + w := imported(t, newMemStore()) + w.clock = time.Date(2026, 10, 1, 9, 0, 0, 0, time.UTC) + r, err := w.k.ReserveNumber("author@g14", KindResearch, "an old effort") + if err != nil { + t.Fatal(err) + } + overview := Section{Name: "00-overview.md", Text: "What an inference layer would be."} + first := effortAsk(r.Number, StatusActive, overview) + first.Date = "2026-10-01" + w.accepted(t, "author@g14", first) + w.clock = time.Date(2026, 10, 15, 9, 0, 0, 0, time.UTC) + next := effortAsk(r.Number, StatusActive, overview, Section{Name: "01-findings.md", Text: "The control plane wrote them."}) + next.Date = "2026-10-01" + if _, err := w.k.Propose("author@g14", next); err == nil || !strings.Contains(err.Error(), "retired-word") { + t.Fatalf("a retired word in new text of an old effort: %v", err) + } +} + +func TestAChangesVerdictLeavesTheServedVersionServedAndGraduatingNeedsABecameLink(t *testing.T) { + w := imported(t, newMemStore()) + r, err := w.k.ReserveNumber("author@g14", KindResearch, "an effort") + if err != nil { + t.Fatal(err) + } + overview := Section{Name: "00-overview.md", Text: "What an inference layer would be."} + w.accepted(t, "author@g14", effortAsk(r.Number, StatusActive, overview)) + key := Key{KindResearch, r.Number} + w.judge.review = `{"verdict":"changes","findings":["01-findings.md: say where the finding comes from"]}` + next := effortAsk(r.Number, StatusActive, overview, Section{Name: "01-findings.md", Text: "A new finding."}) + if _, err := w.k.Propose("author@g14", next); err != nil { + t.Fatal(err) + } + w.reviewAll(t) + e := mustGet(t, w.k.Store, key) + if e.Pending == nil || e.Pending.Reviews[len(e.Pending.Reviews)-1].Verdict != VerdictChanges || + e.Current.Status != StatusActive || e.Current.Section("01-findings.md") != "" { + t.Fatalf("after changes: pending %v, current %+v", e.Pending != nil, e.Current.Sections) + } + grad := effortAsk(r.Number, StatusGraduated, overview) + if _, err := w.k.Propose("author@g14", grad); err == nil || !strings.Contains(err.Error(), "became") { + t.Fatalf("graduated without a became link: %v", err) + } +} diff --git a/modules/mesh-knowledge/cmd/mesh-knowledge/kb.go b/modules/mesh-knowledge/cmd/mesh-knowledge/kb.go index f133dfd5..c11eece6 100644 --- a/modules/mesh-knowledge/cmd/mesh-knowledge/kb.go +++ b/modules/mesh-knowledge/cmd/mesh-knowledge/kb.go @@ -379,7 +379,14 @@ func (k *KB) Propose(caller string, a ProposeAsk) (*Proposed, error) { if !current && a.Kind == KindResearch && r.Effort == "" { r.Effort = held.Current.Status } - refusals = append(refusals, JudgeRecord(r, held, k.lookup(ctx), ref)...) + judged := r + if r.Pending != nil && !r.IsInsight() { + // A new version of an active effort is new text: the dated rules hold it from the day it is proposed, while + // the record keeps its own date (novox/hq issue 844). + judged = r.Clone() + judged.Date = k.now().Format("2006-01-02") + } + refusals = append(refusals, JudgeRecord(judged, held, k.lookup(ctx), ref)...) if raw, _ := json.Marshal(r); len(raw) > MostRecordBytes { refusals = append(refusals, Refusal{Rule: "size", Says: fmt.Sprintf("the record is %d bytes; a record holds at most %d", len(raw), MostRecordBytes)}) }