// mesh-knowledge: the knowledge base, holder of the knowledge-base seat (novox/hq ADR 0290, to-be 55). A Go // bundle the control-node's runtime launches. It holds every decision record and research effort whole — its // fields, its clauses each with a status and a source, its typed links read from both ends, its reasoning — with // every version kept, in a database in the store; answers what holds in one bounded call; takes a record proposed, // checked when it is written, reviewed by its own judgement and accepted, on the operator's proven word when it // needs it; and imports hq's records at a commit. stdout is the MCP channel; what this module says, // it says on stderr. // // mesh-knowledge the bundle: the seat's verbs and the health tool // mesh-knowledge prepare the preparation step: the database's schema brought to this version's // mesh-knowledge keep acts: judges the whole record set every few minutes and keeps the findings // mesh-knowledge watch judges: reads whether the database serves and what the findings say, every // minute, and leaves its verdict in the status file the health tool reads // mesh-knowledge dry-run the import's dry run over a checkout on disk, against no database // // The two long-running processes are the act and the judge, as the mounts module's are: keep is judged by its // unit, watch by the health tool, so the module never judges itself by its own tool alone (ADR 0240 rule 2). package main import ( "context" "encoding/json" "errors" "fmt" "math" "os" "sort" "strings" "sync" "time" stdio "git.novox.be/novox/mesh-sdk/go" "git.novox.be/novox/mesh-sdk/go/asks" ) // Seat is the role this module holds, once for the mesh. const Seat = "knowledge-base" // Verbs are the seat's verbs, as the manifest declares and claims them (ADR 0290 rule 6). var Verbs = []string{"ask", "get", "list", "findings", "holds", "links", "history", "reserve", "propose", "import", "return"} // HealthTool is the module's own tool the node-engine asks how it is. const HealthTool = "knowledge_health" func logf(format string, a ...any) { fmt.Fprintf(os.Stderr, format+"\n", a...) } func readSettings(path string) (Settings, error) { var s Settings if path == "" { return s, errors.New("the module was started without a settings file") } raw, err := os.ReadFile(path) if err != nil { return s, err } if err := json.Unmarshal(raw, &s); err != nil { return s, fmt.Errorf("the settings file is not the module's settings: %v", err) } return s, nil } func main() { if len(os.Args) > 1 { switch os.Args[1] { case "prepare": os.Exit(prepare()) case "keep": os.Exit(keep()) case "watch": os.Exit(watch(os.Getenv("MESH_KNOWLEDGE_STATUS"))) case "dry-run": os.Exit(dryRunAtTheTerminal(os.Args[2:])) } } st := &lazyStore{open: func(ctx context.Context) (*PgStore, error) { return PgStoreFromEnv(ctx) }} k := newKB(st) serve(k, os.Getenv("MESH_KNOWLEDGE_STATUS")) } // newKB is the knowledge base as the runtime launches it: everything it reaches, over the bus. func newKB(store Store) *KB { file := os.Getenv("MESH_KNOWLEDGE_SETTINGS") return &KB{ Store: store, Records: busRecords{}, Reserve: busReserver, Judge: busJudgement(os.Getenv("MESH_NODE")), Asks: sdkAskBus{}, Notify: busNotifier, Clear: busClearer, Emit: func(event string, body any) error { return stdio.Emit(event, body) }, Settings: func() (Settings, error) { return readSettings(file) }, Logf: logf, } } // serve runs the bundle: the verbs, the operator's answers heard, and the background work. func serve(k *KB, status string) { k.kick = make(chan struct{}, 1) go hear(k) go background(k) if err := stdio.Serve("", append(Tools(k, callerOf), healthTool(status, k.now), highestTool(k))); err != nil { logf("%v", err) os.Exit(1) } } // callerOf is who called, as the runtime proves it: the one place the caller is read. The tool runner takes the // caller's bus user from the subject the call arrived on and hands it through the SDK (hq issue 365); Unproven where // it proves none. It is never read from the call's arguments, so no caller can name another. func callerOf(args map[string]any) string { return stdio.CallerOf(args) } // hear subscribes to the router's word on this module's asks, asked again until the runtime takes it. func hear(k *KB) { for wait := 2 * time.Second; ; wait = min(wait*2, time.Minute) { time.Sleep(wait) err := stdio.Subscribe(asks.Seat+".decided."+Module, func(e stdio.Envelope) error { var w asks.Warrant if json.Unmarshal(e.Body, &w) == nil { k.HeardEnd(w) } return nil }) if err == nil { logf("[mesh-knowledge] hears the operator's answers") return } logf("[mesh-knowledge] does not hear the operator's answers yet (%v); asking again in %s", err, wait) } } // background reads the router's record of every waiting ask once a minute, withdraws reservations never // proposed, reviews every proposal waiting for its review — at once when one is proposed — and drafts and // confirms clauses one step at a time, a review always before the next drafting step: the agent judges one // question at a time, and a proposal does not wait behind the import's drafting. func background(k *KB) { tick := time.NewTicker(time.Minute) defer tick.Stop() time.Sleep(5 * time.Second) review := func() { for { said, err := k.ReviewStep() if err != nil { logf("[mesh-knowledge] %v", err) return } if said == "" { return } logf("[mesh-knowledge] %s", said) } } for { if said, err := k.Reconcile(); err != nil { logf("[mesh-knowledge] reading the operator's answers: %v", err) } else { for _, s := range said { logf("[mesh-knowledge] %s", s) } } if n, err := k.Withdraw(); err != nil { logf("[mesh-knowledge] withdrawing reservations: %v", err) } else if n > 0 { logf("[mesh-knowledge] withdrew %d reservation(s) never proposed", n) } review() // The keeper judges the record set in a process of its own, which tells nobody: its findings are told here. k.TellFindings() for { said, err := k.DraftStep() if err != nil { logf("[mesh-knowledge] %v", err) break } if said == "" { break } logf("[mesh-knowledge] %s", said) review() } select { case <-tick.C: case <-k.kick: } } } // ---- the bus ------------------------------------------------------------------------------------------------- // busReserver asks the issue tracker for a number (issue-tracker.reserve, hq issue 424). func busReserver(kind, title string) (int, error) { raw, err := stdio.Ask("seat:issue-tracker.reserve", map[string]any{"kind": kind, "for": title}) if err != nil { return 0, err } var out struct { Kind string `json:"kind"` Number int `json:"number"` } if err := json.Unmarshal(raw, &out); err != nil || out.Number < 1 || out.Kind != kind { return 0, fmt.Errorf("the issue tracker's answer is not a %s number: %.200s", kind, raw) } return out.Number, nil } // busJudgement asks the agent module on this node for one judgement (ADR 0278). func busJudgement(node string) Judgement { return judgementOver(stdio.Ask, node) } // The bound a judgement asks scales with its question (novox/hq issue 508): ADR 0297, eleven clauses and eleven // links, was never reviewed within the agent module's 25 seconds, while short records took 14 to 25. Each KiB of // question adds JudgePerKiB to JudgeFloor, up to MostJudgeWithin, which the agent module takes at most. A sized // bound was chosen over asking clause by clause: every judgement pays the agent's start, which alone is most of // those 14 to 25 seconds, so a dozen clause-sized judgements would each risk the same 25 seconds, take minutes // together, and still need a judgement of the whole record for what binds its clauses together. const ( JudgeFloor = 25 * time.Second JudgePerKiB = 2 * time.Second MostJudgeWithin = 120 * time.Second ) // JudgeWithin is the bound a judgement of this question asks. func JudgeWithin(question string) time.Duration { return min(JudgeFloor+time.Duration(len(question)>>10)*JudgePerKiB, MostJudgeWithin) } // judgementOver asks the agent module through ask. A bound past one bus call's 30 seconds is answered pending with // a ticket when the judgement is not done in the call, and collected from the node that holds it with // claude_code_judged (novox/hq issue 508). func judgementOver(ask func(key string, body any) (json.RawMessage, error), node string) Judgement { return func(question string, schema map[string]any, model string) (json.RawMessage, string, error) { key := "claude-code.claude_code_judge" if node != "" { key += "@" + node } within := JudgeWithin(question) body := map[string]any{"question": question, "schema": schema, "model": model} if within > JudgeFloor { body["within"] = int(within / time.Second) } raw, err := ask(key, body) if err != nil && body["within"] != nil && strings.Contains(err.Error(), "not within") { // An agent module from before issue 508 takes no within: asked again without it, under its own bound. delete(body, "within") raw, err = ask(key, body) } // Past the judgement's bound the agent module ends it and says so; the deadline only guards against a // module that answers pending for ever. deadline := time.Now().Add(within + time.Minute) for { if err != nil { return nil, "", err } var out struct { Answer json.RawMessage `json:"answer"` Model string `json:"model"` Node string `json:"node"` Ticket string `json:"ticket"` Pending bool `json:"pending"` } if err := json.Unmarshal(raw, &out); err != nil { return nil, "", fmt.Errorf("the agent module's answer is not a judgement: %.200s", raw) } if !out.Pending { if len(out.Answer) == 0 { return nil, "", fmt.Errorf("the agent module's answer is not a judgement: %.200s", raw) } return out.Answer, fmt.Sprintf("the agent on %s (%s)", out.Node, out.Model), nil } if out.Ticket == "" || out.Node == "" { return nil, "", fmt.Errorf("the agent module answered pending with no ticket or node: %.200s", raw) } if time.Now().After(deadline) { return nil, "", fmt.Errorf("the agent on %s still had not answered %s after its bound of %s", out.Node, out.Ticket, within) } raw, err = ask("claude-code.claude_code_judged@"+out.Node, map[string]any{"ticket": out.Ticket}) } } } // busNotifier tells the operator's channel, as a warning: it is the one severity below urgent. func busNotifier(key, summary, headline, explanation, resolved string) error { body := map[string]any{"key": key, "subject": "the knowledge base", "severity": "warning", "summary": summary, "headline": headline, "explanation": explanation} if resolved != "" { body["resolved"] = resolved } _, err := stdio.Ask("seat:operator-channel.notify", body) return err } // busClearer tells the operator's channel that what it was told under key is over: the message is edited to say // resolved (novox/hq issue 497). func busClearer(key, resolved string) error { _, err := stdio.Ask("seat:operator-channel.notify", map[string]any{"key": key, "subject": "the knowledge base", "clear": true, "resolved": resolved}) return err } // ---- the store, opened when it is first needed ------------------------------------------------------------ // lazyStore opens the database on the first call, and again after a failure, so a database that arrives late is // not fatal; and refuses to serve a schema behind this version. type lazyStore struct { open func(context.Context) (*PgStore, error) mu sync.Mutex pg *PgStore } func (l *lazyStore) store(ctx context.Context) (*PgStore, error) { l.mu.Lock() defer l.mu.Unlock() if l.pg == nil { pg, err := l.open(ctx) if err != nil { return nil, fmt.Errorf("the knowledge base's database cannot be opened: %w", err) } l.pg = pg } if err := l.pg.Ready(ctx); err != nil { return nil, err } return l.pg, nil } // Ready says why the store cannot serve, or nil. func (l *lazyStore) Ready(ctx context.Context) error { _, err := l.store(ctx); return err } func (l *lazyStore) Get(ctx context.Context, k Key) (*Entry, error) { s, err := l.store(ctx) if err != nil { return nil, err } return s.Get(ctx, k) } func (l *lazyStore) All(ctx context.Context) ([]*Entry, error) { s, err := l.store(ctx) if err != nil { return nil, err } return s.All(ctx) } func (l *lazyStore) Create(ctx context.Context, v Version) error { s, err := l.store(ctx) if err != nil { return err } return s.Create(ctx, v) } func (l *lazyStore) Save(ctx context.Context, v Version, current, clearPending bool, revision int64) error { s, err := l.store(ctx) if err != nil { return err } return s.Save(ctx, v, current, clearPending, revision) } func (l *lazyStore) Versions(ctx context.Context, k Key) ([]Version, error) { s, err := l.store(ctx) if err != nil { return nil, err } return s.Versions(ctx, k) } func (l *lazyStore) Search(ctx context.Context, q string, limit int) ([]Hit, error) { s, err := l.store(ctx) if err != nil { return nil, err } return s.Search(ctx, q, limit) } func (l *lazyStore) Inbound(ctx context.Context, k Key) ([]Inbound, error) { s, err := l.store(ctx) if err != nil { return nil, err } return s.Inbound(ctx, k) } func (l *lazyStore) SetFindings(ctx context.Context, f []Finding) error { s, err := l.store(ctx) if err != nil { return err } return s.SetFindings(ctx, f) } func (l *lazyStore) Findings(ctx context.Context) ([]Finding, error) { s, err := l.store(ctx) if err != nil { return nil, err } return s.Findings(ctx) } func (l *lazyStore) Told(ctx context.Context, topic string) (bool, error) { s, err := l.store(ctx) if err != nil { return false, err } return s.Told(ctx, topic) } func (l *lazyStore) ToldOf(ctx context.Context, prefix string) ([]string, error) { s, err := l.store(ctx) if err != nil { return nil, err } return s.ToldOf(ctx, prefix) } func (l *lazyStore) Untold(ctx context.Context, key string) error { s, err := l.store(ctx) if err != nil { return err } return s.Untold(ctx, key) } // ---- prepare, check, dry-run -------------------------------------------------------------------------------- func prepare() int { ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) defer cancel() s, err := PgStoreFromEnv(ctx) if err != nil { logf("[mesh-knowledge] preparing the database: %v", err) return 1 } defer s.Close() said, err := s.Prepare(ctx) for _, line := range said { logf("[mesh-knowledge] %s", line) } if err != nil { logf("[mesh-knowledge] preparing the database failed: %v", err) return 1 } return 0 } // KeepEvery is how often keep judges the whole record set; WatchEvery how often watch looks. const ( KeepEvery = 5 * time.Minute WatchEvery = time.Minute ) // keep judges the whole record set every few minutes and keeps the findings in the database: a change that // reached the database by any way is judged within minutes, not only one an acceptance made. func keep() int { for { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) s, err := PgStoreFromEnv(ctx) if err == nil { if err = s.Ready(ctx); err == nil { k := &KB{Store: s, Logf: logf} f := k.judgeSet(ctx) logf("[mesh-knowledge] the whole record set judged: %d finding(s)", len(f)) } s.Close() } if err != nil { logf("[mesh-knowledge] the whole record set cannot be judged now: %v", err) } cancel() time.Sleep(KeepEvery) } } // watch reads, every minute, whether the database serves this version and what the whole-set findings say, // and leaves its verdict in the status file. func watch(status string) int { if status == "" { logf("[mesh-knowledge] watch was started without MESH_KNOWLEDGE_STATUS") return 2 } for { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) var answer HealthAnswer s, err := PgStoreFromEnv(ctx) if err != nil { answer = HealthAnswer{State: "unhealthy", Why: "the knowledge base's database cannot be opened: " + err.Error()} } else { answer = (&KB{Store: s}).Health(s) s.Close() } cancel() answer.At = time.Now().UTC() if err := writeStatus(status, answer); err != nil { logf("[mesh-knowledge] the status could not be written: %v", err) } time.Sleep(WatchEvery) } } func writeStatus(path string, a HealthAnswer) error { raw, err := json.Marshal(a) if err != nil { return err } tmp := path + ".next" if err := os.WriteFile(tmp, raw, 0o600); err != nil { return err } return os.Rename(tmp, path) } // StatusStale is how old the watch's verdict may be before the health tool says the watch stopped looking. const StatusStale = 3 * WatchEvery // ReadStatus is the watch's last verdict, as the health tool answers it: unhealthy when there is none, or when it // is older than StatusStale. func ReadStatus(path string, now time.Time) HealthAnswer { raw, err := os.ReadFile(path) if err != nil { return HealthAnswer{State: "unhealthy", Why: "the watch has left no verdict yet: " + err.Error()} } var a HealthAnswer if err := json.Unmarshal(raw, &a); err != nil || (a.State != "healthy" && a.State != "unhealthy") { return HealthAnswer{State: "unhealthy", Why: "the watch's verdict cannot be read"} } if age := now.Sub(a.At); age > StatusStale { return HealthAnswer{State: "unhealthy", Why: fmt.Sprintf("the watch has not looked for %s (its last verdict: %s)", age.Round(time.Second), a.Why)} } a.Healthy = a.State == "healthy" return a } // dryRunAtTheTerminal is the import's dry run over a checkout on disk, against an empty knowledge base: what the // import would create, before the module is rolled out. Only the verb writes. func dryRunAtTheTerminal(args []string) int { if len(args) != 1 { logf("usage: mesh-knowledge dry-run ") return 2 } k := &KB{Store: newMemStore(), Logf: logf} out, err := k.Import(Unproven, ImportAsk{DryRun: true}, dirRecords{dir: args[0]}) if err != nil { logf("%v", err) return 1 } enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") _ = enc.Encode(out) return 0 } // ---- health --------------------------------------------------------------------------------------------------- // Healther is a store that can say why it cannot serve. type Healther interface { Ready(ctx context.Context) error } // HealthAnswer is the health tool's answer (novox/hq ADR 0240, ADR 0283): healthy or unhealthy, and why; At is // when the watch looked. type HealthAnswer struct { State string `json:"state"` Healthy bool `json:"healthy"` Why string `json:"why"` At time.Time `json:"at,omitempty"` } // Health is whether the knowledge base works: unhealthy when its database cannot serve. A rule that judges the whole // record set and fails is a fault of the records, which their authors repair, not of the module: it is told to the // operator as a warning naming the rule and the records (TellFindings), and named here, but leaves the module // healthy (the operator's decision of 2026-10-12 on novox/hq issue 504). So does a proposal that ran out of review // tries (novox/hq issue 508). Neither holds the module's own rollouts. func (k *KB) Health(ready Healther) HealthAnswer { ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second) defer cancel() if ready != nil { if err := ready.Ready(ctx); err != nil { return HealthAnswer{State: "unhealthy", Why: err.Error()} } } findings, err := k.Store.Findings(ctx) if err != nil { return HealthAnswer{State: "unhealthy", Why: "the whole-set findings cannot be read: " + err.Error()} } var says []string for _, f := range findings { says = append(says, "the rule "+f.Rule+": "+f.Says) } sort.Strings(says) all, err := k.Store.All(ctx) if err != nil { return HealthAnswer{State: "unhealthy", Why: "the records cannot be read: " + err.Error()} } waiting := "" if unreviewed := Unreviewed(all); len(unreviewed) > 0 { n := len(unreviewed) if n > 8 { unreviewed = append(unreviewed[:8], fmt.Sprintf("and %d more", n-8)) } waiting = fmt.Sprintf("%d proposed record(s) could not be reviewed in %d tries and wait for their authors to propose them again: %s", n, MostReviewTries, strings.Join(unreviewed, "; ")) } why := "the database serves and every rule that judges the whole record set holds" if len(says) > 0 { if len(says) > 8 { says = append(says[:8], fmt.Sprintf("and %d more", len(says)-8)) } why = fmt.Sprintf("the database serves; %d rule(s) that judge the whole record set fail, told to the operator as a warning "+ "for the records' authors to repair: %s", len(findings), strings.Join(says, "; ")) } if waiting != "" { why += "; " + waiting } return HealthAnswer{State: "healthy", Healthy: true, Why: why} } func healthTool(status string, now func() time.Time) stdio.Tool { return stdio.Tool{Name: HealthTool, Description: "How the knowledge base is, as its watch last saw it: healthy when its database serves this version, unhealthy " + "otherwise. A rule that judges the whole record set and fails — a decision accepted that nothing cites, a supersession " + "or a change said on one end only, a record extending a superseded one — is named but leaves it healthy: it is told " + "to the operator as a warning for the records' authors (novox/hq issue 504). So is a proposed record that ran out of " + "review tries: it waits on its author (novox/hq issue 508).", Run: func(map[string]any) (any, error) { return ReadStatus(status, now()), nil }} } // ---- the verbs -------------------------------------------------------------------------------------------------- // args reads one call's arguments strictly: an argument the verb does not take is refused, never ignored — and no // argument can say who the caller is. type args struct { verb string m map[string]any } func readArgs(verb string, m map[string]any, takes ...string) (args, error) { allowed := map[string]bool{} for _, k := range takes { allowed[k] = true } var unknown []string for k := range m { if !allowed[k] { unknown = append(unknown, k) } } if len(unknown) > 0 { sort.Strings(unknown) return args{}, fmt.Errorf("%s takes %s; it does not take %s", verb, strings.Join(takes, ", "), strings.Join(unknown, ", ")) } return args{verb: verb, m: m}, nil } func (a args) str(k string) (string, error) { v, ok := a.m[k] if !ok || v == nil { return "", nil } s, ok := v.(string) if !ok { return "", fmt.Errorf("%s is text", k) } return s, nil } func (a args) strs(k string) ([]string, error) { v, ok := a.m[k] if !ok || v == nil { return nil, nil } switch t := v.(type) { case string: return []string{t}, nil case []any: out := make([]string, 0, len(t)) for _, e := range t { s, ok := e.(string) if !ok { return nil, fmt.Errorf("every entry of %s is text", k) } out = append(out, s) } return out, nil } return nil, fmt.Errorf("%s is a list of text", k) } func (a args) num(k string, required bool) (int, error) { v, ok := a.m[k] if !ok || v == nil { if required { return 0, fmt.Errorf("%s needs %s", a.verb, k) } return 0, nil } switch t := v.(type) { case float64: if t >= 0 && t <= 1e6 && t == math.Trunc(t) { return int(t), nil } case string: if kindNumber.MatchString(strings.TrimSpace(t)) { return atoi(strings.TrimSpace(t)), nil } } return 0, fmt.Errorf("%s is a whole number, such as 276", k) } func (a args) flag(k string) (bool, error) { v, ok := a.m[k] if !ok || v == nil { return false, nil } b, ok := v.(bool) if !ok { return false, fmt.Errorf("%s is true or false", k) } return b, nil } // key reads kind (decision when absent) and number. func (a args) key() (Key, error) { kind, err := a.str("kind") if err != nil { return Key{}, err } if kind == "" { kind = KindDecision } if !contains(Kinds, kind) { return Key{}, fmt.Errorf("kind is decision or research, not %q", kind) } n, err := a.num("number", true) if err != nil { return Key{}, err } if n < 1 { return Key{}, errors.New("number is a record's number, from 1") } return Key{kind, n}, nil } func text(d string) map[string]any { return map[string]any{"type": "string", "description": d} } func texts(d string) map[string]any { return map[string]any{"type": "array", "items": map[string]any{"type": "string"}, "description": d} } func integer(d string) map[string]any { return map[string]any{"type": "integer", "minimum": 1, "description": d} } func flag(d string) map[string]any { return map[string]any{"type": "boolean", "description": d} } func kindArg() map[string]any { return map[string]any{"type": "string", "enum": Kinds, "description": "decision (the default) or research"} } func boundArg() map[string]any { return map[string]any{"type": "integer", "minimum": LeastBound, "maximum": MostBound, "description": "the most bytes the answer may hold (default 4096)"} } func schema(required []string, props map[string]any) map[string]any { s := map[string]any{"type": "object", "properties": props, "additionalProperties": false} if len(required) > 0 { s["required"] = required } return s } // Tools are the seat's verbs, each said in plain words: what it does and what it refuses. caller is who called, // as the runtime proves it. func Tools(k *KB, caller func(map[string]any) string) []stdio.Tool { return []stdio.Tool{ {Name: Seat + ".ask", Description: "Ask what holds about something, in plain words — \"what holds about the merge window, and why\" — and get the " + "decision records and research efforts that bear on it, ranked, with their matching clauses: each clause says whether " + "it is in force, what changed or superseded it, or that it is only drafted and not the record's word; each record says " + "what changed it since and how to read its reasoning. One call, within a bound it states (4 KiB unless asked for more), " + "saying what it cut.", Input: schema([]string{"question"}, map[string]any{"question": map[string]any{"type": "string", "maxLength": MostQuestion, "description": "the question, in plain words, at most 500 characters"}, "bound": boundArg()}), Run: func(m map[string]any) (any, error) { a, err := readArgs("ask", m, "question", "bound") if err != nil { return nil, err } q, err := a.str("question") if err != nil { return nil, err } b, err := a.num("bound", false) if err != nil { return nil, err } return k.Ask(q, b) }}, {Name: Seat + ".get", Description: "One record by its number: its title, status, topic, date and deciders; its clauses, each with whether it holds; " + "its links both ways; what changed it since; its versions; its reviews, and the operator's ask when it waits for one. " + "Its reasoning (context, options, consequences, how it is checked; a research effort's documents) only with reasoning, " + "or one section by name; an earlier version with version. Within a bound it states, 4 KiB unless asked for more, " + "saying what it cut.", Input: schema([]string{"number"}, map[string]any{"kind": kindArg(), "number": integer("the record's number, such as 276"), "reasoning": flag("include its reasoning whole"), "section": text("only this section of its reasoning, by name"), "version": integer("an earlier version"), "bound": boundArg()}), Run: func(m map[string]any) (any, error) { a, err := readArgs("get", m, "kind", "number", "reasoning", "section", "version", "bound") if err != nil { return nil, err } key, err := a.key() if err != nil { return nil, err } g := GetAsk{Kind: key.Kind, Number: key.Number} if g.Reasoning, err = a.flag("reasoning"); err != nil { return nil, err } if g.Section, err = a.str("section"); err != nil { return nil, err } if g.Version, err = a.num("version", false); err != nil { return nil, err } if g.Bound, err = a.num("bound", false); err != nil { return nil, err } return k.Get(g) }}, {Name: Seat + ".list", Description: "The records that match, newest first, number by number, one short row each — number, kind, title, status, " + "topic, date and versions — optionally only those of one kind (decision or research), at one status, of one topic of " + "the reading order, or named by flag: research-done is an active research effort naming what it became in a became " + "link to a decision, every such decision accepted — a candidate for graduated; proposed-old is a proposal whose " + "current version is more than a day old. before pages the newest-first list: give the answer's next to read on below it.", Input: schema(nil, map[string]any{"kind": kindArg(), "status": map[string]any{"type": "string", "enum": AllStatuses, "description": "only records at this status"}, "topic": text("only records of this topic, as the reading order groups them"), "flag": map[string]any{"type": "string", "enum": ListFlags, "description": "only the research efforts done (research-done), or the proposals grown old (proposed-old)"}, "before": integer("only records numbered below this: the next of the page before, to read on"), "limit": map[string]any{"type": "integer", "minimum": 1, "maximum": MostListResult, "description": "at most this many rows (default 50)"}, }), Run: func(m map[string]any) (any, error) { a, err := readArgs("list", m, "kind", "status", "topic", "flag", "before", "limit") if err != nil { return nil, err } var ask ListAsk if ask.Kind, err = a.str("kind"); err != nil { return nil, err } if ask.Status, err = a.str("status"); err != nil { return nil, err } if ask.Topic, err = a.str("topic"); err != nil { return nil, err } if ask.Flag, err = a.str("flag"); err != nil { return nil, err } if ask.Before, err = a.num("before", false); err != nil { return nil, err } if ask.Limit, err = a.num("limit", false); err != nil { return nil, err } return k.List(ask) }}, {Name: Seat + ".findings", Description: "The whole-set findings kept: every failure of the rules that judge the whole record set, each saying which " + "records it names and what the rule says of them — a record accepted and cited by nothing, a supersession or a " + "change no accepted record makes. With number, only the findings that name that record.", Input: schema(nil, map[string]any{"number": text("only the findings naming this record, such as 276 or research 045")}), Run: func(m map[string]any) (any, error) { a, err := readArgs("findings", m, "number") if err != nil { return nil, err } number, err := a.str("number") if err != nil { return nil, err } return k.Findings(number) }}, {Name: Seat + ".holds", Description: "The clauses in force now, for a topic of the reading order or for one record, each with the record it comes " + "from; a clause changed in part says by what. Never a clause only drafted from prose: those are counted and not served. " + "Within a bound it states, 4 KiB unless asked for more, saying what it cut.", Input: schema(nil, map[string]any{"topic": text("a topic, such as the mesh"), "kind": kindArg(), "number": integer("one record's number"), "bound": boundArg()}), Run: func(m map[string]any) (any, error) { a, err := readArgs("holds", m, "topic", "kind", "number", "bound") if err != nil { return nil, err } topic, err := a.str("topic") if err != nil { return nil, err } b, err := a.num("bound", false) if err != nil { return nil, err } var key *Key if _, given := m["number"]; given { kk, err := a.key() if err != nil { return nil, err } key = &kk } if key != nil && topic != "" { return nil, errors.New("holds takes a topic or a record's number, not both") } return k.Holds(topic, key, b) }}, {Name: Seat + ".links", Description: "A record's typed links, both ways: what it supersedes, changes (clause by clause), extends, became or cites, " + "and every record that points at it, each with how it was found.", Input: schema([]string{"number"}, map[string]any{"kind": kindArg(), "number": integer("the record's number")}), Run: func(m map[string]any) (any, error) { a, err := readArgs("links", m, "kind", "number") if err != nil { return nil, err } key, err := a.key() if err != nil { return nil, err } return k.Links(key) }}, {Name: Seat + ".history", Description: "A record's versions, every one kept: who made it, when, why, and whether it was a progressive insight; with " + "from and to, the difference between those two versions, line by line. Within a bound it states, 4 KiB unless asked for " + "more, saying what it cut.", Input: schema([]string{"number"}, map[string]any{"kind": kindArg(), "number": integer("the record's number"), "from": integer("the earlier version to compare"), "to": integer("the later version to compare"), "bound": boundArg()}), Run: func(m map[string]any) (any, error) { a, err := readArgs("history", m, "kind", "number", "from", "to", "bound") if err != nil { return nil, err } key, err := a.key() if err != nil { return nil, err } from, err := a.num("from", false) if err != nil { return nil, err } to, err := a.num("to", false) if err != nil { return nil, err } b, err := a.num("bound", false) if err != nil { return nil, err } return k.History(key, from, to, b) }}, {Name: Seat + ".reserve", Description: "Reserve the next decision or research number for a record you are about to propose, from the issue " + "tracker's counter: never one another caller got. The number is spent whether or not its record is ever accepted; " + "one never proposed is withdrawn after a week. Refused: a kind other than decision or research, an empty title.", Input: schema([]string{"kind", "for"}, map[string]any{"kind": map[string]any{"type": "string", "enum": Kinds}, "for": text("the title of the record the number is for")}), Run: func(m map[string]any) (any, error) { a, err := readArgs("reserve", m, "kind", "for") if err != nil { return nil, err } kind, err := a.str("kind") if err != nil { return nil, err } title, err := a.str("for") if err != nil { return nil, err } return k.ReserveNumber(caller(m), kind, title) }}, {Name: Seat + ".propose", Description: "Propose a record whole under a reserved number: its title, topic, date and deciders; for a decision its " + "clauses, each opening with a bold lead (\"**What is decided.** …\"), and its reasoning in sections (Context, " + "Considered Options, Consequences, How it is checked, References); for a research effort its status and documents; its " + "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 (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 " + "Approve), on changes it stays proposed with the findings for its author to revise, and when no judgement can be had " + "it stays proposed and says so. Nothing proposed is in force.", Input: schema([]string{"number", "title"}, map[string]any{ "kind": kindArg(), "number": integer("the number reserve gave"), "title": text("one line"), "topic": text("for a decision: a topic of the reading order, such as the mesh"), "date": text("the day, YYYY-MM-DD (today when absent)"), "deciders": texts("who decided"), "clauses": texts("a decision's clauses in order, each \"**Lead.** the statement\""), "sections": map[string]any{"type": "array", "description": "the reasoning, in order: each a name and its markdown", "items": map[string]any{"type": "object", "properties": map[string]any{"name": text("such as Context"), "text": text("markdown")}, "required": []string{"name", "text"}}}, "links": map[string]any{"type": "array", "description": "its links: kind, to (\"0276\", \"0276 §4\", \"research 045\", \"issue 365\", a design's path), from (the clause it goes out from)", "items": map[string]any{"type": "object", "properties": map[string]any{"kind": map[string]any{"type": "string", "enum": LinkKinds}, "to": text("the target"), "from": integer("the clause of this record it goes out from")}, "required": []string{"kind", "to"}}}, "homes": texts("hq documents that cite it, such as a design's path; a decision needs one unless an accepted record links it"), "needs-operator": flag("it holds only once the operator approves it"), "brief": map[string]any{"type": "object", "description": "required with needs-operator: what the operator's phone shows " + "in place of the clauses (issue 639) — a headline of at most 60 characters saying what it changes for the operator, " + "not the title; a summary of two or three short sentences, at most 300 characters, on what changes, why and what it " + "costs or risks; at most three points of at most 160 characters. No path, address, dotted name or long word", "properties": map[string]any{"headline": text("what it changes for the operator"), "summary": text("two or three short sentences"), "points": texts("at most three, only where they add something")}, "required": []string{"headline", "summary"}}, "status": map[string]any{"type": "string", "enum": ResearchStatuses, "description": "a research effort's status"}, "insight": text("for an accepted record: a progressive insight, saying what stood before"), "why": text("one line: why this version"), }), Run: func(m map[string]any) (any, error) { a, err := readArgs("propose", m, "kind", "number", "title", "topic", "date", "deciders", "clauses", "sections", "links", "homes", "needs-operator", "brief", "status", "insight", "why") if err != nil { return nil, err } key, err := a.key() if err != nil { return nil, err } p := ProposeAsk{Kind: key.Kind, Number: key.Number} for _, f := range []struct { k string p *string }{{"title", &p.Title}, {"topic", &p.Topic}, {"date", &p.Date}, {"status", &p.Status}, {"insight", &p.Insight}, {"why", &p.Why}} { if *f.p, err = a.str(f.k); err != nil { return nil, err } } for _, f := range []struct { k string p *[]string }{{"deciders", &p.Deciders}, {"clauses", &p.Clauses}, {"homes", &p.Homes}} { if *f.p, err = a.strs(f.k); err != nil { return nil, err } } if p.NeedsOperator, err = a.flag("needs-operator"); err != nil { return nil, err } if err := decode(m["sections"], &p.Sections, "sections is a list of {name, text}"); err != nil { return nil, err } if err := decode(m["links"], &p.Links, "links is a list of {kind, to, from}"); err != nil { return nil, err } if m["brief"] != nil { p.Brief = &Brief{} if err := decode(m["brief"], p.Brief, "brief is {headline, summary, points}"); err != nil { return nil, err } } return k.Propose(caller(m), p) }}, {Name: Seat + ".import", Description: "Import hq's decision records and research efforts, read through the records module at the commit its checkout " + "stands at, each whole at its own number as its first version naming that commit. It only creates a record that is not " + "held, never overwrites one, never moves a number counter, and run again changes nothing and says so. A decision written " + "as prose, or with no Decision section, waits for its clauses to be drafted and confirmed. With refresh it also reads " + "again every record it holds that nobody changed since its import, and writes what it lost or misread as a new version " + "naming why; a record changed since is never touched, and a second refresh changes nothing. With dry-run it writes " + "nothing and says what it would do, record by record; with commit it refuses a checkout standing elsewhere.", Input: schema(nil, map[string]any{"dry-run": flag("read everything, write nothing, say what would be done"), "commit": text("the commit of hq the record must stand at (a prefix is enough)"), "refresh": flag("also repair what it holds that only the import ever wrote, from hq at this commit")}), Run: func(m map[string]any) (any, error) { a, err := readArgs("import", m, "dry-run", "commit", "refresh") if err != nil { return nil, err } var ask ImportAsk if ask.DryRun, err = a.flag("dry-run"); err != nil { return nil, err } if ask.Refresh, err = a.flag("refresh"); err != nil { return nil, err } if ask.Commit, err = a.str("commit"); err != nil { return nil, err } return k.Import(caller(m), ask, k.Records) }}, {Name: Seat + ".return", Description: "Send the clauses drafted from a prose decision back to be drafted again, with your reason, which the next " + "drafting reads. Until they are drafted and confirmed again the record serves none. Refused: a record whose clauses its " + "author wrote, an empty reason, and a record whose clauses another record changed or links by number (a redraft " + "numbers them anew; a record that changes the drafted clause says it is wrong instead).", Input: schema([]string{"number", "reason"}, map[string]any{"kind": kindArg(), "number": integer("the record's number"), "reason": text("what is wrong with the drafted clauses")}), Run: func(m map[string]any) (any, error) { a, err := readArgs("return", m, "kind", "number", "reason") if err != nil { return nil, err } key, err := a.key() if err != nil { return nil, err } reason, err := a.str("reason") if err != nil { return nil, err } return k.Return(caller(m), key, reason) }}, } } // decode reads a structured argument strictly. func decode(v any, into any, says string) error { if v == nil { return nil } raw, err := json.Marshal(v) if err != nil { return errors.New(says) } dec := json.NewDecoder(strings.NewReader(string(raw))) dec.DisallowUnknownFields() if err := dec.Decode(into); err != nil { return fmt.Errorf("%s (%v)", says, err) } return nil }