Auto-mode classifier now distinguishes local validator rejection,
capability-gate rejection, transport failure before API, and successful
server-side mutation. Pre-API validator failures no longer consume
server-side mutation budget; the final report separately accounts for
local failed attempts, blocked API attempts, and successful server-side
mutations.
Recovered from preserved unpublished commit b46f0f9 via native MCP
unpublished-claim recovery (#772) and author-worktree lock binding (#618),
reconciled onto current master.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Closes#772
Recovery now dispatches on observed publication state: published_owning_pr
(remote branch exists; head equality #753 or strict descendancy #768,
unchanged) and unpublished_claim (no remote branch, no PR; ownership proven
by the durable lock record plus a local HEAD strictly descending from the
server-observed base the branch was cut from).
The absence of a remote head is never itself permission. Recovery writes are
compare-and-swap against a lock generation, and the mutating lock path and
read-only diagnostic assessor share one evaluator.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Recovered preserved candidate d06198b onto current master 0c2f45a via clean
cherry-pick. Active foreign leases are excluded before allocator ranking;
controller_instance_id ownership is persisted; dashboard matches allocator
exclusion. Stable patch-id bacafc5f… preserved.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Author mutation tools now resolve workspace via explicit worktree_path,
env bindings, or the active author issue lock — never silent fallback to
the control checkout/master. Missing configured bindings fail closed with
operator recovery; create_issue and create_issue_comment agree.
Recovered onto 0568f44 from preserved candidate cbf56ccd (AUTHOR_RECOVERY).
LLM_LOCK_ID=author-618-recovery-508eb3162d01-1784569919
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Remediates review 479/481 findings F1 and F2 on PR #767.
F1: issue #607 AC4 requires that an existing linked Gitea issue receives a
recurrence comment when Sentry events continue. That path did not exist.
This adds:
- incident_bridge.incident_recurred() — true only when event_count or
last_seen genuinely advanced, compared against the pre-upsert link row, so
an unchanged rescan stays silent and the exactly-once property holds.
- incident_bridge.build_recurrence_comment_body() — reuses the same
redact_text path as build_gitea_issue_body, so redaction is not
re-implemented.
- A comment_issue_fn hook on reconcile_incident, invoked only on
OUTCOME_UPDATED with genuinely new events. The durable incident_links row
is written first, so a comment failure degrades to "link updated, comment
withheld" without corrupting the mapping or creating a duplicate issue.
- _incident_recurrence_comment_fn() in gitea_mcp_server.py, routing through
the sanctioned gitea_create_issue_comment path named in issue #607. It
returns None on dry runs and when the profile lacks gitea.issue.comment, so
the AC8 dry-run default and disabled-mode safety hold.
- comment_issue_fn threading through sentry_incident_bridge.watchdog(), with
the per-issue recurrence_comment outcome recorded in the scan result.
F2: observation_from_issue never populates a fingerprint, so the "deduped by
Sentry issue id and fingerprint" claim was unsupported. The
gitea_sentry_reconcile_issue and gitea_sentry_watchdog docstrings now state
the real dedupe basis: provider identity (provider, base URL, org, project,
Sentry issue id).
Tests: five new AC4 cases in tests/test_sentry_incident_bridge.py covering a
comment on the second scan, dry-run silence, no-new-events silence, link
durability when the comment fails, and comment redaction. Focused suite
43 passed (was 38).
Refs #607
Permit fail-closed recovery when a clean local head is a strict
descendant of the recorded PR/remote head, with server-side ancestry
proof. Propagate recovery evidence through commit, push, and PR
duplicate gates so an owning PR is not re-blocked as competing work.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Reviewed-on: #680
Reviewed-by: sysadmin <[email protected]>
Operator break-glass merge record
PR #680 was independently assessed at head `7c77df0c527f06ae4196e010e65537d361805a7b`.
* Gitea reports the PR open and mergeable.
* The focused anti-stomp suite passed: 43 tests.
* The technical review found the change approve-worthy.
* The previous approval is stale because it applies to an earlier head.
* A fresh formal approval could not be submitted because every MCP namespace remained bound to pre-#694 startup SHA `f65069d…`, while live master is `3e78db5…`.
This is an operator-authorized break-glass merge caused by stale MCP runtime parity, not an unresolved code finding. Proceeding with **Create merge commit** only if the PR head remains exactly `7c77df0c527f06ae4196e010e65537d361805a7b`, Gitea still reports it mergeable, and no new blocking checks or feedback appear.
Reviewed-on: #679
Reviewed-by: sysadmin <[email protected]>
Operator break-glass merge of PR #679 authorized because the native MCP
gitea_adopt_merger_pr_lease path returned non-retryable internal_error
despite an authoritative active reviewer lease. PR #679 was formally
approved by Review #446 at exact head
78cc37a977 and was conflict-free and
mergeable. No LLM received credentials or used a direct API/CLI fallback.
When gitea_lock_issue adopts an existing own branch, the live tool
response now carries explicit, citable adoption-proof fields so #473-style
recovery sessions can quote the lock output directly instead of inferring
adoption from separate offline checks.
- issue_lock_adoption.py: add DECISION_LABELS + decision_label()/
safe_next_action() helpers; extend build_adoption_proof() with
adoption_decision, adopted, adopted_branch, adopted_branch_head,
matcher_summary (boundary-safe reason), competing_branch_check, and
safe_next_action for all outcomes; add build_non_adoption_lock_proof()
for adoption-free NO_MATCH metadata.
- gitea_mcp_server.py: attach adoption-free adoption_check block to normal
(non-adopt) lock responses so they cannot be misread as claiming adoption.
- docs/llm-workflow-runbooks.md: document the adoption/adoption_check
response blocks and instruct recovery reports to cite them directly.
- tests: explicit-field proof for ADOPT/BLOCK_COMPETING/NO_MATCH,
substring-collision boundary safety (issue-42 vs issue-420), non-adoption
proof, and MCP-level live-response assertions.
BLOCK_COMPETING lock attempts still fail closed (raise); no raw API
fallback, branch deletion, force-push, or manual lock seeding introduced.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Recreation of the #204 work from closed PR #205 (invalid provenance), rebuilt
cleanly on master under the prgs author identity with no PR #203 content:
- Add gitea_lock_issue MCP tool: locks exactly one issue to its branch name,
fails closed on branch/issue-number mismatch and on issues already tied to
an open PR (by head branch or Closes/Fixes reference).
- gitea_create_pr now requires the issue lock: head must match the locked
branch, title/body must contain Closes/Fixes #<locked issue> exactly, and
ambiguous references (equivalent / related / same as) are rejected.
- scripts/worktree-start refuses to create an issue-linked worktree unless the
lock file exists and matches the requested branch.
- assess_controller_handoff rejects handoffs whose selected issue / opened PR
fields carry multiple numbers or fuzzy equivalence wording.
- Tests: TestIssueLocking (lock + create_pr gates), handoff exact-reference
tests, worktree-start lock coverage.
Closes#204
Co-Authored-By: Claude Fable 5 <[email protected]>