From 24d05701d357b76e4d053150865d34cb5ff40d00 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 15 Oct 2026 21:22:17 +0200 Subject: [PATCH 1/3] An active research effort takes new versions, reviewed as proposals (hq issue 844) --- modules/mesh-knowledge/README.md | 2 +- .../cmd/mesh-knowledge/active_effort_test.go | 78 +++++++++++++++++++ .../cmd/mesh-knowledge/answers.go | 2 +- .../mesh-knowledge/cmd/mesh-knowledge/kb.go | 25 ++++-- .../mesh-knowledge/cmd/mesh-knowledge/main.go | 2 +- .../cmd/mesh-knowledge/operator.go | 6 +- .../cmd/mesh-knowledge/record.go | 4 + .../cmd/mesh-knowledge/review.go | 4 +- 8 files changed, 110 insertions(+), 13 deletions(-) create mode 100644 modules/mesh-knowledge/cmd/mesh-knowledge/active_effort_test.go diff --git a/modules/mesh-knowledge/README.md b/modules/mesh-knowledge/README.md index a8d11cc3..8bef489d 100644 --- a/modules/mesh-knowledge/README.md +++ b/modules/mesh-knowledge/README.md @@ -21,7 +21,7 @@ operator, it is accepted only on the operator's Approve, given on a channel that | `links` | `number`, `kind` | the record's typed links, out and in | | `history` | `number`, `kind`, `from`, `to`, `bound` | every version; with `from` and `to`, the difference between the two, line by line | | `reserve` | `kind`, `for` | the next decision or research number, asked of the issue tracker (`issue-tracker.reserve`); spent whether or not its record is accepted | -| `propose` | the record whole | proposed, or refused naming every rule it breaks; an accepted record changes only by a progressive insight (`insight`). Each version proposed is then reviewed by the knowledge base itself (below) | +| `propose` | the record whole | proposed, or refused naming every rule it breaks; an accepted record changes only by a progressive insight (`insight`), but an active research effort takes new versions freely, each reviewed as a proposal. Each version proposed is then reviewed by the knowledge base itself (below) | | `import` | `dry-run`, `commit` | hq's decision records and research efforts at the commit the records module stands at, each whole at its own number | | `return` | `number`, `reason` | drafted clauses sent back to be drafted again; refused for a record whose clauses another record changed or links by number | diff --git a/modules/mesh-knowledge/cmd/mesh-knowledge/active_effort_test.go b/modules/mesh-knowledge/cmd/mesh-knowledge/active_effort_test.go new file mode 100644 index 00000000..6a87a83b --- /dev/null +++ b/modules/mesh-knowledge/cmd/mesh-knowledge/active_effort_test.go @@ -0,0 +1,78 @@ +package main + +import ( + "strings" + "testing" +) + +// effortAsk is a research effort proposal with the given documents. +func effortAsk(n int, status string, docs ...Section) ProposeAsk { + return ProposeAsk{Kind: KindResearch, Number: n, Title: "A vendor-neutral inference layer", Date: "2026-10-11", + Deciders: []string{"jochen"}, Status: status, Sections: docs, + Links: []LinkAsk{{Kind: LinkTouches, To: "0290"}}} +} + +// novox/hq issue 844: an active research effort takes new versions freely, each reviewed as a proposal. +func TestAnActiveEffortTakesANewVersionWithANewDocument(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} + reviews := len(w.judge.reviews) + next := effortAsk(r.Number, StatusActive, overview, Section{Name: "01-findings.md", Text: "A new finding."}) + next.Why = "findings added" + if _, err := w.k.Propose("author@g14", next); err != nil { + t.Fatalf("an active effort refused a new version: %v", err) + } + e := mustGet(t, w.k.Store, key) + if e.Pending == nil || e.Current.Section("01-findings.md") != "" { + t.Fatalf("the new version was served before its review") + } + w.reviewAll(t) + q := w.judge.reviews[reviews] + if strings.Contains(q, "a correction of fact (a progressive insight)") { + t.Fatalf("the review judged a new version as an insight:\n%.400s", q) + } + e = mustGet(t, w.k.Store, key) + if e.Pending != nil || e.Current.Status != StatusActive || e.Current.Section("01-findings.md") != "A new finding." || + len(e.Current.Insights) != 0 { + t.Fatalf("after the review: %s, pending %v, insights %v", e.Current.Status, e.Pending != nil, e.Current.Insights) + } + h, err := w.k.History(key, 0, 0, 0) + if err != nil { + t.Fatal(err) + } + for _, v := range h.Versions { + if v.Insight { + t.Errorf("version %d is marked an insight", v.N) + } + } +} + +func TestAGraduatedEffortAndADecisionStillTakeOnlyAnInsight(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."} + grad := effortAsk(r.Number, StatusGraduated, overview) + grad.Links = append(grad.Links, LinkAsk{Kind: LinkBecame, To: "0290"}) + w.accepted(t, "author@g14", grad) + next := effortAsk(r.Number, StatusGraduated, overview, Section{Name: "01-findings.md", Text: "More."}) + next.Links = grad.Links + if _, err := w.k.Propose("author@g14", next); err == nil || !strings.Contains(err.Error(), "progressive insight") { + t.Fatalf("a graduated effort took a plain version: %v", err) + } + n := w.reserved(t, "author@g14") + w.accepted(t, "author@g14", valid(n)) + p := valid(n) + p.Sections[2].Text = "Changed." + if _, err := w.k.Propose("author@g14", p); err == nil || !strings.Contains(err.Error(), "progressive insight") { + t.Fatalf("a decision took a plain version: %v", err) + } +} diff --git a/modules/mesh-knowledge/cmd/mesh-knowledge/answers.go b/modules/mesh-knowledge/cmd/mesh-knowledge/answers.go index d632437a..6245228e 100644 --- a/modules/mesh-knowledge/cmd/mesh-knowledge/answers.go +++ b/modules/mesh-knowledge/cmd/mesh-knowledge/answers.go @@ -357,7 +357,7 @@ func (k *KB) Get(a GetAsk) (*Got, error) { g.Reserved = fmt.Sprintf("reserved by %s at %s for %q", r.ReservedBy, r.ReservedAt.Format("2006-01-02 15:04 UTC"), r.ReservedFor) } if e.Pending != nil && a.Version == 0 { - g.Pending = fmt.Sprintf("version %d, a progressive insight, waits for its review: history shows what it changes", e.PendingVersion) + g.Pending = fmt.Sprintf("version %d, a progressive insight or a new version of an active effort, waits for its review: history shows what it changes", e.PendingVersion) } if r.Ask != nil { g.Waiting = "the operator's ask: " + r.Ask.Said diff --git a/modules/mesh-knowledge/cmd/mesh-knowledge/kb.go b/modules/mesh-knowledge/cmd/mesh-knowledge/kb.go index f82917b8..f133dfd5 100644 --- a/modules/mesh-knowledge/cmd/mesh-knowledge/kb.go +++ b/modules/mesh-knowledge/cmd/mesh-knowledge/kb.go @@ -335,7 +335,10 @@ func (k *KB) Propose(caller string, a ProposeAsk) (*Proposed, error) { return nil, fmt.Errorf("%s has a progressive insight by %s waiting for its review: only its author, proven by the "+ "runtime, revises it (this call is by %s). Nothing was proposed", key, held.Pending.Author, caller) } - if strings.TrimSpace(a.Insight) == "" { + // An active research effort is still being researched: it takes new versions freely, each reviewed as a + // proposal while the served version stays (novox/hq issue 844). Every other accepted record needs an insight. + revision := a.Kind == KindResearch && held.Current.Status == StatusActive && strings.TrimSpace(a.Insight) == "" + if strings.TrimSpace(a.Insight) == "" && !revision { return nil, fmt.Errorf("%s is %s: it changes only by a new record, or by a progressive insight, a correction of fact "+ "that leaves the decision standing (give insight: what stood before). Nothing was proposed", key, held.Current.Status) } @@ -349,8 +352,14 @@ func (k *KB) Propose(caller string, a ProposeAsk) (*Proposed, error) { r.Pending = &PendingState{Insight: Insight{Date: k.now().Format("2006-01-02"), Before: strings.TrimSpace(a.Insight), By: caller}} r.Date = held.Current.Date r.Effort = a.Status + if revision { + r.Pending = &PendingState{} + } if why == "" { why = "a progressive insight proposed" + if revision { + why = "a new version proposed" + } } default: return nil, fmt.Errorf("%s is %s and takes no proposal; nothing was proposed", key, held.Working().Status) @@ -381,7 +390,7 @@ func (k *KB) Propose(caller string, a ProposeAsk) (*Proposed, error) { } return nil, fmt.Errorf("%s is refused by %d rule(s), and nothing was written:\n- %s", key, len(refusals), strings.Join(lines, "\n- ")) } - v := Version{By: caller, At: k.now(), Why: why, Record: r, Insight: !current} + v := Version{By: caller, At: k.now(), Why: why, Record: r, Insight: r.IsInsight()} if err := k.Store.Save(ctx, v, current, false, held.Revision); err != nil { return nil, fmt.Errorf("%s could not be written (%v); nothing was proposed", key, err) } @@ -397,7 +406,10 @@ func (k *KB) Propose(caller string, a ProposeAsk) (*Proposed, error) { if r.NeedsOperator { says += ", and then the operator's Approve" } - if !current { + if !current && !r.IsInsight() { + says = fmt.Sprintf("A new version of %s is proposed as version %d. The knowledge base reviews it now; the served version stays "+ + "until that review approves it", key, n) + } else if !current { 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) } @@ -416,8 +428,11 @@ func (k *KB) accept(ctx context.Context, held *Entry, w *Record, by, why, warran at := k.now() if insight { in := r.Pending.Insight + isInsight := r.IsInsight() r.Pending = nil - r.Insights = append(r.Insights, in) + if isInsight { + r.Insights = append(r.Insights, in) + } r.Status = held.Current.Status if r.Kind == KindResearch && contains(ResearchStatuses, r.Effort) && held.Current.Status != StatusSuperseded { r.Status = r.Effort @@ -442,7 +457,7 @@ func (k *KB) accept(ctx context.Context, held *Entry, w *Record, by, why, warran if warrant != "" { why += "; " + warrant } - if err := k.Store.Save(ctx, Version{By: by, At: at, Why: why, Record: r, Insight: insight}, true, true, held.Revision); err != nil { + if err := k.Store.Save(ctx, Version{By: by, At: at, Why: why, Record: r, Insight: insight && w.IsInsight()}, true, true, held.Revision); err != nil { return nil, fmt.Errorf("%s could not be accepted (%v); it stays proposed", r.Key(), err) } k.emit(EventAccepted, r, by, why, held.Versions+1) diff --git a/modules/mesh-knowledge/cmd/mesh-knowledge/main.go b/modules/mesh-knowledge/cmd/mesh-knowledge/main.go index 2c4f1153..2eb6a1ef 100644 --- a/modules/mesh-knowledge/cmd/mesh-knowledge/main.go +++ b/modules/mesh-knowledge/cmd/mesh-knowledge/main.go @@ -917,7 +917,7 @@ func Tools(k *KB, caller func(map[string]any) string) []stdio.Tool { "links (supersedes, changes \"0276 §4\", extends, became, cites), and whether it needs the operator, with the brief its ask shows. Refused, naming " + "every reason at once and writing nothing, when it breaks one of the 30 rules that judge one record or its prose — a " + "link to nothing, a clause without a bold lead, a superseded record cited alone, an unknown topic, a retired word. A " + - "proposal is revised by proposing it again; an accepted record changes only by a progressive insight (insight: what " + + "proposal is revised by proposing it again; an accepted record (an active research effort excepted, which takes new versions) changes only by a progressive insight (insight: what " + "stood before). The knowledge base then reviews each version itself, in a judgement that sees only the record, the " + "records it links and the rules. A decision that names no home and that no accepted record links is not judged: " + "its review asks changes for the whole-set rule cited, which its acceptance would break. On approve it is accepted (a record that needs the operator waits for the operator's " + diff --git a/modules/mesh-knowledge/cmd/mesh-knowledge/operator.go b/modules/mesh-knowledge/cmd/mesh-knowledge/operator.go index b130cc53..5f05e26d 100644 --- a/modules/mesh-knowledge/cmd/mesh-knowledge/operator.go +++ b/modules/mesh-knowledge/cmd/mesh-knowledge/operator.go @@ -192,7 +192,7 @@ func (k *KB) askOperator(ctx context.Context, held *Entry, w *Record, by string, if err != nil || e == nil { return "", fmt.Errorf("%s: %s, and that could not be kept (%v)", w.Key(), said, err) } - if err := k.Store.Save(ctx, Version{By: ReviewBy, At: k.now(), Why: said, Record: w, Insight: e.Pending != nil}, + if err := k.Store.Save(ctx, Version{By: ReviewBy, At: k.now(), Why: said, Record: w, Insight: e.Pending != nil && e.Pending.IsInsight()}, e.Pending == nil, false, e.Revision); err != nil { return "", fmt.Errorf("%s: %s, and that could not be kept (%v)", w.Key(), said, err) } @@ -208,7 +208,7 @@ func (k *KB) askOperator(ctx context.Context, held *Entry, w *Record, by string, return unasked(fmt.Sprintf("the ask could not be made (%v)", err)) } w.Ask = oa - if err := k.Store.Save(ctx, Version{By: by, At: k.now(), Why: "reviewed: approved; the operator is asked", Record: w, Insight: held.Pending != nil}, + if err := k.Store.Save(ctx, Version{By: by, At: k.now(), Why: "reviewed: approved; the operator is asked", Record: w, Insight: held.Pending != nil && held.Pending.IsInsight()}, held.Pending == nil, false, held.Revision); err != nil { return "", fmt.Errorf("%s: the review could not be kept (%v); the operator was not asked", w.Key(), err) } @@ -267,7 +267,7 @@ func (k *KB) Reconcile() ([]string, error) { func (k *KB) endAsk(ctx context.Context, e *Entry, w *Record, why string) string { w = w.Clone() w.Ask.Said = why - if err := k.Store.Save(ctx, Version{By: "the knowledge base", At: k.now(), Why: "the operator's ask " + why, Record: w, Insight: e.Pending != nil}, + if err := k.Store.Save(ctx, Version{By: "the knowledge base", At: k.now(), Why: "the operator's ask " + why, Record: w, Insight: e.Pending != nil && e.Pending.IsInsight()}, e.Pending == nil, false, e.Revision); err != nil { return fmt.Sprintf("%s: the end of its ask could not be kept (%v)", w.Key(), err) } diff --git a/modules/mesh-knowledge/cmd/mesh-knowledge/record.go b/modules/mesh-knowledge/cmd/mesh-knowledge/record.go index f364e5b0..4ad61e16 100644 --- a/modules/mesh-knowledge/cmd/mesh-knowledge/record.go +++ b/modules/mesh-knowledge/cmd/mesh-knowledge/record.go @@ -360,6 +360,10 @@ type PendingState struct { Insight Insight `json:"insight"` } +// IsInsight says the record is waiting as a progressive insight, a correction of fact that says what stood before. +// A pending version without one is a new version of an active research effort (novox/hq issue 844). +func (r *Record) IsInsight() bool { return r.Pending != nil && r.Pending.Insight.Before != "" } + // Key is the record's key. func (r *Record) Key() Key { return Key{r.Kind, r.Number} } diff --git a/modules/mesh-knowledge/cmd/mesh-knowledge/review.go b/modules/mesh-knowledge/cmd/mesh-knowledge/review.go index 0fe414a2..24fbcf57 100644 --- a/modules/mesh-knowledge/cmd/mesh-knowledge/review.go +++ b/modules/mesh-knowledge/cmd/mesh-knowledge/review.go @@ -111,7 +111,7 @@ var reviewSchema = map[string]any{ func ReviewQuestion(r *Record, linked []*Record) string { var b strings.Builder what := "a proposed " + r.Kind + " record" - if r.Pending != nil { + if r.IsInsight() { what = "a correction of fact (a progressive insight) proposed to an accepted " + r.Kind + " record; what stood before: " + r.Pending.Insight.Before } fmt.Fprintf(&b, "You review %s of the mesh's knowledge base, %s. You see only the record, the records it links, and the "+ @@ -475,7 +475,7 @@ func (k *KB) reviewOne(key Key) (string, error) { version = now.PendingVersion } save := func(r *Record, why string) error { - return k.Store.Save(ctx, Version{By: by, At: k.now(), Why: why, Record: r, Insight: now.Pending != nil}, + return k.Store.Save(ctx, Version{By: by, At: k.now(), Why: why, Record: r, Insight: now.Pending != nil && now.Pending.IsInsight()}, now.Pending == nil, false, now.Revision) } if err != nil { -- 2.54.0 From ebfec399373496b6f58704feb5400b12fe9d2168 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 15 Oct 2026 21:26:55 +0200 Subject: [PATCH 2/3] Hold an active effort's new version to the dated rules of the day it is proposed (hq issue 844) --- .../cmd/mesh-knowledge/active_effort_test.go | 47 +++++++++++++++++++ .../mesh-knowledge/cmd/mesh-knowledge/kb.go | 9 +++- 2 files changed, 55 insertions(+), 1 deletion(-) 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)}) } -- 2.54.0 From f75e1a91d957362e6d418db587bedd2de8683f0e Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 15 Oct 2026 21:29:55 +0200 Subject: [PATCH 3/3] Carry the proposal day only into the dated rules, so an old effort's marked insight stands (hq issue 844) --- .../cmd/mesh-knowledge/active_effort_test.go | 25 +++++++++++++++++++ .../mesh-knowledge/cmd/mesh-knowledge/kb.go | 11 ++++---- .../cmd/mesh-knowledge/rules.go | 9 ++++++- 3 files changed, 38 insertions(+), 7 deletions(-) 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 ead658a6..3302dec0 100644 --- a/modules/mesh-knowledge/cmd/mesh-knowledge/active_effort_test.go +++ b/modules/mesh-knowledge/cmd/mesh-knowledge/active_effort_test.go @@ -123,3 +123,28 @@ func TestAChangesVerdictLeavesTheServedVersionServedAndGraduatingNeedsABecameLin t.Fatalf("graduated without a became link: %v", err) } } + +// An earlier marked insight in an old effort's text does not predate the effort, and a new version is accepted. +func TestAnOldActiveEffortWithAMarkedInsightTakesANewVersion(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.\n\n**Progressive insight — 2026-10-05.** A fact was corrected."} + 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: "A new finding."}) + next.Date = "2026-10-01" + if _, err := w.k.Propose("author@g14", next); err != nil { + t.Fatalf("a new version with an earlier marked insight: %v", err) + } + w.reviewAll(t) + e := mustGet(t, w.k.Store, Key{KindResearch, r.Number}) + if e.Pending != nil || e.Current.Section("01-findings.md") != "A new finding." || e.Current.Date != "2026-10-01" { + t.Fatalf("after the review: pending %v, date %s", e.Pending != nil, e.Current.Date) + } +} diff --git a/modules/mesh-knowledge/cmd/mesh-knowledge/kb.go b/modules/mesh-knowledge/cmd/mesh-knowledge/kb.go index c11eece6..4c469359 100644 --- a/modules/mesh-knowledge/cmd/mesh-knowledge/kb.go +++ b/modules/mesh-knowledge/cmd/mesh-knowledge/kb.go @@ -379,14 +379,13 @@ func (k *KB) Propose(caller string, a ProposeAsk) (*Proposed, error) { if !current && a.Kind == KindResearch && r.Effort == "" { r.Effort = held.Current.Status } - judged := r + // 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). + day := r.Date 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") + day = k.now().Format("2006-01-02") } - refusals = append(refusals, JudgeRecord(judged, held, k.lookup(ctx), ref)...) + refusals = append(refusals, JudgeRecordOn(r, held, k.lookup(ctx), ref, day)...) 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)}) } diff --git a/modules/mesh-knowledge/cmd/mesh-knowledge/rules.go b/modules/mesh-knowledge/cmd/mesh-knowledge/rules.go index 3f657d63..fbacf6c0 100644 --- a/modules/mesh-knowledge/cmd/mesh-knowledge/rules.go +++ b/modules/mesh-knowledge/cmd/mesh-knowledge/rules.go @@ -123,9 +123,16 @@ func allText(r *Record) string { // JudgeRecord holds a proposed record to the 30 rules. held is the entry its number holds (a reservation, the // proposal being revised, or the accepted record an insight corrects); lookup reads every other record. func JudgeRecord(r *Record, held *Entry, lookup Lookup, ref *Reference) []Refusal { + return JudgeRecordOn(r, held, lookup, ref, r.Date) +} + +// JudgeRecordOn is JudgeRecord with the day the dated rules are held from: the record's own day for a new +// record, the day of the proposal for a new version of an active research effort (novox/hq issue 844). The rules +// that read the record's date (the date and insight-form rules) keep reading r.Date. +func JudgeRecordOn(r *Record, held *Entry, lookup Lookup, ref *Reference, day string) []Refusal { var out []Refusal refuse := func(rule, format string, a ...any) { - if Holds(rule, r.Kind, r.Date) { + if Holds(rule, r.Kind, day) { out = append(out, Refusal{Rule: rule, Says: fmt.Sprintf(format, a...)}) } } -- 2.54.0