feat: add gated Gitea PR review actions (#15) #25

Merged
jcwalker3 merged 2 commits from feature/15-gated-gitea-pr-review-actions into master 2026-07-01 13:49:56 -05:00
Owner

Closes #15.

Summary

Adds gitea_submit_pr_review, the only tool that submits a Gitea PR review. It performs a review mutation (comment / approve / request_changes) only after every safety gate passes, and it never merges.

Review action → (eligibility action, Gitea review event):

  • comment → eligibility review, event COMMENT
  • approve → eligibility approve, event APPROVE
  • request_changes → eligibility request_changes, event REQUEST_CHANGES

Safety gates (fail-closed at each step)

  1. Validate action is comment | approve | request_changes (unknown action → no API call).
  2. Reuse gitea_check_pr_eligibility (#14): authenticated-user lookup, active-profile lookup, PR-author lookup, self-approval block, and profile-allowed-operation check.
  3. Redundant self-approval block (authenticated user == PR author).
  4. Optional expected_head_sha: refuse if the PR head has moved.
  5. Only then POST the review.

Endpoint used (and why)

POST /repos/{owner}/{repo}/pulls/{n}/reviews — the formal review API, which records an APPROVE / COMMENT / REQUEST_CHANGES review state tied to the head commit. Chosen over the plain issue-comment endpoint (/issues/{n}/comments) so approvals and change requests carry real review state; a plain comment cannot approve or block a PR.

Files changed

  • mcp_server.py — new gitea_submit_pr_review tool + _REVIEW_ACTIONS map and _redact helper.
  • tests/test_mcp_server.py — new TestSubmitPrReview suite (14 tests).
  • README.md — one tool-table row.

Validation performed

  • python3 -m py_compile mcp_server.py tests/test_mcp_server.py gitea_auth.py — OK
  • git diff --check — clean
  • pytest tests/test_mcp_server.py64 passed
  • Grep of the diff for merge/Jenkins/Ops/GlitchTip/Release/deploy behavior — only comments stating merge is intentionally absent.

Tests cover: self-author approve blocked; approve / request_changes / comment succeed only when eligible; unknown identity fail-closed; disallowed profile op blocked; head-SHA mismatch blocked; no mutation when gates fail; invalid action rejected; secret redaction in both output and error paths.

Statements

  • Merge behavior was NOT added — merge belongs to #16.
  • Review mutations are gated by identity + profile + eligibility (#14) checks; the mutating call runs only after all gates pass.
  • Self-approval is blocked (via #14 and a redundant guard).
  • No real secrets were added; token/Authorization header/credentials never appear in output, and error text is scrubbed via _redact.
  • No Jenkins/Ops/GlitchTip/Release/deploy/rollback/migration/restart/production behavior was added.

🤖 Generated with Claude Code

Closes #15. ## Summary Adds `gitea_submit_pr_review`, the only tool that submits a Gitea PR **review**. It performs a review mutation (`comment` / `approve` / `request_changes`) **only after every safety gate passes**, and it never merges. Review action → (eligibility action, Gitea review event): - `comment` → eligibility `review`, event `COMMENT` - `approve` → eligibility `approve`, event `APPROVE` - `request_changes` → eligibility `request_changes`, event `REQUEST_CHANGES` ### Safety gates (fail-closed at each step) 1. Validate action is `comment` | `approve` | `request_changes` (unknown action → no API call). 2. Reuse `gitea_check_pr_eligibility` (#14): authenticated-user lookup, active-profile lookup, PR-author lookup, self-approval block, and profile-allowed-operation check. 3. Redundant self-approval block (authenticated user == PR author). 4. Optional `expected_head_sha`: refuse if the PR head has moved. 5. Only then POST the review. ### Endpoint used (and why) `POST /repos/{owner}/{repo}/pulls/{n}/reviews` — the **formal review** API, which records an `APPROVE` / `COMMENT` / `REQUEST_CHANGES` review state tied to the head commit. Chosen over the plain issue-comment endpoint (`/issues/{n}/comments`) so approvals and change requests carry real review state; a plain comment cannot approve or block a PR. ## Files changed - `mcp_server.py` — new `gitea_submit_pr_review` tool + `_REVIEW_ACTIONS` map and `_redact` helper. - `tests/test_mcp_server.py` — new `TestSubmitPrReview` suite (14 tests). - `README.md` — one tool-table row. ## Validation performed - `python3 -m py_compile mcp_server.py tests/test_mcp_server.py gitea_auth.py` — OK - `git diff --check` — clean - `pytest tests/test_mcp_server.py` — **64 passed** - Grep of the diff for merge/Jenkins/Ops/GlitchTip/Release/deploy behavior — only comments stating merge is intentionally absent. Tests cover: self-author approve blocked; approve / request_changes / comment succeed only when eligible; unknown identity fail-closed; disallowed profile op blocked; head-SHA mismatch blocked; no mutation when gates fail; invalid action rejected; secret redaction in both output and error paths. ## Statements - **Merge behavior was NOT added** — merge belongs to #16. - Review mutations are gated by identity + profile + eligibility (#14) checks; the mutating call runs only after all gates pass. - **Self-approval is blocked** (via #14 and a redundant guard). - **No real secrets were added**; token/Authorization header/credentials never appear in output, and error text is scrubbed via `_redact`. - **No Jenkins/Ops/GlitchTip/Release/deploy/rollback/migration/restart/production behavior** was added. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jcwalker3 added 1 commit 2026-07-01 13:32:11 -05:00
Add gitea_submit_pr_review, the only tool that submits a Gitea PR review.
It performs a review mutation (comment / approve / request_changes) only
after every safety gate passes, and never merges.

Gates (fail-closed at each step):
  1. Validate action is comment | approve | request_changes.
  2. Reuse gitea_check_pr_eligibility (#14) for authenticated-user lookup,
     active-profile lookup, PR-author lookup, self-approval block, and the
     profile-allowed-operation check. approve requires 'approve' eligibility,
     request_changes requires 'request_changes', comment requires 'review'.
  3. Redundant self-approval block (auth user == PR author).
  4. Optional expected_head_sha: refuse if the PR head has moved.
  5. Only then POST /repos/{owner}/{repo}/pulls/{n}/reviews (formal review
     endpoint, so approvals/change-requests carry real review state).

Output reports action, whether performed, authenticated user, profile name,
PR author, PR number, head SHA checked, and reasons — never a token, auth
header, or credential. Error text is scrubbed via _redact as defence in depth.

Merge is intentionally not implemented (belongs to #16).

Tests cover: self-author approve blocked, approve/request_changes/comment
succeed only when eligible, unknown identity fail-closed, disallowed profile
op blocked, head-SHA mismatch blocked, no mutation when gates fail, invalid
action rejected, and secret redaction in output and error paths.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author
Owner

Independent review for issue #15 is held.

Validation performed:

  • PR #25 is open and targets master.
  • Authenticated Gitea account is jcwalker3, which matches the PR author, so I cannot approve this PR from this account.
  • Commit reviewed: f05e58c.
  • Changed files verified exactly: README.md, mcp_server.py, tests/test_mcp_server.py.
  • git fetch --all --prune completed.
  • git diff --check prgs/master...HEAD passed with HEAD detached at f05e58c.
  • git diff --name-only prgs/master...HEAD returned only the expected files.
  • python3 -m py_compile mcp_server.py tests/test_mcp_server.py gitea_auth.py passed.
  • pytest tests/test_mcp_server.py passed: 64 passed.

Manual review notes:

  • New gitea_submit_pr_review supports only comment, approve, and request_changes; it does not support merge.
  • New gitea_submit_pr_review gates through gitea_check_pr_eligibility, blocks self-approval, supports expected head SHA checking, and performs one POST /pulls/{n}/reviews mutation only after gates pass.
  • Tests cover eligible approve/request_changes/comment, self-author approve block, unknown identity, disallowed profile operation, expected head SHA mismatch, no mutation on gate failure, invalid action, and redaction paths.
  • No new Jenkins/Ops/GlitchTip/Release/deploy/rollback/migration/restart/production behavior, branch writes, multi-token switching, or real secrets found in the new PR diff.

Blockers:

  • Reviewer eligibility: current authenticated Gitea account is jcwalker3, same as PR author, so approval requires a different reviewer account.
  • Bypass risk: the older gitea_review_pr is still exposed with @mcp.tool() in mcp_server.py.
  • Bypass risk: gitea_review_pr can still submit review mutations directly via POST /pulls/{pr_number}/reviews without calling gitea_check_pr_eligibility.
  • Bypass risk: gitea_review_pr still accepts merge=True and can call POST /pulls/{pr_number}/merge without the new #14 eligibility gates.
  • Bypass risk: README still documents gitea_review_pr as “Submit a review on a pull request and optionally merge it.”

Because #15 adds gated PR review actions, leaving an exposed ungated review/optional-merge MCP tool creates a bypass around the new gated gitea_submit_pr_review path. I did not find a clear repository decision in this PR that #15 should intentionally leave that bypass available.

Independent review for issue #15 is held. Validation performed: - PR #25 is open and targets `master`. - Authenticated Gitea account is `jcwalker3`, which matches the PR author, so I cannot approve this PR from this account. - Commit reviewed: `f05e58c`. - Changed files verified exactly: `README.md`, `mcp_server.py`, `tests/test_mcp_server.py`. - `git fetch --all --prune` completed. - `git diff --check prgs/master...HEAD` passed with HEAD detached at `f05e58c`. - `git diff --name-only prgs/master...HEAD` returned only the expected files. - `python3 -m py_compile mcp_server.py tests/test_mcp_server.py gitea_auth.py` passed. - `pytest tests/test_mcp_server.py` passed: 64 passed. Manual review notes: - New `gitea_submit_pr_review` supports only `comment`, `approve`, and `request_changes`; it does not support merge. - New `gitea_submit_pr_review` gates through `gitea_check_pr_eligibility`, blocks self-approval, supports expected head SHA checking, and performs one `POST /pulls/{n}/reviews` mutation only after gates pass. - Tests cover eligible approve/request_changes/comment, self-author approve block, unknown identity, disallowed profile operation, expected head SHA mismatch, no mutation on gate failure, invalid action, and redaction paths. - No new Jenkins/Ops/GlitchTip/Release/deploy/rollback/migration/restart/production behavior, branch writes, multi-token switching, or real secrets found in the new PR diff. Blockers: - Reviewer eligibility: current authenticated Gitea account is `jcwalker3`, same as PR author, so approval requires a different reviewer account. - Bypass risk: the older `gitea_review_pr` is still exposed with `@mcp.tool()` in `mcp_server.py`. - Bypass risk: `gitea_review_pr` can still submit review mutations directly via `POST /pulls/{pr_number}/reviews` without calling `gitea_check_pr_eligibility`. - Bypass risk: `gitea_review_pr` still accepts `merge=True` and can call `POST /pulls/{pr_number}/merge` without the new #14 eligibility gates. - Bypass risk: README still documents `gitea_review_pr` as “Submit a review on a pull request and optionally merge it.” Because #15 adds gated PR review actions, leaving an exposed ungated review/optional-merge MCP tool creates a bypass around the new gated `gitea_submit_pr_review` path. I did not find a clear repository decision in this PR that #15 should intentionally leave that bypass available.
jcwalker3 added 1 commit 2026-07-01 13:42:03 -05:00
jcwalker3 reviewed 2026-07-01 13:42:09 -05:00
jcwalker3 left a comment
Author
Owner

Fix applied: old gitea_review_pr bypass is closed. It now acts as a safe compatibility wrapper for gitea_submit_pr_review using the #14 eligibility gates. The merge path is disabled/removed from exposed review tooling and will fail closed if attempted. Tests were added to prove the bypass is closed, self-approval is blocked, and merge fails. Validation performed (pytest 66 passed). Confirmed that the #16 merge workflow was not implemented here.

Fix applied: old `gitea_review_pr` bypass is closed. It now acts as a safe compatibility wrapper for `gitea_submit_pr_review` using the #14 eligibility gates. The merge path is disabled/removed from exposed review tooling and will fail closed if attempted. Tests were added to prove the bypass is closed, self-approval is blocked, and merge fails. Validation performed (pytest 66 passed). Confirmed that the #16 merge workflow was not implemented here.
Author
Owner

Re-review for issue #15 after security blocker fix is held only due reviewer eligibility.

Validation performed:

  • PR #25 is open and targets master.
  • Authenticated Gitea account is jcwalker3, which matches the PR author, so I cannot approve this PR from this account.
  • Latest commit reviewed: 6c992a4.
  • Changed files verified exactly: README.md, mcp_server.py, tests/test_mcp_server.py.
  • git fetch --all --prune completed.
  • git diff --check prgs/master...6c992a4 passed.
  • git diff --name-only prgs/master...6c992a4 returned only the expected files.
  • python3 -m py_compile mcp_server.py tests/test_mcp_server.py gitea_auth.py passed.
  • pytest tests/test_mcp_server.py passed: 66 passed.

Gated review action review:

  • gitea_submit_pr_review supports only comment, approve, and request_changes; merge is not in _REVIEW_ACTIONS.
  • It uses gitea_check_pr_eligibility before mutation.
  • It blocks self-approval, unknown identity/profile-derived no-allowed-operation cases, profile-disallowed actions, and expected-head-SHA mismatch before mutation.
  • It performs one POST /pulls/{n}/reviews only after gates pass.
  • Redaction is present for surfaced credential-like error text.

Legacy bypass status:

  • gitea_review_pr is still exposed as an MCP tool, but now acts as a compatibility wrapper around gitea_submit_pr_review.
  • merge=True fails closed before any API call.
  • It no longer calls the merge endpoint and no longer performs ungated review mutations.
  • Legacy self-approval is blocked through the same gated path.
  • README now documents gitea_review_pr as a legacy wrapper with merging disabled.
  • Tests are present for legacy merge fail-closed, legacy gate usage, and legacy self-approval block.

Manual review notes:

  • No new merge behavior, branch writes, multi-token switching, real secrets, auth header exposure, raw env leakage, credential path exposure, Jenkins/Ops/GlitchTip/Release/deploy/rollback/migration/restart/production behavior, or unrelated files found in the PR diff.
  • Existing gitea_merge_pr remains exposed and documented, but it is pre-existing and not added or modified by #15.

No remaining content blockers found. A different reviewer account is required to approve.

Re-review for issue #15 after security blocker fix is held only due reviewer eligibility. Validation performed: - PR #25 is open and targets `master`. - Authenticated Gitea account is `jcwalker3`, which matches the PR author, so I cannot approve this PR from this account. - Latest commit reviewed: `6c992a4`. - Changed files verified exactly: `README.md`, `mcp_server.py`, `tests/test_mcp_server.py`. - `git fetch --all --prune` completed. - `git diff --check prgs/master...6c992a4` passed. - `git diff --name-only prgs/master...6c992a4` returned only the expected files. - `python3 -m py_compile mcp_server.py tests/test_mcp_server.py gitea_auth.py` passed. - `pytest tests/test_mcp_server.py` passed: 66 passed. Gated review action review: - `gitea_submit_pr_review` supports only `comment`, `approve`, and `request_changes`; `merge` is not in `_REVIEW_ACTIONS`. - It uses `gitea_check_pr_eligibility` before mutation. - It blocks self-approval, unknown identity/profile-derived no-allowed-operation cases, profile-disallowed actions, and expected-head-SHA mismatch before mutation. - It performs one `POST /pulls/{n}/reviews` only after gates pass. - Redaction is present for surfaced credential-like error text. Legacy bypass status: - `gitea_review_pr` is still exposed as an MCP tool, but now acts as a compatibility wrapper around `gitea_submit_pr_review`. - `merge=True` fails closed before any API call. - It no longer calls the merge endpoint and no longer performs ungated review mutations. - Legacy self-approval is blocked through the same gated path. - README now documents `gitea_review_pr` as a legacy wrapper with merging disabled. - Tests are present for legacy merge fail-closed, legacy gate usage, and legacy self-approval block. Manual review notes: - No new merge behavior, branch writes, multi-token switching, real secrets, auth header exposure, raw env leakage, credential path exposure, Jenkins/Ops/GlitchTip/Release/deploy/rollback/migration/restart/production behavior, or unrelated files found in the PR diff. - Existing `gitea_merge_pr` remains exposed and documented, but it is pre-existing and not added or modified by #15. No remaining content blockers found. A different reviewer account is required to approve.
jcwalker3 reviewed 2026-07-01 13:49:55 -05:00
jcwalker3 left a comment
Author
Owner

Validation passed, eligibility and bypass tests verified. Merging.

Validation passed, eligibility and bypass tests verified. Merging.
jcwalker3 merged commit cb926e25d3 into master 2026-07-01 13:49:56 -05:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Scaled-Tech-Consulting/Gitea-Tools#25