Skip to content

Preserve Crossplane pause context in agent summaries - #2333

Merged
nadaverell merged 5 commits into
mainfrom
fix/crossplane-pause-summary-context
Oct 26, 2026
Merged

nadaverell merged 5 commits into
mainfrom
fix/crossplane-pause-summary-context

Conversation

@nadaverell

@nadaverell nadaverell commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Crossplane operators can intentionally stop reconciliation while a resource still has recorded readiness or a separate reconcile error. Agent summaries can lose that intent, so a list, search result or Diagnose kickoff describes a paused object as simply Ready and omits why changes are not taking effect.

This adds optional crossplanePause context to existing resource summaries, list/search enrichment, resource detail context and the Diagnose kickoff. requested records the exact crossplane.io/paused: "true" annotation; acknowledged also records the corroborating Synced=False/ReconcilePaused condition. This is annotation intent: exact known built-in group/kind identities (including dynamically read APIService and CustomResourceDefinition) are excluded even when typed resources are converted for generic reads, while foreign CRDs with colliding kinds remain eligible. Acknowledgement makes no freshness, generation, pause-start or child-controller claim. Omission does not rule out another pause mechanism.

On generic CRD rows carrying pause intent, recorded Ready and independent Synced errors remain separate, including rows with an ancestor-derived status. Curated extractors preserve their own readiness ratios and diagnostics if an annotation is copied onto them. Bare summaries retain the pause fact with context=none; Compact retains the exact annotation and raw conditions. The common Summary entry also gives every terminating CRD its deletion/finalizer context and brings the runtime-object CRD path into parity with direct unstructured calls, including existing curated extractors.

The canonical identity table also gains APIService and CustomResourceDefinition, which Radar already reads dynamically. Existing consumers now apply their native group and cluster scope to identity inference, GitOps membership and permission lookups. Opt-in usage statistics recognize those two native resource-view names; custom names and contents remain excluded. Static typed-routing gates are unchanged.

The Diagnose kickoff describes availability as recorded and keeps pause independent from issues and audit findings. No endpoint, MCP tool eligibility, UI, action or issue-suppression changes; existing authorization rules remain in place. Older producers may omit the additive field. Machine-facing descriptions explain requested versus stored acknowledgement and the limits of absence; the machine catalog grows by 531 bytes to 63,761 against a deliberate 63,800-byte limit, preserving existing routing and coverage guidance.

Validation:

  • make tsc and the complete frontend/embed/Go make build pipeline passed, including the final reviewed six-PR integration. Final focused MCP read-contract/catalog/routing checks passed (83.216s), and the native/foreign Argo membership regression passed (1.021s). Catalog: 63,761 bytes within the explicit 63,800-byte allowance. Head 61bced5 integrates main’s manager context alongside the reviewed pause context. All five affected shared-package suites and focused MCP read/catalog/routing checks passed after rebase; the catalog is 63,761 bytes against 63,800. Fresh CI is running. Four commits remain patch-identical; the first commit’s two conflicts retain both features.
  • Focused tests cover requested/recorded pause, independent failure, absent/case-mismatched intent, curated readiness/error preservation, native conversion, foreign kind collisions, Compact secret safety and terminating metadata. An Argo membership regression covers the two dynamically read native identities and foreign same-Kind resources. Generic unpaused summaries retain their pre-existing readiness/error behavior; this patch does not claim universal error parity.
  • Eleven actual REST/read-only MCP checks passed across ten uniquely owned inert EKS fixtures, including enriched and bare lists, detail, searches, Compact, no-Ready acknowledgement, error preservation and refresh. These stored-status fixtures do not establish natural Crossplane or cloud reconciliation. No paid diagnosis run was invoked; kickoff-to-prompt behavior is exercised by fake-client tests.
  • Product visual-test is skipped because this change has no rendered UI delta. The local review packet passed wide/narrow, JavaScript-disabled and offline checks; the long verification-section capture is partial, while its clean narrow top/verdict captures are readable. JavaScript analysis passed; the aggregate CodeQL result is neutral because its Go configuration is missing relative to main.

Final combined full-source review covered this exact feature in the six-PR integration and found no blocker. All four low-severity findings were triaged with source evidence; canonical identity coupling and per-tool wording remain deliberate tradeoffs. Malformed duplicate Synced conditions can differ between Go first-match and UI any-match; valid Crossplane conditions are unique by type, and shared vectors remain a maintenance follow-up. The reviewed integration build passed. Fresh CI for the main integration is pending; the feature remains approved as scoped.

@nadaverell
nadaverell force-pushed the fix/crossplane-pause-summary-context branch 3 times, most recently from cf58f53 to 5349768 Compare October 26, 2026 12:11
@nadaverell
nadaverell marked this pull request as ready for review October 26, 2026 12:30
@nadaverell
nadaverell requested a review from hisco as a code owner October 26, 2026 12:30
@cursor

cursor Bot commented Oct 26, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot uses usage-based billing for this team and requires on-demand usage to be enabled.

A team admin can enable on-demand usage in the Cursor dashboard.

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Preserve Crossplane pause context in agent summaries

🐞 Bug fix âœĻ Enhancement 🧊 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

â€Ē Expose exact Crossplane pause intent and stored acknowledgement across summaries, resource
 context, and Diagnose kickoff.
â€Ē Keep recorded readiness and independent failures visible while excluding intentional pause
 conditions from Issues.
â€Ē Preserve CRD deletion context and document the limits of pause evidence.
Diagram

graph TD
  CRD["CRD object"] --> Pause["Pause classifier"] --> Summary["AI summary"] --> List["List and search"]
  Pause --> Context["Resource context"] --> Detail["Resource detail"]
  Context --> List
  Context --> Diagnose["Diagnose kickoff"]
  CRD --> Issues["Condition issues"]
Loading
High-Level Assessment

Keep the shared, narrowly scoped pause classification and additive projections. Condition-only inference would lose unacknowledged requests, while suppressing all issues for paused resources would hide independent failures; neither meets the stated behavior.

Files changed (19) +667 / -17

Enhancement (8) +73 / -8
ai_diagnose.goCarry pause state into Diagnose health detection +1/-0

Carry pause state into Diagnose health detection

â€Ē Copies the resource summary's pause state into the Diagnose kickoff health signal alongside recorded health.

internal/server/ai_diagnose.go

prune.goRetain the exact pause annotation in Compact output +3/-0

Retain the exact pause annotation in Compact output

â€Ē Allows crossplane.io/paused through Compact annotation pruning without allowing other Crossplane annotations by prefix.

pkg/ai/context/prune.go

summary.goAdd pause to summaries and route runtime CRDs through CRD extraction +5/-0

Add pause to summaries and route runtime CRDs through CRD extraction

â€Ē Adds the optional crossplanePause summary field. Dispatches runtime unstructured objects to the existing CRD summary path for parity with direct unstructured reads.

pkg/ai/context/summary.go

prompt.goFrame Diagnose pause separately from availability +11/-0

Frame Diagnose pause separately from availability

â€Ē Adds optional pause state to the health signal and describes its evidence in the kickoff prompt. Qualifies recorded availability while leaving issue-driven investigation behavior intact.

pkg/investigation/prompt.go

build.goAdd pause state to resource detail context +1/-1

Add pause state to resource detail context

â€Ē Populates resourceContext with the shared Crossplane pause classification during context construction.

pkg/resourcecontext/build.go

crossplane_pause.goCentralize exact Crossplane pause classification +38/-0

Centralize exact Crossplane pause classification

â€Ē Defines requested and acknowledged states from the exact annotation and stored Synced condition. Excludes exact built-in group/kind identities, including native objects converted to unstructured form.

pkg/resourcecontext/crossplane_pause.go

summary.goRetain pause-only per-row summary context +7/-5

Retain pause-only per-row summary context

â€Ē Includes pause state in summaryContext and returns context even when pause is its only triage fact.

pkg/resourcecontext/summary.go

types.goExpose optional pause fields on context contracts +7/-2

Expose optional pause fields on context contracts

â€Ē Adds crossplanePause to detail and per-row context types and documents its bounded per-row size.

pkg/resourcecontext/types.go

Bug fix (2) +31 / -3
source_conditions.goExclude explicit reconciliation pauses from condition issues +2/-1

Exclude explicit reconciliation pauses from condition issues

â€Ē Skips a Synced=False/ReconcilePaused warning only when the exact pause annotation is present, leaving other actionable conditions eligible.

internal/issues/source_conditions.go

summary_crd.goPreserve pause, lifecycle, and independent status in CRD summaries +29/-2

Preserve pause, lifecycle, and independent status in CRD summaries

â€Ē Applies pause and deletion fields at the shared CRD summary entry point, including curated extractors. For generic paused rows, retains recorded Ready or Available status and a separate non-pause Synced failure.

pkg/ai/context/summary_crd.go

Tests (6) +545 / -1
source_conditions_test.goTest pause filtering without losing independent failures +109/-0

Test pause filtering without losing independent failures

â€Ē Covers exact annotation matching, replacement conditions, grouped results, and independent failures. Checks that existing generation and onset handling remain intact.

internal/issues/source_conditions_test.go

tools_catalog_test.goAdjust and report the MCP catalog byte budget +5/-1

Adjust and report the MCP catalog byte budget

â€Ē Raises the deliberate catalog limit to accommodate the new guidance and logs measured catalog size during the budget test.

internal/mcp/tools_catalog_test.go

ai_diagnose_pause_test.goTest Diagnose pause detection and native exclusion +95/-0

Test Diagnose pause detection and native exclusion

â€Ē Verifies requested and acknowledged CRD pause states in kickoff signals. Confirms that a converted native Deployment with the annotation does not acquire Crossplane pause context.

internal/server/ai_diagnose_pause_test.go

crossplane_pause_test.goTest CRD summary pause and lifecycle projections +165/-0

Test CRD summary pause and lifecycle projections

â€Ē Covers summary states, independent readiness and Synced errors, Compact annotation retention, and deletion context. Also checks runtime-object parity, curated extractors, source immutability, and Secret redaction.

pkg/ai/context/crossplane_pause_test.go

crossplane_pause_test.goTest pause-aware Diagnose prompt framing +53/-0

Test pause-aware Diagnose prompt framing

â€Ē Checks distinct language for requested and acknowledged pauses without treating pause alone as a problem. Verifies that independent issues and audit findings still drive investigation.

pkg/investigation/crossplane_pause_test.go

crossplane_pause_test.goTest pause classification and context projections +118/-0

Test pause classification and context projections

â€Ē Covers annotation and condition combinations, optional JSON fields, context tiers, and objects without health. Verifies native identity exclusion, foreign kind collisions, and non-mutation.

pkg/resourcecontext/crossplane_pause_test.go

Documentation (3) +18 / -5
integrations.mdDocument Crossplane pause behavior across agent surfaces +4/-0

Document Crossplane pause behavior across agent surfaces

â€Ē Explains when the intentional pause condition is excluded from Issues and how pause context appears in summaries, enrichment, and Diagnose. Clarifies that stored acknowledgement does not establish freshness or child-controller state.

docs/integrations.md

mcp.mdDescribe pause semantics in MCP resource responses +6/-0

Describe pause semantics in MCP resource responses

â€Ē Documents optional pause states, their evidence limits, and their independence from readiness and issues. Covers bare summaries, Compact output, and terminating CRD rows.

docs/mcp.md

tools.goExplain pause fields in MCP tool descriptions +8/-5

Explain pause fields in MCP tool descriptions

â€Ē Adds requested-versus-acknowledged guidance to list, get, and search descriptions and identifies pause in per-row context schemas. States that pause is independent of readiness and absence does not rule out other mechanisms.

internal/mcp/tools.go

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) ðŸŽĻ UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Native resources can appear paused ✓ Resolved
Description
CrossplanePauseFor relies on BuiltinGroup, whose table omits native APIService and
CustomResourceDefinition identities. When either dynamically read resource carries the annotation,
its summaries and detail context report Crossplane pause intent despite the native-resource
exclusion.
Code

pkg/resourcecontext/crossplane_pause.go[R30-31]

+	if group, builtin := resourceid.BuiltinGroup(u.GetKind()); builtin && group == u.GroupVersionKind().Group {
+		return ""
Evidence
The new filter treats an identity as native only if BuiltinGroup recognizes its kind. That table
expressly does not aim to be exhaustive and contains neither APIService nor
CustomResourceDefinition, while the dynamic cache explicitly watches both as unstructured resources.

pkg/resourcecontext/crossplane_pause.go[25-37]
pkg/resourceid/builtin.go[29-78]
pkg/resourceid/builtin.go[117-120]
internal/k8s/dynamic_cache.go[213-214]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The pause identity check misses native kinds served dynamically because the built-in lookup is deliberately non-exhaustive.
## Fix Focus Areas
- pkg/resourcecontext/crossplane_pause.go[25-32]
- pkg/resourceid/builtin.go[29-78]
- internal/k8s/dynamic_cache.go[213-214]
## Recommended Fix
Exclude exact native group/kind identities served through dynamic reads without excluding foreign CRDs that share a kind. Add tests for annotated APIService and CustomResourceDefinition objects.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Some paused rows hide sync errors ✓ Resolved
Description
summarizeGenericCRD returns an ancestor-derived status before reaching the new paused-resource
Ready and Synced extraction. An annotated generic resource with nonempty status.ancestors and
top-level Ready=True plus Synced=False/ReconcileError retains crossplanePause but omits both
ready and the independent issue.
Code

pkg/ai/context/summary_crd.go[R497-499]

+	if pause != "" {
+		if ready, found := conditions.Find(obj, "Ready"); found {
+			s.Ready = ready.Status
Evidence
A nonempty ancestor list causes the generic summarizer to return before the added pause block. The
ancestor detector accepts that shape without checking for top-level conditions, whereas the
condition reader used by the new block can read those conditions.

pkg/ai/context/summary_crd.go[480-509]
pkg/ai/context/summary_crd.go[598-612]
pkg/conditions/conditions.go[36-67]
pkg/ai/context/summary_crd.go[17-25]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new paused-resource fields are unreachable when a generic resource has a nonempty ancestor list, even if it also publishes top-level conditions.
## Fix Focus Areas
- pkg/ai/context/summary_crd.go[480-509]
## Recommended Fix
Populate the paused-resource Ready and independent Synced issue fields before the ancestor-status early return, while preserving its existing Status choice. Test a paused resource with both nonempty ancestors and top-level Ready and Synced conditions.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
ðŸ’Ą Did you know, you can type 'qodo, fix this' on a finding and the fix lands right on your PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread pkg/resourcecontext/crossplane_pause.go
Comment thread pkg/ai/context/summary_crd.go Outdated
@nadaverell
nadaverell changed the base branch from main to fix/paused-reconcile-condition October 26, 2026 14:06
Base automatically changed from fix/paused-reconcile-condition to main October 26, 2026 15:43
@nadaverell
nadaverell force-pushed the fix/crossplane-pause-summary-context branch from d351c45 to e3455b9 Compare October 26, 2026 15:49
@nadaverell
nadaverell force-pushed the fix/crossplane-pause-summary-context branch from e3455b9 to 61bced5 Compare October 26, 2026 16:53
@nadaverell
nadaverell merged commit a8e8f50 into main Oct 26, 2026
11 checks passed
@nadaverell
nadaverell deleted the fix/crossplane-pause-summary-context branch October 26, 2026 17:01
nadaverell added a commit that referenced this pull request Oct 26, 2026
Kubernetes can accept edits or a deletion request while a Crossplane
resource's controller defers reconciliation. The generic confirmations
do not explain that distinction, making a deletion that remains
Terminating easy to mistake for a failed request and encouraging force
deletion that can bypass cleanup.

This adds context inside the relevant operation:

- Single and bulk delete confirmations identify selected paused
resources holding known Crossplane finalizers. Force confirmation
explains the cleanup it can bypass; annotation alone, or an object
without finalizers, does not get a definite deletion-blocking claim.
- YAML review states whether the server preview keeps or clears the
pause request and covers every submitted document, including unselected
ones. Baselines remain paired with their own server document. Direct
saves explain that omitting an annotation from server-side apply YAML
does not guarantee removal; unavailable previews label unverified state.
Strict Create does not imply unknown existing state.
- Acknowledged resources without spec references use the shared
pause-evidence helper. Single-target editing retains raw condition
evidence when normalized review YAML strips status. Baselines and raw
evidence bind to the effective target’s group, Kind, namespace and name,
so another API group cannot borrow the original object’s pause. Generic
Apply does not infer controller identity from an annotation alone.
Diagnose confirmation uses the existing caller-readable target query.

Removing the annotation requests resumption and may allow pending work,
including deletion; it does not confirm controller activity or imply
that related controllers paused. Existing permission checks and write
callbacks remain authoritative.

Validation: the full `make build` pipeline (frontend, embed, Go), 46
focused notice/review/editor tests, type-check and all current-head CI
passed on the reviewed feature at `8704cb693`. After rebasing on current
main/UI, all feature additions and removals are unchanged. Main’s
cascade and phone controls are preserved; 45 focused
deletion/notice/review tests and the type-check passed, followed by all
required CI on `5762d6986`. Current head `52da1e50c` is rebased onto
main including #2333; its only later product adjustment removes the old
“stuck” reporting sentence. All 35 focused notice/dialog tests and `make
tsc` passed for that adjustment; all required CI now passes on this
head, including the full application build and Settings browser
journeys. Source integration against #2041 head `63a51deea` preserves
both pause notices and its new outcome/selection guards. The sole merge
conflict is the inherited #2333/#2041 MCP catalog budget test; #2041
needs rebase and combined catalog measurement. This is source evidence,
not a combined live-test claim. These include different-group
direct/unavailable review, same-target served-version clearing,
unchanged direct submission, and the initial editor debounce. Actual EKS
API/UI checks validated persisted edits, server prediction differences
and accepted deletion retaining its finalizer despite 409. The
integrated instance rechecked raw ref-less acknowledgement through
status-free review, normal/force context and mixed selection without
another write; desktop screenshots at light 1280×800 and dark 1920×1080
passed inspection. All owned inert fixtures and the app process were
cleaned up.

The final combined source/approach review covered the unchanged feature
patches before the rebase and their parent/context interactions: no
blocker, with four Low observations retained under evidence-backed
scoped dispositions. Qodo’s initial debounce finding was fixed; its
acceptance inference was rejected using conditional-copy and Apply-gate
evidence. The recurring local editor timing flake remains documented;
the exact final 11-test rerun and CI passed with unchanged limits.
Synthetic fixtures cannot establish natural Crossplane cloud
reconciliation.

This builds on the ownership/deletion-impact preview already merged in
#2050. #2041 separately reports accepted deletion waiting on finalizers
as progress and changes confirmation closure/outcome handling. The
Crossplane notice is pre-confirmation context and does not depend on
either the old error response or the new accepted-result response. No
DELETE result parsing, propagation, MCP deletion eligibility or
confirmation-token behavior changes here.
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