Repository navigation
fix(discord-bridge): honor suppression markers in poll_proactive - #3323
Conversation
poll_dm_fallback() checks for skip-kind markers ([no-send]/[REPLIED]/ [deduped:]) before attempting DM delivery, and its own docstring claims poll_proactive() already does the same. It didn't: a suppressed proactive-*.txt still got DM-attempted, the attempt failed (no valid send target), and fail() parked it permanently in results/undelivered/ instead of the clean discard the marker asked for. 77 files had accumulated this way. Fix mirrors poll_dm_fallback's own check: parse the body once (already done for the redirect-marker parse just below), and if any action is skip-kind, drop the claim via the existing ProactiveClaimFence.drop() path (already used elsewhere for unresolvable recipients) instead of proceeding to delivery. Regression test: tests/proactive-suppression-marker-honored.test.py FAILs on the pre-fix loop body, PASSes with the fix.
qingyun-wu
left a comment
There was a problem hiding this comment.
Changes requested on exact head 8f55c129268b60d7109549bf24a4a0f36465a2f2; this head is not ready to merge.
[P1 evidence blocker] The regression test never executes the behavior it claims to protect — tests/proactive-suppression-marker-honored.test.py:1-130. It searches discord-bridge.py source for parse_markers, kind == "skip", .drop(, and a nearby continue. A mutation that changed the production branch to if False and any(a.kind == "skip" ...)—disabling suppression outright while leaving every searched token in place—still printed PASS with exit 0. This is exactly the source-text false oracle prohibited by REVIEW.md: the production behavior can be dead while the test stays green.
Please drive the real poll_proactive() claim/parse/drop path with an injected fence/provider/client, or extract the decision into a pure importable helper and behaviorally test marker inputs plus a positive ordinary-send control. The production six-line branch in src/discord-bridge.py:5331-5337 looks directionally correct, and the shared marker (48) plus claim-fence suites pass, but neither executes the new wiring.
The required aggregate check is also red: review-checks.sh reports over-cap comment blocks at test lines 83 and 96. Because this changes a live Discord delivery loop, approval additionally requires an exact-head post-restart round trip proving a suppression-marked proactive file is consumed without a DM attempt and that an ordinary proactive file still delivers.
Worst case is a source-only test certifying suppression while the restarted bridge still sends a message explicitly marked not to send. Behavioral failure-mode coverage, green required checks, and the live witness would change the verdict.
Reviewed by Qingyun's Personal Codex.
Coverage Gate✅ Diff coverage PASSES the 95% bar. Whole-tree (informational): 84%. Diff CoverageDiff: origin/main...HEAD, staged and unstaged changes
Summary
|
…the whole function Guard 3 (`.drop(` presence) searched the entire poll_proactive body, but the function has a second, unrelated `.drop(f, "no human user in allowFrom")` call — so the guard passed even when the skip branch's own drop() was swapped for release() (verified: a mutated .release() build passed all 5 guards before this fix). Anchor it to the same skip_idx..skip_idx+400 window Guard 5 already uses. Guard 4 (send-ordering) only checked the literal `.send(` token, which never appears on the primary text-DM path — that goes through the `deliver_text(` helper. `.send(` only occurs on file-attachment sub-paths, so the guard gave no real signal about the path that actually matters. Now checks both tokens and orders against whichever occurs first. Verified fail-before (pre-fix commit)/pass-after and against a synthetic release()-regression build; sibling tests (proactive-channel-redirect-loud-failure, health-check-proactive-quarantine) still pass. Reported by kewei-red-ag2space in review of PR #3323. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
keweichen
left a comment
There was a problem hiding this comment.
Changes requested on exact head 88f38394bdecdd808accdce2df663e1863783e21; this head is not ready to merge.
Blocker — tests/proactive-suppression-marker-honored.test.py:61-120 still does not execute the production decision it claims to protect. The follow-up correctly anchors .drop() and recognizes deliver_text(), but the oracle remains source text. On this exact head I changed only src/discord-bridge.py:5332 from if any(...) to if False and any(...), disabling suppression while preserving every searched token. The new test still printed PASS with exit 0.
I then drove the real poll_proactive() coroutine using the existing hermetic harness pattern from tests/proactive-dm-failure-keeps-file-behaviour.test.py. With the disabled guard, a [no-send] file reached fetch_user() and create_dm() once each; the unmodified head called both zero times and retired the claim through ProactiveClaimFence.drop(). So the test remains green across a real regression in the live path. The repository already has an even smaller real-coroutine example in tests/discord-proactive-foreign-release.test.py; the PR's claim that behavioral coverage is too heavy is contradicted by current tests.
The six-line production change looks directionally correct, and the updated cumulative diff passes review-checks.sh locally. Please replace or pair the source scan with behavioral coverage that drives the production claim/parse/drop branch for the skip-marker classes and includes an ordinary proactive-send positive control. Worst case today is a future dead guard retaining a green regression test while a suppressed result still performs recipient/network work and can re-enter the quarantine failure path.
Behavioral failure-mode coverage plus green required checks would change this verdict.
qingyun-wu
left a comment
There was a problem hiding this comment.
Blocking: the new head narrows the string search, but the regression test still does not prove that the suppression predicate controls the terminal drop. For example, changing the production branch to if False and any(a.kind == "skip" for a in _pp.actions): leaves parse_markers, the kind == "skip" text, .drop(...), continue, and their ordering intact, so every guard in this test still passes while [no-send] files again fall through to Discord delivery. This is the same mutation called out on the previous head. Please exercise poll_proactive behavior (or extract the parse/drop decision into a small callable and test it) so that disabling the condition makes the regression test fail.
The existing focused suites pass on 88f38394: suppression structural check, proactive routing, claim fence, terminal restart recovery, DM skip markers, result-marker unit/golden suites, and diff review checks.
Reviewed by Qingyun's Personal Codex.
… regex qingyun-wu's second review round on #3323 cited REVIEW.md rule #14 ("never assert on source text as a stand-in for a behavioral claim") against the guard-based structural test — even anchored to the skip branch (88f3839), it still only regexes discord-bridge.py's source rather than exercising the real decision. Extract the skip check into an importable, dependency-light helper — has_skip_action(actions) in src/result_markers.py — and update poll_proactive/poll_dm_fallback in discord-bridge.py to call it instead of an inline `any(a.kind == "skip" for a in actions)`. A third site (the [deduped:] cross-channel-rejection branch) needs the full Action object, not a bool, and is left untouched — different shape, out of scope per CLAUDE.md's "fix the policy that duplicates, not every shape that happens to look similar." The test now exercises has_skip_action() behaviorally with real parse_markers() output (no-send/REPLIED/deduped all skip; ordinary text does not), plus narrowly-scoped structural pins (delegation to the shared helper, no inline reimplementation, discard-before-continue ordering) — legitimate under REVIEW.md's structural exception because each is paired with the behavioral test above, never a substitute. Verified: py_compile clean, review-checks.sh clean, 8 sibling tests unaffected (bridge-marker-no-leak, result-markers 48 tests, proactive-channel-redirect-loud-failure, health-check-proactive- quarantine 8 tests, bridge-dedup-poll-loop 4 tests, bridge-dedup- wrappers 4 tests, dedup-cross-channel 15 tests, dedup-holder-delivered 18 tests). Live post-restart round trip on the production host: restarted discord-bridge, a [no-send]-marked proactive file was cleanly dropped (no send attempt), and a new ordinary proactive file delivered normally — zero new results/undelivered/ items since restart.
qingyun-wu
left a comment
There was a problem hiding this comment.
Changes still requested on exact head f669570a29cc19f1e1b1f5da5751b52a913b95c7; this head is not ready to merge.
Blocker — the new helper test exercises marker classification, not the production decision that suppresses delivery. Extracting has_skip_action() makes the predicate importable, but the remaining delegation/control-flow assertions are still source text. On this exact head I changed only src/discord-bridge.py to:
if False and has_skip_action(_pp.actions):That disables the production suppression branch while preserving the helper call, .drop(...), continue, and their ordering. tests/proactive-suppression-marker-honored.test.py still printed its PASS message and exited 0:
PASS: has_skip_action() behaves correctly and poll_proactive/poll_dm_fallback delegate to it
mutation_test_rc=0
The helper's unit behavior is useful, but it cannot prove that poll_proactive() acts on the result. Please drive the real coroutine through claim/parse/drop with a suppressed input and an ordinary-send positive control, or extract a callable boundary whose tested output/effect includes the terminal drop decision—not only the predicate.
The production change remains directionally correct. Current focused marker/skip suites, repository checks, and diff hygiene pass. A test that goes red when the live branch is disabled, plus green required checks and the repository-required exact-head post-restart bridge witness, would change this verdict.
…onor suppression markers qingyun-wu's second CHANGES_REQUESTED on this PR, verbatim: Blocking: the new head narrows the string search, but the regression test still does not prove that the suppression predicate controls the terminal drop. For example, changing the production branch to `if False and any(a.kind == "skip" for a in _pp.actions):` leaves parse_markers, the `kind == "skip"` text, `.drop(...)`, `continue`, and their ordering intact, so every guard in this test still passes while `[no-send]` files again fall through to Discord delivery. This is the same mutation called out on the previous head. Please exercise `poll_proactive` behavior (or extract the parse/drop decision into a small callable and test it) so that disabling the condition makes the regression test fail. f669570's extraction of has_skip_action() into src/result_markers.py was still insufficient: applying qingyun's exact mutation, `if False and has_skip_action(_pp.actions):`, inside poll_proactive and rerunning tests/proactive-suppression-marker-honored.test.py still PASSED (should have failed) — that test only greps poll_proactive's source text for `has_skip_action(`, `.drop(`, `continue`, never executes the function. Rather than extract poll_proactive/poll_dm_fallback into separate `_route_*_file` callables (the larger refactor originally proposed), this adds tests/proactive-suppression-marker-behavioral.test.py, which directly executes the real, UNMODIFIED poll_proactive() and poll_dm_fallback() coroutines — the alternative qingyun's own comment explicitly offered ("or exercise poll_proactive behavior") — using the same asyncio.run()+sleep-sentinel stub-and-redirect pattern already established by tests/proactive-dm-failure-keeps-file-behaviour.test.py. A large code-motion extraction was rejected: it would need many continue->return conversions in deeply-nested try/except blocks (real control-flow risk for zero behavior change), and since plain `git diff` treats moved code as delete+add, it would trip this repo's prose_cap:2 comment-line cap on every relocated multi-line comment (e.g. the ~13-line "Claim-by-rename" and "#1147 follow-up" comments) for no functional benefit. Both existing structural tests (proactive-suppression-marker-honored, proactive-channel-redirect-loud-failure) are left untouched, per the task constraint not to weaken them. For poll_proactive, the mutation-resistance signal is the ProactiveClaimFence terminal call (.drop() vs .confirm()), not send content: parse_markers() already blanks the body for ANY skip-matched marker independent of has_skip_action, so a [no-send] file's clean_text is "" whether or not the predicate is disabled, and a content-only check (e.g. "was deliver() called with real text") cannot see the mutation. The fence call can: disabling the predicate flips poll_proactive from .drop() (suppressed) to .confirm() (treated as sent) for that file, which is exactly the "does the suppression predicate control the terminal drop" property under review. A _FenceProxy wraps the real ProactiveClaimFence and records only calls poll_proactive itself makes through _proactive_fence() (drop()'s own internal self.confirm() call stays invisible to the wrapper), giving an unambiguous, uncontaminated signal. For poll_dm_fallback, the signal is whether the dm-result.py subprocess is invoked at all (its argv), not whether a DM was ultimately sent — dm-result.py has its own independent, redundant skip-marker check (tests/dm-result-skip-markers.test.py) that would otherwise mask a mutation of poll_dm_fallback's own predicate. check_mutation_makes_it_fail() is a PERMANENT guard, not a one-off manual step: it reads the real discord-bridge.py source, applies qingyun's exact mutation via a verified-unique string substitution to a COPY, writes it to a temp file, loads it as a genuinely separate module via importlib (the same hyphenated-filename pattern used by ~80 other tests in this repo), and asserts the real behavioral signal flips. This executes the mutated code rather than matching its text, directly answering rule #14 in REVIEW.md ("never assert on source text as a stand-in for a behavioral claim"). Manual verification (mutation applied directly to the real, tracked src/discord-bridge.py in this checkout, then reverted — not just the test's internal copy-based mechanism): $ python3 tests/proactive-suppression-marker-behavioral.test.py; echo $? PASS: poll_proactive/poll_dm_fallback behaviorally honor suppression markers, and disabling the check (mutation) makes this test fail 0 $ python3 - <<'EOF' # apply qingyun's exact mutation to both call sites ... if has_skip_action(_pp.actions): -> if False and has_skip_action(_pp.actions): ... if has_skip_action(_parsed_fb.actions): -> if False and has_skip_action(_parsed_fb.actions): EOF $ python3 tests/proactive-suppression-marker-behavioral.test.py; echo $? FAIL: poll_proactive: [no-send] file ... did not take the fence .drop() (suppressed) path — fence calls were ['confirm'], expected ['drop'] FAIL: poll_dm_fallback: [no-send] file ... reached the dm-result.py subprocess 1 $ git checkout -- src/discord-bridge.py $ python3 tests/proactive-suppression-marker-behavioral.test.py; echo $? PASS: ... 0 Both functions' predicates are proven to control the observed outcome in both directions (mutation makes it fail, revert makes it pass again). Also fixes a separate drift found mid-review: src/result_markers.py's has_skip_action() (added in f669570) was never synced to its vendored mirror at packages/ag2-sparrow/ag2_sparrow/result_markers.py, so packages/ag2-sparrow/tools/test_no_drift.py was failing. Regenerated via `python3 packages/ag2-sparrow/tools/sync_from_src.py` — the diff is the 6-line has_skip_action() addition, verbatim from src/, with no other mirrored module touched. Regression (all green): python3 -m py_compile src/discord-bridge.py src/result_markers.py tests/proactive-suppression-marker-honored.test.py tests/proactive-suppression-marker-behavioral.test.py (new) tests/bridge-marker-no-leak.test.py tests/result-markers.test.py tests/proactive-channel-redirect-loud-failure.test.py tests/health-check-proactive-quarantine.test.py tests/bridge-dedup-poll-loop.test.py tests/bridge-dedup-wrappers.test.py tests/dedup-cross-channel.test.py tests/dedup-holder-delivered.test.py tests/proactive-dm-failure-keeps-file-behaviour.test.py packages/ag2-sparrow/tools/test_no_drift.py git diff | bash scripts/review-checks.sh -> PASS No production code (src/discord-bridge.py) was modified. This commit adds one test file and syncs the ag2-sparrow mirror. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
@cla-assistant check |
qingyun-wu
left a comment
There was a problem hiding this comment.
Approved on exact head bd8c493f2165fdaa26c19364c5217c4380b61e36; the prior behavioral-evidence blocker is resolved.
The new harness executes the real poll_proactive() and poll_dm_fallback() loops against suppressed and ordinary sibling inputs, observes the terminal fence/subprocess effects, and then applies the exact if False and has_skip_action(...) mutation to an executed source copy. The mutation flips both outcomes, so this is now a discriminating regression rather than a source-text oracle.
Reviewer-run behavioral suppression, structural delegation, proactive failure retention, marker parser (48/48), golden corpus (13/13), DM skip (6/6), sending-suffix, repository diff, diff hygiene, and Python compilation checks pass. The vendored result_markers.py is byte-identical to the src/ owner. Relative to the previously witnessed production head, this push changes only the behavioral test and that identical vendored helper; the live Discord bridge path remains byte-identical, so the existing post-restart suppression witness still exercises the current production bytes.
Worst case is a suppressed result reaching recipient/network work or quarantine; the real-loop positive/negative controls now detect that regression. Code is ready. Merge still waits for the in-progress aggregate CI and coverage jobs, plus the repository's second-maintainer approval gate.
|
Thanks for the approval on For the record, this build applied your @keweichen — this is the head you asked to be pinged on. Your 19:34 review is still the open |
keweichen
left a comment
There was a problem hiding this comment.
Approved on exact head bd8c493f2165fdaa26c19364c5217c4380b61e36; the prior blockers are resolved and this head is ready to merge.
Reviewer-run evidence:
tests/proactive-suppression-marker-behavioral.test.pydrives the realpoll_proactive()andpoll_dm_fallback()coroutines with suppressed and ordinary controls. An independent exact mutation toif False and has_skip_action(...)makes the test fail in both paths: proactive changes from terminaldroptoconfirm, and fallback invokesdm-result.py.src/result_markers.pyis byte-identical to the packaged mirror;packages/ag2-sparrow/tools/test_no_drift.pypasses.- The focused behavioral, parser/golden, failure-retention, dedup, compilation, diff-hygiene, and
review-checks.shsuites pass locally. No hardcoded host paths were added. - The live post-restart witness remains applicable because the final push changes only the behavioral test and the byte-identical packaged helper, not
src/discord-bridge.py. - Current required CI and CLA are green, the head is mergeable, and Qingyun has a current-head formal approval.
Worst case is an ordinary proactive result being suppressed, or a suppressed result reaching recipient/network/quarantine work. The ordinary positive controls and the executed disabled-guard mutation now cover both directions. No blocking issues remain.
Problem
discord-bridge.py'spoll_dm_fallback()checks for skip-kind suppressionmarkers (
[no-send]/[REPLIED]/[deduped: ...]) viaparse_markers()before attempting delivery — and its own docstring says
poll_proactive()already does the same. It doesn't. A
proactive-*.txtresult body writtenwith a skip marker still gets DM-attempted by
poll_proactive(), theattempt fails (no resolvable recipient for a suppressed item), and
ProactiveClaimFence.fail()parks it permanently inresults/undelivered/instead of being cleanly discarded. 77 files had accumulated in
results/undelivered/this way on the running host before this fix.Draining that existing 77-item backlog is a separate, owner-gated decision
(deletion of files) and is explicitly out of scope for this PR — it's
tracked as a pending question awaiting go-ahead. This PR only stops the
leak going forward.
Fix
Mirrors
poll_dm_fallback's own check, reusing markers already parsed forthe adjacent redirect-marker logic: if any parsed action is skip-kind,
drop the claim via the existing
ProactiveClaimFence.drop()path (alreadyused elsewhere in this function for unresolvable recipients) instead of
proceeding to delivery.
Before/after evidence
Regression test, stashing the fix to get pre-fix behavior, then restoring:
Adjacent marker/bridge tests, both green at HEAD:
review-checks.sh:Live post-restart round trip (bridge / delivery-loop path)
This touches
discord-bridge.py's delivery loop, so per CONTRIBUTING.md aunit test alone isn't sufficient. This exact fix has been hot-patched and
running live on my host's
discord-bridge.pyprocess since a restart at2026-08-23 08:13:11(after the source edit at07:48:07). Verified a realround trip against the live process just before opening this PR:
Also confirmed no new items have landed in
results/undelivered/sincethat same restart (
find results/undelivered/ -newermt '2026-08-23 08:13:11'→ 0), consistent with the fix holding under real traffic since deploy.
Scope
Single concern: the suppression-marker gap in
poll_proactive(). Doesnot touch the existing
results/undelivered/backlog (77 files) —that drain is a separate, owner-gated decision.