mesh/delivery merged: source.main moved to novox/mesh-catalog@843281c0e5f0
mesh/check checking 447e6a13cd68 on 565d0a848a85
138 lines
4.5 KiB
Go
138 lines
4.5 KiB
Go
package main
|
|
|
|
// The marks on a clause (changed, and by what) follow the accepted links, both ways: an acceptance that adds a
|
|
// changes link sets the mark (accept), and a later accepted version that drops the link withdraws it here. Without
|
|
// this a mark outlived its link, so holds and the whole-set rule `partial` said "changed" while links and get said
|
|
// nothing (ADR 0536 v6 changed ADR 0525 §10; v12 only cited it).
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
)
|
|
|
|
// MarksTool is the module's tool that recomputes every mark now.
|
|
const MarksTool = "knowledge_marks"
|
|
|
|
// markedBy reads "ADR 0536 §4" or "research 045" as the record it names.
|
|
func markedBy(s string) (Key, bool) {
|
|
f := strings.Fields(s)
|
|
if len(f) < 2 {
|
|
return Key{}, false
|
|
}
|
|
n, err := strconv.Atoi(f[1])
|
|
if err != nil {
|
|
return Key{}, false
|
|
}
|
|
switch strings.ToLower(f[0]) {
|
|
case "adr":
|
|
return Key{KindDecision, n}, true
|
|
case "research":
|
|
return Key{KindResearch, n}, true
|
|
}
|
|
return Key{}, false
|
|
}
|
|
|
|
// StaleMarks drops, on every record of the set, each "changed by" mark whose changing record is accepted and no
|
|
// longer links the clause with a changes link; a clause left with no mark is in force again. A mark of a record
|
|
// not accepted (a change confirmed ahead of its acceptance) or of a record not held stays. It changes the
|
|
// records in place and answers the keys it changed with what it did.
|
|
func StaleMarks(set []*Record) map[Key][]string {
|
|
byKey := map[Key]*Record{}
|
|
for _, r := range set {
|
|
byKey[r.Key()] = r
|
|
}
|
|
did := map[Key][]string{}
|
|
for _, t := range set {
|
|
if t.Status == StatusSuperseded {
|
|
continue
|
|
}
|
|
for i := range t.Clauses {
|
|
c := &t.Clauses[i]
|
|
if c.Status != ClauseChanged {
|
|
continue
|
|
}
|
|
var kept []string
|
|
for _, by := range c.ChangedBy {
|
|
from, ok := markedBy(by)
|
|
src := byKey[from]
|
|
if !ok || src == nil || !src.Accepted() || changesClause(src, t.Key(), c.N) {
|
|
kept = append(kept, by)
|
|
continue
|
|
}
|
|
did[t.Key()] = append(did[t.Key()], fmt.Sprintf("§%d no longer changed by %s", c.N, by))
|
|
}
|
|
if len(kept) != len(c.ChangedBy) {
|
|
c.ChangedBy = kept
|
|
if len(kept) == 0 {
|
|
c.Status = ClauseInForce
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return did
|
|
}
|
|
|
|
func changesClause(src *Record, to Key, clause int) bool {
|
|
for _, l := range src.Links {
|
|
if k, ok := l.To.Record(); ok && l.Kind == LinkChanges && k == to && l.To.Clause == clause {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// RecomputeMarks withdraws every stale mark of the record set now, each a new version of the record it frees, and
|
|
// says what it withdrew. judgeSet runs it first, so an acceptance, the background work every few minutes and a
|
|
// start all agree with the links.
|
|
func (k *KB) RecomputeMarks(ctx context.Context) ([]string, error) {
|
|
all, err := k.Store.All(ctx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("the record set cannot be read to recompute its marks: %v", err)
|
|
}
|
|
set := make([]*Record, len(all))
|
|
for i, e := range all {
|
|
set[i] = e.Current.Clone()
|
|
}
|
|
did := StaleMarks(set)
|
|
var said []string
|
|
for i, e := range all {
|
|
what := did[e.Current.Key()]
|
|
if len(what) == 0 {
|
|
continue
|
|
}
|
|
why := strings.Join(what, "; ") + " (its changing record's accepted links no longer change it)"
|
|
if err := k.Store.Save(ctx, Version{By: "the knowledge base", At: k.now(), Why: why, Record: set[i]}, true, false, e.Revision); err != nil {
|
|
k.logf("[mesh-knowledge] the marks of %s could not be recomputed (%v): the next judgement tries again", e.Current.Key(), err)
|
|
continue
|
|
}
|
|
k.emit(EventChanged, set[i], "the knowledge base", why, e.Versions+1)
|
|
said = append(said, fmt.Sprintf("%s: %s", e.Current.Key(), why))
|
|
}
|
|
return said, nil
|
|
}
|
|
|
|
func marksTool(k *KB) stdio.Tool {
|
|
return stdio.Tool{Name: MarksTool,
|
|
Description: "Recompute every clause's changed and changed-by marks from the accepted links now, withdrawing each mark whose " +
|
|
"changing record no longer links the clause (the clause is in force again), and answer what it withdrew. The " +
|
|
"knowledge base does this on every acceptance and every few minutes; this runs it at once.",
|
|
Input: schema(nil, map[string]any{}),
|
|
Run: func(m map[string]any) (any, error) {
|
|
if _, err := readArgs(MarksTool, m); err != nil {
|
|
return nil, err
|
|
}
|
|
k.mu.Lock()
|
|
said, err := k.RecomputeMarks(context.Background())
|
|
k.mu.Unlock()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
findings := k.judgeSet(context.Background())
|
|
return map[string]any{"withdrawn": said, "findings": len(findings)}, nil
|
|
}}
|
|
}
|