Commit Graph

32 Commits

Author SHA1 Message Date
sysadmin f04cf44975 feat: gate gitea_merge_pr behind identity/profile/eligibility + confirmation (#16)
Replace the ungated gitea_merge_pr with a gated merge workflow. This is now
the only merge path the MCP server exposes; the merge API is called only
after every safety gate passes.

Gates (fail-closed at each step):
  1. Merge method is merge | squash | rebase.
  2. Explicit confirmation: confirmation must equal "MERGE PR <n>" (without it,
     zero API calls are made).
  3. Reuse gitea_check_pr_eligibility (#14) with action 'merge': proves the
     authenticated identity, the active profile (and that it allows merge), the
     PR author, blocks self-merge, requires the PR open, and fails closed when
     the PR is not mergeable or mergeability is unknown.
  4. Optional expected_head_sha: refuse if the PR head moved.
  5. Optional expected_changed_files: refuse if the PR's changed file set differs.
  6. Redundant self-merge block (auth user == PR author).

The force/ignore-checks option was removed — Gitea's own mergeable signal
(which reflects branch-protection required reviews/checks) must be positive,
so required approval/check state is honoured, never bypassed.

Output reports performed, authenticated user, profile name, PR author, PR
number, head SHA checked, merge method, gates passed/blocked, and merge
result / merge commit — never a token, auth header, or credential. Error text
is scrubbed via _redact.

Surface audit: no ungated merge path remains. The /merge endpoint appears only
inside gitea_merge_pr; gitea_review_pr fails closed on merge=True before any
API call; gitea_submit_pr_review has no merge parameter and 'merge' is not a
reviewable action. Tests assert all three.

Tests cover: merge succeeds only when all gates pass; self-author blocked;
unknown identity/profile blocked; profile without merge permission blocked;
missing/wrong confirmation blocked (no API call); head-SHA mismatch blocked;
changed-files mismatch blocked; closed PR blocked; non-mergeable blocked;
unknown mergeability fail-closed; no merge call when gates fail; invalid merge
method rejected; output and error redaction; and the no-ungated-merge-path audit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 15:03:49 -04:00
jcwalker3 cb926e25d3 Merge pull request 'feat: add gated Gitea PR review actions (#15)' (#25) from feature/15-gated-gitea-pr-review-actions into master 2026-07-01 13:49:56 -05:00
sysadmin 6c992a4fa7 fix: close gitea_review_pr ungated bypass (#15) 2026-07-01 14:42:00 -04:00
sysadmin f05e58c847 feat: add gated gitea_submit_pr_review review actions (#15)
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>
2026-07-01 14:31:34 -04:00
jcwalker3 ca51fba44d Merge pull request 'feat: add read-only gitea_check_pr_eligibility (#14)' (#24) from feature/14-gitea-pr-eligibility-checks into master 2026-07-01 13:20:31 -05:00
sysadmin baf4eae30b test: cover self-approve block and unknown-mergeability fail-closed (#14)
Add two explicit eligibility tests requested in review of PR #24:
- self-author blocked from 'approve' (eligible=false, reason
  "authenticated user is PR author").
- 'merge' fails closed when Gitea reports mergeable=None (eligible=false,
  reason "PR mergeability unknown").

Tests only; no implementation change. Behavior already enforced by
gitea_check_pr_eligibility.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 14:02:48 -04:00
sysadmin fbbbd5359e feat: add read-only gitea_check_pr_eligibility (#14)
Add a read-only MCP tool that decides whether the current authenticated
identity + active runtime profile is eligible to review, approve,
request_changes, or merge a specific PR. Evaluation only — it never
reviews, approves, requests changes, merges, or mutates anything.

Inspects: authenticated username (/user), active profile metadata
(allowed/forbidden operations), and PR facts (author, state, head SHA,
mergeability). Returns {eligible, requested_action, authenticated_user,
profile_name, pr_author, pr_state, head_sha, mergeable, reasons}.

Fail-closed rules:
- unknown action / unknown remote -> not eligible
- action not in allowed ops, or in forbidden ops -> not eligible
- identity undetermined -> not eligible
- authenticated user == PR author -> cannot approve/merge
- PR not open -> not eligible
- merge requires a positive mergeable signal

No token/auth-header exposure. No review/approve/request-changes
mutation. No merge mutation. No multi-token switching. No
Jenkins/Ops/GlitchTip/Release/deploy behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 13:54:49 -04:00
jcwalker3 92acb36406 Merge pull request 'feat: add read-only gitea_get_profile discovery tool (#13)' (#23) from feature/13-gitea-profile-discovery into master 2026-07-01 12:50:05 -05:00
sysadmin 38c96d5815 feat: add read-only gitea_get_profile discovery tool (#13)
Add a read-only MCP tool that reports the active runtime execution
profile so an LLM can inspect what the current process is configured to
do before deciding whether to attempt an action later.

- gitea_get_profile: returns profile_name, allowed/forbidden operation
  categories, audit_label, token_source_name (a NAME, never a value),
  base_url, remote, resolved server, and — optionally — the verified
  authenticated username. Identity resolution fails soft and marks
  identity_status (verified/unknown/unavailable/not_resolved); the
  profile config is always returned. Never mutates Gitea.
- gitea_auth.get_profile(): extended with forbidden_operations,
  audit_label, token_source_name from env (non-secret metadata).
- .env.example / README: document the new optional metadata vars and
  the discovery tool.
- tests: metadata parsing, verified/unavailable/unknown identity paths,
  skip-identity, and secret-redaction.

Read-only. No token exposure. No multi-token switching. No PR
eligibility, review, or merge workflow. No Jenkins/Ops/GlitchTip/
Release/deploy behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 13:41:14 -04:00
jcwalker3 769bec05e7 Merge pull request 'feat: support separate Gitea MCP runtime profiles via env config (#19)' (#22) from feature/19-gitea-runtime-profiles-env into master 2026-07-01 12:35:41 -05:00
sysadmin e31612027d feat: support separate Gitea MCP runtime profiles via env config (#19)
Allow the same MCP server to run as separate MCP entries, each with its
own token and profile name, so roles stay task-scoped (the profile is
the role, not the LLM).

- gitea_auth.get_profile(): reads GITEA_PROFILE_NAME,
  GITEA_ALLOWED_OPERATIONS, GITEA_BASE_URL as non-secret metadata.
  Never reads/returns/logs the token.
- gitea_whoami now surfaces the safe profile metadata (name + allowed
  operations) alongside identity; token still never exposed.
- .env.example: placeholder-only template for a runtime profile.
- .gitignore: track .env.example while keeping real .env* ignored.
- README: document multiple env-configured MCP entries.
- tests: profile defaults/parsing, token-never-included, whoami surfaces
  profile without leaking token.

One token + one profile per process. No multi-token switching in a
single runtime. No approve/merge/eligibility workflow. No
Jenkins/Ops/GlitchTip/Release/deploy behavior. No real secrets.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 13:21:59 -04:00
jcwalker3 28feef3c11 Merge pull request 'docs: define task-scoped Gitea MCP execution profile model (#12)' (#21) from feature/12-gitea-execution-profile-model into master 2026-07-01 12:15:06 -05:00
sysadmin 5aad2e62d9 docs: define task-scoped Gitea MCP execution profile model (#12)
Add docs/gitea-execution-profiles.md defining the execution profile
model for gitea-mcp: profile metadata shape, five reference profiles
(gitea-issue-manager, gitea-author, gitea-reviewer, gitea-merger,
gitea-owner), allowed/forbidden operation model, identity + fail-closed
rules, and self-review/self-merge prevention.

Model/documentation only. No runtime profile switching, no multi-token
loading, no approve/merge/eligibility workflow, no secrets. Runtime
config (#19), discovery (#13), eligibility (#14), review (#15), merge
(#16), and audit logging (#18) are explicitly deferred.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 13:03:24 -04:00
sysadmin 89fe118279 Merge pull request 'feat: add read-only gitea_whoami authenticated-user lookup (#11)' (#20) from feature/11-gitea-authenticated-user-lookup into master
Reviewed-on: #20
2026-07-01 12:00:17 -05:00
sysadmin 03e28c159e feat: add read-only gitea_whoami authenticated-user lookup (#11)
Add a read-only MCP tool that calls Gitea's authenticated-user
endpoint (GET /api/v1/user) and returns safe identity metadata only:
username, display name, user id, email, server, and remote.

This lets future review/merge workflows prove which Gitea account the
MCP server is authenticated as, so self-review/self-merge can be
detected before acting — the blocker discovered during PR #8 dogfooding.

- Never returns the token, Authorization header, password, or secrets.
- Fails closed with a clear error if identity cannot be determined.
- No mutation; no profile switching; no review/approve/merge behavior.

Tests: identity mapping, secret-redaction, fail-closed, unknown-remote.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 12:42:37 -04:00
jcwalker3 952e308a18 Merge pull request 'docs: Document MCP security model and trust boundaries' (#8) from feature/52-security-docs into master 2026-07-01 10:40:15 -05:00
sysadmin b402de83fe docs: fix trailing whitespace and add approved naming for #52
Address reviewer blockers on PR #8:
- Remove trailing whitespace in credential-isolation.md and release-workflows.md
- Add approved naming coverage (MCP Control Plane / mcp-control-plane project
  and repo names; common, gitea-mcp, jenkins-mcp, ops-mcp, release-mcp packages)
  to tool-boundaries.md

Documentation-only. No code, scaffolding, or config changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 04:17:30 -04:00
sysadmin 509ff7574a docs: Document MCP security model and trust boundaries for #52 2026-07-01 03:46:45 -04:00
sysadmin 989856a007 feat: add PR review and edit tools to CLI and MCP server 2026-06-26 06:24:19 -04:00
sysadmin ccbb44c81e docs: document new PR and label management tools in README 2026-06-24 00:15:07 -04:00
sysadmin 82fcd5a4bc feat: expand MCP server tools for PR and label management, add helper CLI scripts
Closes #7
2026-06-24 00:14:47 -04:00
sysadmin 8b1c115647 feat: add auth.py backwards compatibility wrapper 2026-06-21 22:36:59 -04:00
sysadmin 80a71f2020 feat: bypass Cloudflare block & convert close/mark issue shell scripts to Python 2026-06-21 22:32:37 -04:00
sysadmin 51296c88a3 refactor: rename auth.py to gitea_auth.py and ignore env files 2026-06-21 22:27:40 -04:00
sysadmin 203e9d4cb7 docs: add per-client MCP setup instructions
Expandable sections for Antigravity, Claude Code, generic MCP clients,
and Codex/non-MCP fallback to CLI scripts.
2026-06-21 20:20:30 -04:00
sysadmin b7e195e426 feat: add MCP server + shared auth module (#7, #1)
- New: mcp_server.py — FastMCP stdio server exposing 7 tools:
  gitea_create_issue, gitea_create_pr, gitea_close_issue,
  gitea_list_issues, gitea_view_issue, gitea_mark_issue,
  gitea_mirror_refs
- New: auth.py — shared authentication and API helpers
  (get_credentials, get_auth_header, api_request, repo_api_url)
- Refactored: create_pr.py, create_issue.py, manage_labels.py
  to use shared auth module (eliminates credential duplication)
- New: tests/test_mcp_server.py — 17 tests for all MCP tools
- Updated: tests/test_credentials.py — now tests auth.py directly
- Updated: tests/test_create_issue.py — adapted for refactored imports
- New: requirements.txt — frozen venv deps (mcp[cli], pytest)
- Updated: README.md — MCP server as primary interface
- Config: added gitea-tools to mcp_config.json

Closes #1. Resolves #2, #5. Relates to #7.
2026-06-21 20:08:07 -04:00
sysadmin dd6f1308c1 feat: add mirror_refs.sh for bidirectional ref syncing
- mirror_refs.sh: additive branch+tag mirroring between dadeschools (HTTPS)
  and prgs (SSH:2222). Dry-run default, --apply to execute, --force for
  diverged branches. Uses bare repo cache for isolation.
- test_mirror_refs.py: flag parsing, safety defaults, brace-delimited refspec
  validation, and local bare-repo integration tests (FF detection, branch/tag
  comparison).
- README.md: document mirror_refs.sh, test suite, and multi-instance auth.
2026-06-21 18:08:53 -04:00
sysadmin c4c9993039 test: add comprehensive test suite
- test_create_issue.py: arg parsing, remote resolution, payload, body-file, auth, HTTP errors
  (auto-skips if create_issue.py is inaccessible due to macOS sandbox)
- test_create_pr.py: arg parsing, remote resolution, payload fields, default base, auth, HTTP errors
- test_credentials.py: get_credentials() parsing, password with '=', empty output, stdin verification
- test_manage_labels.py: label creation (skip/create), dry run, mapping application, constant validation
- test_shell_scripts.py: close_issue.sh and mark_issue.sh arg validation and error messages

28 passed, 12 skipped (macOS sandbox on create_issue.py).
2026-06-21 17:26:18 -04:00
sysadmin 7404f768d3 chore: improve tooling quality and docs
- close_issue.sh: add set -euo pipefail, argument validation, confirmation output
- mark_issue.sh: track previously untracked claim/release script
- create_pr.sh: remove hardcoded one-off (use create_pr.py instead)
- README.md: reflect current toolset with usage examples
- .gitignore: ignore venv/ and __pycache__/
2026-06-21 17:11:44 -04:00
sysadmin d3659534ef feat: parameterize create_issue.py, track manage_labels.py
- create_issue.py: argparse single-issue creator mirroring create_pr.py
  (--remote {dadeschools,prgs}, --title/--body/--body-file, host overrides),
  replacing the one-shot hardcoded ROADMAP backfill batch.
- manage_labels.py: add the label set + mapping tool (executable).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 17:03:12 -04:00
sysadmin ed63310b71 feat: parameterize create_pr.py and add PRGS remote
- argparse: --remote {dadeschools,prgs}, --title/--head/--base/--body,
  --body-file (or '-' for stdin), and --host/--org/--repo overrides.
- REMOTES table: dadeschools (gitea.dadeschools.net/Contractor) and
  prgs (gitea.prgs.cc/Scaled-Tech-Consulting).
- Print 'PR #N: <url>' on success; surface API error body on failure.
- Fix credential parsing to split('=', 1) so tokens containing '=' work.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 16:59:48 -04:00
sysadmin e7f4b2732c Initial commit 2026-06-21 15:35:57 -04:00