Skip to content

fix(discord-bridge): honor suppression markers in poll_proactive - #3323

Merged
qingyun-wu merged 4 commits into
mainfrom
fix/proactive-quarantine-suppression-markers
Aug 23, 2026
Merged

qingyun-wu merged 4 commits into
mainfrom
fix/proactive-quarantine-suppression-markers

Conversation

@sonichi

@sonichi sonichi commented Aug 23, 2026

Copy link
Copy Markdown
Owner

Problem

discord-bridge.py's poll_dm_fallback() checks for skip-kind suppression
markers ([no-send] / [REPLIED] / [deduped: ...]) via parse_markers()
before attempting delivery — and its own docstring says poll_proactive()
already does the same. It doesn't. A proactive-*.txt result body written
with a skip marker still gets DM-attempted by poll_proactive(), the
attempt fails (no resolvable recipient for a suppressed item), and
ProactiveClaimFence.fail() parks it permanently in results/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 for
the adjacent redirect-marker logic: if any parsed action is skip-kind,
drop the claim via the existing ProactiveClaimFence.drop() path (already
used 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:

$ git stash push -- src/discord-bridge.py
$ python3 tests/proactive-suppression-marker-honored.test.py; echo "exit: $?"
FAIL: poll_proactive doesn't check for a skip-kind marker action —
[no-send]/[REPLIED]/[deduped:] bodies would still be DM-attempted
exit: 1

$ git stash pop
$ python3 tests/proactive-suppression-marker-honored.test.py; echo "exit: $?"
PASS: poll_proactive honors suppression markers (no-send/REPLIED/deduped)
exit: 0

Adjacent marker/bridge tests, both green at HEAD:

$ python3 tests/bridge-marker-no-leak.test.py
PASS: bridges route marker decisions through parse_markers + parser strips
all markers from body.

$ python3 tests/sending-suffix-is-proactive-only.test.py
sending-suffix-is-proactive-only: 6/6 passed

review-checks.sh:

$ git diff origin/main -- src/discord-bridge.py | bash scripts/review-checks.sh
prose-cap: PASS (comment blocks within cap 2, exts .py)
review-checks: PASS (hardcoded-paths + root-artifacts + prose-cap clean)

Live post-restart round trip (bridge / delivery-loop path)

This touches discord-bridge.py's delivery loop, so per CONTRIBUTING.md a
unit test alone isn't sufficient. This exact fix has been hot-patched and
running live on my host's discord-bridge.py process since a restart at
2026-08-23 08:13:11 (after the source edit at 07:48:07). Verified a real
round trip against the live process just before opening this PR:

$ echo -e '[no-send]\nLive round-trip test ...' > \
    "$WORKSPACE/results/proactive-livetest-quarantine-fix-1787512285.txt"

# ~8s later, from the live discord-bridge.log:
  [proactive] skipped (suppression marker): proactive-livetest-quarantine-fix-1787512285.sending
  [fence] dropping proactive-livetest-quarantine-fix-1787512285.sending: suppression marker (no-send/deduped/REPLIED)

# file is gone from results/, and did NOT land in results/undelivered/:
$ ls "$WORKSPACE/results/proactive-livetest-quarantine-fix-"*
no matches found
$ ls "$WORKSPACE/results/undelivered/"*livetest*
no matches found

Also confirmed no new items have landed in results/undelivered/ since
that 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(). Does
not touch the existing results/undelivered/ backlog (77 files) —
that drain is a separate, owner-gated decision.

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 qingyun-wu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@github-actions

github-actions Bot commented Aug 23, 2026 •

Copy link
Copy Markdown
Contributor

Coverage Gate

✅ Diff coverage PASSES the 95% bar. Whole-tree (informational): 84%.

Diff Coverage

Diff: origin/main...HEAD, staged and unstaged changes

  • src/discord-bridge.py (100%)
  • src/result_markers.py (100%)

Summary

  • Total: 8 lines
  • Missing: 0 lines
  • Coverage: 100%

…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 keweichen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 qingyun-wu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 qingyun-wu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@github-actions

Copy link
Copy Markdown
Contributor

@cla-assistant check

@qingyun-wu qingyun-wu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@sonichi

sonichi commented Aug 23, 2026

Copy link
Copy Markdown
Owner Author

Thanks for the approval on bd8c493f — confirmed independently on this end too (all required CI green: python standalone tests, typecheck + node & shell tests, diff coverage >= 95% (python), test_no_drift.py/mirror-sync, ruff, shellcheck, all refuse * guards).

For the record, this build applied your bd8c493f fix to the live production tree and additionally caught a second, independent staleness gap the diff itself didn't touch: the live tree's copy of tests/proactive-suppression-marker-honored.test.py predated branch commits 88f38394/f669570a and still asserted the old literal kind == "skip" string-check Guard 2 instead of the current has_skip_action(...) delegation check — it was failing against the live discord-bridge.py, which already uses the newer delegation style. Fixed by syncing the branch-HEAD version of that test into the live tree; confirmed passing there now. No restart was needed for bd8c493f itself (test-file + mirror-sync only; grepped src/*.py and skills/*/scripts/*.py — no ag2_sparrow.result_markers-style import site exists, only discord-bridge.py's bare from result_markers import ... has_skip_action resolving directly to src/result_markers.py).

@keweichen — this is the head you asked to be pinged on. Your 19:34 review is still the open CHANGES_REQUESTED blocking merge (the "second-maintainer approval gate" qingyun-wu's approval mentions) — could you take another look at bd8c493f? Both of your findings from the Discord follow-up (CI drift via the ungenerated mirror, and the test not exercising the real poll_proactive/poll_dm_fallback skip path) are addressed: the mirror is byte-identical again, and the new behavioral test drives the real coroutines against mocked-discord.py with a literal if False and has_skip_action(...) mutation proof in both directions for both functions.

@keweichen keweichen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.py drives the real poll_proactive() and poll_dm_fallback() coroutines with suppressed and ordinary controls. An independent exact mutation to if False and has_skip_action(...) makes the test fail in both paths: proactive changes from terminal drop to confirm, and fallback invokes dm-result.py.
  • src/result_markers.py is byte-identical to the packaged mirror; packages/ag2-sparrow/tools/test_no_drift.py passes.
  • The focused behavioral, parser/golden, failure-retention, dedup, compilation, diff-hygiene, and review-checks.sh suites 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.

@qingyun-wu
qingyun-wu merged commit f8d13a7 into main Aug 23, 2026
22 checks passed
@qingyun-wu
qingyun-wu deleted the fix/proactive-quarantine-suppression-markers branch August 23, 2026 21:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants