refactor(messaging): give the [OPTIONS:] trailer parse one owner - #5716
Conversation
Design Review (Fable 5) — ✅ PASSDesign-level review of The merged diff is confirmed behavior-preserving: each of the six call sites keeps its exact historical policy (streaming channels pass Design-Verdict: PASS Six drifting copies collapse into one parameterized parse, with the single real policy difference kept explicit, a non-vacuous ratchet, and the Slack non-duplicate correctly excluded. [DESIGN-REVIEWED] 4a5d071 |
GPT 5.6 Review — ✅ no blocking findingsGPT 5.6 completed its review of This comment is updated in place on each push. Review detailsNo findings. False positive or not applicable? A repository writer can comment: |
Opus 4.8 Review — ✅ no blocking findingsReviewed Review detailsI've verified the refactor thoroughly. Let me confirm the equivalence of the trickiest case — No findings. [OPUS-REVIEWED] 4a5d071 Verdict parsed from the review's SHA-scoped output markers for commit False positive or not applicable? A repository writer can comment: |
First Principles Review (Fable 5) — 🟡 CONCERNSPremise-level review of First-Principles-Verdict: CONCERNS The consolidation is real and cause-level, but the ratchet's "one owner" claim already has a survivor: What this change shipsIntent: stop six per-channel copies of the
Watch
Subtractions
[FIRST-PRINCIPLES-REVIEWED] 4a5d071 |
Six channels each carried the same parse -- search the shared trailer regex, rstrip the body, split the group on `|`, drop the blanks, decide what to do with an unfinished marker. Three of them (Discord, Telegram, Teams) were identical down to the comment. render_options_as_text could not serve any of them because it returns only the body, and a widget renderer needs the choices too. split_options_trailer(text, *, hide_partial=False) -> (body, choices) is now the only parse. A duplicated parse drifts silently: every copy reads correctly in isolation, so nothing goes red when one stops agreeing. hide_partial is the one thing the channels genuinely disagree about, so it is a parameter rather than a policy baked in. A streaming surface hides an unfinished [OPTIONS fragment because it may be a marker mid-flight and the next frame re-renders anyway; a buffered surface that sends once keeps it, because it cannot tell a fragment from prose and cutting prose is permanent. The default is the non-destructive reading, so a caller that forgets degrades toward a one-frame cosmetic artifact rather than toward deleting text nobody can recover, and every streaming caller states True explicitly. slack/format.py::extract_options is deliberately left alone: it parses the LINE grammar (OPTIONS_RE_LINE, end-of-line, MULTILINE), not the end-of-buffer trailer, so converging it would silently stop matching a marker that ends a line mid-message. Different grammar, not a duplicate. A ratchet greps the tree for a re-derived parse and records both allowed sites, so the next channel cannot quietly add a seventh copy.
b85de7a to
4a5d071
Compare
iamwhatever
left a comment
There was a problem hiding this comment.
Tier 1 auto-approve: refactor (8 files). Criteria: no conflict, no requested changes, security path denylist clean, design-doc gate clean, SAST annotations clean, security checklist all-NO, AI reviewers green. Category: code-move only — six per-channel copies of the trailing [OPTIONS:] parse collapse into one split_options_trailer owner in messaging/renderer.py, with the buffered-vs-streaming partial-marker choice lifted to a hide_partial parameter so each channel keeps the behaviour it already had (verified on the webex and wecom call sites: the extracted helper is byte-for-byte the logic it replaces). Spec files changed as a ride-along (a minority of the diff on both file count and changed lines), not reviewed as a design decision: docs/system-specs/modules/messaging.md.
…#6876) Five symbols with zero references anywhere in the repository, each confirmed superseded or orphaned rather than merely un-called by a static scan. - slack/blocks.py `config_panel` — emits inline `mc_config_{key}` toggles. The router has no `== "mc_config_*"` branch and no `startswith("mc_config")` branch; the live config path is the modal view handler `register_view_handler("mc_config_panel", ...)` reading `values["channels_block"]["mc_config_channels"]`. Unroutable. - slack/blocks.py `agent_buttons` — emits `mc_agent_select_{name}` / `mc_agent_select_off`, but the router matches `action_id == "mc_agent_select"` by exact equality (interactions.py:859) and the live producer is the static_select at events.py:334. Unroutable. - discord/transport_dispatch.py `_receipt_finish_cancelled_locked` — orphaned member of a live family: the sibling `_receipt_flip_locked` IS called (transport_dispatch.py:808), while teams/webex call `self._queue.finish_cancelled_locked(...)` directly rather than wrapping it. - discord/session_resume.py `_history_dashboard_key` — superseded by the shared `messaging/session_resume.py::history_dashboard_key`, which is used at session_resume.py:236 and tested in test/test_teams_sessions.py. - discord/renderer.py `_options` — orphaned by #5716, which gave the `[OPTIONS:]` trailer parse one owner. `_extract_options` remains used at renderer.py:675, 831, 833. No test companions to delete: all five have zero references under test/ too. No behaviour change — none of the deleted code was reachable. Held back deliberately (reported in the PR body): prompt_allowlist, confirmation_dialog, build_subagent_ack_block, send_message_draft, MessageContext, CallableReactionSink. Co-authored-by: Joe Guo <zejiangg@amazon.com>
Follow-up to #5218, which is where I noticed this: six channels each carried the same
[OPTIONS:]parse, and three of them were identical down to the comment.What was duplicated
Every widget-capable renderer had the same eight lines — search the shared trailer regex,
rstripthe body, split the group on|, drop the blanks, then decide what to do with an unfinished marker:discord/renderer.py::_extract_optionstelegram/renderer.py::_extract_optionsteams/renderer.py::_extract_optionswecom/renderer.py::_render_options_as_textformat_overflowwebex/renderer.py::_split_trailermessaging/renderer.py::render_options_as_textrender_options_as_textcould not serve any of the widget channels, which is why they each grew a copy: it returns only the body, and a widget renderer needs the choices too.A parse duplicated per channel drifts per channel, and does it silently — every copy reads correctly in isolation, so nothing goes red when one of them stops agreeing with the others.
The one thing they genuinely disagree about
hide_partialis a parameter, not a policy baked into the helper, because the channels are each right for their own surface:True— a streaming surface. Text is still arriving, so an unfinished[OPTIONSfragment really may be a marker mid-flight. Hiding it keeps reserved protocol off screen, and the next frame re-renders from the full buffer, so nothing is lost.False— a buffered surface that sends once. It cannot tell a live fragment from the assistant's prose, and cutting prose is permanent: a reply endingsee the [OPTIONS sectionhas to keep its last four words.The default is
Falsebecause the two failure directions are not symmetric — a needless keep flashes markup for one frame, a needless cut deletes text nobody can recover. So a caller that forgets degrades toward the cosmetic failure, and every streaming caller saysTrueout loud, which also makes the destructive choice greppable.Webex is the one channel that needs both answers, and it keeps them:
hide_partial=Trueon the status frame, the default on the final answer.What I deliberately did NOT converge
slack/format.py::extract_optionsstays as it is. It looks like a seventh copy and isn't: it parsesOPTIONS_RE_LINE(re.MULTILINE, end-of-line), not the end-of-bufferOPTIONS_RE_TRAILERevery other channel uses. Routing it through this helper would silently stop matching a marker that ends a line mid-message. Different grammar, not a duplicate.I caught this by migrating it first and then checking which regex it aliased — worth stating plainly, because on a skim it is the most obvious candidate in the set.
One behaviour trap in the shared path
render_options_as_textreachesapply_options_capunconditionally rather than behind anif not choicesguard, and that is load-bearing: a matched-but-empty trailer ([OPTIONS: ]) must still have the marker stripped, while a no-match must not touch the text. Those two are indistinguishable from the choice list alone. With no matchsplit_options_trailerreturns the text unchanged and the cap is the identity on an empty list, so one call covers all three cases. My first attempt did add that guard and would have shipped[OPTIONS: ]as visible text; there is now a test for it.Tests
TestSplitOptionsTrailer— both policies, the empty-vs-absent trailer distinction, the quoted-marker-mid-answer case the end anchor exists for, and a test pinning the direction of the default (not just its value) so a future edit cannot flip it to the destructive reading unnoticed.TestOnlyOneTrailerParseExists— a ratchet that greps the tree for a re-derived parse, records both allowed sites (the helper, andconstants.split_trailing_protocol_suffix, which answers a different question) plus the Slack exemption and why, and asserts it is not vacuous so a grep that stops matching cannot make it pass forever.Behaviour-preserving by construction: each call site keeps its own historical answer. 1,312 tests across the six affected channels pass unchanged, and the full suite is 63,232 passed / 0 failed. mypy (1101 files), flake8, isort, the black baseline, docs-lint, brand, harness-parity and scrub all clean.
Net -143/+276 (the growth is tests and the rationale; production code shrinks by ~90 lines).
Note for whoever merges second
#5708 touches the same
from kiro_crew.messaging.renderer import …line inwebex/renderer.py(it addsnew_approval_nonce; this addssplit_options_trailer). Trivial import-line conflict, no semantic overlap.