Files
mesh-catalog/modules/claude-code/cmd/claude-code/proposals.go
T
jochen eadf301f9f An ask reads at a glance on the phone: a headline, a summary and its points, the rest in a note (issues 383 and 639)
The operator would not read the Telegram ask for ADR 0343: thirty plain lines, every clause on one, no bold,
no summary. A settings proposal was the same wall of text, its values masked so the approval was blind.

- messenger: an ask's message is the asker's headline and explanation alone; who asked and when, what each
  answer does, the expiry, where else it is answered and the asker's Details travel as the message's note
  (asks.Message.Note). Answered, the message is one line, "You chose Approve at 16:06 on Telegram ·
  <headline>". An explanation that does not read at a glance (asks.Glance) is shown and logged. On a kind
  that proves who answers, an ask's values are shown whole (asks.Ask.Whole), held to the secret rule alone
  (from the earlier work on issue 383, #178).
- telegram: every message in HTML, never reworded: the title bold, a key line monospace, the note apart in
  a collapsed blockquote; markup escaped. A tap is compared by its words, whitespace aside, and a message
  shown before is still compared by the digest it was kept under.
- desk-channel: the note follows the body.
- mesh-knowledge: a record that needs the operator carries a brief (headline, summary, at most three
  points), the 30th per-record rule; its ask shows the brief and never the clauses. An ask made before is
  composed again as it was asked (form), so its warrant still holds.
- claude-code: an instructions proposal's explanation is one summary line (the proposer's, or the
  module's), its text in the Details; a question's context is at most 300 characters and its recommendation
  one point. Older proposals and questions keep their form.

The SDK is pinned at mesh-sdk 9588871 (fix/639-an-ask-reads-at-a-glance), which adds Message.Note and
asks.Glance.
2026-10-13 17:16:50 +02:00

1250 lines
50 KiB
Go

package main
// Shared instructions on the operator's approval (novox/hq ADR 0274, amending issue 340's decision 2).
//
// An agent may PROPOSE an instruction section for every Claude session on the mesh, or on some nodes: a rule,
// a hand-over, the context a later session needs. A proposal is text and nothing else. It is kept in the
// module's `proposals` state and asked of the operator through the operator channel (novox/hq ADR 0259) as an
// ask whose two answers, Approve and Decline, are both at the level approve, so only a channel that proves who
// answered (Telegram, today) can carry either. Nothing is active until the operator approves.
//
// **Every node proves the approval itself.** The `proposals` state is written by every node's tool runner, so a
// record there proves nothing. Each instance of this module, for each proposal it has not decided yet:
//
// 1. reads the router's own record of the ask, under this module's name (the bus lets only the router write it,
// and lets this module read only its own asks);
// 2. composes the ask again from the proposal — the words, the options, the expiry, the digest of the text —
// and holds the router's warrant to it: the same ask digest, an option it offered, at its level, in time
// (asks.Warrant.For), and the option's bound act is this proposal's (asks.Option.Performs). An ask published
// under this module's name with other words, or a proposal changed after it was asked, finds the digests
// differ and nothing is done;
// 3. applies the approved text to its own file, `approved.json` in the module's state directory, which is what
// the managed instruction file renders from. A proposal decided here is never decided again: a warrant heard
// twice, or replayed, changes nothing.
//
// Removing an approved section is a proposal too, approved the same way: a record deleted from the bus removes
// nothing, because the node's own file is what renders.
import (
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"sync"
"time"
"unicode"
"unicode/utf8"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/asks"
)
// Module is this module's name: the one the bus grants its asks under.
const Module = "claude-code"
// ProposalsState is the module's state the proposals are kept in.
const ProposalsState = "proposals"
// MaxProposalBytes is the most one proposed text may hold: it is read by every session it reaches, every time.
const MaxProposalBytes = 16 << 10
// RecordFor and RecordEvery bound how long, and how often, a node asks the controller to record a warrant it
// decided while the controller has not taken it.
const (
RecordFor = 7 * 24 * time.Hour
RecordEvery = 10 * time.Minute
)
// RouterKeeps is how long the router keeps its record of an ask after it closed (novox/hq ADR 0259 §3): a node
// that has not read it by then cannot prove the answer.
const RouterKeeps = 30 * 24 * time.Hour
// ProposalLasts is how long the operator has to answer: a day less a margin, so the router never refuses it for
// lasting a day and a moment (the SDK's bound for an approval).
const ProposalLasts = asks.ApproveLasts - 10*time.Minute
// What a proposal does.
const (
ActionAdd = "add"
ActionReplace = "replace"
ActionRemove = "remove"
)
// The two answers, both at the level approve.
const (
OptionApprove = "approve"
OptionDecline = "decline"
)
// How much of the text the operator's phone is shown: all of it when it is short and every line may leave the
// mesh, else its first lines.
const (
WholeTextBytes = 2000
WholeTextLines = 40
FirstBytes = 1200
FirstLines = 20
)
// Proposal is one proposed change to the shared instructions, as the `proposals` state keeps it. Every field the
// ask is composed from is here, so any node composes the same ask from it.
type Proposal struct {
ID string `json:"id"`
Action string `json:"action"`
Scope string `json:"scope"`
Nodes []string `json:"nodes,omitempty"`
Name string `json:"name"`
Text string `json:"text,omitempty"`
Digest string `json:"digest"`
From string `json:"from"`
At time.Time `json:"at"`
// Summary is what the operator's phone shows of it, at a glance, in its proposer's words; empty, the module
// says what it does (novox/hq issue 639).
Summary string `json:"summary,omitempty"`
// Form is how its ask is composed, so every node composes the same one again to hold the warrant to it: 0 for
// a proposal made before issue 639 (its text in the explanation), AskGlance for one read at a glance.
Form int `json:"form,omitempty"`
// Asked is empty once the ask was published, or says why it was not; nothing composes from it.
Asked string `json:"asked,omitempty"`
}
// textDigest is the digest a proposal binds: SHA-256 over the text's bytes.
func textDigest(text string) string {
sum := sha256.Sum256([]byte(text))
return "sha256:" + hex.EncodeToString(sum[:])
}
// Fingerprint is the digest as the operator is shown it: its first 24 hexadecimal digits in groups of four, so
// no word of it is long enough for the router to take for a secret.
func (p Proposal) Fingerprint() string { return fingerprintOf(p.Digest) }
func fingerprintOf(digest string) string {
hexed := strings.TrimPrefix(digest, "sha256:")
if len(hexed) < 24 {
return hexed
}
var groups []string
for i := 0; i < 24; i += 4 {
groups = append(groups, hexed[i:i+4])
}
return strings.Join(groups, " ")
}
// Lines is how many lines the text has.
func (p Proposal) Lines() int {
if p.Text == "" {
return 0
}
return len(strings.Split(strings.TrimRight(p.Text, "\n"), "\n"))
}
// where says which sessions a proposal reaches, in words.
func (p Proposal) where() string {
if p.Scope == ScopeMesh {
return "every Claude session on the mesh"
}
return "every Claude session on " + joinAnd(p.Nodes)
}
func joinAnd(list []string) string {
switch len(list) {
case 0:
return ""
case 1:
return list[0]
}
return strings.Join(list[:len(list)-1], ", ") + " and " + list[len(list)-1]
}
// Act is what an answer binds: this proposal, whole, and the answer. The router's warrant names the ask's digest,
// and the ask names this act's digest in the option, so an approval authorises this text and nothing else.
func (p Proposal) Act(option string) asks.Act {
return asks.Act{"verb": Module + ".instructions", "option": option, "proposal": p.ID, "action": p.Action,
"scope": p.Scope, "nodes": strings.Join(p.Nodes, ","), "name": p.Name, "digest": p.Digest, "from": p.From,
"at": p.At.UTC().Format(time.RFC3339Nano)}
}
// AskGlance is the form of an ask read at a glance (Proposal.Form, Question.Form; novox/hq issue 639).
const AskGlance = 1
// Ask is the ask for a proposal, composed from it alone: every node composes the same one, and holds the
// router's warrant to it — in the form it was asked in.
func (p Proposal) Ask() asks.Ask {
if p.Form == AskGlance {
return p.glanceAsk()
}
return p.listedAsk()
}
// glanceAsk is the ask read at a glance on the phone (novox/hq issue 639): its headline, and as its explanation
// the summary of what it does; the text, where it came from, its fingerprint and where to read all of it are its
// Details, which the router notes after the answers, collapsed where the channel can.
func (p Proposal) glanceAsk() asks.Ask {
verb := map[string]string{ActionAdd: "Add", ActionReplace: "Replace", ActionRemove: "Remove"}[p.Action]
shown, whole := excerpt(p.Text)
part := ""
if p.Action != ActionRemove && !whole {
part = " Part shown"
}
headline := fmt.Sprintf("%s Claude instructions '%s'?%s", verb, p.Name, part)
if len([]rune(headline)) > asks.HeadlineLength {
headline = verb + " Claude instructions?" + part
}
var d strings.Builder
if p.Action == ActionRemove {
fmt.Fprintf(&d, "Proposed through the claude-code module on %s at %s UTC. The approved text it removes has the fingerprint %s.",
p.From, p.At.UTC().Format("15:04 on 2 Jan"), p.Fingerprint())
} else {
if whole {
d.WriteString("The text:\n" + shown + "\n")
} else {
d.WriteString("NOT SHOWN IN FULL here. Its first lines:\n" + shown + "\n")
}
fmt.Fprintf(&d, "Proposed through the claude-code module on %s at %s UTC: %d lines, %d bytes, fingerprint %s.",
p.From, p.At.UTC().Format("15:04 on 2 Jan"), p.Lines(), len(p.Text), p.Fingerprint())
}
fmt.Fprintf(&d, "\nRead all of it at the controller's terminal: mesh-cli ask claude-code claude_code_proposals '{\"id\": \"%s\"}'.", p.ID)
does := "the text becomes part of those sessions' instructions"
if p.Action == ActionRemove {
does = "the instructions are removed from those sessions"
}
return p.withOptions(asks.Ask{ID: p.ID, Headline: headline, Explanation: p.summary(), Details: d.String()}, does)
}
// summary is what the proposal does, at a glance: its proposer's words, or the module's.
func (p Proposal) summary() string {
if s := strings.TrimSpace(p.Summary); s != "" {
return s
}
lines := fmt.Sprintf("these %d lines", p.Lines())
if p.Lines() == 1 {
lines = "this line"
}
where := strings.ToUpper(p.where()[:1]) + p.where()[1:]
switch p.Action {
case ActionRemove:
return fmt.Sprintf("%s stops reading the instructions '%s' from its next start.", where, p.Name)
case ActionReplace:
return fmt.Sprintf("%s reads %s in place of the instructions '%s', as the mesh's own word, from its next start until "+
"they are removed the same way.", where, lines, p.Name)
}
return fmt.Sprintf("%s reads %s as the mesh's own word, from its next start until they are removed the same way.", where, lines)
}
// SummaryProblem says why a proposer's summary cannot be shown at a glance, or "".
func SummaryProblem(summary string) string {
switch s := strings.TrimSpace(summary); {
case s == "":
return ""
case strings.Contains(s, "\n"):
return "the summary is more than one line"
case len([]rune(s)) > asks.SummaryLength:
return fmt.Sprintf("the summary is %d characters, more than the %d a phone shows at a glance", len([]rune(s)), asks.SummaryLength)
case !shownLine(s):
return "the summary names a path, an address, a host name or a long word, which a phone is not shown"
}
return ""
}
// withOptions is an ask with what every form of it carries: who answers, its expiry, what it is about, and its two
// options, each binding its act.
func (p Proposal) withOptions(a asks.Ask, does string) asks.Ask {
a.Who, a.Expires, a.OnExpiry = asks.Operator, p.At.Add(ProposalLasts), "the proposal is discarded; nothing changes"
a.About = "instructions." + p.Scope + "." + strings.Join(p.Nodes, "+") + "." + p.Name
if len(p.Nodes) == 0 {
a.About = "instructions." + p.Scope + "." + p.Name
}
for _, o := range []struct{ id, label, does string }{
{OptionApprove, "Approve", does},
{OptionDecline, "Decline", "nothing changes; the proposal is discarded"},
} {
binds, _ := asks.ActDigest(p.Act(o.id))
a.Options = append(a.Options, asks.Option{ID: o.id, Label: o.label, Does: o.does, Level: asks.Approve, Binds: binds})
}
return a
}
// listedAsk is the ask as it was composed before issue 639, the text in its explanation: composed only to hold the
// warrant of a proposal made then.
func (p Proposal) listedAsk() asks.Ask {
verb := map[string]string{ActionAdd: "Add", ActionReplace: "Replace", ActionRemove: "Remove"}[p.Action]
shown, whole := excerpt(p.Text)
part := ""
if p.Action != ActionRemove && !whole {
part = " Part shown"
}
headline := fmt.Sprintf("%s Claude instructions '%s'?%s", verb, p.Name, part)
if len([]rune(headline)) > asks.HeadlineLength {
headline = verb + " Claude instructions?" + part
}
var b strings.Builder
switch p.Action {
case ActionAdd:
fmt.Fprintf(&b, "Add instructions '%s' to %s?\n\n", p.Name, p.where())
case ActionReplace:
fmt.Fprintf(&b, "Replace the instructions '%s' in %s?\n\n", p.Name, p.where())
case ActionRemove:
fmt.Fprintf(&b, "Remove the instructions '%s' from %s?\n\n", p.Name, p.where())
}
fmt.Fprintf(&b, "An agent proposed this through the claude-code module on %s, at %s UTC.", p.From,
p.At.UTC().Format("15:04 on 2 Jan"))
if p.Action == ActionRemove {
fmt.Fprintf(&b, " The approved text it removes has the fingerprint %s.", p.Fingerprint())
} else {
fmt.Fprintf(&b, " The text has %d lines, %d bytes, fingerprint %s. Once approved, every new session there "+
"reads it as the mesh's own word until it is removed the same way.\n\n", p.Lines(), len(p.Text), p.Fingerprint())
if whole {
b.WriteString("The whole text:\n\n" + shown)
} else {
fmt.Fprintf(&b, "NOT SHOWN IN FULL here. Its first lines:\n\n%s\n\nRead all of it before you answer, at the controller's terminal: "+
"mesh-cli ask claude-code claude_code_proposals '{\"id\": \"%s\"}'. The fingerprint there must be %s.",
shown, p.ID, p.Fingerprint())
}
}
does := "the text becomes part of those sessions' instructions"
if p.Action == ActionRemove {
does = "the instructions are removed from those sessions"
}
a := asks.Ask{ID: p.ID, Headline: headline, Explanation: strings.TrimRight(b.String(), "\n"), Who: asks.Operator,
Expires: p.At.Add(ProposalLasts), OnExpiry: "the proposal is discarded; nothing changes",
About: "instructions." + p.Scope + "." + strings.Join(p.Nodes, "+") + "." + p.Name}
if len(p.Nodes) == 0 {
a.About = "instructions." + p.Scope + "." + p.Name
}
for _, o := range []struct{ id, label, does string }{
{OptionApprove, "Approve", does},
{OptionDecline, "Decline", "nothing changes; the proposal is discarded"},
} {
binds, _ := asks.ActDigest(p.Act(o.id))
a.Options = append(a.Options, asks.Option{ID: o.id, Label: o.label, Does: o.does, Level: asks.Approve, Binds: binds})
}
return a
}
// excerpt is what of a text the operator's phone is shown, and whether it is the whole text. A line that might
// carry what may not leave the mesh (a path, an address, a long unbroken word) is not shown, and a marker says
// so; the router refuses an ask whose words carry one, and the whole text is read at the terminal.
func excerpt(text string) (string, bool) {
lines := strings.Split(strings.TrimRight(text, "\n"), "\n")
whole := len(text) <= WholeTextBytes && len(lines) <= WholeTextLines
limitBytes, limitLines := FirstBytes, FirstLines
if whole {
limitBytes, limitLines = WholeTextBytes, WholeTextLines
}
var out []string
size, hidden := 0, 0
flush := func() {
if hidden > 0 {
out = append(out, fmt.Sprintf("[%d line(s) not shown here: a path, an address or a long word]", hidden))
hidden = 0
}
}
for i, line := range lines {
if i >= limitLines || size+len(line) > limitBytes {
whole = false
break
}
if !shownLine(line) {
whole = false
hidden++
continue
}
flush()
out = append(out, line)
size += len(line) + 1
}
flush()
return strings.Join(out, "\n"), whole
}
var (
unbroken = regexp.MustCompile(`[A-Za-z0-9+/=_]{20,}`)
hexRun = regexp.MustCompile(`(?i)[0-9a-f]{16,}`)
numbersAt = regexp.MustCompile(`\d+\.\d+\.\d+`)
hostLike = regexp.MustCompile(`(?i)\b[a-z0-9-]+(\.[a-z0-9-]+)*\.[a-z]{2,12}\b`)
assigned = regexp.MustCompile(`(?i)(password|passwd|passphrase|secret|token|api[_-]?key|credential|private[_-]?key)\s*[=:]`)
)
// shownLine says whether a line may be shown on the phone: no path, address, host-like name, long unbroken word
// or value given to a secret's name. Deliberately narrower than the router's own rule, so an ask is rarely
// refused for its words; a line kept back is read at the terminal.
func shownLine(line string) bool {
if strings.ContainsAny(line, "/\\@") || strings.Contains(line, "~") || strings.Contains(line, "$") {
return false
}
for _, re := range []*regexp.Regexp{unbroken, hexRun, numbersAt, assigned} {
if re.MatchString(line) {
return false
}
}
// A sentence's end ("word. Next") never matches, nor does "e.g."; anything else shaped like a.name is held back.
return !hostLike.MatchString(line)
}
// anyImport is an @ the agent's reader could take for a file to read in: one at a text's start or after anything that
// is not a letter or a digit — any whitespace, Unicode's included, or punctuation. Wider than the vendor's own
// pattern on purpose: an e-mail-like word is the one kind let through.
var anyImport = regexp.MustCompile(`(?:^|[^\p{L}\p{N}])@\S`)
// ownHeading is a heading of level one to three, the levels the managed instruction file's own sections use.
var ownHeading = regexp.MustCompile(`(?m)^[ \t]{0,3}#{1,3}([ \t]|$)[^\n]*`)
// setextHeading is a non-blank line underlined with = or -; htmlHeading an HTML heading of level one to three.
var (
setextHeading = regexp.MustCompile(`(?m)^[ \t]{0,3}\S[^\n]*\n[ \t]{0,3}(=+|-+)[ \t]*$`)
htmlHeading = regexp.MustCompile(`(?i)<h[1-3][\s>]`)
)
// invisible names the first character of a text a reader would not see and a session would read, or "": a
// control character but a line break and a tab, a format character (zero-width, bidirectional overrides, the
// tag block), private use, a noncharacter, and any space or separator but the plain space.
func invisible(text string) string {
for _, r := range text {
switch {
case r == '\n' || r == '\t' || r == ' ':
continue
case unicode.Is(unicode.Cc, r):
return fmt.Sprintf("a control character (U+%04X)", r)
case unicode.Is(unicode.Cf, r):
return fmt.Sprintf("an invisible format character (U+%04X)", r)
case (r >= 0xFE00 && r <= 0xFE0F) || (r >= 0xE0100 && r <= 0xE01EF):
return fmt.Sprintf("a variation selector (U+%04X)", r)
case r == 0x3164 || r == 0x115F || r == 0x1160 || r == 0xFFA0 || r == 0x2800:
return fmt.Sprintf("a blank letter (U+%04X)", r)
case unicode.Is(unicode.Co, r):
return fmt.Sprintf("a private-use character (U+%04X)", r)
case unicode.Is(unicode.Zs, r) || unicode.Is(unicode.Zl, r) || unicode.Is(unicode.Zp, r):
return fmt.Sprintf("a space or separator other than the plain space (U+%04X)", r)
case r == utf8.RuneError || (r >= 0xFDD0 && r <= 0xFDEF) || r&0xFFFE == 0xFFFE:
return fmt.Sprintf("a noncharacter (U+%04X)", r)
}
}
return ""
}
// ProposalProblem says why a proposal, as kept on the bus, is not one this module would have made, or "": every
// node judges it again before it applies an approval, because anything holding a tool runner's credential can
// write the record and ask for it under this module's name.
func ProposalProblem(p Proposal) string {
switch {
case !asks.UsableID(p.ID):
return "its id is not one"
case !itemName.MatchString(p.Name):
return "its name is not one"
case !nodeName.MatchString(p.From):
return "where it came from is not a node's name"
case p.At.IsZero():
return "it says no time"
case p.Scope == ScopeMesh && len(p.Nodes) > 0:
return "the mesh scope names no nodes"
case p.Scope == ScopeNode && len(p.Nodes) == 0:
return "the node scope names its nodes"
case p.Scope != ScopeMesh && p.Scope != ScopeNode:
return "its scope is neither mesh nor node"
}
if p.Scope == ScopeNode {
if problem := nodesProblem(p.Nodes); problem != "" {
return problem
}
for i := 1; i < len(p.Nodes); i++ {
if p.Nodes[i-1] >= p.Nodes[i] {
return "its nodes are not listed once each, in order"
}
}
}
switch p.Action {
case ActionAdd, ActionReplace:
if problem := ProposedTextProblem(p.Text); problem != "" {
return problem
}
if textDigest(p.Text) != p.Digest {
return "its text is not the text its digest names"
}
case ActionRemove:
if p.Text != "" || !strings.HasPrefix(p.Digest, "sha256:") {
return "a removal carries no text, and the digest of the text it removes"
}
default:
return "its action is not add, replace or remove"
}
return ""
}
// secretShaped are shapes a shared text never carries: it is read by every session and shown on a phone.
var secretShaped = []*regexp.Regexp{
regexp.MustCompile(`-----BEGIN [A-Z ]+-----`),
regexp.MustCompile(`\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}`),
regexp.MustCompile(`\b(gh[pousr]_[A-Za-z0-9]{20,}|glpat-[A-Za-z0-9_-]{16,}|sk-[A-Za-z0-9_-]{16,}|xox[abprs]-[A-Za-z0-9-]{10,}|AKIA[0-9A-Z]{16})`),
regexp.MustCompile(`(?i)\b(password|passwd|passphrase|token|api[_-]?key|apikey|private[_-]?key)\s*[=:]\s*\S`),
}
// ProposedTextProblem says why a text cannot be proposed, or "": empty, too long, not text, or something a
// session would run or read in by itself (issue 340's refusals, kept): a shell line, front matter, a hook, a tool
// server, a permission mode or pre-approved tools, or an `@` reference, which the instruction file reads in as a
// file; or anything shaped like a secret.
func ProposedTextProblem(text string) string {
switch {
case strings.TrimSpace(text) == "":
return "the text is empty"
case len(text) > MaxProposalBytes:
return fmt.Sprintf("the text is %d bytes, more than the %d a shared section may hold: every session reads all of it", len(text), MaxProposalBytes)
case !utf8.ValidString(text) || strings.ContainsRune(text, 0):
return "the text is not text"
case invisible(text) != "":
return "the text holds " + invisible(text) + ", which a phone does not show and a session reads; a shared section is plain visible text"
case shellLine.MatchString(text):
return "the text holds a shell line (!`…` or ```!), which a session may run by itself; nothing proposed here runs (novox/hq issue 340)"
}
if keys, problem := frontMatterKeys(text); problem != "" || len(keys) > 0 {
for _, key := range keys {
norm := strings.ToLower(strings.NewReplacer("-", "", "_", "").Replace(key))
if why, ok := runsFrontMatter[norm]; ok {
return fmt.Sprintf("the text's front matter says %q: %s; nothing proposed here runs or loosens what runs (novox/hq issue 340)", key, why)
}
}
return "the text opens with front matter, which a session may act on; a shared section is plain text (novox/hq issue 340)"
}
if m := anyImport.FindString(text); m != "" {
return fmt.Sprintf("the text holds %q: an @ after anything but a letter or a digit may be read in as a file, "+
"and a shared section refers to no file (novox/hq issue 340)", strings.TrimSpace(m))
}
if m := setextHeading.FindString(text); m != "" {
return "the text underlines a line with = or -, which makes it a heading that would read as a section of the mesh's own"
}
if m := htmlHeading.FindString(text); m != "" {
return fmt.Sprintf("the text holds %q, a heading that would read as a section of the mesh's own", m)
}
if m := ownHeading.FindString(text); m != "" {
return fmt.Sprintf("the text holds the heading %q, which would read as a section of the mesh's own; a shared "+
"section uses headings of level four or deeper", strings.TrimSpace(m))
}
for _, re := range secretShaped {
if re.MatchString(text) {
return "the text holds something shaped like a secret, which a shared section never carries"
}
}
return ""
}
// ---- what this node approved ------------------------------------------------------------------------
// ApprovedSection is one approved section as this node keeps it.
type ApprovedSection struct {
Name string `json:"name"`
Scope string `json:"scope"`
Nodes []string `json:"nodes,omitempty"`
Text string `json:"text"`
Digest string `json:"digest"`
Proposal string `json:"proposal"`
From string `json:"from"`
Approved time.Time `json:"approved"`
By string `json:"by"`
}
// ProposalDecision is what this node made of one proposal: once decided, never again.
type ProposalDecision struct {
Outcome string `json:"outcome"`
At time.Time `json:"at"`
Says string `json:"says,omitempty"`
// Recorded says the controller's hand-act log holds the warrant; Record is why not, while it does not.
// Warranted says a person's choice held here: Approve or Decline, recorded in the hand-act log.
Warranted bool `json:"warranted,omitempty"`
Recorded bool `json:"recorded,omitempty"`
// Heard says the decision came from the router's word on the decided subject, not its record: a record of a
// choice found later is still taken.
Heard bool `json:"heard,omitempty"`
Record string `json:"record,omitempty"`
LastTry time.Time `json:"last-try,omitempty"`
}
// Approved is this node's own record: the sections it renders, and every proposal it decided.
type Approved struct {
Sections map[string]ApprovedSection `json:"sections"`
Decided map[string]ProposalDecision `json:"decided"`
// Changed is, for each section's place, when the operator approved the last change applied to it here — an
// addition, a replacement or a removal. An approval older than it is never applied: an earlier approval
// replayed, or read late, does not undo a later one.
Changed map[string]time.Time `json:"changed,omitempty"`
// Refused is the proposals whose answer did not hold here, and why: asked again on every look, so an honest
// approval refused for a reason that passes (a node still on an older version) is taken when it holds.
Refused map[string]string `json:"refused,omitempty"`
}
func (p Paths) approved() string { return filepath.Join(p.State, "approved.json") }
// sectionKey is one section's place: its scope, the nodes it is for and its name, so a section for some nodes is
// never replaced or removed by a proposal for other nodes.
func sectionKey(scope string, nodes []string, name string) string {
return scope + "/" + strings.Join(nodes, ",") + "/" + name
}
// ReadApproved is this node's record, or an empty one when there is none yet. A record that cannot be read is
// an error, never taken for an empty one: that would forget every decision and open them all again.
func ReadApproved(p Paths) (Approved, error) {
var a Approved
raw, err := fsys.ReadFile(p.approved())
switch {
case errors.Is(err, os.ErrNotExist):
case err != nil:
return a, fmt.Errorf("this node's record of what the operator approved (%s) cannot be read: %w", p.approved(), err)
default:
if err := json.Unmarshal(raw, &a); err != nil {
return a, fmt.Errorf("this node's record of what the operator approved (%s) is not readable JSON: %w; "+
"nothing is decided or rendered from it until it is", p.approved(), err)
}
}
if a.Sections == nil {
a.Sections = map[string]ApprovedSection{}
}
if a.Decided == nil {
a.Decided = map[string]ProposalDecision{}
}
if a.Changed == nil {
a.Changed = map[string]time.Time{}
}
if a.Refused == nil {
a.Refused = map[string]string{}
}
return a, nil
}
func writeApproved(p Paths, a Approved) error {
raw, err := indented(a)
if err != nil {
return err
}
tmp := p.approved() + ".next"
f, err := os.OpenFile(tmp, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
if err != nil {
return err
}
if _, err := f.Write(raw); err != nil {
_ = f.Close()
return err
}
// On the disk before it takes the old one's place: a crash leaves the old record or the new, never an empty one.
if err := f.Sync(); err != nil {
_ = f.Close()
return err
}
if err := f.Close(); err != nil {
return err
}
if err := os.Rename(tmp, p.approved()); err != nil {
return err
}
if dir, err := os.Open(p.State); err == nil {
_ = dir.Sync()
_ = dir.Close()
}
return nil
}
// ApprovedHere is the approved sections that reach this node, by scope then name.
func ApprovedHere(a Approved, node string) []ApprovedSection {
var out []ApprovedSection
for _, s := range a.Sections {
// Judged again as it renders: a record changed on disk renders nothing a proposal could not hold.
if !itemName.MatchString(s.Name) || !nodeName.MatchString(s.From) || ProposedTextProblem(s.Text) != "" ||
textDigest(s.Text) != s.Digest || (s.Scope != ScopeMesh && s.Scope != ScopeNode) ||
(s.Scope == ScopeNode && (len(s.Nodes) == 0 || nodesProblem(s.Nodes) != "")) {
continue
}
if s.Scope == ScopeMesh || contains(s.Nodes, node) {
out = append(out, s)
}
}
sort.Slice(out, func(i, j int) bool {
if out[i].Scope != out[j].Scope {
return out[i].Scope < out[j].Scope
}
return out[i].Name < out[j].Name
})
return out
}
func contains(list []string, s string) bool {
for _, x := range list {
if x == s {
return true
}
}
return false
}
// ---- the bus, as proposals reach it -----------------------------------------------------------------
// AskBus is the operator channel as this module reaches it: publish an ask under its own name, read the router's
// record of one of its asks, and the kinds of the channel bench with what each promises.
type AskBus interface {
Publish(subject string, body any, id string) error
Record(id string) (*stdio.StateEntry, error)
Kinds() ([]stdio.KindHeld, error)
}
// ProposalStore is the `proposals` state.
type ProposalStore interface {
Create(key string, value any) error
Put(key string, value any) error
Keys() ([]string, error)
Get(key string) (json.RawMessage, bool, error)
}
// Recorder records a warrant in the controller's hand-act log: the controller reads the warrant from the
// router's own record, never from this module.
type Recorder func(ask string) error
type sdkAskBus struct{}
func (sdkAskBus) Publish(subject string, body any, id string) error {
return stdio.SeatPublish(subject, body, id)
}
func (sdkAskBus) Record(id string) (*stdio.StateEntry, error) { return stdio.SeatRecord("", id) }
func (sdkAskBus) Kinds() ([]stdio.KindHeld, error) { return stdio.SeatKinds() }
// recordWarrant asks the controller to record a warrant this module acted on.
func recordWarrant(ask string) error {
_, err := stdio.Ask("seat:mesh-controller.warranted", map[string]any{"asker": Module, "ask": ask})
return err
}
// ---- proposing --------------------------------------------------------------------------------------
// ProposeInput is what an agent proposes.
type ProposeInput struct {
Scope string
Nodes []string
Name string
Text string
Remove bool
// Summary is what the operator's phone shows of it, in one line (novox/hq issue 639); empty, the module says it.
Summary string
}
// AtTheTerminalInstead is the other way, said whenever a proposal is refused for want of a channel.
const AtTheTerminalInstead = "the operator may set it at the controller's terminal instead: mesh-cli settings set claude-code " +
"'{\"instructions\": {\"<name>\": [\"line\", …]}}' [--node <node>] (novox/hq issue 340)"
// approverReady says why no channel that proves who answered can carry an approval, when this module can tell:
// the channel bench's kinds, where its membership names them, and none promising a choice, an exact rendering and
// a verified sender. **The router is the judge** (novox/hq ADR 0259 §4): it refuses an ask none of its ready
// channels can carry, on the decided subject, and Propose waits for that word. A module that only asks is issued
// no kinds (they are issued for a kinded bench a module uses or watches, and this module uses the operator
// channel alone), so an empty list says nothing — read as "none" it refused every proposal on the live mesh on
// 2026-10-10, with Telegram linked and proven.
func approverReady(bus AskBus) string {
kinds, err := bus.Kinds()
if err != nil || len(kinds) == 0 {
return ""
}
channels := 0
for _, k := range kinds {
if k.Seat == "channel" {
channels++
}
}
if channels == 0 {
return ""
}
for _, k := range kinds {
if k.Seat == "channel" && k.Promises("choice") && k.Promises("exact-render") && k.Promises("verified-sender") {
return ""
}
}
return "no channel that proves who answers (one promising a verified sender, such as Telegram) is on the mesh"
}
func newProposalID() string {
var b [8]byte
_, _ = rand.Read(b[:])
return "instr-" + hex.EncodeToString(b[:])
}
// Propose records a proposal and asks the operator. Nothing becomes active here or anywhere: every node applies it
// only on the operator's warrant, which it proves itself (Reconcile).
func Propose(p Paths, in ProposeInput, store ProposalStore, bus AskBus, now time.Time) (map[string]any, error) {
refuse := func(why string) (map[string]any, error) {
return nil, errors.New(why + ". Nothing was proposed")
}
if in.Scope != ScopeMesh && in.Scope != ScopeNode {
return refuse(fmt.Sprintf("%q is not a scope a shared section is proposed at: mesh (every node) or node (the nodes given); "+
"a home is registered with claude_code_instructions_register", in.Scope))
}
if !itemName.MatchString(in.Name) {
return refuse(fmt.Sprintf("%q is not a name: lower-case letters, digits and -, at most 64", in.Name))
}
var nodes []string
if in.Scope == ScopeNode {
for _, n := range in.Nodes {
if n == "all" {
return refuse("\"all\" is the mesh scope: propose it with scope mesh")
}
if !contains(nodes, n) {
nodes = append(nodes, n)
}
}
if len(nodes) == 0 {
nodes = []string{p.Node}
}
if problem := nodesProblem(nodes); problem != "" {
return refuse(problem)
}
sort.Strings(nodes)
} else if len(in.Nodes) > 0 {
return refuse("the mesh scope reaches every node; name nodes with scope node")
}
approved, err := ReadApproved(p)
if err != nil {
return nil, err
}
active, isActive := approved.Sections[sectionKey(in.Scope, nodes, in.Name)]
if problem := SummaryProblem(in.Summary); problem != "" {
return refuse(problem)
}
prop := Proposal{ID: newProposalID(), Scope: in.Scope, Nodes: nodes, Name: in.Name, From: p.Node, At: now.UTC(),
Summary: strings.TrimSpace(in.Summary), Form: AskGlance}
switch {
case in.Remove:
if !isActive {
return refuse(fmt.Sprintf("no approved section %q at the %s scope is held on this node to remove; one set at the "+
"controller's terminal is removed there", in.Name, in.Scope))
}
prop.Action, prop.Digest = ActionRemove, active.Digest
default:
if problem := ProposedTextProblem(in.Text); problem != "" {
return refuse(problem)
}
prop.Action, prop.Text, prop.Digest = ActionAdd, in.Text, textDigest(in.Text)
if isActive {
if active.Digest == prop.Digest {
return refuse(fmt.Sprintf("the section %q already holds exactly this text", in.Name))
}
prop.Action = ActionReplace
}
}
if terminal, _ := terminalText(p); len(terminal) > 0 {
for _, it := range terminal {
if it.Kind == KindInstructions && it.Name == in.Name {
return refuse(fmt.Sprintf("a section %q is set at the controller's terminal; it is changed there", in.Name))
}
}
}
if why := approverReady(bus); why != "" {
return refuse(why + "; " + AtTheTerminalInstead)
}
if waiting := waitingProposals(store, approved, now); waiting >= asks.MostOpen {
return refuse(fmt.Sprintf("%d proposals already wait for the operator's answer, and the operator is asked at most %d "+
"questions at once; claude_code_proposals lists them", waiting, asks.MostOpen))
}
if problem := ProposalProblem(prop); problem != "" {
return refuse(problem)
}
ask := prop.Ask()
if err := ask.Check(now); err != nil {
return refuse(err.Error())
}
if err := store.Create(prop.ID, prop); err != nil {
return nil, fmt.Errorf("the proposal could not be kept, so the operator was not asked: %w", err)
}
if err := bus.Publish(asks.AskSubject(Module), ask, ask.ID); err != nil {
prop.Asked = "not asked: " + err.Error()
_ = store.Put(prop.ID, prop)
say("proposal %s (%s %s %s) could not be asked: %v", prop.ID, prop.Action, prop.Scope, prop.Name, err)
return nil, fmt.Errorf("the operator could not be asked (%v); the proposal is kept and will never become active. "+
"Propose it again, or %s", err, AtTheTerminalInstead)
}
say("proposal %s (%s %s %s, fingerprint %s) asked of the operator", prop.ID, prop.Action, prop.Scope, prop.Name, prop.Fingerprint())
// The router's word, before answering: it refuses at once an ask no channel can carry, and records one it took.
taken := "the router has not said yet whether it took the ask; claude_code_proposals shows it"
defer func() { endedMu.Lock(); delete(ended, prop.ID); endedMu.Unlock() }()
for deadline := time.Now().Add(RouterAnswersWithin); time.Now().Before(deadline); {
if w, ended := endedAsk(prop.ID); ended {
return nil, fmt.Errorf("the router did not ask the operator: the ask %s (%s). Nothing is active anywhere; %s",
w.Outcome, w.Words, AtTheTerminalInstead)
}
if entry, err := bus.Record(prop.ID); err == nil && entry != nil {
taken = "the router took the ask and shows it on the channels that can carry it"
break
}
time.Sleep(RouterAnswersEvery)
}
return map[string]any{
"router": taken,
"proposed": prop.ID, "action": prop.Action, "reaches": prop.where(), "fingerprint": prop.Fingerprint(),
"asked": "the operator is asked on a channel that proves who answers: \"" + ask.Headline + "\"",
"expires": ask.Expires.Format(time.RFC3339),
"until then": "nothing changes on any node; the text becomes part of those sessions only on the operator's Approve, " +
"and is discarded on Decline or at expiry. claude_code_proposals shows where it stands",
}, nil
}
// waitingProposals counts the proposals still open: asked, not decided here, not expired.
func waitingProposals(store ProposalStore, approved Approved, now time.Time) int {
keys, err := store.Keys()
if err != nil {
return 0
}
n := 0
for _, k := range keys {
prop, ok := readProposal(store, k)
if !ok || prop.Asked != "" {
continue
}
if _, decided := approved.Decided[prop.ID]; decided || now.After(prop.At.Add(ProposalLasts)) {
continue
}
n++
}
return n
}
func readProposal(store ProposalStore, key string) (Proposal, bool) {
raw, found, err := store.Get(key)
if err != nil || !found {
return Proposal{}, false
}
var prop Proposal
if json.Unmarshal(raw, &prop) != nil || prop.ID != key {
return Proposal{}, false
}
return prop, true
}
// How long Propose waits for the router's word on a new ask, and how often it looks.
var (
RouterAnswersWithin = 8 * time.Second
RouterAnswersEvery = 250 * time.Millisecond
)
// ended is the asks the router ended without a choice, as heard on the decided subject — which only the router may
// publish, for this module alone. A refused ask leaves no record of the router's, so this is where its refusal is
// read.
var (
endedMu sync.Mutex
ended = map[string]asks.Warrant{}
)
func endedAsk(id string) (asks.Warrant, bool) {
endedMu.Lock()
defer endedMu.Unlock()
w, ok := ended[id]
return w, ok
}
// HeardEnd takes the router's word that one of this module's asks ended without a choice: refused, expired,
// cancelled or replaced. The proposal is discarded here at once. Nothing is applied on such a word, so it needs no
// proof beyond its subject; a choice is only ever taken from the router's record (Reconcile).
func HeardEnd(p Paths, store ProposalStore, w asks.Warrant, now time.Time) (string, error) {
if w.Asker != Module || w.Outcome == asks.OutcomeChosen || !asks.UsableID(w.Ask) {
return "", nil
}
reconcileMu.Lock()
defer reconcileMu.Unlock()
prop, known := readProposal(store, w.Ask)
if !known || (w.About != "" && w.About != prop.Ask().About) {
return "", nil
}
// Said to a proposer waiting on it only once the discard is kept: what it reads next is already there (the
// flake of the router's refusal test under a loaded build agent, 2026-10-12).
tell := func() {
endedMu.Lock()
ended[w.Ask] = w
endedMu.Unlock()
}
approved, err := ReadApproved(p)
if err != nil {
tell()
return "", err
}
if _, done := approved.Decided[w.Ask]; done {
tell()
return "", nil
}
// Heard, not read from a record: Reconcile still reads the router's record of it, so an ask the router took after
// all (its queue offered it again once a channel was ready) is answered as any other.
d := ProposalDecision{At: now.UTC(), Says: w.Says(), Outcome: "discarded: the ask " + string(w.Outcome), Heard: true}
if w.Words != "" {
d.Outcome += " (" + w.Words + ")"
}
approved.Decided[w.Ask] = d
delete(approved.Refused, w.Ask)
err = writeApproved(p, approved)
tell()
if err != nil {
return "", err
}
return "proposal " + w.Ask + ": " + d.Outcome, nil
}
// ---- deciding, on every node ------------------------------------------------------------------------
// routerRecord is the router's record of one ask, as far as this module reads it.
type routerRecord struct {
State string `json:"state"`
Ask *asks.Ask `json:"ask"`
Warrant *asks.Warrant `json:"warrant"`
}
var reconcileMu sync.Mutex
// Reconcile decides, on this node, every proposal it has not decided yet whose ask the router has closed, each held
// to the warrant as described at the top of this file, oldest warrant first, and never one older than the last
// change applied to the same section. It answers whether what this node renders changed, and a line for each
// decision.
func Reconcile(p Paths, store ProposalStore, bus AskBus, record Recorder, now time.Time) (bool, []string, error) {
reconcileMu.Lock()
defer reconcileMu.Unlock()
approved, err := ReadApproved(p)
if err != nil {
return false, nil, err // nothing is decided over a record that cannot be read
}
keys, err := store.Keys()
if err != nil {
return false, nil, err
}
type closed struct {
prop Proposal
w asks.Warrant
}
var ready []closed
discarded := false
for _, k := range keys {
prop, ok := readProposal(store, k)
if !ok {
continue
}
d, done := approved.Decided[prop.ID]
if done && !d.Heard {
continue
}
entry, err := bus.Record(prop.ID)
if done {
// Discarded on the router's word alone: only a record of a person's choice reopens it.
var rec routerRecord
if err == nil && entry != nil && json.Unmarshal(entry.Value, &rec) == nil && rec.Warrant != nil &&
rec.Warrant.Outcome == asks.OutcomeChosen {
ready = append(ready, closed{prop, *rec.Warrant})
}
continue
}
if err == nil && entry == nil && now.After(prop.At.Add(ProposalLasts+time.Hour)) {
// Ended, and the router keeps no record of it: refused before it was taken, or kept past its time. Either
// way no answer can be proven, so it is discarded and nothing is applied.
approved.Decided[prop.ID] = ProposalDecision{At: now.UTC(),
Outcome: "discarded: the router keeps no record of its ask (refused before it was taken, or forgotten)"}
delete(approved.Refused, prop.ID)
discarded = true
continue
}
if err != nil || entry == nil {
continue // open, not yet recorded, or not readable now: asked again on the next look
}
var rec routerRecord
if json.Unmarshal(entry.Value, &rec) != nil || rec.State == "open" || rec.Warrant == nil {
continue
}
ready = append(ready, closed{prop, *rec.Warrant})
}
sort.SliceStable(ready, func(i, j int) bool { return ready[i].w.At.Before(ready[j].w.At) })
changed, wrote := false, discarded
var said []string
for _, c := range ready {
d, applied, line, final := decide(p, &approved, c.prop, c.w, now)
if !final {
// Not held here now: said once per reason, and judged again on the next look.
if approved.Refused[c.prop.ID] != d.Outcome {
approved.Refused[c.prop.ID] = d.Outcome
said = append(said, line)
wrote = true
}
continue
}
delete(approved.Refused, c.prop.ID)
approved.Decided[c.prop.ID] = d
changed = changed || applied
said = append(said, line)
wrote = true
}
// A refusal is kept while the router keeps its record: after that it can never hold.
for id := range approved.Refused {
if prop, ok := readProposal(store, id); !ok || now.Sub(prop.At) > ProposalLasts+RouterKeeps {
delete(approved.Refused, id)
wrote = true
}
}
// The hand-act log: one entry per warrant mesh-wide (the controller keeps one per ask, read from the router's
// own record), asked by every node that decided it, and asked again every ten minutes for a week while the
// controller has not taken it.
if record != nil {
for id, d := range approved.Decided {
if d.Recorded || !d.Warranted || now.Sub(d.At) > RecordFor ||
(!d.LastTry.IsZero() && now.Sub(d.LastTry) < RecordEvery) {
continue
}
d.LastTry = now.UTC()
if err := record(id); err != nil {
d.Record = "not recorded in the hand-act log yet: " + err.Error()
} else {
d.Recorded, d.Record = true, ""
}
approved.Decided[id] = d
wrote = true
}
}
if !wrote {
return false, nil, nil
}
if err := writeApproved(p, approved); err != nil {
return false, said, fmt.Errorf("what this node decided could not be kept: %w", err)
}
return changed, said, nil
}
// decide holds one closed ask's warrant to its proposal, and applies it to this node's record when it holds. final
// is false when the answer did not hold here: it is judged again on the next look, and nothing is applied.
func decide(p Paths, approved *Approved, prop Proposal, w asks.Warrant, now time.Time) (ProposalDecision, bool, string, bool) {
d := ProposalDecision{At: now.UTC(), Says: w.Says()}
refused := func(why string) (ProposalDecision, bool, string, bool) {
d.Outcome = "refused: " + why
say("REFUSED the answer to proposal %s: %s", prop.ID, why)
return d, false, "proposal " + prop.ID + ": refused, " + why, false
}
if w.Asker != Module || w.Ask != prop.ID {
return refused(fmt.Sprintf("the router's record is for %s's ask %s", w.Asker, w.Ask))
}
if w.Outcome != asks.OutcomeChosen {
d.Outcome = "discarded: the ask " + string(w.Outcome)
if w.Words != "" {
d.Outcome += " (" + w.Words + ")"
}
return d, false, "proposal " + prop.ID + ": " + d.Outcome, true
}
// The record is judged as this module would have made it: anything holding a tool runner's credential can
// write one, and ask for it under this module's name.
if problem := ProposalProblem(prop); problem != "" {
return refused("the proposal is not one this module makes: " + problem)
}
option, err := w.For(Module, prop.Ask())
if err != nil {
return refused(err.Error())
}
if err := option.Performs(prop.Act(option.ID)); err != nil {
return refused(err.Error())
}
d.Warranted = true
if option.ID == OptionDecline {
d.Outcome = "declined"
return d, false, "proposal " + prop.ID + ": declined by the operator; nothing changes", true
}
if option.ID != OptionApprove {
return refused("the option " + option.ID + " is not one a proposal offers")
}
key := sectionKey(prop.Scope, prop.Nodes, prop.Name)
if last, ok := approved.Changed[key]; ok && !w.At.After(last) {
d.Outcome = "approved, and superseded here: a change of this section approved later was applied first"
return d, false, "proposal " + prop.ID + ": " + d.Outcome, true
}
d.Outcome = "approved"
reaches := prop.Scope == ScopeMesh || contains(prop.Nodes, p.Node)
switch prop.Action {
case ActionRemove:
held, ok := approved.Sections[key]
if !ok || held.Digest != prop.Digest {
d.Outcome = "approved, nothing to remove here: this node holds no section " + prop.Name + " with that fingerprint"
approved.Changed[key] = w.At.UTC()
return d, false, "proposal " + prop.ID + ": " + d.Outcome, true
}
delete(approved.Sections, key)
default:
approved.Sections[key] = ApprovedSection{Name: prop.Name, Scope: prop.Scope, Nodes: prop.Nodes, Text: prop.Text,
Digest: prop.Digest, Proposal: prop.ID, From: prop.From, Approved: w.At.UTC(), By: w.Says()}
}
approved.Changed[key] = w.At.UTC()
say("proposal %s (%s %s %s, fingerprint %s): %s", prop.ID, prop.Action, prop.Scope, prop.Name, prop.Fingerprint(), w.Says())
return d, reaches, "proposal " + prop.ID + ": approved, " + prop.Action + " " + prop.Name, true
}
// ---- the list ---------------------------------------------------------------------------------------
// ListProposals is every proposal on the bus, newest first, with where the router has it and what this node made
// of it; with an id, that one with its whole text. And the sections approved here.
func ListProposals(p Paths, store ProposalStore, bus AskBus, id string, now time.Time) (map[string]any, error) {
approved, err := ReadApproved(p)
if err != nil {
return nil, err
}
keys, err := store.Keys()
if err != nil {
return nil, err
}
var props []Proposal
for _, k := range keys {
if prop, ok := readProposal(store, k); ok && (id == "" || prop.ID == id) {
props = append(props, prop)
}
}
if id != "" && len(props) == 0 {
return nil, fmt.Errorf("no proposal %q is kept on the bus", id)
}
sort.Slice(props, func(i, j int) bool { return props[i].At.After(props[j].At) })
var rows []map[string]any
for _, prop := range props {
row := map[string]any{"id": prop.ID, "action": prop.Action, "name": prop.Name, "reaches": prop.where(),
"from": prop.From, "at": prop.At.Format(time.RFC3339), "fingerprint": prop.Fingerprint(), "lines": prop.Lines(),
"bytes": len(prop.Text)}
if d, ok := approved.Decided[prop.ID]; ok {
row["here"] = d
} else if why, ok := approved.Refused[prop.ID]; ok {
row["here"] = "the answer did not hold here, judged again on every look: " + why
} else if prop.Asked != "" {
row["here"] = prop.Asked + "; it never becomes active"
} else if now.After(prop.At.Add(ProposalLasts + RouterKeeps)) {
row["here"] = "cannot be proven here: the router keeps its record of an answer 30 days, and this node never " +
"read it, so nothing is rendered here; propose it again, or set it at the controller's terminal"
} else if now.After(prop.At.Add(ProposalLasts)) {
row["here"] = "not decided here yet: the ask has ended, and the router's word on it has not been read here"
} else {
row["here"] = "waiting for the operator's answer; nothing is active"
}
if id != "" {
row["text"] = prop.Text
row["digest"] = prop.Digest
if problem := ProposalProblem(prop); problem != "" {
row["warning"] = "no node will apply this proposal: " + problem
}
// Whether the operator's phone shows this text: the ask composed here from the record against the ask
// the router took, both read now.
if entry, err := bus.Record(prop.ID); err == nil && entry != nil {
var rec routerRecord
if json.Unmarshal(entry.Value, &rec) == nil {
row["router"] = rec.State
switch {
case rec.Ask == nil:
row["the phone"] = "cannot be compared: the router's record keeps no ask"
case rec.Ask.Digest() == prop.Ask().Digest():
row["the phone"] = "shows the ask composed from exactly this text (fingerprint " + prop.Fingerprint() + ")"
default:
row["the phone"] = "shows ANOTHER ask than this record composes: do not approve it"
}
}
}
}
rows = append(rows, row)
}
var here []map[string]any
for _, s := range ApprovedHere(approved, p.Node) {
here = append(here, map[string]any{"name": s.Name, "scope": s.Scope, "nodes": s.Nodes, "proposal": s.Proposal,
"from": s.From, "approved": s.Approved.Format(time.RFC3339), "by": s.By,
"fingerprint": fingerprintOf(s.Digest)})
}
return map[string]any{"proposals": rows, "approved here": here,
"how": "a proposal becomes active on a node only when that node has held the operator's warrant, read from the " +
"router's own record, to the proposal's exact text (novox/hq ADR 0274)"}, nil
}