Cause. Since mesh-catalog #278 (issues 383/639), the Telegram channel sends every message with parse_mode: HTML: the title in <b>, key lines in <code>, the note in <blockquote expandable>, and <, >, & escaped. The real Bot API reads that markup and gives back plain text on a tap (callback_query.message.text), which is what the channel's tap check digests (wordsDigest). The lab's fake Bot API (asks/faketelegram_test.go) kept the text exactly as sent, tags and entities included, and returned that on a tap. The digests differed, so the proof's tap was refused with "That answer was not taken: this message does not show what the mesh put there".
Live impact: none.operator-channel.asks shows every ask since #278 was delivered (2026-10-13 21:16 local) answered on Telegram, for example ADR 0353 at 23:42, 0349, 0350, 0352, 0348 and 0351, all chosen via telegram with user id verified. The telegram-runtime journal on novox has no refused tap since then.
Fix. For sendMessage/editMessageText with parse_mode: HTML, the fake now stores and returns the message as Telegram shows it: tags removed, entities unescaped (shownOfHTML). The channel's code is unchanged.
Tests.TestTheFakePhoneShowsHTMLAsTelegramDoes (new; asks/merge-check.sh now runs it too). The end-to-end proof TestTheOperatorsAnswerEndToEnd, which fails on main against mesh-catalog main (mesh-lab#103's repo-check), is the proof of the fix: this PR's repo-check runs it against the catalogue the mesh runs. go vet and gofmt are clean. I ran the unit test locally; the four-minute end-to-end proof runs on the build seat.
Fixes issue 692.
**Cause.** Since mesh-catalog #278 (issues 383/639), the Telegram channel sends every message with `parse_mode: HTML`: the title in `<b>`, key lines in `<code>`, the note in `<blockquote expandable>`, and `<`, `>`, `&` escaped. The real Bot API reads that markup and gives back plain text on a tap (`callback_query.message.text`), which is what the channel's tap check digests (`wordsDigest`). The lab's fake Bot API (`asks/faketelegram_test.go`) kept the text exactly as sent, tags and entities included, and returned that on a tap. The digests differed, so the proof's tap was refused with "That answer was not taken: this message does not show what the mesh put there".
**Live impact: none.** `operator-channel.asks` shows every ask since #278 was delivered (2026-10-13 21:16 local) answered on Telegram, for example ADR 0353 at 23:42, 0349, 0350, 0352, 0348 and 0351, all `chosen` via telegram with user id verified. The `telegram-runtime` journal on novox has no refused tap since then.
**Fix.** For `sendMessage`/`editMessageText` with `parse_mode: HTML`, the fake now stores and returns the message as Telegram shows it: tags removed, entities unescaped (`shownOfHTML`). The channel's code is unchanged.
**Tests.** `TestTheFakePhoneShowsHTMLAsTelegramDoes` (new; `asks/merge-check.sh` now runs it too). The end-to-end proof `TestTheOperatorsAnswerEndToEnd`, which fails on main against mesh-catalog main (mesh-lab#103's repo-check), is the proof of the fix: this PR's repo-check runs it against the catalogue the mesh runs. go vet and gofmt are clean. I ran the unit test locally; the four-minute end-to-end proof runs on the build seat.
Fixes issue 692.
mesh/repo-check head dcaf843b8a6e merged into trunk 1316a405a630 — pass: THE CHANGE ALTERS ITS OWN CHECK (asks/merge-check.sh): main's version judged it; t…
Since mesh-catalog #278 the channel sends every message in HTML (issues 383, 639). The fake Bot API kept
the text as sent, tags and entities included, and gave that back on a tap; the channel compares a tap by
the plain words it showed, so the proof's tap was refused as "this message does not show what the mesh put
there". Telegram reads the markup and gives the plain words back; the fake now does too.
Deliverynovox/mesh-lab@dcaf843b8a6e — delivered since 2026-10-14T00:45:43Z
Delivery plan — builds nothing: the change touches no module of the mesh's graph
Transitions
2026-10-14 00:37 (new) → proposed (announced): novox/mesh-lab#104's head announced
2026-10-14 00:41 proposed → checked (checked): a verdict names this commit and the trunk's head as its trunk commit
2026-10-14 00:41 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail
2026-10-14 00:45 ready → published (merged): on the trunk its modules follow: merged there, and its walk opened — or nothing for a walk to move
2026-10-14 00:45 published → delivered (done): nothing for a walk to move
The commit's note under refs/notes/mesh-plan keeps every transition: git log --notes=mesh-plan.
<!-- mesh-delivery:view -->
**Delivery** `novox/mesh-lab@dcaf843b8a6e` — **delivered** since 2026-10-14T00:45:43Z
**Delivery plan** — builds nothing: the change touches no module of the mesh's graph
**Transitions**
- 2026-10-14 00:37 (new) → proposed (announced): novox/mesh-lab#104's head announced
- 2026-10-14 00:41 proposed → checked (checked): a verdict names this commit and the trunk's head as its trunk commit
- 2026-10-14 00:41 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail
- 2026-10-14 00:45 ready → published (merged): on the trunk its modules follow: merged there, and its walk opened — or nothing for a walk to move
- 2026-10-14 00:45 published → delivered (done): nothing for a walk to move
The commit's note under `refs/notes/mesh-plan` keeps every transition: `git log --notes=mesh-plan`.
Verdict: APPROVE at dcaf843b — correct, and the proof still proves what it says; merge before #103.
Independent review (spawned by the main session on g14; not the author).
Checks at this head:
mesh/merge-gate: success — "PASS — the change touches no module of the mesh's graph" (build-1791938266390971910, shanks)
mesh/repo-check: success — "PASS — THE CHANGE ALTERS ITS OWN CHECK (asks/merge-check.sh): main's version judged it; ... its merge-check.sh passed, each part in its toolchain". TestTheOperatorsAnswerEndToEnd passed all 9 subtests against mesh-catalog ebf7f377, the catalogue the mesh runs.
mesh/delivery: success — "ready: it delivers once merged"
What I checked:
The conversion against the channel (mesh-catalog modules/telegram/cmd/telegram/render.go, channel.go). The channel sends <b> title, <code> key lines, <blockquote expandable> note, and escapes only & < > (escape()). shownOfHTML strips tags first and unescapes after, so an escaped <b> in the words comes back as the literal <b> and is not stripped, and &lt; comes back as < (one pass). That is what Telegram gives back in message.text for these tags. The <blockquote expandable> attribute is covered by [^>]*. html.UnescapeString covers " and numeric entities too.
The digest still has teeth. Step 3 (approve) now matches, so step 5's tap via tappedOn with "Flip ALL the switches?" is refused by the channel's wordsDigest comparison against a digest that is otherwise known to match; before this change every tap was refused, so the tampered-message refusal proved nothing. Steps 2, 7 and 8 still require the router's refusal, which is reached only after the channel's word check passes.
Locally (niced): gofmt clean, go vet clean, TestTheFakePhoneShowsHTMLAsTelegramDoes passes. Main's merge-check.sh judged this PR, so the new unit test has not yet run on the build seat; it runs from the next PR on.
Non-blocking notes:
The fake is more lenient than Telegram: html.UnescapeString accepts every named entity (Telegram supports only < > & " and numeric ones and rejects others), and a bare < not followed by a letter is left as is where Telegram would refuse the message. The channel never sends either, so the proof is unaffected; a stricter fake could refuse them to catch a future escaping regression in the channel.
The unit test does not exercise ". Adding it to the input would match the entity list the issue names.
Step 5 could also assert the tamper toast ("does not show what the mesh put there") via toldTapper, so the tampered tap is proven refused for that reason and not dropped for another.
Verdict: APPROVE at dcaf843b — correct, and the proof still proves what it says; merge before #103.
Independent review (spawned by the main session on g14; not the author).
Checks at this head:
- mesh/merge-gate: success — "PASS — the change touches no module of the mesh's graph" (build-1791938266390971910, shanks)
- mesh/repo-check: success — "PASS — THE CHANGE ALTERS ITS OWN CHECK (asks/merge-check.sh): main's version judged it; ... its merge-check.sh passed, each part in its toolchain". TestTheOperatorsAnswerEndToEnd passed all 9 subtests against mesh-catalog ebf7f377, the catalogue the mesh runs.
- mesh/delivery: success — "ready: it delivers once merged"
What I checked:
1. The conversion against the channel (mesh-catalog modules/telegram/cmd/telegram/render.go, channel.go). The channel sends `<b>` title, `<code>` key lines, `<blockquote expandable>` note, and escapes only & < > (escape()). shownOfHTML strips tags first and unescapes after, so an escaped `<b>` in the words comes back as the literal `<b>` and is not stripped, and `&lt;` comes back as `<` (one pass). That is what Telegram gives back in message.text for these tags. The `<blockquote expandable>` attribute is covered by `[^>]*`. html.UnescapeString covers " and numeric entities too.
2. The digest still has teeth. Step 3 (approve) now matches, so step 5's tap via tappedOn with "Flip ALL the switches?" is refused by the channel's wordsDigest comparison against a digest that is otherwise known to match; before this change every tap was refused, so the tampered-message refusal proved nothing. Steps 2, 7 and 8 still require the router's refusal, which is reached only after the channel's word check passes.
3. Locally (niced): gofmt clean, go vet clean, TestTheFakePhoneShowsHTMLAsTelegramDoes passes. Main's merge-check.sh judged this PR, so the new unit test has not yet run on the build seat; it runs from the next PR on.
Non-blocking notes:
- The fake is more lenient than Telegram: html.UnescapeString accepts every named entity (Telegram supports only < > & " and numeric ones and rejects others), and a bare `<` not followed by a letter is left as is where Telegram would refuse the message. The channel never sends either, so the proof is unaffected; a stricter fake could refuse them to catch a future escaping regression in the channel.
- The unit test does not exercise ". Adding it to the input would match the entity list the issue names.
- Step 5 could also assert the tamper toast ("does not show what the mesh put there") via toldTapper, so the tampered tap is proven refused for that reason and not dropped for another.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Cause. Since mesh-catalog #278 (issues 383/639), the Telegram channel sends every message with
parse_mode: HTML: the title in<b>, key lines in<code>, the note in<blockquote expandable>, and<,>,&escaped. The real Bot API reads that markup and gives back plain text on a tap (callback_query.message.text), which is what the channel's tap check digests (wordsDigest). The lab's fake Bot API (asks/faketelegram_test.go) kept the text exactly as sent, tags and entities included, and returned that on a tap. The digests differed, so the proof's tap was refused with "That answer was not taken: this message does not show what the mesh put there".Live impact: none.
operator-channel.asksshows every ask since #278 was delivered (2026-10-13 21:16 local) answered on Telegram, for example ADR 0353 at 23:42, 0349, 0350, 0352, 0348 and 0351, allchosenvia telegram with user id verified. Thetelegram-runtimejournal on novox has no refused tap since then.Fix. For
sendMessage/editMessageTextwithparse_mode: HTML, the fake now stores and returns the message as Telegram shows it: tags removed, entities unescaped (shownOfHTML). The channel's code is unchanged.Tests.
TestTheFakePhoneShowsHTMLAsTelegramDoes(new;asks/merge-check.shnow runs it too). The end-to-end proofTestTheOperatorsAnswerEndToEnd, which fails on main against mesh-catalog main (mesh-lab#103's repo-check), is the proof of the fix: this PR's repo-check runs it against the catalogue the mesh runs. go vet and gofmt are clean. I ran the unit test locally; the four-minute end-to-end proof runs on the build seat.Fixes issue 692.
Delivery
novox/mesh-lab@dcaf843b8a6e— delivered since 2026-10-14T00:45:43ZDelivery plan — builds nothing: the change touches no module of the mesh's graph
Transitions
The commit's note under
refs/notes/mesh-plankeeps every transition:git log --notes=mesh-plan.Verdict: APPROVE at
dcaf843b— correct, and the proof still proves what it says; merge before #103.Independent review (spawned by the main session on g14; not the author).
Checks at this head:
What I checked:
<b>title,<code>key lines,<blockquote expandable>note, and escapes only & < > (escape()). shownOfHTML strips tags first and unescapes after, so an escaped<b>in the words comes back as the literal<b>and is not stripped, and&lt;comes back as<(one pass). That is what Telegram gives back in message.text for these tags. The<blockquote expandable>attribute is covered by[^>]*. html.UnescapeString covers " and numeric entities too.Non-blocking notes:
<not followed by a letter is left as is where Telegram would refuse the message. The channel never sends either, so the proof is unaffected; a stricter fake could refuse them to catch a future escaping regression in the channel.