feat: canonical cleanup for stale #332 review decision locks (#594)

Durable review-decision locks (#559) can outlive the PR they protect.
When the last terminal mutation references a PR that is already
merged/closed, expose gitea_cleanup_stale_review_decision_lock so a
reviewer can clear the lock with identity/profile gates and a durable
audit trail — without weakening #332 for open PRs or deleting
session-state files by hand.

Also auto-clear same-profile locks after a successful merge of the
approved PR, and document allowed vs forbidden cleanup.

Closes #594
This commit is contained in:
2026-07-09 15:18:48 -04:00
parent 6913ac98f1
commit 3f3f880147
6 changed files with 938 additions and 1 deletions
+66
View File
@@ -1049,6 +1049,72 @@ Special states:
Final reports must not claim a comment was posted when
`canonical_comment_validation.allowed` is false (#496 AC14).
## Stale #332 review-decision lock cleanup (#594)
#332 hard-stops a reviewer session after a terminal live review mutation
(`approve` / `request_changes`). After #559 those locks are **durable** under
`~/.cache/gitea-tools/session-state/review_decision_lock-<profile>.json`, so they
can outlive the PR they protected.
### When cleanup is **allowed**
Use `gitea_cleanup_stale_review_decision_lock` from a **reviewer** profile when:
1. A durable review-decision lock has a terminal live mutation, **and**
2. Live Gitea state shows that terminal PR is already **merged** or **closed**
(so no same-PR merge sequence remains), **and**
3. The active profile identity matches the lock, **and**
4. Authenticated identity can be verified.
Workflow:
```text
# 1) Assess only (default)
gitea_cleanup_stale_review_decision_lock(apply=false, remote=prgs, ...)
# 2) Apply after is_moot=true and cleanup_allowed=true
gitea_cleanup_stale_review_decision_lock(
apply=true,
expected_terminal_pr=<merged-or-closed-pr>,
remote=prgs,
...
)
```
Successful apply:
* clears in-memory + durable decision lock for that profile
* returns a structured `audit` payload
* posts an audit comment on the mooted PR when `gitea.pr.comment` is allowed
(`post_audit_comment=true` default)
Same-profile auto-expire: if `gitea_merge_pr` succeeds on a PR that this same
profile just approved, the decision lock is cleared after merge. Cross-profile
locks (reviewer vs merger) still require the cleanup tool.
### When cleanup is **forbidden**
Do **not** clear when:
* the last terminal PR is still **open** (active #332 hard-stop — continue merge
for that approve, or stop after request_changes)
* live PR state cannot be fetched (fail closed)
* profile identity does not match the lock
* identity cannot be verified
* `expected_terminal_pr` does not match the last terminal PR
**Never** use manual deletion of session-state files as the normal workflow.
**Never** use cleanup to skip review of an open PR or to bypass eligibility /
mark-ready / submit gates on active work.
### Related tools
| Tool | Purpose |
|------|---------|
| `gitea_cleanup_stale_review_decision_lock` | Moot decision-lock cleanup (#594) |
| `gitea_authorize_review_correction` | Operator-approved correction after a **mistaken** live review on still-active work (#211) |
| `gitea_cleanup_post_merge_moot_lease` | Moot **lease** cleanup after merge (#515) — different object |
## Fail-closed behavior
Before any mutating action the workflow verifies identity, active profile,