Skip to content

feat(issues): incident_parent on the per-resource path (drawer + MCP get_resource/diagnose)#1057

Merged
nadaverell merged 3 commits into
mainfrom
feat/incident-pointer-per-resource
Jul 4, 2026
Merged

feat(issues): incident_parent on the per-resource path (drawer + MCP get_resource/diagnose)#1057
nadaverell merged 3 commits into
mainfrom
feat/incident-pointer-per-resource

Conversation

@nadaverell

@nadaverell nadaverell commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Completes the symptom→root cause-pointer feature (#1056) by extending the incident_parent reverse pointer to the per-resource surfaces — the resource drawer's Operational Issues block, and the MCP get_resource / diagnose tools. #1056 shipped the pointer on the cluster Issues view + MCP issues; this closes the one remaining gap so every surface that shows an issue can show why it exists.

What changed

The per-resource path (RelatedIssues, behind /api/issues/resource/..., get_resource, diagnose) composed issues in flat/ungrouped mode, where the whole-row coverage gate can't be evaluated — so incident_parent was deliberately left unset there (#1056 even removed the drawer back-pointer because nothing populated it).

Now RelatedIssues runs the grouped-mode enrichment (enrichDiagnosticContext(grouped, flat, grouped, p)) after grouping — exactly mirroring the cluster path — so the grouped issues get the same coverage-gated incident_parent. It's one extra enrichment pass (not a second Compose); the link computation reuses the already-composed flat + grouped sets.

  • Backend: one-line addition in RelatedIssues.
  • Frontend: re-adds the drawer back-pointer in ResourceIssuesSection ("Caused by: ‹kind/name›", honest per-fact-type wording, clickable) — now that the path populates it.
  • MCP get_resource / diagnose carry incident_parent on their related issues automatically.

The coverage gate, best-parent rule, and direction allow-list are unchanged — the per-resource path now simply runs the same well-gated assignment.

Scope note — slightly broader than the pointer alone: the grouped-mode pass also recomputes ChangeContext on the grouped rows and collapses blast-radius related_issues to grouped refs with counts, so the drawer/MCP payloads now match what the cluster Issues view shows (previously they carried the flat representative's uncollapsed context). This is the intended parity, not an accident. The GitOps insights resolver's precomposed RelatedIssuesFrom path deliberately skips the enrichment — it projects issues down to Reason/Message/Category/Severity, so the pointer would be dropped anyway; the contract is documented on RelatedIssuesFrom and pinned by test.

Testing

  • go test ./internal/issues ./internal/mcp ./internal/server + pkg/issuesapi — green; make tsc green.
  • New test TestRelatedIssues_PopulatesIncidentParent: a PVC + a covered unschedulable pod → RelatedIssues returns the pod issue with incident_parent → PVC/data (high).
  • New test TestRelatedIssues_IncidentParentCoverageGate (review follow-up): a root covering only 2 of 3 members of a grouped symptom must NOT attach the pointer — mutation-verified (passing the flat set as the grouped argument fails it).
  • New test TestRelatedIssuesFrom_NoIncidentParentOnBarePair (review follow-up): pins that a bare GroupIssues pair yields rows without the pointer.
  • Live-verified on kind: induced a pending PVC + a 2-replica Deployment mounting it → /api/issues/resource/Deployment/... returned the unschedulable issue with incident_parent → PVC/data (high), and the resource drawer's Operational Issues rendered "Caused by: PersistentVolumeClaim / data" (clickable, 0 console errors).

Not included

  • node blast-radius live-verify: still unit-tested only. Inducing a node MemoryPressure/DiskPressure/PIDPressure condition requires actually exhausting the node's (host-shared) memory/disk on the single-node test cluster — too risky to do safely. The pointer + forward link are unit-tested; live verification waits for a disposable multi-node environment.
  • Demotion / true incident grouping: still its own deferred effort.

Note

Medium Risk
Changes causal attribution on per-resource APIs and drawer UI; logic reuses existing coverage gates but wrong enrichment inputs could mis-parent grouped symptoms.

Overview
Extends coverage-gated incident_parent (symptom → root) to per-resource issue surfaces by running grouped-mode enrichDiagnosticContext inside RelatedIssues after GroupIssues, matching the cluster Issues path. MCP get_resource / diagnose and /api/issues/resource/... pick this up without separate API changes.

Contract: RelatedIssuesFrom still only matches; callers that pass a bare GroupIssues pair (e.g. GitOps insights compose-once) do not get incident_parent unless they enrich themselves. Comments in diagnostic_context.go, grouping.go, and tests document that split.

UI: ResourceIssuesSection shows the back-pointer (incidentParentLabel + optional clickable kind / name) when the API populates incident_parent.

Tests: Happy path (PVC → unschedulable pod), whole-row coverage gate (partial fan-out → no pointer), and bare-pair vs enriched RelatedIssues behavior.

Reviewed by Cursor Bugbot for commit 197030a. Bugbot is set up for automated code reviews on this repo. Configure here.

…resource/diagnose)

RelatedIssues now runs the grouped-mode enrichment (mirroring the cluster path) so
the symptom→root pointer is coverage-gated and populated for the resource drawer,
get_resource, and diagnose — not just the cluster Issues view + MCP issues. Re-adds
the drawer back-pointer render (removed in #1056 when the path didn't populate it).
@nadaverell nadaverell requested a review from hisco as a code owner June 29, 2026 19:45
…(codex)

The per-resource RelatedIssues path now assigns incident_parent via a grouped-mode
re-enrichment, so the gate + foldGroup comments no longer say it's unset there.
…ontract on the per-resource path

Review follow-ups:
- New TestRelatedIssues_IncidentParentCoverageGate: a root covering only 2 of
  3 members of a grouped symptom must not attach the pointer (guards the
  grouped argument at the RelatedIssues enrichment call — mutation-verified).
- New TestRelatedIssuesFrom_NoIncidentParentOnBarePair: a bare GroupIssues
  pair yields rows without the pointer; enrichment is the caller's
  responsibility (pins the GitOps insights resolver's divergence as a
  decision, not an accident).
- Correct foldGroup's "Both paths" comment (the precomposed
  RelatedIssuesFrom path never assigns the pointer) + document the contract
  on RelatedIssuesFrom; reword the diagnostic_context gate comment so
  ?view=flat doesn't read as the only ungrouped caller.
- Drawer: hoist issue.incident_parent to drop the non-null assertion.
@nadaverell nadaverell merged commit 896bdbe into main Jul 4, 2026
9 checks passed
@nadaverell nadaverell deleted the feat/incident-pointer-per-resource branch July 4, 2026 00:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant