@@ -690,6 +690,89 @@ def test_build_pr_fingerprint_self_login_excluded_even_when_authorized():
690690 assert fingerprint .human_events == (HumanEvent (id = 1 , body = "authorized other" ),)
691691
692692
693+ def test_build_pr_fingerprint_excludes_bot_command_bodies_across_all_sources ():
694+ pr = _FakePR (
695+ issue_comments = [
696+ _FakeComment (1 , _FakeActor ("alice" , "User" ), "please fix" ),
697+ _FakeComment (2 , _FakeActor ("alice" , "User" ), "@pytorchbot merge" ),
698+ ],
699+ review_comments = [
700+ _FakeComment (3 , _FakeActor ("bob" , "User" ), "nit: rename this" ),
701+ _FakeComment (4 , _FakeActor ("bob" , "User" ), "@pytorchmergebot rebase" ),
702+ ],
703+ reviews = [
704+ _FakeReview (5 , _FakeActor ("carol" , "User" ), "lgtm" ),
705+ _FakeReview (6 , _FakeActor ("carol" , "User" ), "@claude take a look" ),
706+ ],
707+ )
708+
709+ fingerprint = _build_fp (pr )
710+
711+ # One bot-command body per source is dropped whole; the three normal human comments survive, so a
712+ # trusted author's '@pytorchbot merge' never perturbs the digest.
713+ assert fingerprint .human_events == (
714+ HumanEvent (id = 1 , body = "please fix" ),
715+ HumanEvent (id = 3 , body = "nit: rename this" ),
716+ HumanEvent (id = 5 , body = "lgtm" ),
717+ )
718+
719+
720+ def test_build_pr_fingerprint_bot_command_excluded_on_top_of_authorized_filter ():
721+ pr = _FakePR (
722+ issue_comments = [
723+ _FakeComment (1 , _FakeActor ("alice" , "User" ), "genuine review note" ),
724+ _FakeComment (2 , _FakeActor ("alice" , "User" ), "@greenlight status" ),
725+ ],
726+ review_comments = [],
727+ reviews = [],
728+ )
729+
730+ # alice is authorized, so the author filter keeps both comments; the bot-command body is still
731+ # dropped, proving is_bot_command runs in addition to (not instead of) the author filter.
732+ fingerprint = _build_fp (pr , authorized_logins = frozenset ({"alice" }))
733+
734+ assert fingerprint .human_events == (HumanEvent (id = 1 , body = "genuine review note" ),)
735+
736+
737+ def test_build_pr_fingerprint_bot_command_does_not_change_eval_hash ():
738+ authorized = frozenset ({"alice" })
739+ base = _FakePR (
740+ issue_comments = [
741+ _FakeComment (1 , _FakeActor ("alice" , "User" ), "please fix" ),
742+ _FakeComment (2 , _FakeActor ("alice" , "User" ), "one more thing" ),
743+ ],
744+ review_comments = [],
745+ reviews = [],
746+ )
747+ base_hash = compute_pr_hash (_build_fp (base , authorized_logins = authorized ))
748+
749+ with_bot_command = _FakePR (
750+ issue_comments = [
751+ _FakeComment (1 , _FakeActor ("alice" , "User" ), "please fix" ),
752+ _FakeComment (2 , _FakeActor ("alice" , "User" ), "one more thing" ),
753+ _FakeComment (3 , _FakeActor ("alice" , "User" ), "@pytorchbot merge" ),
754+ ],
755+ review_comments = [],
756+ reviews = [],
757+ )
758+ # A '@pytorchbot merge' from the same authorized author is dropped whole, so the eval_hash is
759+ # unchanged and the scan does not re-dispatch a review over it.
760+ assert compute_pr_hash (_build_fp (with_bot_command , authorized_logins = authorized )) == base_hash
761+
762+ with_normal_comment = _FakePR (
763+ issue_comments = [
764+ _FakeComment (1 , _FakeActor ("alice" , "User" ), "please fix" ),
765+ _FakeComment (2 , _FakeActor ("alice" , "User" ), "one more thing" ),
766+ _FakeComment (3 , _FakeActor ("alice" , "User" ), "actually, rename this" ),
767+ ],
768+ review_comments = [],
769+ reviews = [],
770+ )
771+ # Control: swapping only that one body for a non-command comment moves the eval_hash, proving
772+ # the equality above is not vacuous.
773+ assert compute_pr_hash (_build_fp (with_normal_comment , authorized_logins = authorized )) != base_hash
774+
775+
693776def test_fingerprint_pr_threads_authorized_logins ():
694777 pr = _FakePR (
695778 issue_comments = [
@@ -735,12 +818,12 @@ def _golden_pr() -> _FakePR:
735818 )
736819
737820
738- def test_build_pr_fingerprint_golden_hash_scheme_v5 ():
739- """End-to-end golden: build_pr_fingerprint -> compute_pr_hash pins the scheme-v5 digest.
821+ def test_build_pr_fingerprint_golden_hash_scheme_v6 ():
822+ """End-to-end golden: build_pr_fingerprint -> compute_pr_hash pins the current-scheme (v6) digest.
740823
741- Guards against drift in is_bot / BOT_LOGINS / self_login exclusion and the
742- PR-field mapping. Uses the default scheme_version (5 ); a future
743- HASH_SCHEME_VERSION bump regenerates this literal.
824+ Guards against drift in is_bot / BOT_LOGINS / is_bot_command / self_login exclusion and the
825+ PR-field mapping. Uses the default scheme_version (6 ); a future HASH_SCHEME_VERSION bump
826+ regenerates this literal.
744827 """
745828 pr = _golden_pr ()
746829 fingerprint = _build_fp (pr , self_login = "greenlight" )
@@ -749,7 +832,7 @@ def test_build_pr_fingerprint_golden_hash_scheme_v5():
749832 HumanEvent (id = 1 , body = "please fix" ),
750833 HumanEvent (id = 6 , body = "lgtm" ),
751834 )
752- assert compute_pr_hash (fingerprint ) == "9d0506bd3e887a00d858f49e653cab9f913f185674a475582706cc83fcae70d4 "
835+ assert compute_pr_hash (fingerprint ) == "51a4e58faef3da88e3fe8506437a21d0d7e8060256a2428c410d37a2e8122926 "
753836
754837
755838@pytest .mark .parametrize ("null_login" , [None , "" ])
@@ -904,18 +987,19 @@ def test_fingerprint_pr_allow_skip_no_decision_builds_fingerprint_fetching_revie
904987 assert "get_review_comments" in pr .calls
905988
906989
907- def test_fingerprint_pr_hash_is_byte_identical_to_pre_skip_scheme ():
908- """Characterization: a non-skipped PR's eval_hash equals the pre-refactor digest .
990+ def test_fingerprint_pr_golden_hash_scheme_v6 ():
991+ """End-to-end golden for the current scheme (v6) through the fingerprint_pr entry point .
909992
910- Pins the digest produced before reviews were threaded through build_pr_fingerprint,
911- proving the fingerprint payload is unchanged for PRs that are still fingerprinted.
993+ fingerprint_pr leaves self_login unset, so (unlike test_build_pr_fingerprint_golden_hash_scheme_v6)
994+ the greenlight self-note survives and this pins a distinct digest. A HASH_SCHEME_VERSION bump
995+ regenerates this literal.
912996 """
913997 pr = _golden_pr ()
914998 client = _FakeScanClient (_FakeScanRepo (pr ))
915999
9161000 result = github_client .fingerprint_pr (client , "pytorch/pytorch" , 9 )
9171001
918- assert result == ("head-sha" , "bcf6a1d21566873cd1da85fa724bd1e9996e213b869ed28544751c7e4e06a0f4 " )
1002+ assert result == ("head-sha" , "fed90fac1ad308b7149a622b6d81da3f309ab933d12c85f427d300534f700408 " )
9191003
9201004
9211005class _FakeVerdictReview :
0 commit comments