package main // The answers (novox/hq ADR 0290 rule 6, to-be 55 §2 and §3): ask, get, holds, links and history, each the shape // an agent needs. ask, get and holds stay under a bound they state, 4 KiB unless the caller asks for more, and say // when they were cut and how to read the rest. A drafted clause is marked as such and never served as in force. import ( "context" "encoding/json" "fmt" "sort" "strconv" "strings" ) // MostQuestion is the most characters a question to ask may hold. const MostQuestion = 500 // The bound of an answer. const ( DefaultBound = 4 << 10 MostBound = 64 << 10 LeastBound = 1 << 10 ) func boundOf(b int) int { switch { case b <= 0: return DefaultBound case b < LeastBound: return LeastBound case b > MostBound: return MostBound } return b } func size(v any) int { raw, _ := json.Marshal(v) return len(raw) } // fit is the largest n (from 0 to total) whose answer stays within the bound. func fit(total, bound int, build func(n int) any) (any, int) { lo, hi := 0, total for lo < hi { mid := (lo + hi + 1) / 2 if size(build(mid)) <= bound { lo = mid } else { hi = mid - 1 } } return build(lo), lo } func clip(s string, n int) string { s = strings.Join(strings.Fields(s), " ") if len([]rune(s)) <= n { return s } return string([]rune(s)[:n-1]) + "…" } // held says, for a clause, whether it holds and what changed it, in the words an agent reads. func held(r *Record, c Clause) string { switch { case c.Source == SourceDrafted: s := "drafted, not confirmed: not the record's word, never in force" if c.Unconfirmed != "" { s += " (the second judgement did not confirm it: " + c.Unconfirmed + ")" } if len(c.ChangedBy) > 0 { if c.Status == ClauseSuperseded { s += "; superseded by " + strings.Join(c.ChangedBy, ", ") } else { s += "; changed by " + strings.Join(c.ChangedBy, ", ") + ": read both" } } return s case r.Status == StatusProposed || c.Status == ClauseProposed: return "proposed: not in force" case c.Status == ClauseChanged: return "changed by " + strings.Join(c.ChangedBy, ", ") + ": read both" case c.Status == ClauseSuperseded: if len(c.ChangedBy) > 0 { return "superseded by " + strings.Join(c.ChangedBy, ", ") } return "superseded" case c.Source == SourceConfirmed: return "in force (drafted from prose, confirmed)" } return "in force" } // Served says whether a clause may be served as holding: in force or changed, and not drafted. func Served(r *Record, c Clause) bool { return r.Accepted() && c.Source != SourceDrafted && (c.Status == ClauseInForce || c.Status == ClauseChanged) } // AnswerClause is one clause in an answer. type AnswerClause struct { N int `json:"n"` Text string `json:"text"` Holds string `json:"holds"` Source string `json:"source,omitempty"` } // AnswerRecord is one record in an answer to ask. type AnswerRecord struct { Record string `json:"record"` Title string `json:"title"` Status string `json:"status"` Clauses []AnswerClause `json:"clauses,omitempty"` Note string `json:"note,omitempty"` Since []string `json:"changed-since,omitempty"` Read string `json:"read"` } // Asked is the answer to ask. type Asked struct { Question string `json:"question"` Records []AnswerRecord `json:"records"` Cut string `json:"cut,omitempty"` Bound int `json:"bound"` Says string `json:"says,omitempty"` } func readHow(k Key) string { return fmt.Sprintf("get {\"kind\": %q, \"number\": %d, \"reasoning\": true}", k.Kind, k.Number) } // since is what changed a record since it was accepted: the accepted records that supersede or change it. func (k *KB) since(ctx context.Context, key Key) []string { in, err := k.Store.Inbound(ctx, key) if err != nil { return nil } var out []string for _, x := range in { if x.Link.Kind != LinkSupersedes && x.Link.Kind != LinkChanges { continue } from, err := k.Store.Get(ctx, x.From) if err != nil || from == nil || !from.Current.Accepted() { continue } s := x.From.String() if x.Link.From > 0 { s += fmt.Sprintf(" §%d", x.Link.From) } switch { case x.Link.Kind == LinkSupersedes: s += " supersedes it" case x.Link.To.Clause > 0: s += fmt.Sprintf(" changes §%d", x.Link.To.Clause) default: s += " changes it in part; which clause is not judged yet" } out = append(out, s) } return out } // Ask is the records and clauses bearing on a question, ranked, each saying whether it holds or what changed it. func (k *KB) Ask(question string, bound int) (*Asked, error) { ctx := context.Background() question = strings.TrimSpace(question) if question == "" { return nil, fmt.Errorf("ask needs a question, in plain words") } if len([]rune(question)) > MostQuestion { return nil, fmt.Errorf("a question is at most %d characters; this one is %d: ask it in fewer words", MostQuestion, len([]rune(question))) } bound = boundOf(bound) hits, err := k.Store.Search(ctx, question, 80) if err != nil { return nil, fmt.Errorf("the knowledge base cannot be searched (%v)", err) } type group struct { key Key clauses []int record bool // score is the record's own rank, its best clause's, and a little of each other clause's. score, best, rest float64 } var order []*group byKey := map[Key]*group{} for _, h := range hits { g := byKey[h.Key] if g == nil { g = &group{key: h.Key} byKey[h.Key] = g order = append(order, g) } if h.Clause > 0 { g.clauses = append(g.clauses, h.Clause) if h.Rank > g.best { g.rest += g.best g.best = h.Rank } else { g.rest += h.Rank } } else { g.record = true g.score += h.Rank } } for _, g := range order { g.score += g.best + g.rest/4 } sort.SliceStable(order, func(i, j int) bool { return order[i].score > order[j].score }) var records []AnswerRecord for _, g := range order { e, err := k.Store.Get(ctx, g.key) if err != nil || e == nil { continue } r := e.Current ar := AnswerRecord{Record: g.key.String(), Title: r.Title, Status: r.Status, Read: readHow(g.key), Since: k.since(ctx, g.key)} sort.Ints(g.clauses) for _, n := range g.clauses { if c := r.Clause(n); c != nil { ar.Clauses = append(ar.Clauses, AnswerClause{N: n, Text: clip(c.Text, 220), Holds: held(r, *c), Source: c.Source}) } } if len(ar.Clauses) == 0 { drafted := 0 for _, c := range r.Clauses { if c.Source == SourceDrafted { drafted++ } } switch { case len(r.Clauses) == 0 && r.Kind == KindDecision: ar.Note = "its decision is written as prose and its clauses are not drafted yet: read its Decision section" case drafted > 0: ar.Note = fmt.Sprintf("%d of its clauses are drafted and not confirmed: none of them is served as in force", drafted) } } records = append(records, ar) } out := &Asked{Question: question, Bound: bound} if len(records) == 0 { out.Records = []AnswerRecord{} out.Says = "nothing the knowledge base holds bears on that; ask in other words" return out, nil } // Within the bound: the most records that fit; when not even one fits whole, its clauses shortened. build := func(n int) any { a := *out a.Records = records[:n] if n < len(records) { names := []string{} for _, r := range records[n:min(len(records), n+6)] { names = append(names, r.Record) } a.Cut = fmt.Sprintf("%d more record(s) bear on it and are not shown within %d bytes (%s…): ask with a larger bound, or narrower words", len(records)-n, bound, strings.Join(names, ", ")) } return &a } v, n := fit(len(records), bound, build) if n == 0 { first := records[0] for i := range first.Clauses { first.Clauses[i].Text = clip(first.Clauses[i].Text, 80) } records[0] = first v, _ = fit(len(records), bound, build) } return v.(*Asked), nil } // ---- get ----------------------------------------------------------------------------------------------------- // GetAsk is what get takes. type GetAsk struct { Kind string Number int Reasoning bool Section string Version int Bound int } // Got is the answer to get. type Got struct { Record string `json:"record"` Title string `json:"title"` Status string `json:"status"` Topic string `json:"topic,omitempty"` Date string `json:"date,omitempty"` Deciders []string `json:"deciders,omitempty"` Version int `json:"version"` Versions int `json:"versions"` Clauses []AnswerClause `json:"clauses,omitempty"` Links []string `json:"links,omitempty"` LinkedFrom []string `json:"linked-from,omitempty"` Since []string `json:"changed-since,omitempty"` Sections []string `json:"sections,omitempty"` Reasoning []Section `json:"reasoning,omitempty"` Insights []Insight `json:"insights,omitempty"` Pending string `json:"pending,omitempty"` Waiting string `json:"waiting,omitempty"` Reviews []Review `json:"reviews,omitempty"` Drafting *Drafting `json:"drafting,omitempty"` Review string `json:"review,omitempty"` Source string `json:"source,omitempty"` Reserved string `json:"reserved,omitempty"` NeedsOperat bool `json:"needs-operator,omitempty"` // Brief is what the operator's ask shows of it (issue 639). Brief *Brief `json:"brief,omitempty"` Cut string `json:"cut,omitempty"` Bound int `json:"bound"` Read string `json:"read,omitempty"` // Reconstructed, Amended and Touches are the record's fields of those names; Fields, any other it holds as // written (hq issue 487). Reconstructed *bool `json:"reconstructed,omitempty"` Amended []string `json:"amended,omitempty"` Touches []string `json:"touches,omitempty"` Fields map[string]string `json:"fields,omitempty"` } // Get is one record: its clauses, its links both ways, what changed it since; its reasoning only when asked. func (k *KB) Get(a GetAsk) (*Got, error) { ctx := context.Background() if a.Kind == "" { a.Kind = KindDecision } key := Key{a.Kind, a.Number} e, err := k.Store.Get(ctx, key) if err != nil { return nil, fmt.Errorf("the knowledge base cannot be read (%v)", err) } if e == nil { return nil, fmt.Errorf("the knowledge base holds no %s", key) } r, version := e.Current, e.CurrentVersion if a.Version > 0 { vs, err := k.Store.Versions(ctx, key) if err != nil { return nil, err } if a.Version > len(vs) { return nil, fmt.Errorf("%s has %d version(s); there is no version %d", key, len(vs), a.Version) } r, version = vs[a.Version-1].Record, a.Version } bound := boundOf(a.Bound) g := &Got{Record: key.String(), Title: r.Title, Status: r.Status, Topic: r.Topic, Date: r.Date, Deciders: r.Deciders, Version: version, Versions: e.Versions, Insights: r.Insights, Reviews: r.Reviews, Drafting: r.Drafting, Source: r.Source, NeedsOperat: r.NeedsOperator, Brief: r.Brief, Bound: bound, Since: k.since(ctx, key), Reconstructed: r.Reconstructed, Amended: r.Amended, Touches: r.Touches, Fields: r.Extra} if r.Status == StatusReserved || r.Status == StatusWithdrawn { 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 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 } if a.Version == 0 { g.Review = reviewSays(e.Working()) } for _, c := range r.Clauses { g.Clauses = append(g.Clauses, AnswerClause{N: c.N, Text: c.Text, Holds: held(r, c), Source: c.Source}) } for _, l := range r.Links { g.Links = append(g.Links, l.String()) } if in, err := k.Store.Inbound(ctx, key); err == nil { for _, x := range in { s := x.From.String() + " " if x.Link.From > 0 { s += fmt.Sprintf("§%d ", x.Link.From) } s += x.Link.Kind if x.Link.To.Clause > 0 { s += fmt.Sprintf(" §%d", x.Link.To.Clause) } g.LinkedFrom = append(g.LinkedFrom, s) } } for _, s := range r.Sections { g.Sections = append(g.Sections, s.Name) if a.Reasoning || (a.Section != "" && strings.EqualFold(a.Section, s.Name)) { g.Reasoning = append(g.Reasoning, s) } } if a.Section != "" && len(g.Reasoning) == 0 { return nil, fmt.Errorf("%s has no section %q; it has %s", key, a.Section, strings.Join(g.Sections, ", ")) } if !a.Reasoning && a.Section == "" { g.Read = "its reasoning: " + readHow(key) + ", or one section with section" } if size(g) <= bound { return g, nil } // Cut: the reasoning first, section by section from the end, then the reviews but the last, then the links // from the end, then the clauses' text, then clauses; each cut is said. Room is kept for saying it. room := bound - 600 var cut []string if len(g.Reasoning) > 0 { reasoning := g.Reasoning v, n := fit(len(reasoning), room, func(n int) any { c := *g; c.Reasoning = reasoning[:n]; return &c }) *g = *(v.(*Got)) if n < len(reasoning) { left := []string{} for _, s := range reasoning[n:] { left = append(left, s.Name) } cut = append(cut, fmt.Sprintf("the sections %s are not shown within %d bytes: ask for one with section, or raise bound", strings.Join(left, ", "), bound)) if spare := room - size(g); n == 0 && spare > 400 { s := reasoning[0] s.Text = clip(s.Text, spare/2) g.Reasoning = []Section{s} cut[len(cut)-1] = fmt.Sprintf("the section %s is shortened, and %s not shown: ask for one with section, or raise bound", s.Name, strings.Join(append([]string{}, left[1:]...), ", ")) } } } if size(g) > room && len(g.Reviews) > 1 { cut = append(cut, fmt.Sprintf("%d earlier review(s) are not shown: history lists every version", len(g.Reviews)-1)) g.Reviews = g.Reviews[len(g.Reviews)-1:] } for _, which := range []string{"linked-from", "links"} { if size(g) <= room { break } list := func(c *Got) *[]string { if which == "links" { return &c.Links } return &c.LinkedFrom } full := *list(g) v, n := fit(len(full), room, func(n int) any { c := *g; *list(&c) = full[:n]; return &c }) if n < len(full) { *g = *(v.(*Got)) cut = append(cut, fmt.Sprintf("%d of its %s are not shown: links lists them all", len(full)-n, which)) } } if size(g) > room { for i := range g.Clauses { g.Clauses[i].Text = clip(g.Clauses[i].Text, 160) } cut = append(cut, "the clauses are shortened: raise bound to read them whole") } if size(g) > room { clauses := g.Clauses v, n := fit(len(clauses), room, func(n int) any { c := *g; c.Clauses = clauses[:n]; return &c }) *g = *(v.(*Got)) cut = append(cut, fmt.Sprintf("%d clause(s) are not shown: raise bound", len(clauses)-n)) } g.Cut = strings.Join(cut, "; ") return g, nil } // ---- holds --------------------------------------------------------------------------------------------------- // HeldClause is one clause in force, with the record it comes from. type HeldClause struct { Record string `json:"record"` N int `json:"n"` Text string `json:"text"` Holds string `json:"holds"` } // Holding is the answer to holds. type Holding struct { Of string `json:"of"` Clauses []HeldClause `json:"clauses"` NotYet string `json:"not-served,omitempty"` Cut string `json:"cut,omitempty"` Bound int `json:"bound"` } // Holds is the clauses in force for a topic or a record: never a drafted one, and a confirmed one marked. func (k *KB) Holds(topic string, key *Key, bound int) (*Holding, error) { ctx := context.Background() bound = boundOf(bound) var records []*Record of := "" switch { case key != nil: e, err := k.Store.Get(ctx, *key) if err != nil { return nil, err } if e == nil { return nil, fmt.Errorf("the knowledge base holds no %s", key) } records, of = []*Record{e.Current}, key.String() case strings.TrimSpace(topic) != "": all, err := k.Store.All(ctx) if err != nil { return nil, err } for _, e := range all { if e.Current.Topic == topic { records = append(records, e.Current) } } of = "the topic " + topic if len(records) == 0 { return nil, fmt.Errorf("no record has the topic %q", topic) } default: return nil, fmt.Errorf("holds takes a topic or a record's number") } out := &Holding{Of: of, Bound: bound, Clauses: []HeldClause{}} drafted, prose := 0, 0 for _, r := range records { if r.Kind == KindDecision && r.Accepted() && len(r.Clauses) == 0 { prose++ } for _, c := range r.Clauses { if c.Source == SourceDrafted && r.Accepted() && c.Status != ClauseSuperseded { drafted++ } if Served(r, c) { out.Clauses = append(out.Clauses, HeldClause{Record: r.Key().String(), N: c.N, Text: c.Text, Holds: held(r, c)}) } } } var notes []string if drafted > 0 { notes = append(notes, fmt.Sprintf("%d drafted clause(s) are not served: they are not confirmed as the record's word", drafted)) } if prose > 0 { notes = append(notes, fmt.Sprintf("%d accepted decision(s) write their decision as prose and have no clauses yet", prose)) } out.NotYet = strings.Join(notes, "; ") clauses := out.Clauses v, n := fit(len(clauses), bound, func(n int) any { c := *out c.Clauses = clauses[:n] if n < len(clauses) { c.Cut = fmt.Sprintf("%d more clause(s) are in force and not shown within %d bytes: raise bound, or ask holds of one record", len(clauses)-n, bound) } return &c }) if n == 0 && len(clauses) > 0 { for i := range clauses { clauses[i].Text = clip(clauses[i].Text, 160) } v, _ = fit(len(clauses), bound, func(n int) any { c := *out c.Clauses = clauses[:n] c.Cut = fmt.Sprintf("the clauses are shortened, and %d not shown within %d bytes: raise bound", len(clauses)-n, bound) return &c }) } return v.(*Holding), nil } // ---- links --------------------------------------------------------------------------------------------------- // Linked is the answer to links. type Linked struct { Record string `json:"record"` Out []LinkRow `json:"out"` In []LinkRow `json:"in"` } // LinkRow is one link, read from either end. type LinkRow struct { Kind string `json:"kind"` From string `json:"from"` To string `json:"to"` Source string `json:"source,omitempty"` Unresolved bool `json:"unresolved,omitempty"` } // Links is a record's links, both ways. func (k *KB) Links(key Key) (*Linked, error) { ctx := context.Background() e, err := k.Store.Get(ctx, key) if err != nil { return nil, err } if e == nil { return nil, fmt.Errorf("the knowledge base holds no %s", key) } out := &Linked{Record: key.String(), Out: []LinkRow{}, In: []LinkRow{}} from := func(k Key, clause int) string { if clause > 0 { return k.String() + " §" + strconv.Itoa(clause) } return k.String() } for _, l := range e.Current.Links { out.Out = append(out.Out, LinkRow{Kind: l.Kind, From: from(key, l.From), To: l.To.String(), Source: l.Source, Unresolved: l.Unresolved}) } in, err := k.Store.Inbound(ctx, key) if err != nil { return nil, err } for _, x := range in { out.In = append(out.In, LinkRow{Kind: x.Link.Kind, From: from(x.From, x.Link.From), To: x.Link.To.String(), Source: x.Link.Source}) } return out, nil } // ---- history ------------------------------------------------------------------------------------------------- // VersionRow is one version in a history. type VersionRow struct { N int `json:"n"` By string `json:"by"` At string `json:"at"` Why string `json:"why"` Status string `json:"status"` Insight bool `json:"insight,omitempty"` Served bool `json:"served,omitempty"` Pending bool `json:"pending,omitempty"` } // Historied is the answer to history. type Historied struct { Record string `json:"record"` Versions []VersionRow `json:"versions"` From int `json:"from,omitempty"` To int `json:"to,omitempty"` Diff []string `json:"diff,omitempty"` Same bool `json:"same,omitempty"` Cut string `json:"cut,omitempty"` Bound int `json:"bound,omitempty"` } // History is a record's versions, and the difference between two of them, line by line. func (k *KB) History(key Key, from, to, bound int) (*Historied, error) { ctx := context.Background() e, err := k.Store.Get(ctx, key) if err != nil { return nil, err } if e == nil { return nil, fmt.Errorf("the knowledge base holds no %s", key) } vs, err := k.Store.Versions(ctx, key) if err != nil { return nil, err } out := &Historied{Record: key.String()} for _, v := range vs { out.Versions = append(out.Versions, VersionRow{N: v.N, By: v.By, At: v.At.UTC().Format("2006-01-02 15:04:05 UTC"), Why: v.Why, Status: v.Record.Status, Insight: v.Insight, Served: v.N == e.CurrentVersion, Pending: v.N == e.PendingVersion}) } if from == 0 && to == 0 { out.Bound = boundOf(bound) versions := out.Versions v, m := fit(len(versions), out.Bound-300, func(m int) any { c := *out; c.Versions = versions[len(versions)-m:]; return &c }) *out = *(v.(*Historied)) if m < len(versions) { out.Cut = fmt.Sprintf("the %d oldest versions are not listed within %d bytes: raise bound", len(versions)-m, out.Bound) } return out, nil } if from < 1 || to < 1 || from > len(vs) || to > len(vs) { return nil, fmt.Errorf("%s has versions 1 to %d; from and to name two of them", key, len(vs)) } out.From, out.To, out.Bound = from, to, boundOf(bound) diff := Diff(Render(vs[from-1].Record), Render(vs[to-1].Record)) out.Same = len(diff) == 0 // Within the bound: the most of the difference's lines that fit, cut at a hunk's line and said; and when the // versions alone do not fit, the oldest of them left out first. versions := out.Versions v, n := fit(len(diff), out.Bound-300, func(n int) any { c := *out; c.Diff = diff[:n]; return &c }) *out = *(v.(*Historied)) var cut []string if n < len(diff) { cut = append(cut, fmt.Sprintf("%d of the difference's %d lines are not shown within %d bytes: raise bound, or compare nearer versions", len(diff)-n, len(diff), out.Bound)) } if size(out) > out.Bound-300 { v, m := fit(len(versions), out.Bound-300, func(m int) any { c := *out; c.Versions = versions[len(versions)-m:]; return &c }) *out = *(v.(*Historied)) cut = append(cut, fmt.Sprintf("the %d oldest versions are not listed", len(versions)-m)) } out.Cut = strings.Join(cut, "; ") return out, nil } // MostDiffWork bounds the work of a difference: the lines of the two texts' changed middles multiplied. Past it, // the middle is given whole as removed and added — still exact, only less narrow. const MostDiffWork = 4_000_000 // Diff is the difference of two texts, line by line: each hunk headed "@@ -a,n +b,m @@", its lines "-" removed // and "+" added, by the longest common subsequence of their lines. func Diff(a, b string) []string { x, y := strings.Split(a, "\n"), strings.Split(b, "\n") // The common head and tail cost nothing to find; only the middle between them is compared. head := 0 for head < len(x) && head < len(y) && x[head] == y[head] { head++ } tail := 0 for tail < len(x)-head && tail < len(y)-head && x[len(x)-1-tail] == y[len(y)-1-tail] { tail++ } x, y = x[head:len(x)-tail], y[head:len(y)-tail] n, m := len(x), len(y) if n*m > MostDiffWork { var out []string if n+m > 0 { out = append(out, fmt.Sprintf("@@ -%d,%d +%d,%d @@", head+1, n, head+1, m)) } for _, l := range x { out = append(out, "-"+l) } for _, l := range y { out = append(out, "+"+l) } return out } lcs := make([][]int, n+1) for i := range lcs { lcs[i] = make([]int, m+1) } for i := n - 1; i >= 0; i-- { for j := m - 1; j >= 0; j-- { if x[i] == y[j] { lcs[i][j] = lcs[i+1][j+1] + 1 } else { lcs[i][j] = max(lcs[i+1][j], lcs[i][j+1]) } } } var out []string var hunk []string ha, hb, ca, cb := 0, 0, 0, 0 flush := func() { if len(hunk) > 0 { out = append(out, fmt.Sprintf("@@ -%d,%d +%d,%d @@", ha, ca, hb, cb)) out = append(out, hunk...) hunk = nil } } i, j := 0, 0 for i < n || j < m { switch { case i < n && j < m && x[i] == y[j]: flush() i++ j++ case i < n && (j == m || lcs[i+1][j] >= lcs[i][j+1]): if len(hunk) == 0 { ha, hb, ca, cb = head+i+1, head+j+1, 0, 0 } hunk = append(hunk, "-"+x[i]) ca++ i++ default: if len(hunk) == 0 { ha, hb, ca, cb = head+i+1, head+j+1, 0, 0 } hunk = append(hunk, "+"+y[j]) cb++ j++ } } flush() return out } // reviewSays is where a proposed version stands in the knowledge base's own review, in words; "" when nothing waits. func reviewSays(r *Record) string { j := r.Judging if j == nil || r.Status != StatusProposed { return "" } switch j.State { case JudgingDue: return "waiting for the knowledge base's review" case JudgingUnproven: return "not reviewed: " + j.Reason } // When it is asked again is said, so a pause is not taken for a review lost (novox/hq issue 497). return fmt.Sprintf("not reviewed: %s (%s; no judgement that could not be had counts as an approval", j.Reason, j.owedSays()) }