A proposal reads whole on the phone, and Telegram shows every message formatted (hq issue 383)
mesh/delivery-group group fix/383-a-proposal-reads-whole-on-the-phone checking: 0 of 2 member(s) ready
mesh/merge-gate pass: builds claude-code, messenger, telegram → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did with…
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer head of the same pull request

The operator could not read a settings proposal on the phone: its values were masked ("+ shares:
media=‹path›"), a how-to paragraph followed the fingerprint, and nothing was formatted.

messenger: an ask may carry its explanation whole (asks.Ask.Whole); the router shows it in
place of the explanation only on a kind the controller issued as verified-sender and that was
given the ask's answers, and holds it to the secret rule alone (CheckSecret): a path or an
address is what the operator approves, a secret's shape leaves the mesh nowhere and refuses the
ask as a whole. Everywhere else, and for every condition, the explanation under the content rule
stands. An ask's own message offers Details when the ask says what Details shows
(asks.Ask.Details): the fingerprint and where to read the proposal whole. The expiry line reads
"Expires <when>: <what then>".

telegram: every message is sent in HTML, formatted and never reworded: the title bold, a key
line or a fingerprint in monospace, the chosen line bold, and every '<', '>' and '&' escaped
first. What a tap is compared by stays the plain words, which is what Telegram gives back.

claude-code: an instruction proposal's ask has the same shape — what is asked, the text, who
proposed it and when; the fingerprint and the how-to are the Details answer's; where the sender
is proven, a line with a path or an address is shown, one shaped like a secret is not.

Takes mesh-sdk go at the commit that adds Whole and Details to an ask.
This commit is contained in:
jochen
2026-10-10 15:38:00 +02:00
parent 64feabc6c3
commit 96ba664cb4
20 changed files with 512 additions and 91 deletions
@@ -165,10 +165,14 @@ func (p Proposal) Act(option string) asks.Act {
}
// 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.
// router's warrant to it. Its shape is a settings proposal's (novox/hq issue 383): what is asked, the text, who
// proposed it and when; the fingerprint and where to read all of it are the Details answer's. The explanation
// keeps back every line that might not leave the mesh; the whole (asks.Ask.Whole), shown only where the sender
// is proven, keeps back only a line shaped like a secret or a long unbroken word.
func (p Proposal) Ask() asks.Ask {
verb := map[string]string{ActionAdd: "Add", ActionReplace: "Replace", ActionRemove: "Remove"}[p.Action]
shown, whole := excerpt(p.Text)
shown, whole := excerpt(p.Text, shownLine, "a path, an address or a long word")
wholeShown, _ := excerpt(p.Text, wholeLine, "a long word or a secret's shape")
part := ""
if p.Action != ActionRemove && !whole {
part = " Part shown"
@@ -177,37 +181,46 @@ func (p Proposal) Ask() asks.Ask {
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())
compose := func(text string) string {
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())
}
if p.Action != ActionRemove {
if !whole {
b.WriteString("NOT SHOWN IN FULL here; its first lines:\n\n")
}
b.WriteString(text + "\n\n")
}
fmt.Fprintf(&b, "Proposed by an agent through the claude-code module on %s, at %s UTC.", p.From,
p.At.UTC().Format("15:04 on 2 Jan"))
return b.String()
}
var d strings.Builder
if p.Action == ActionRemove {
fmt.Fprintf(&d, "The approved text it removes has the fingerprint %s.\n", p.Fingerprint())
} else {
fmt.Fprintf(&d, "The text has %d lines, %d bytes, fingerprint %s.\n", p.Lines(), len(p.Text), p.Fingerprint())
d.WriteString("Once approved, every new session there reads it as the mesh's own word until it is removed the same way.\n")
}
fmt.Fprintf(&d, "Read all of it at the controller's terminal: mesh-cli ask claude-code claude_code_proposals "+
"'{\"id\": \"%s\"}'. The fingerprint there must be %s.", 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,
a := asks.Ask{ID: p.ID, Headline: headline, Explanation: compose(shown), 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}
About: "instructions." + p.Scope + "." + strings.Join(p.Nodes, "+") + "." + p.Name,
Details: d.String()}
if p.Action != ActionRemove && wholeShown != shown {
a.Whole = compose(wholeShown)
}
if len(p.Nodes) == 0 {
a.About = "instructions." + p.Scope + "." + p.Name
}
@@ -221,10 +234,10 @@ func (p Proposal) Ask() asks.Ask {
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) {
// excerpt is what of a text the operator's phone is shown, and whether it is the whole text: every line that
// may be shown (shownLine for the explanation, wholeLine for the whole), up to the bounds; a line kept back is
// counted in a marker that says why (hidden), and the whole text is read at the terminal.
func excerpt(text string, may func(string) bool, hidden string) (string, bool) {
lines := strings.Split(strings.TrimRight(text, "\n"), "\n")
whole := len(text) <= WholeTextBytes && len(lines) <= WholeTextLines
limitBytes, limitLines := FirstBytes, FirstLines
@@ -232,11 +245,11 @@ func excerpt(text string) (string, bool) {
limitBytes, limitLines = WholeTextBytes, WholeTextLines
}
var out []string
size, hidden := 0, 0
size, kept := 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
if kept > 0 {
out = append(out, fmt.Sprintf("[%d line(s) not shown here: %s]", kept, hidden))
kept = 0
}
}
for i, line := range lines {
@@ -244,9 +257,9 @@ func excerpt(text string) (string, bool) {
whole = false
break
}
if !shownLine(line) {
if !may(line) {
whole = false
hidden++
kept++
continue
}
flush()
@@ -265,6 +278,19 @@ var (
assigned = regexp.MustCompile(`(?i)(password|passwd|passphrase|secret|token|api[_-]?key|credential|private[_-]?key)\s*[=:]`)
)
// wholeLine says whether a line may be shown where the sender is proven (asks.Ask.Whole): a path, an address or
// a host-like name may, as what the operator approves; a long unbroken word, a hexadecimal run or a value given
// to a secret's name may not — the router refuses the whole ask for a secret's shape, so nothing that might be
// one is risked.
func wholeLine(line string) bool {
for _, re := range []*regexp.Regexp{unbroken, hexRun, assigned} {
if re.MatchString(line) {
return false
}
}
return true
}
// 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.
@@ -153,8 +153,18 @@ func TestAProposalAsksTheOperatorAtTheLevelApproveAndChangesNothing(t *testing.T
if a.Headline != "Add Claude instructions 'handoff-from-g14'?" {
t.Errorf("headline: %q", a.Headline)
}
if !strings.Contains(a.Explanation, "The whole text:\n\n"+strings.TrimRight(handover, "\n")) {
t.Errorf("a short text is shown whole: %q", a.Explanation)
if !strings.Contains(a.Explanation, "?\n\n"+strings.TrimRight(handover, "\n")+"\n\nProposed by an agent through the claude-code module on g14, at ") {
t.Errorf("a short text is shown whole, then who proposed it: %q", a.Explanation)
}
// The shape of a settings proposal (issue 383): no fingerprint and no how-to in the message; both in Details.
if strings.Contains(a.Explanation, "ingerprint") || strings.Contains(a.Explanation, "mesh-cli") || strings.Contains(a.Explanation, "Once approved") {
t.Errorf("the message carries what Details shows: %q", a.Explanation)
}
if !strings.Contains(a.Details, "fingerprint "+fingerprintOf(textDigest(handover))) || !strings.Contains(a.Details, "mesh-cli ask claude-code claude_code_proposals '{\"id\": \""+id+"\"}'") {
t.Errorf("Details: %q", a.Details)
}
if a.Whole != "" {
t.Errorf("a text every line of which is shown has whole words of its own: %q", a.Whole)
}
if len(a.Options) != 2 {
t.Fatalf("options: %+v", a.Options)
@@ -420,9 +430,30 @@ func TestALongTextIsShownInPartAndReadWholeAtTheTerminal(t *testing.T) {
if strings.Contains(a.Explanation, "/etc/claude-code") || !strings.Contains(a.Explanation, "[1 line(s) not shown here") {
t.Errorf("a line with a path was shown: %q", a.Explanation)
}
if !strings.Contains(a.Explanation, "mesh-cli ask claude-code claude_code_proposals") ||
!strings.Contains(a.Explanation, "fingerprint there must be "+out["fingerprint"].(string)) {
t.Errorf("the phone does not say where to read all of it: %q", a.Explanation)
if !strings.Contains(a.Explanation, "NOT SHOWN IN FULL here") || strings.Contains(a.Explanation, "mesh-cli ask claude-code claude_code_proposals") {
t.Errorf("the message does not say it is cut, or says where to read all of it (Details does): %q", a.Explanation)
}
if !strings.Contains(a.Details, "mesh-cli ask claude-code claude_code_proposals") ||
!strings.Contains(a.Details, "fingerprint there must be "+out["fingerprint"].(string)) {
t.Errorf("Details do not say where to read all of it: %q", a.Details)
}
// Where the sender is proven, the line with the path is shown; the cut stands.
if !strings.Contains(a.Whole, "See /etc/claude-code for the managed files.") || !strings.Contains(a.Whole, "NOT SHOWN IN FULL here") ||
strings.Contains(a.Whole, "not shown here") {
t.Errorf("the whole words: %q", a.Whole)
}
// The whole words keep back what might be a secret's shape (Propose refuses such a text before it is asked;
// this holds the ask's own composition to it), and nothing else.
for line, may := range map[string]bool{
"See /etc/claude-code for the managed files.": true,
"Ask on nas.lan, or mail ops@example.com.": true,
"token: 0123456789abcdef0123456789abcdef": false,
"api_key=whatever": false,
"abcdefghijklmnopqrstuvwxyz0123456789": false,
} {
if wholeLine(line) != may {
t.Errorf("wholeLine(%q) = %v", line, !may)
}
}
if len(a.Explanation) > 3000 {
t.Errorf("the explanation is %d bytes", len(a.Explanation))