Files
Gitea-Tools/docs/mcp-tool-inventory.md
sysadminandClaude Opus 4.8 243f52dc79 feat(lease): make the author task heartbeat load-bearing (#790 Slice A)
Slice A of Issue #790, per the controller reassessment in comment 13958. Does
not close the issue: terminal retirement (Slice B) and the read-side generation
check plus the #760 renewal re-scope (Slice C) are deliberately not implemented.

The defect. `issue_lock_store.assess_lock_freshness` parsed `last_heartbeat_at`
and then never consulted it. Liveness was decided by an absolute four-hour
`expires_at` and by PID liveness, and the recorded PID is the long-lived MCP
daemon rather than the authoring task, so an abandoned claim stayed live for the
full four hours. A tree-wide search found the field written in exactly one place
and advanced by nothing. Issue #787 / PR #789 hit this; Issue #760 / PR #791 hit
it again, blocking reconciliation for over five hours after its work had landed.

A1 — central policy. New `lease_policy` declares every duration for every task
class in one place: author initial/sliding TTL 10 minutes, heartbeat cadence 2,
stale warning 5, missed-heartbeat grace 10, absolute cap 8 hours, recovery grace
10, terminal race-drain 2. It ships first so the first heartbeat and TTL
behavior to run reads from it (AC-N7). The duplicated four-hour literal is gone
from both `issue_lock_store` and `gitea_mcp_server`. Reviewer, merger, and
conflict-fix classes are declared but not rewired — Slice C moves those call
sites — and a test asserts the declaration still equals the constants #747 and
`pr_work_lease` own, so the two cannot drift apart unnoticed.

A2 — load-bearing freshness, with two deliberate asymmetries. An alive PID never
establishes freshness anywhere (AC-N2); it is recorded as evidence and no branch
returns live because of it. A dead PID still marks a lease stale, and that band
still precedes every heartbeat evaluation, so #753 dead-session recovery keys on
exactly the classification it always did. New bands `stale_missed_heartbeat` and
`stale_absolute_cap` are classified in `branch_cleanup_guard` rather than
falling through to unknown-status, and still block unless the ownership record
proves `reclaim_allowed is True`. A heartbeat lease carrying no heartbeat is
contradictory and fails closed. `assess_expired_lock_reclaim` accepts a lapsed
heartbeat as reclaim grounds for heartbeat-lifecycle leases only: under this
lifecycle the heartbeat is the liveness proof, and also requiring a dead PID
would reinstate the original defect.

A3/A4 — task-session identity and the writer. `mint_task_session_id` produces an
ownership key containing no process identifier, since the daemon PID is reused
by every task it serves and identifies none of them. `heartbeat_session_lock`
writes inside the existing per-issue flock under the #772 generation
compare-and-swap, verifying exact issue, branch, realpath-normalized worktree,
claimant username, claimant profile, and recorded session identifier. It cannot
acquire, take over, or revive: a lease past its grace is refused and must use
the reclaim path, so a session that stopped proving liveness cannot restore
ownership retroactively. New `gitea_heartbeat_issue_lock` gates on the same
authority as `lock_issue`, being strictly narrower.

A5 — legacy compatibility (AC-N8). The explicit `lifecycle_version` marker, never
a timestamp comparison, discriminates legacy from heartbeat leases: a legacy lock
has `last_heartbeat_at == created_at` forever precisely because nothing advanced
it, and a freshly minted heartbeat lease has them equal too, so the equality
carries no information in either direction. Legacy locks keep their recorded
absolute expiry and are never evaluated against the short grace, so deployment
cannot make an existing claim instantly reclaimable. They leave that state only
by terminal retirement (Slice B) or by `rebind_legacy_lock`, which re-verifies
the exact owner and mints a genuine identifier and first heartbeat while
preserving the original claim under `legacy_origin`. Rebinding a lapsed legacy
lease is refused; that belongs to #760 renewal or #601 reclaim.

A6 — native coverage. Review #499 proved assessor-level tests miss discard
points, so `tests/test_issue_790_heartbeat_mcp_path.py` drives the real tools
against a real git repository and a real durable lock: lock creation and
read-back, policy window, freshness, survival of `verify_lock_for_mutation`,
invariance of the duplicate-work and linked-open-PR gates, CAS rejection,
foreign-session and foreign-claimant refusal, alive-PID-only refusal, missed
heartbeat, legacy protection on deployment, and legacy rebinding.

Tests. New suites 55 passed. Lock and lease regression set (issue_lock_store,
lease_lifecycle, #753, #755, #760 x2, #768, #772, lock registration, worktree,
adoption, duplicate gate, branch cleanup guard, capability invariants, claim
heartbeat, worktrees) 383 passed with 98 subtests. Full suite 4295 passed, 11
failed, 6 skipped, 499 subtests passed, against a clean master baseline worktree
at 620ed6e9 that reports 11 failed and 4240 passed — the same eleven node IDs.
The 55-test delta is exactly the new suites; no new failures.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_011u6GKSJwwrrYjguPjs1aK5
2026-07-22 04:19:48 -04:00

5.9 KiB

Registered MCP tool inventory

This is the canonical list of tools the Gitea-Tools MCP server registers. It exists because documentation and the registered inventory drifted: the workflow documented a gitea_edit_issue tool that no namespace had ever registered, so a mutation could be planned against a tool that did not exist and only fail at execution time (#781).

The rule

Documentation must never name a tool an actor cannot reach.

Two guards enforce it, both in tests/test_issue_781_edit_issue_tool.py:

  1. The list below must equal the registered tool set exactly — sorted, no duplicates, nothing missing in either direction. Adding a tool without documenting it fails, and documenting a tool without registering it fails.
  2. Every backticked gitea_* / mcp_* identifier in skills/**/*.md must be a registered tool. Module and script names that share the prefix are listed explicitly in mcp_tool_inventory.NON_TOOL_IDENTIFIERS rather than being waved through by a looser pattern.

Updating this file

When you add or remove an @mcp.tool(), regenerate the block below:

PYTEST_CURRENT_TEST=1 venv/bin/python -c "
import mcp_server, mcp_tool_inventory
print(mcp_tool_inventory.render_inventory_block(
    mcp_server.mcp._tool_manager._tools))
"

Replace everything between the markers with that output. Do not hand-edit individual entries — the generator and the guard share one ordering rule.

Registered tools

Namespaces (gitea-tools, gitea-reviewer, gitea-merger, gitea-reconciler) register the same tool set; what differs per namespace is the execution profile that gates each call, not which tools exist.

  • gitea_abandon_workflow_lease
  • gitea_acquire_conflict_fix_lease
  • gitea_acquire_merger_pr_lease
  • gitea_acquire_reviewer_pr_lease
  • gitea_activate_profile
  • gitea_adopt_merger_pr_lease
  • gitea_adopt_workflow_lease
  • gitea_allocate_next_work
  • gitea_assess_already_landed_reconciliation
  • gitea_assess_conflict_fix_classification
  • gitea_assess_conflict_fix_push
  • gitea_assess_gitea_operation_path
  • gitea_assess_master_parity
  • gitea_assess_mcp_namespace_health
  • gitea_assess_pr_sync_status
  • gitea_assess_review_merge_state_machine
  • gitea_assess_reviewer_pr_lease
  • gitea_assess_terminal_label_hygiene
  • gitea_assess_work_issue_duplicate
  • gitea_assess_worktree_cleanup_integrity
  • gitea_audit_config
  • gitea_audit_runtime_recovery_contamination
  • gitea_audit_stable_branch_contamination
  • gitea_audit_worktree_cleanup
  • gitea_authorize_reconciliation_cleanup_phase
  • gitea_authorize_review_correction
  • gitea_capability_stop_terminal_report
  • gitea_capture_branches_worktree_snapshot
  • gitea_check_pr_eligibility
  • gitea_cleanup_merged_pr_branch
  • gitea_cleanup_obsolete_reviewer_comment_lease
  • gitea_cleanup_post_merge_moot_lease
  • gitea_cleanup_stale_claims
  • gitea_cleanup_stale_review_decision_lock
  • gitea_cleanup_terminal_pr_labels
  • gitea_close_issue
  • gitea_commit_files
  • gitea_consume_irrecoverable_decision_lock_provenance
  • gitea_create_issue
  • gitea_create_issue_comment
  • gitea_create_label
  • gitea_create_pr
  • gitea_delete_branch
  • gitea_diagnose_review_decision_lock
  • gitea_diagnose_reviewer_pr_lease_handoff
  • gitea_diagnose_terminal
  • gitea_dry_run_pr_review
  • gitea_edit_issue
  • gitea_edit_pr
  • gitea_expire_workflow_leases
  • gitea_get_authenticated_user
  • gitea_get_current_user
  • gitea_get_file
  • gitea_get_pr_review_feedback
  • gitea_get_profile
  • gitea_get_runtime_context
  • gitea_get_shell_health
  • gitea_heartbeat_issue_lock
  • gitea_heartbeat_reviewer_pr_lease
  • gitea_inspect_workflow_lease
  • gitea_issue_irrecoverable_provenance_authorization
  • gitea_list_dependency_edges
  • gitea_list_issue_comments
  • gitea_list_issues
  • gitea_list_labels
  • gitea_list_profiles
  • gitea_list_prs
  • gitea_list_workflow_leases
  • gitea_load_review_workflow
  • gitea_lock_issue
  • gitea_mark_final_review_decision
  • gitea_mark_issue
  • gitea_merge_pr
  • gitea_mirror_refs
  • gitea_observability_link_issue
  • gitea_observability_list_projects
  • gitea_observability_reconcile_incident
  • gitea_post_heartbeat
  • gitea_quarantine_contaminated_review
  • gitea_reclaim_expired_workflow_lease
  • gitea_reconcile_already_landed_pr
  • gitea_reconcile_issue_claims
  • gitea_reconcile_merged_cleanups
  • gitea_reconcile_superseded_by_merged_pr
  • gitea_record_daemon_process_kill_attempt
  • gitea_record_irrecoverable_decision_lock_provenance
  • gitea_record_pre_review_command
  • gitea_record_shell_spawn_outcome
  • gitea_record_stable_branch_push_attempt
  • gitea_release_merger_pr_lease
  • gitea_release_reviewer_pr_lease
  • gitea_release_workflow_lease
  • gitea_resolve_task_capability
  • gitea_resume_review_draft
  • gitea_review_pr
  • gitea_route_task_session
  • gitea_save_review_draft
  • gitea_scan_already_landed_open_prs
  • gitea_sentry_get_issue_events
  • gitea_sentry_link_gitea_issue
  • gitea_sentry_list_issues
  • gitea_sentry_reconcile_issue
  • gitea_sentry_watchdog
  • gitea_set_issue_labels
  • gitea_submit_pr_review
  • gitea_update_pr_branch_by_merge
  • gitea_validate_review_final_report
  • gitea_view_issue
  • gitea_view_pr
  • gitea_whoami
  • gitea_workflow_dashboard
  • mcp_check_workflow_skill_preflight
  • mcp_get_control_plane_guide
  • mcp_get_skill_guide
  • mcp_list_project_skills

Issue-content editing

gitea_edit_issue is the only path that changes an issue's title or body. It PATCHes the issue endpoint, refuses a pull-request number, sends only the fields the caller named, and proves the result by read-after-write — including that state, labels, assignees, and milestone did not move.

gitea_edit_pr remains pull-request-only. The two paths never merge: a single tool that accepted either kind would make the narrower capability reachable through the wider one.