messenger, telegram: a second tap on the recorded answer is said as recorded, and the fallback claims nothing (issue 372 review)
mesh/merge-gate pass: builds messenger, telegram → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
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 review found that a double tap while the phone's spinner turned would be
answered 'That answer was not taken', reading as if the approval failed; it is
now 'You already chose Approve at 13:30: recorded.' and does nothing more. The
fallback toast no longer promises a change that may not come (a stranger's
tap), the wait outlasts the router's 10 s wait for the controller's root word,
the channel's own refusals stay in the chat too, and the store's error words
stay in the journal.
This commit is contained in:
jochen
2026-10-10 14:05:41 +02:00
parent 020c05fe65
commit 35c4de7d08
6 changed files with 75 additions and 12 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ as the operator.
was not taken — and this channel shows that as the tap's own acknowledgement, the short line Telegram
shows over the chat. A word with more to it (a refusal's reason, Details) is also said in the chat, where it
stays. Nothing is acknowledged before the router's word, so the phone never says "recorded" for what was
not; without a word in 8 seconds the tap is acknowledged with words that claim nothing, and the word when it
not; without a word in 13 seconds the tap is acknowledged with words that claim nothing, and the word when it
comes is said in the chat. A secret is never an acknowledgement.
- **A code** (six digits, the one shown on the desk when linking) is asked of the router as a proof, by
request and reply: never in an event, never kept, and deleted from the chat at once.
+1 -1
View File
@@ -271,7 +271,7 @@ func (b *Bot) Edit(chat, id int64, text string, buttons [][2]string) error {
return err
}
// AnswerTap ends the phone's spinner for a tapped button, at once.
// AnswerTap ends the phone's spinner for a tapped button, showing words over the chat when there are any.
func (b *Bot) AnswerTap(id, text string) error {
body := map[string]any{"callback_query_id": id}
if text != "" {
+11 -5
View File
@@ -54,9 +54,11 @@ const (
StandingEvery = 5 * time.Minute
// TapWait is how long a tap's acknowledgement waits for the router's word on it (issue 372): the phone
// shows its spinner meanwhile, and Telegram takes an acknowledgement only for a short while.
TapWait = 8 * time.Second
// It is longer than the router may wait for the controller's word on root before it judges an answer
// (messenger's RootFreeWithin, 10 seconds), so a slow controller still leaves the word to the toast.
TapWait = 13 * time.Second
// TapPending is a tap's acknowledgement when the router said nothing within TapWait: it claims nothing.
TapPending = "Passed to the mesh, which has not decided yet. The question changes when it has."
TapPending = "Passed to the mesh; nothing is confirmed yet."
// ToastLength is what a tap's acknowledgement shows (Telegram's own bound).
ToastLength = 200
// FixedLine is what anything the bot does not take is answered with.
@@ -324,7 +326,9 @@ func (c *Channel) Handle(u Update) {
// the token changed, or one it never showed, answers nothing (the review of 2026-10-09).
if want, err := c.Store.Shown(cb.Message.Chat.ID, cb.Message.MessageID); err != nil || want == "" ||
want != wordsDigest(cb.Message.Text) {
c.answerTap(cb.ID, "That answer was not taken: this message does not show what the mesh put there. Nothing was done.")
words := "That answer was not taken: this message does not show what the mesh put there. Nothing was done."
c.answerTap(cb.ID, words)
c.reply(cb.Message.Chat.ID, words) // a toast goes; the chat keeps why
c.Logf("[telegram] a tap on a message not showing what this channel put there was refused")
return
}
@@ -340,7 +344,9 @@ func (c *Channel) Handle(u Update) {
ch := asks.Choice{ID: id, Ticket: cb.Data, Sender: senderOf(cb.From), At: now}
if err := c.Publish(asks.IntakeSubject(asks.Chosen, Kind), ch, id); err != nil {
c.Logf("[telegram] a tap could not be said to the router: %v", err)
c.settleTap(id, "The mesh could not take this answer now. Nothing was done. Try again in a minute.")
words := "The mesh could not take this answer now. Nothing was done. Try again in a minute."
c.settleTap(id, words)
c.reply(cb.Message.Chat.ID, words)
return
}
c.After(TapWait, func() { c.settleTap(id, TapPending) })
@@ -546,7 +552,7 @@ func (c *Channel) Status() map[string]any {
out["rules"] = []string{
"it shows what the router sends and says what was chosen; whether the sender is the operator is the router's to judge, from the account linked there (novox/hq ADR 0259)",
"a button carries only the router's one-time ticket",
"only a private chat with the account itself counts; a tap is acknowledged with the router's word on it — what was recorded, or why not — or, without one in 8 seconds, with words that claim nothing",
"only a private chat with the account itself counts; a tap is acknowledged with the router's word on it — what was recorded, or why not — or, without one in 13 seconds, with words that claim nothing",
"/start asks to link this account; the code shown on the desk is checked by request and reply, never in an event, and deleted from the chat",
"updates are long-polled: outbound only, no route into the mesh",
"up to 4096 characters arrive whole; longer is cut and the cut said",