Skip to content

Guard PR bodies against premature payment-status wording (#1107)#1177

Open
yanyishuai wants to merge 1 commit into
ramimbo:mainfrom
yanyishuai:fix/issue-1107-pr-payment-language
Open

Guard PR bodies against premature payment-status wording (#1107)#1177
yanyishuai wants to merge 1 commit into
ramimbo:mainfrom
yanyishuai:fix/issue-1107-pr-payment-language

Conversation

@yanyishuai

@yanyishuai yanyishuai commented Jun 29, 2026

Copy link
Copy Markdown

Summary

Adds a bounded public payment/status-language guard for MergeWork submission drafts and PR bodies.

  • New scripts/public_payment_language.py helper detects deprecated Payout boundary headings, legacy "not confirmed or withdrawable" phrasing, and reserved paid/settled/received/withdrawable claim assertions.
  • New scripts/check_pr_payment_language.py CLI for standalone PR-body or text-file checks.
  • scripts/submission_quality_gate.py now fails when premature payment/status wording is present and suggests neutral Submission status language instead.

Why

Docs already reserve payment/status words for proof-backed states, but the quality gate did not enforce that rule. This closes the manual review gap for open PRs like #1072/#1075/#1077.

Tests

python -m pytest tests/test_public_payment_language.py -q
python -m pytest tests/test_submission_quality_gate.py -q -k payment

Wallet

Do4v7foHJvRJLpRRoGaVPWX6DDEjX3yTK7J91gpwUQpE

Closes #1107

Summary by CodeRabbit

  • New Features

    • Added checks that detect and flag premature payment/status wording in submissions and pull request text.
    • Added clearer reporting, including a concise summary of any flagged language and a suggested replacement.
  • Bug Fixes

    • Quality checks now fail submissions that use reserved payment/status claims, helping keep review text consistent and neutral.
    • Clean submissions using acceptable lifecycle wording now pass these checks.

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@yanyishuai, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 56 minutes and 11 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 8875b079-5fbf-4673-9095-711ac7be40d3

📥 Commits

Reviewing files that changed from the base of the PR and between fbde72f and 6d89af2.

📒 Files selected for processing (5)
  • scripts/check_pr_payment_language.py
  • scripts/public_payment_language.py
  • scripts/submission_quality_gate.py
  • tests/test_public_payment_language.py
  • tests/test_submission_quality_gate.py
📝 Walkthrough

Walkthrough

Adds scripts/public_payment_language.py with regex-based detection of premature payment/status wording. Wires find_payment_language_violations into evaluate_submission in submission_quality_gate.py as a new payment_status_language check. Adds scripts/check_pr_payment_language.py, a CLI for CI that fetches PR bodies from GitHub and fails on violations. Covers all three with unit tests.

Changes

Payment Language Guard

Layer / File(s) Summary
Violation detector: patterns, allowlist, and formatter
scripts/public_payment_language.py, tests/test_public_payment_language.py
Compiled regexes detect deprecated payout-boundary headings, legacy withdrawable phrasing, and reserved payment assertions. An allowlist skips acceptable lifecycle wording. find_payment_language_violations returns deduplicated, ordered violations; format_violation_report renders a human-readable report. Five unit tests cover flagged and allowed cases.
Submission quality gate integration
scripts/submission_quality_gate.py, tests/test_submission_quality_gate.py
Imports the detector and SUGGESTED_REPLACEMENT; inserts a payment_status_language check into evaluate_submission after the evidence block, failing with a preview of up to two violations plus a "+N more" suffix, or passing with a clean message. Three integration tests assert fail/pass outcomes.
CI script for PR body checks
scripts/check_pr_payment_language.py
Standalone CLI reads text from --text-file or fetches a GitHub PR via REST API using GH_TOKEN/GITHUB_TOKEN. Outputs JSON or formatted text; exits 1 only when --fail-on-issues is set and violations exist.

Possibly related issues

🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Title check ✅ Passed The title is short, concrete, and accurately names the changed surface: PR-body payment-status wording checks.
Description check ✅ Passed The description covers Summary, Why, Tests, and issue closure; it is mostly complete though the Evidence and MRWK template sections are missing.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Mergework Public Artifact Hygiene ✅ Passed Only adds payment/status-language checks and tests; no README/docs/PR-comment text with investment, price, cash-out, or private-security claims.
Bounty Pr Focus ✅ Passed PR text cites issue #1107, not Bounty/Refs; changed files are narrowly scoped to payment/status-language checks and tests.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4ac2489e-7f1f-4882-9747-4afb086748a8

📥 Commits

Reviewing files that changed from the base of the PR and between 3bc87d2 and fbde72f.

📒 Files selected for processing (5)
  • scripts/check_pr_payment_language.py
  • scripts/public_payment_language.py
  • scripts/submission_quality_gate.py
  • tests/test_public_payment_language.py
  • tests/test_submission_quality_gate.py

Comment on lines +68 to +69
pr = _load_pull_request(args.repo, args.pr)
text = "\n".join(str(pr.get(key) or "") for key in ("title", "body"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Check only the PR body in GitHub mode.

This CLI is described as a PR-body checker, but this path concatenates the title and body. A compliant PR body will still fail if the title says something like ban "paid" wording, which changes the gate's contract from the one described in this PR.

Suggested fix
-        text = "\n".join(str(pr.get(key) or "") for key in ("title", "body"))
+        text = str(pr.get("body") or "")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pr = _load_pull_request(args.repo, args.pr)
text = "\n".join(str(pr.get(key) or "") for key in ("title", "body"))
pr = _load_pull_request(args.repo, args.pr)
text = str(pr.get("body") or "")

Comment on lines +52 to +79
def find_payment_language_violations(text: str) -> list[str]:
"""Return human-readable violations for premature payment/status wording."""
if not text or not text.strip():
return []

violations: list[str] = []
if _PAYOUT_BOUNDARY_RE.search(text):
violations.append(
"deprecated 'Payout boundary' heading found; prefer neutral 'Submission status' wording"
)
if _LEGACY_WITHDRAWABLE_RE.search(text):
violations.append(
"legacy 'not confirmed or withdrawable' phrasing found; use neutral submission status language"
)

for line in text.splitlines():
stripped = line.strip()
if not stripped or stripped.startswith("#"):
continue
if _line_is_allowlisted(line):
continue
for pattern in _RESERVED_STATUS_ASSERTION_RES:
if pattern.search(line):
violations.append(
"reserved payment/status wording used as a claim assertion: "
f"{stripped[:120]}"
)
break

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Run all payment-language rules through the same per-line classifier.

The initial whole-text search bypasses _line_is_allowlisted, so instructional lines like Do not write "not confirmed or withdrawable" still fail. Then stripped.startswith("#") skips headings entirely, so ## Claim is paid or ## Withdrawable status never reaches the reserved-assertion regexes. That gives this detector both false positives and false negatives.

Suggested fix
 def find_payment_language_violations(text: str) -> list[str]:
     """Return human-readable violations for premature payment/status wording."""
     if not text or not text.strip():
         return []

     violations: list[str] = []
-    if _PAYOUT_BOUNDARY_RE.search(text):
-        violations.append(
-            "deprecated 'Payout boundary' heading found; prefer neutral 'Submission status' wording"
-        )
-    if _LEGACY_WITHDRAWABLE_RE.search(text):
-        violations.append(
-            "legacy 'not confirmed or withdrawable' phrasing found; use neutral submission status language"
-        )

     for line in text.splitlines():
         stripped = line.strip()
-        if not stripped or stripped.startswith("#"):
+        if not stripped:
             continue
         if _line_is_allowlisted(line):
             continue
+        if _PAYOUT_BOUNDARY_RE.search(line):
+            violations.append(
+                "deprecated 'Payout boundary' heading found; prefer neutral 'Submission status' wording"
+            )
+        if _LEGACY_WITHDRAWABLE_RE.search(line):
+            violations.append(
+                "legacy 'not confirmed or withdrawable' phrasing found; use neutral submission status language"
+            )
         for pattern in _RESERVED_STATUS_ASSERTION_RES:
             if pattern.search(line):
                 violations.append(
                     "reserved payment/status wording used as a claim assertion: "
                     f"{stripped[:120]}"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def find_payment_language_violations(text: str) -> list[str]:
"""Return human-readable violations for premature payment/status wording."""
if not text or not text.strip():
return []
violations: list[str] = []
if _PAYOUT_BOUNDARY_RE.search(text):
violations.append(
"deprecated 'Payout boundary' heading found; prefer neutral 'Submission status' wording"
)
if _LEGACY_WITHDRAWABLE_RE.search(text):
violations.append(
"legacy 'not confirmed or withdrawable' phrasing found; use neutral submission status language"
)
for line in text.splitlines():
stripped = line.strip()
if not stripped or stripped.startswith("#"):
continue
if _line_is_allowlisted(line):
continue
for pattern in _RESERVED_STATUS_ASSERTION_RES:
if pattern.search(line):
violations.append(
"reserved payment/status wording used as a claim assertion: "
f"{stripped[:120]}"
)
break
def find_payment_language_violations(text: str) -> list[str]:
"""Return human-readable violations for premature payment/status wording."""
if not text or not text.strip():
return []
violations: list[str] = []
for line in text.splitlines():
stripped = line.strip()
if not stripped:
continue
if _line_is_allowlisted(line):
continue
if _PAYOUT_BOUNDARY_RE.search(line):
violations.append(
"deprecated 'Payout boundary' heading found; prefer neutral 'Submission status' wording"
)
if _LEGACY_WITHDRAWABLE_RE.search(line):
violations.append(
"legacy 'not confirmed or withdrawable' phrasing found; use neutral submission status language"
)
for pattern in _RESERVED_STATUS_ASSERTION_RES:
if pattern.search(line):
violations.append(
"reserved payment/status wording used as a claim assertion: "
f"{stripped[:120]}"
)
break

Comment thread tests/test_public_payment_language.py

@qingfeng312 qingfeng312 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CI is currently failing on the new submission-quality gate test.

The implementation adds the payment_status_language check to evaluate_submission, so the result now contains one additional check entry. tests/test_submission_quality_gate.py::test_submission_quality_gate_passes_open_bounty_with_evidence still asserts the old exact set of six checks and fails because the left side includes {"payment_status_language": "pass"}.

Please update the expected check set in that test, and any related fixed-shape assertions, so the new payment-status-language guard is covered by the passing path.

Evidence: GitHub Actions run 28343359265, job 83961953872, 1 failed, 912 passed.

@yanyishuai yanyishuai force-pushed the fix/issue-1107-pr-payment-language branch from fbde72f to 6d89af2 Compare June 29, 2026 04:44
@yanyishuai

Copy link
Copy Markdown
Author

@qingfeng312 Addressed the failing quality-gate test.

  • Updated test_submission_quality_gate_passes_open_bounty_with_evidence to include the new payment_status_language: pass check
  • Ruff format/lint clean on the payment-language modules

Latest head: 6d89af2bd6df. CI: pass. Please re-review.

@yanyishuai

Copy link
Copy Markdown
Author

@qingfeng312 CI is green on the latest head (6d89af2bd6df) for #1107. Could you take another look when you have a moment?

1 similar comment
@yanyishuai

Copy link
Copy Markdown
Author

@qingfeng312 CI is green on the latest head (6d89af2bd6df) for #1107. Could you take another look when you have a moment?

@qingfeng312 qingfeng312 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Follow-up on current head 6d89af2bd6df91cadfbf427a2e0987ae74ec6440.

The earlier quality-gate expectation blocker has been resolved. I rechecked the payment/status-language detector, the standalone PR-body checker, the submission quality gate integration, and the associated tests.

The updated test coverage now includes the new payment_status_language pass case, and the guard remains focused on reserved payment/status wording while allowing neutral lifecycle language.

Validation checked: GitHub CI Quality, readiness, docs, and image checks passed on run 28349183723; CodeRabbit status is success on this head. I did not find a remaining blocker for the scoped #1107 change.

@yanyishuai

Copy link
Copy Markdown
Author

@qingfeng312 Thanks again for the approval on #1107 — the payment-language guard remains green on 6d89af2bd6df (quality gate + focused tests).

Merge-ready whenever convenient.

@yanyishuai

Copy link
Copy Markdown
Author

@qingfeng312 Follow-up on #1107 — payment-language guard still passes the full quality gate on 6d89af2bd6df with approval in place.

Merge-ready whenever convenient.

5 similar comments
@yanyishuai

Copy link
Copy Markdown
Author

@qingfeng312 Follow-up on #1107 — payment-language guard still passes the full quality gate on 6d89af2bd6df with approval in place.

Merge-ready whenever convenient.

@yanyishuai

Copy link
Copy Markdown
Author

@qingfeng312 Follow-up on #1107 — payment-language guard still passes the full quality gate on 6d89af2bd6df with approval in place.

Merge-ready whenever convenient.

@yanyishuai

Copy link
Copy Markdown
Author

@qingfeng312 Follow-up on #1107 — payment-language guard still passes the full quality gate on 6d89af2bd6df with approval in place.

Merge-ready whenever convenient.

@yanyishuai

Copy link
Copy Markdown
Author

@qingfeng312 Follow-up on #1107 — payment-language guard still passes the full quality gate on 6d89af2bd6df with approval in place.

Merge-ready whenever convenient.

@yanyishuai

Copy link
Copy Markdown
Author

@qingfeng312 Follow-up on #1107 — payment-language guard still passes the full quality gate on 6d89af2bd6df with approval in place.

Merge-ready whenever convenient.

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.

Proposed work: guard PR bodies against premature payment-status wording

2 participants