fix: post AC4 recurrence comments on linked incident issues (#607)
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
This commit is contained in:
@@ -590,6 +590,7 @@ def watchdog(
|
||||
max_pages: int = DEFAULT_MAX_PAGES,
|
||||
http_fn: HttpFn | None = None,
|
||||
create_issue_fn: Any = None,
|
||||
comment_issue_fn: Any = None,
|
||||
reconcile_fn: Callable[..., dict[str, Any]] | None = None,
|
||||
fetch_events: bool = True,
|
||||
) -> dict[str, Any]:
|
||||
@@ -597,6 +598,9 @@ def watchdog(
|
||||
|
||||
Dry-run by default. ``apply=True`` additionally requires the bridge to be
|
||||
explicitly enabled via ``MCP_SENTRY_ISSUE_BRIDGE_ENABLED``.
|
||||
|
||||
``comment_issue_fn`` carries the sanctioned issue-comment route used for
|
||||
AC4 recurrence comments on already-linked issues; dry runs never comment.
|
||||
"""
|
||||
result: dict[str, Any] = {
|
||||
"success": False,
|
||||
@@ -682,6 +686,7 @@ def watchdog(
|
||||
mappings=list(mappings or []),
|
||||
apply=bool(apply),
|
||||
create_issue_fn=create_issue_fn,
|
||||
comment_issue_fn=comment_issue_fn,
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001 - one bad issue must not abort the scan
|
||||
result["failed"] += 1
|
||||
@@ -702,6 +707,7 @@ def watchdog(
|
||||
"outcome": reconciled.get("outcome"),
|
||||
"gitea_issue": reconciled.get("gitea_issue"),
|
||||
"existing_link": reconciled.get("existing_link"),
|
||||
"recurrence_comment": reconciled.get("recurrence_comment"),
|
||||
"reasons": reconciled.get("reasons"),
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user