fix(photon): send markdown messages with URLs as text - #53283
Conversation
teknium1
left a comment
There was a problem hiding this comment.
Thanks for targeting the shared Photon /send path. Current main still routes every markdown payload through spectrumMarkdown(text) at plugins/platforms/photon/sidecar/index.mjs:734; both normal and standalone sends converge there via plugins/platforms/photon/adapter.py:1481-1484 and :1697-1700.
Problems
tests/plugins/platforms/photon/test_url_send_path.py:20-50reads source text and matches regexes. Those tests do not execute the builder-selection behavior, so they can pass despite a runtime regression or fail after a behavior-preserving refactor.
Suggested changes
- Test the selection behavior through an extracted helper or a mocked send boundary: markdown plus an
http(s)URL should choose the text builder, while URL-free markdown should choose the markdown builder.
This is an automated hermes-sweeper review.
|
|
||
|
|
||
| def _source() -> str: | ||
| return SIDECAR.read_text(encoding="utf-8") |
There was a problem hiding this comment.
These tests inspect source text rather than execute builder selection. Please exercise a small extracted selector or mocked send boundary so markdown+URL and URL-free markdown are verified as runtime behavior rather than regex shape.
…ict probe semantics Maintainer rework of #45580 (issue #54036) on top of the contributor's cherry-pick, which targeted spectrum-ts 3.1.0 while main pins 8.0.0: Sidecar (primary detection, new): - stream-staleness.mjs: pure decision rules, executable under node. * classifyProbeRejection: only a not-found-shaped rejection of the synthetic-id read counts as a completed round-trip (ALIVE); any other rejection is INCONCLUSIVE — never alive. The original /probe treated ANY rejection as alive, which was too loose. * shouldProbe: probe only after 10+ min of stream silence (configurable via PHOTON_STREAM_SILENCE_PROBE_MS; <=0 disables) with a cooldown. * isZombieSuspect: zombie only on silence past threshold AND a probe-proven live channel. Silence alone NEVER degrades (shared lines can be quiet for hours); inconclusive probes NEVER degrade (network may be down — the iterator will throw and the re-subscribe loop recovers on its own). - index.mjs: track last inbound-iterator yield (noteInboundYield), run a 30s watchdog tick, and on a confirmed zombie feed markStreamDegraded -> the existing exit-75 restart path. /healthz gains a stream.staleness block (silentForMs, threshold, lastProbeOutcome, zombieSuspected). /probe reworked to strict semantics: 200 only on a proven round-trip, 503 with outcome hung|inconclusive otherwise. Adapter (second layer, reworked): - _probe_once returns tri-state alive|hung|inconclusive; only a hung sidecar HTTP call counts toward the respawn counter — inconclusive resets nothing and triggers nothing. - default probe_interval_seconds 60 -> 600 (conservative; avoid restart storms on quiet lines). - _monitor_sidecar_health surfaces zombieSuspected from /healthz as a warning; the fatal UPSTREAM_STREAM_DEGRADED path is unchanged and fires when the sidecar escalates. Tests: test_zombie_stream_watchdog.py executes the real node decision module and drives the adapter against mocked /healthz responses; test_presence_watchdog.py updated for the tri-state probe. Also adds contributor mappings for nickkarhan (#53283) and vaibhavjnf (#45580).
…ict probe semantics Maintainer rework of #45580 (issue #54036) on top of the contributor's cherry-pick, which targeted spectrum-ts 3.1.0 while main pins 8.0.0: Sidecar (primary detection, new): - stream-staleness.mjs: pure decision rules, executable under node. * classifyProbeRejection: only a not-found-shaped rejection of the synthetic-id read counts as a completed round-trip (ALIVE); any other rejection is INCONCLUSIVE — never alive. The original /probe treated ANY rejection as alive, which was too loose. * shouldProbe: probe only after 10+ min of stream silence (configurable via PHOTON_STREAM_SILENCE_PROBE_MS; <=0 disables) with a cooldown. * isZombieSuspect: zombie only on silence past threshold AND a probe-proven live channel. Silence alone NEVER degrades (shared lines can be quiet for hours); inconclusive probes NEVER degrade (network may be down — the iterator will throw and the re-subscribe loop recovers on its own). - index.mjs: track last inbound-iterator yield (noteInboundYield), run a 30s watchdog tick, and on a confirmed zombie feed markStreamDegraded -> the existing exit-75 restart path. /healthz gains a stream.staleness block (silentForMs, threshold, lastProbeOutcome, zombieSuspected). /probe reworked to strict semantics: 200 only on a proven round-trip, 503 with outcome hung|inconclusive otherwise. Adapter (second layer, reworked): - _probe_once returns tri-state alive|hung|inconclusive; only a hung sidecar HTTP call counts toward the respawn counter — inconclusive resets nothing and triggers nothing. - default probe_interval_seconds 60 -> 600 (conservative; avoid restart storms on quiet lines). - _monitor_sidecar_health surfaces zombieSuspected from /healthz as a warning; the fatal UPSTREAM_STREAM_DEGRADED path is unchanged and fires when the sidecar escalates. Tests: test_zombie_stream_watchdog.py executes the real node decision module and drives the adapter against mocked /healthz responses; test_presence_watchdog.py updated for the tri-state probe. Also adds contributor mappings for nickkarhan (#53283) and vaibhavjnf (#45580).
…ict probe semantics Maintainer rework of #45580 (issue #54036) on top of the contributor's cherry-pick, which targeted spectrum-ts 3.1.0 while main pins 8.0.0: Sidecar (primary detection, new): - stream-staleness.mjs: pure decision rules, executable under node. * classifyProbeRejection: only a not-found-shaped rejection of the synthetic-id read counts as a completed round-trip (ALIVE); any other rejection is INCONCLUSIVE — never alive. The original /probe treated ANY rejection as alive, which was too loose. * shouldProbe: probe only after 10+ min of stream silence (configurable via PHOTON_STREAM_SILENCE_PROBE_MS; <=0 disables) with a cooldown. * isZombieSuspect: zombie only on silence past threshold AND a probe-proven live channel. Silence alone NEVER degrades (shared lines can be quiet for hours); inconclusive probes NEVER degrade (network may be down — the iterator will throw and the re-subscribe loop recovers on its own). - index.mjs: track last inbound-iterator yield (noteInboundYield), run a 30s watchdog tick, and on a confirmed zombie feed markStreamDegraded -> the existing exit-75 restart path. /healthz gains a stream.staleness block (silentForMs, threshold, lastProbeOutcome, zombieSuspected). /probe reworked to strict semantics: 200 only on a proven round-trip, 503 with outcome hung|inconclusive otherwise. Adapter (second layer, reworked): - _probe_once returns tri-state alive|hung|inconclusive; only a hung sidecar HTTP call counts toward the respawn counter — inconclusive resets nothing and triggers nothing. - default probe_interval_seconds 60 -> 600 (conservative; avoid restart storms on quiet lines). - _monitor_sidecar_health surfaces zombieSuspected from /healthz as a warning; the fatal UPSTREAM_STREAM_DEGRADED path is unchanged and fires when the sidecar escalates. Tests: test_zombie_stream_watchdog.py executes the real node decision module and drives the adapter against mocked /healthz responses; test_presence_watchdog.py updated for the tri-state probe. Also adds contributor mappings for nickkarhan (#53283) and vaibhavjnf (#45580).
…ict probe semantics Maintainer rework of #45580 (issue #54036) on top of the contributor's cherry-pick, which targeted spectrum-ts 3.1.0 while main pins 8.0.0: Sidecar (primary detection, new): - stream-staleness.mjs: pure decision rules, executable under node. * classifyProbeRejection: only a not-found-shaped rejection of the synthetic-id read counts as a completed round-trip (ALIVE); any other rejection is INCONCLUSIVE — never alive. The original /probe treated ANY rejection as alive, which was too loose. * shouldProbe: probe only after 10+ min of stream silence (configurable via PHOTON_STREAM_SILENCE_PROBE_MS; <=0 disables) with a cooldown. * isZombieSuspect: zombie only on silence past threshold AND a probe-proven live channel. Silence alone NEVER degrades (shared lines can be quiet for hours); inconclusive probes NEVER degrade (network may be down — the iterator will throw and the re-subscribe loop recovers on its own). - index.mjs: track last inbound-iterator yield (noteInboundYield), run a 30s watchdog tick, and on a confirmed zombie feed markStreamDegraded -> the existing exit-75 restart path. /healthz gains a stream.staleness block (silentForMs, threshold, lastProbeOutcome, zombieSuspected). /probe reworked to strict semantics: 200 only on a proven round-trip, 503 with outcome hung|inconclusive otherwise. Adapter (second layer, reworked): - _probe_once returns tri-state alive|hung|inconclusive; only a hung sidecar HTTP call counts toward the respawn counter — inconclusive resets nothing and triggers nothing. - default probe_interval_seconds 60 -> 600 (conservative; avoid restart storms on quiet lines). - _monitor_sidecar_health surfaces zombieSuspected from /healthz as a warning; the fatal UPSTREAM_STREAM_DEGRADED path is unchanged and fires when the sidecar escalates. Tests: test_zombie_stream_watchdog.py executes the real node decision module and drives the adapter against mocked /healthz responses; test_presence_watchdog.py updated for the tri-state probe. Also adds contributor mappings for nickkarhan (#53283) and vaibhavjnf (#45580).
…ict probe semantics Maintainer rework of #45580 (issue #54036) on top of the contributor's cherry-pick, which targeted spectrum-ts 3.1.0 while main pins 8.0.0: Sidecar (primary detection, new): - stream-staleness.mjs: pure decision rules, executable under node. * classifyProbeRejection: only a not-found-shaped rejection of the synthetic-id read counts as a completed round-trip (ALIVE); any other rejection is INCONCLUSIVE — never alive. The original /probe treated ANY rejection as alive, which was too loose. * shouldProbe: probe only after 10+ min of stream silence (configurable via PHOTON_STREAM_SILENCE_PROBE_MS; <=0 disables) with a cooldown. * isZombieSuspect: zombie only on silence past threshold AND a probe-proven live channel. Silence alone NEVER degrades (shared lines can be quiet for hours); inconclusive probes NEVER degrade (network may be down — the iterator will throw and the re-subscribe loop recovers on its own). - index.mjs: track last inbound-iterator yield (noteInboundYield), run a 30s watchdog tick, and on a confirmed zombie feed markStreamDegraded -> the existing exit-75 restart path. /healthz gains a stream.staleness block (silentForMs, threshold, lastProbeOutcome, zombieSuspected). /probe reworked to strict semantics: 200 only on a proven round-trip, 503 with outcome hung|inconclusive otherwise. Adapter (second layer, reworked): - _probe_once returns tri-state alive|hung|inconclusive; only a hung sidecar HTTP call counts toward the respawn counter — inconclusive resets nothing and triggers nothing. - default probe_interval_seconds 60 -> 600 (conservative; avoid restart storms on quiet lines). - _monitor_sidecar_health surfaces zombieSuspected from /healthz as a warning; the fatal UPSTREAM_STREAM_DEGRADED path is unchanged and fires when the sidecar escalates. Tests: test_zombie_stream_watchdog.py executes the real node decision module and drives the adapter against mocked /healthz responses; test_presence_watchdog.py updated for the tri-state probe. Also adds contributor mappings for nickkarhan (#53283) and vaibhavjnf (#45580).
|
Merged via #73615 — cherry-picked with authorship preserved. The one-line routing fix was sound; the source-grep tests were replaced with behavior tests that execute the real send-format module under node (repo policy: tests must not read source text). |
…ict probe semantics Maintainer rework of NousResearch#45580 (issue NousResearch#54036) on top of the contributor's cherry-pick, which targeted spectrum-ts 3.1.0 while main pins 8.0.0: Sidecar (primary detection, new): - stream-staleness.mjs: pure decision rules, executable under node. * classifyProbeRejection: only a not-found-shaped rejection of the synthetic-id read counts as a completed round-trip (ALIVE); any other rejection is INCONCLUSIVE — never alive. The original /probe treated ANY rejection as alive, which was too loose. * shouldProbe: probe only after 10+ min of stream silence (configurable via PHOTON_STREAM_SILENCE_PROBE_MS; <=0 disables) with a cooldown. * isZombieSuspect: zombie only on silence past threshold AND a probe-proven live channel. Silence alone NEVER degrades (shared lines can be quiet for hours); inconclusive probes NEVER degrade (network may be down — the iterator will throw and the re-subscribe loop recovers on its own). - index.mjs: track last inbound-iterator yield (noteInboundYield), run a 30s watchdog tick, and on a confirmed zombie feed markStreamDegraded -> the existing exit-75 restart path. /healthz gains a stream.staleness block (silentForMs, threshold, lastProbeOutcome, zombieSuspected). /probe reworked to strict semantics: 200 only on a proven round-trip, 503 with outcome hung|inconclusive otherwise. Adapter (second layer, reworked): - _probe_once returns tri-state alive|hung|inconclusive; only a hung sidecar HTTP call counts toward the respawn counter — inconclusive resets nothing and triggers nothing. - default probe_interval_seconds 60 -> 600 (conservative; avoid restart storms on quiet lines). - _monitor_sidecar_health surfaces zombieSuspected from /healthz as a warning; the fatal UPSTREAM_STREAM_DEGRADED path is unchanged and fires when the sidecar escalates. Tests: test_zombie_stream_watchdog.py executes the real node decision module and drives the adapter against mocked /healthz responses; test_presence_watchdog.py updated for the tri-state probe. Also adds contributor mappings for nickkarhan (NousResearch#53283) and vaibhavjnf (NousResearch#45580).
…ict probe semantics Maintainer rework of NousResearch#45580 (issue NousResearch#54036) on top of the contributor's cherry-pick, which targeted spectrum-ts 3.1.0 while main pins 8.0.0: Sidecar (primary detection, new): - stream-staleness.mjs: pure decision rules, executable under node. * classifyProbeRejection: only a not-found-shaped rejection of the synthetic-id read counts as a completed round-trip (ALIVE); any other rejection is INCONCLUSIVE — never alive. The original /probe treated ANY rejection as alive, which was too loose. * shouldProbe: probe only after 10+ min of stream silence (configurable via PHOTON_STREAM_SILENCE_PROBE_MS; <=0 disables) with a cooldown. * isZombieSuspect: zombie only on silence past threshold AND a probe-proven live channel. Silence alone NEVER degrades (shared lines can be quiet for hours); inconclusive probes NEVER degrade (network may be down — the iterator will throw and the re-subscribe loop recovers on its own). - index.mjs: track last inbound-iterator yield (noteInboundYield), run a 30s watchdog tick, and on a confirmed zombie feed markStreamDegraded -> the existing exit-75 restart path. /healthz gains a stream.staleness block (silentForMs, threshold, lastProbeOutcome, zombieSuspected). /probe reworked to strict semantics: 200 only on a proven round-trip, 503 with outcome hung|inconclusive otherwise. Adapter (second layer, reworked): - _probe_once returns tri-state alive|hung|inconclusive; only a hung sidecar HTTP call counts toward the respawn counter — inconclusive resets nothing and triggers nothing. - default probe_interval_seconds 60 -> 600 (conservative; avoid restart storms on quiet lines). - _monitor_sidecar_health surfaces zombieSuspected from /healthz as a warning; the fatal UPSTREAM_STREAM_DEGRADED path is unchanged and fires when the sidecar escalates. Tests: test_zombie_stream_watchdog.py executes the real node decision module and drives the adapter against mocked /healthz responses; test_presence_watchdog.py updated for the tri-state probe. Also adds contributor mappings for nickkarhan (NousResearch#53283) and vaibhavjnf (NousResearch#45580).
Summary
Why
The spectrum-ts markdown builder enables iMessage data detection. In production Photon sends, that path can 500 for messages containing raw PR/preview URLs. iMessage auto-links plain-text URLs, so URL-bearing markdown should use the text builder instead.
Verification
uv run --with pytest pytest tests/plugins/platforms/photon/test_url_send_path.py -q -o 'addopts='→ 3 passednode --check plugins/platforms/photon/sidecar/index.mjsgit diff --check -- plugins/platforms/photon/sidecar/index.mjs tests/plugins/platforms/photon/test_url_send_path.py