Skip to content

ARO-28817: fix(server): exclude deleting resources from spec resync response - #570

Draft
twolff-gh wants to merge 2 commits into
openshift-online:mainfrom
twolff-gh:worktree-fix-resync-deleting
Draft

ARO-28817: fix(server): exclude deleting resources from spec resync response#570
twolff-gh wants to merge 2 commits into
openshift-online:mainfrom
twolff-gh:worktree-fix-resync-deleting

Conversation

@twolff-gh

@twolff-gh twolff-gh commented Aug 3, 2026

Copy link
Copy Markdown

GRPCBrokerService.List and SourceClientImpl.resyncConsumer return soft-deleted resources to the agent during spec resync because FindByConsumerName uses Unscoped(). The agent treats the resync response as "apply these specs" and re-creates ManifestWorks that it already deleted, producing an infinite delete-then-recreate loop.

This is the remaining exposure after ARO-28432 (OnCreate guard), AROSLSRE-1547 (MarkAsDeleting idempotency), and AROSLSRE-1633 (OnUpdate guard), which guard event-driven paths but not the resync path. Filtering is safe because the sdk-go source client's resync handler detects resources present on the agent but absent from the server's response and sends a delete_request for them.

Jira: ARO-28817

Description

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Refactoring (no functional changes)
  • CI/CD or tooling change

Testing

  • Unit tests pass (make test)
  • Integration tests pass (if applicable)
  • Manual verification completed

Checklist

  • My code follows the project's coding conventions
  • I have updated documentation as needed
  • I have added tests that prove my fix/feature works
  • All new and existing tests pass

Summary by CodeRabbit

  • Bug Fixes
    • Resynchronization responses and events now exclude resources marked for deletion.
    • Resource listings return only active resources.
    • Live resources continue to be returned as expected.

…O-28817)

GRPCBrokerService.List and SourceClientImpl.resyncConsumer return
soft-deleted resources to the agent during spec resync because
FindByConsumerName uses Unscoped(). The agent treats the resync
response as "apply these specs" and re-creates ManifestWorks that
it already deleted, producing an infinite delete-then-recreate loop.

This is the remaining exposure after ARO-28432 (OnCreate guard),
AROSLSRE-1547 (MarkAsDeleting idempotency), and AROSLSRE-1633
(OnUpdate guard), which guard event-driven paths but not the resync
path. Filtering is safe because the sdk-go source client's resync
handler detects resources present on the agent but absent from the
server's response and sends a delete_request for them.

Jira: ARO-28817

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Walkthrough

The DAO, broker resynchronization response, and client status-hash batches now exclude resources with a deletion timestamp. Tests cover deleting and live resources.

Changes

Resynchronization filtering

Layer / File(s) Summary
DAO filtering
pkg/dao/resource.go, pkg/dao/mocks/resource.go
FindByConsumerName now excludes resources with a non-null deletion timestamp.
Broker resynchronization filtering
cmd/maestro/server/grpc_broker.go, cmd/maestro/server/grpc_broker_test.go
GRPCBrokerService.List skips deleting resources. Tests verify that deleting resources are excluded and live resources remain included.
Client resynchronization filtering
pkg/client/cloudevents/source_client.go, pkg/client/cloudevents/source_client_test.go
resyncConsumer excludes deleting resources from emitted status-hash batches. The regression test verifies that live resources remain included.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: raelga

🚥 Pre-merge checks | ✅ 10 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Ai-Attribution ⚠️ Warning Both PR commits name Claude Opus 4.6 in Co-Authored-By trailers; neither has an Assisted-by or Generated-by trailer, so the required AI attribution is missing. Replace the AI Co-Authored-By trailers with the required Red Hat Assisted-by or Generated-by attribution.
✅ Passed checks (10 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: excluding deleting resources from the server resync response.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
No-Weak-Crypto ✅ Passed The PR adds deletion filtering, SQL, and tests only; no weak algorithms, custom crypto, or secret comparisons. Existing crypto uses SHA-256 and TLS 1.3.
Container-Privileges ✅ Passed The ARO-28817 patch changes six Go files only; it adds no container/Kubernetes manifests or privileged, host namespace, SYS_ADMIN, root, or allowPrivilegeEscalation settings.
No-Sensitive-Data-In-Logs ✅ Passed The PR adds no logging calls or log fields. Changes only filter deleting resources and use synthetic test IDs such as live-1 and consumer-1.
No-Hardcoded-Secrets ✅ Passed Changed lines add UUID/time test data and a SQL filter; scans found no API keys, tokens, passwords, private keys, credential URLs, or long base64 literals.
No-Injection-Vectors ✅ Passed The PR uses a static SQL predicate with consumerName bound through ?; other changes only filter deletion timestamps and add tests. No listed injection APIs or shell execution were added.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/maestro/server/grpc_broker_test.go`:
- Around line 281-283: Strengthen the List assertions in the exclusion and
inclusion tests around svc.List by decoding each returned ce.Event and verifying
the exact resource ID sets: only live-1 for the deleting-resource exclusion
case, and res-1 and res-2 for the inclusion case. Retain the existing error
checks and use identity-based assertions so duplicates or omitted resources
cannot satisfy the tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: openshift-online/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: df99b5fc-0b8b-409e-a89b-7ba2b5dc03e7

📥 Commits

Reviewing files that changed from the base of the PR and between 298be01 and 3e3dac4.

📒 Files selected for processing (3)
  • cmd/maestro/server/grpc_broker.go
  • cmd/maestro/server/grpc_broker_test.go
  • pkg/client/cloudevents/source_client.go

Comment on lines +281 to +283
evts, err := svc.List(context.Background(), types.ListOptions{ClusterName: "cluster1"})
Expect(err).NotTo(HaveOccurred())
Expect(evts).To(HaveLen(1), "List must exclude resources marked as deleting")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the returned resource identities, not only the event count.

HaveLen(1) can pass if List returns the deleting resource and omits live-1. HaveLen(2) can pass if it returns the same live resource twice. Decode each ce.Event and assert the exact resource ID sets: {live-1} for the exclusion test and {res-1, res-2} for the inclusion test.

Also applies to: 304-306

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/maestro/server/grpc_broker_test.go` around lines 281 - 283, Strengthen
the List assertions in the exclusion and inclusion tests around svc.List by
decoding each returned ce.Event and verifying the exact resource ID sets: only
live-1 for the deleting-resource exclusion case, and res-1 and res-2 for the
inclusion case. Retain the existing error checks and use identity-based
assertions so duplicates or omitted resources cannot satisfy the tests.

…merName (ARO-28817)

Move the root fix from application-layer filtering to the DAO query,
adding `AND deleted_at IS NULL` to FindByConsumerName. This follows the
FindUndelivered precedent and prevents future callers from silently
receiving soft-deleted resources. The application-layer guards in
GRPCBrokerService.List and resyncConsumer are kept as defense-in-depth.

Also adds TestResyncConsumerExcludesDeletingResources to close the
MQTT resync test coverage gap.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/dao/mocks/resource.go`:
- Line 78: Update the soft-delete check in FindUndelivered to use the nullable
state, requiring !resource.DeletedAt.Valid instead of
resource.DeletedAt.Time.IsZero(), while preserving the existing consumer-ID
filter.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: openshift-online/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7a5ecdcb-3aef-4a8e-932d-e3cee6cf9c76

📥 Commits

Reviewing files that changed from the base of the PR and between 3e3dac4 and 593ba84.

📒 Files selected for processing (3)
  • pkg/client/cloudevents/source_client_test.go
  • pkg/dao/mocks/resource.go
  • pkg/dao/resource.go

Comment thread pkg/dao/mocks/resource.go
var resources api.ResourceList
for _, resource := range d.resources {
if resource.ConsumerName == consumerID {
if resource.ConsumerName == consumerID && resource.Meta.DeletedAt.Time.IsZero() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate files =="
fd -a 'resource\.go$' . | sed 's#^\./##'

echo "== inspect target mock =="
file="$(fd 'resource\.go$' . | grep 'pkg/dao/mocks/resource.go' | head -n 1 || true)"
if [ -n "${file:-}" ]; then
  wc -l "$file"
  sed -n '1,140p' "$file" | cat -n
fi

echo "== search dao predicate usage =="
rg -n "deleted_at|DeletedAt|DeletedAt.*IS NULL|IS NULL.*DeletedAt|DeletedAt.Time.IsZero|DeletedAt.Valid" .

Repository: openshift-online/maestro

Length of output: 12264


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== inspect real DAO predicate =="
sed -n '90,150p' pkg/dao/resource.go | cat -n

echo "== inspect Resource metadata =="
sed -n '1,90p' pkg/api/resource_types.go | cat -n
sed -n '45,65p' pkg/api/metadata_types.go | cat -n

echo "== check go.mod gorm versions =="
rg -n 'github.com/go-gorm/gorm|gorm.io/gorm' go.mod go.sum || true

echo "== deterministic GORM DeletedAt null predicate simulation =="
python3 - <<'PY'
# No repo code is executed; this models the observed filter logic.
cases = [
    {"name": "active gorm.DeletedAt default", "Valid": False, "Time": "0001-01-01T00:00:00Z"},
    {"name": "soft deleted", "Valid": True, "Time": "2024-01-01T12:00:00Z"},
    {"name": "invalid valid state same as active in isZero", "Valid": True, "Time": "0001-01-01T00:00:00Z"},
    {"name": "invalid valid state same as deleted in isZero", "Valid": False, "Time": "2024-01-01T12:00:00Z"},
]
print("case\tdeleted_at_null\tmock_Time.IsZero()\tmock_!Valid")
for c in cases:
    time_is_zero = c["Time"] == "0001-01-01T00:00:00Z"
    print(f"{c['name']}\t{not c['Valid']}\t{time_is_zero}\t{not c['Valid']}")
PY

Repository: openshift-online/maestro

Length of output: 6344


Use the nullable soft-delete state in the mock filters.

FindUndelivered also filters on resource.DeletedAt.Time.IsZero() while the DAO uses deleted_at IS NULL; gorm.DeletedAt represents that state with Valid. Use !resource.DeletedAt.Valid here so inconsistent Time/Valid values do not hide or expose deleted resources.

Proposed fix
-		if len(resource.Status) == 0 && resource.DeletedAt.Time.IsZero() && resource.CreatedAt.Before(cutoff) {
+		if len(resource.Status) == 0 && !resource.DeletedAt.Valid && resource.CreatedAt.Before(cutoff) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/dao/mocks/resource.go` at line 78, Update the soft-delete check in
FindUndelivered to use the nullable state, requiring !resource.DeletedAt.Valid
instead of resource.DeletedAt.Time.IsZero(), while preserving the existing
consumer-ID filter.

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