Skip to content

feat(studio): delete persisted operations#2553

Merged
comatory merged 39 commits intomainfrom
ondrej/eng-8830-controlplane-cli-delete-persistent-operation
Mar 2, 2026
Merged

feat(studio): delete persisted operations#2553
comatory merged 39 commits intomainfrom
ondrej/eng-8830-controlplane-cli-delete-persistent-operation

Conversation

@comatory
Copy link
Copy Markdown
Contributor

@comatory comatory commented Feb 25, 2026

@coderabbitai summary

Checklist

  • I have discussed my proposed changes in an issue and have received approval to proceed.
  • I have followed the coding standards of the project.
  • Tests or benchmarks have been added or updated.
  • Documentation has been updated on https://github.com/wundergraph/cosmo-docs.
  • I have read the Contributors Guide.

Adds functionality to delete persisted operations via Studio. The persisted operations are
uploaded via CLI first, only then they can be visible in the Studio.
Organization admins & developers can perform this action.

Note: The video displays "retire..." but now we're using delete instead

Screen.Recording.2026-02-27.at.9.04.02.mov

Testing

  • ensure you can run cosmo locally
  • follow this guide to register and execute a persisted operation
  • visit your graph's clients section, open the relevant client which you used when executing the operation above, click View Operations
  • you should see list of operations, click ▽ toggle button, you should see the clear/trash button
  • click it (you might get warning if the operation was executed)

Try these as well if you have time:

  • use multiple query ... definitions in one persisted operation file, UI should be able to handle large volume of them in a single operation
  • try retiring an operation that was not executed before (you should see alert dialog in case of missing analytics)
  • you should NOT see the button if you're not owner or developer assigned in your org (I was not able to make invitation flow work on my localhost yet, so can't try different roles)

Considerations

  • UI allows to delete operations individually, there's no bulk operation
  • operation must first be deleted succesfully, in order to be able to delete
    next operation
  • modal dialogs are always shown

Note

We believe the limitations above will be a non-issue, once we have support for retiring
the operations via CLI. Bulk retirement should then be possible via scripting, or any
other kind of automations.
We intend to provide ability to override warnings (if operation has traffic) via a flag.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Feb 25, 2026

Router-nonroot image scan passed

✅ No security vulnerabilities found in image:

ghcr.io/wundergraph/cosmo/router:sha-8f21d40aa3e65f9519ea91de3d48cbb937991ac4-nonroot

@comatory comatory force-pushed the ondrej/eng-8830-controlplane-cli-delete-persistent-operation branch from e9c707b to 30f4a7b Compare February 25, 2026 10:47
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 25, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a persisted-operation retirement flow: proto messages and RPC, updated generated client bindings, backend handler and repository deletion, metrics-based safety check and authorization, expanded tests, frontend retire UI and dialog, and a dev debug script/README note.

Changes

Cohort / File(s) Summary
Proto & Generated Client
proto/wg/cosmo/platform/v1/platform.proto, proto/wg/cosmo/common/common.proto, connect/src/wg/cosmo/platform/v1/platform_pb.ts, connect/src/wg/cosmo/platform/v1/platform_connect.ts, connect/src/wg/cosmo/platform/v1/platform-PlatformService_connectquery.ts, connect/src/wg/cosmo/common/common_pb.ts
Added RetirePersistedOperationRequest/Response and RetirePersistedOperation RPC; added enum value WARN_DESTRUCTIVE_OPERATION; regenerated TS protobuf/connect bindings and updated imports/exports for new messages and RPC.
Backend API Wiring
controlplane/src/core/bufservices/PlatformService.ts, controlplane/src/core/bufservices/persisted-operation/retirePersistedOperation.ts
Wired new retirePersistedOperation into PlatformService; implemented handler that authenticates, resolves federated graph, calls authorizer, checks metrics, retires operation via repository, and returns structured responses including warnings.
Repository & Metrics
controlplane/src/core/repositories/OperationsRepository.ts, controlplane/src/core/repositories/analytics/MetricsRepository.ts
Added OperationsRepository.retirePersistedOperation and createPersistedOperationDTO; added MetricsRepository.getPersistedOperationMetrics to fetch persisted-operation request counts used for destructive-operation checks.
Publish Flow Authorization
controlplane/src/core/bufservices/persisted-operation/publishPersistedOperations.ts
Removed previous multi-condition role gate and added explicit opts.authorizer.authorize call with federatedGraph target payload before schema retrieval.
Tests & Test Utilities
controlplane/test/persisted-operations.test.ts, controlplane/src/core/test-util.js
Expanded test suite to cover publish and retire flows (success, not found, permission, destructive warning/force), blob-storage behaviors, and graph lifecycle; exported TestUser, createTestRBACEvaluator, createTestGroup test helpers.
Frontend UI
studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/clients.tsx, studio/src/components/clients/retire-persisted-operation-dialog.tsx
Added per-operation retire action (TrashIcon), mutation retirePersistedOperation with toasts and confirmation flow, dialog component for force confirmation, and disabled/pending UI states.
Dev Docs & Scripts
controlplane/README.md, controlplane/package.json
Added dev:debug npm script and README line instructing to run pnpm dev:debug to attach a debugger.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat(studio): delete persisted operations' accurately reflects the main feature added: implementing deletion (retirement) of persisted operations through the studio UI.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Nitpick comments (2)
controlplane/src/core/bufservices/persisted-operation/retirePersistedOperation.ts (2)

56-75: Redundant ternary — operation is guaranteed truthy here.

After the early return on line 56-63 when !operation, the ternary operation ? { ... } : undefined on line 69 can never be false. Simplify to unconditional assignment.

Proposed simplification
     return {
       response: {
         code: EnumStatusCode.OK,
       },
-      operation: operation
-        ? {
-            id: operation.id,
-            operationId: operation.operationId,
-          }
-        : undefined,
+      operation: {
+        id: operation.id,
+        operationId: operation.operationId,
+      },
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@controlplane/src/core/bufservices/persisted-operation/retirePersistedOperation.ts`
around lines 56 - 75, The response builds an "operation" field using a redundant
ternary even though earlier in retirePersistedOperation (after the early return
when !operation) operation is always truthy; remove the ternary and set
operation unconditionally (e.g., operation: { id: operation.id, operationId:
operation.operationId }) so the response is simpler and avoids unreachable
branches—update the return in retirePersistedOperation accordingly.

14-76: No audit log entry for this destructive operation.

Other mutating operations in the controlplane typically emit audit log entries. Retiring/deleting a persisted operation is a significant action that should be tracked for compliance and debugging. Consider adding an audit log entry on success, following the pattern used by other handlers.

#!/bin/bash
# Check if publishPersistedOperations creates audit log entries for comparison
rg -n "audit" controlplane/src/core/bufservices/persisted-operation/ -A 2 -B 2
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@controlplane/src/core/bufservices/persisted-operation/retirePersistedOperation.ts`
around lines 14 - 76, The handler retirePersistedOperation currently returns
success without recording an audit trail; after the call to
OperationsRepository.retirePersistedOperation and once operation is confirmed
non-null, emit an audit log entry (following the existing audit pattern used by
other persisted-operation handlers) before returning: include actor info from
authContext, headers from ctx.requestHeader, target info (federatedGraph.id and
req.fedGraphName), the operation identifiers (operation.id and
operation.operationId), and result status EnumStatusCode.OK; place this call
immediately after the operation variable is set and before the final return so
successful retires are recorded.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@controlplane/README.md`:
- Line 78: Replace the incorrect verb form in the README summary sentence:
change "If the frontend ensure that the session endpoint `/v1/auth/session` is
called on focus and load. The user might never be logged out again." to use
"ensures" (e.g., "If the frontend ensures that the session endpoint
`/v1/auth/session` is called on focus and load, the user might never be logged
out again.") so the grammar and sentence flow are correct.

In
`@controlplane/src/core/bufservices/persisted-operation/retirePersistedOperation.ts`:
- Around line 51-54: retirePersistedOperation currently only removes the DB
record via OperationsRepository.retirePersistedOperation but leaves the
operation blob in storage; after the DB delete, call
opts.blobStorage.deleteObject() to remove the blob at the same key used by
publishPersistedOperations (format:
`${organizationId}/${federatedGraph.id}/operations/${clientName}/${operationId}.json`)
using organizationId, federatedGraph.id (or federatedGraphId), req.operationId
and the client name (available in the handler context), and handle/log any
delete errors without failing the overall retire flow.

In `@controlplane/src/core/repositories/OperationsRepository.ts`:
- Around line 154-176: The delete in retirePersistedOperation is under-scoped:
change the delete on federatedGraphPersistedOperations to include the
federatedGraphId condition (same as the initial findFirst) so you only remove
rows matching both operationId and this.federatedGraphId (or use the table's
composite primary key if available); ensure the .where on the delete mirrors the
and(eq(federatedGraphPersistedOperations.operationId, operationId),
eq(federatedGraphPersistedOperations.federatedGraphId, this.federatedGraphId))
filter to avoid deleting rows from other graphs.

In `@controlplane/test/persisted-operations.test.ts`:
- Line 239: The test description contains a typo: update the test title string
in the test call (the test starting with "Should escape persistent operation
client name before storing to blog storage") to use "blob storage" instead of
"blog storage" so it reads "Should escape persistent operation client name
before storing to blob storage".

In `@proto/wg/cosmo/platform/v1/platform.proto`:
- Around line 1285-1289: The RetirePersistedOperationRequest message currently
uses only operationId which is ambiguous; update the protobuf by adding a client
identity field (e.g., string clientId) to RetirePersistedOperationRequest so the
request can be scoped to a single client's persisted operation, and then update
any server-side handlers (RPC implementations that read
RetirePersistedOperationRequest) and validation logic to require and use
clientId together with operationId to locate and retire the persisted operation.

In `@studio/src/pages/`[organizationSlug]/[namespace]/graph/[slug]/clients.tsx:
- Around line 373-374: The retire button is currently exposed to all users and
lacks a confirmation step; gate its visibility and clickability using the same
access check used by CreateClient (call checkUserAccess before rendering the
Tooltip/retire button) and disable or hide the button when the user lacks
permission, and add a confirmation modal (or browser confirm) that must be
accepted before invoking the retire handler (e.g., the retireOperation /
handleRetire function) to perform the destructive action; ensure the
confirmation state prevents accidental double submissions and that the access
check and modal are wired into the Tooltip/button render path.
- Around line 373-388: The Button currently uses asChild with TrashIcon which
causes the disabled prop and semantics to be lost; change the Button in the
Tooltip/TooltipTrigger block to not use asChild so it renders a real <button>,
move the onClick handler from TrashIcon to the Button (keep
disabled={isPending}), and keep TrashIcon as a plain child element (no onClick)
so keyboard focus, accessibility, and the disabled state work correctly when
calling mutate({ operationId: op.id, namespace, fedGraphName: slug }) from the
Button's onClick.
- Around line 223-238: The onSuccess handler for the
useMutation(retirePersistedOperation) call currently only shows a toast and
doesn't refresh the operations list; update the onSuccess in the useMutation
call to call refetch() after verifying data.response?.code === EnumStatusCode.OK
(so the UI reloads the operations list), and add the missing semicolon after the
closing parenthesis of the useMutation(...) invocation; key symbols:
useMutation, retirePersistedOperation, onSuccess, refetch, toast,
EnumStatusCode.

---

Nitpick comments:
In
`@controlplane/src/core/bufservices/persisted-operation/retirePersistedOperation.ts`:
- Around line 56-75: The response builds an "operation" field using a redundant
ternary even though earlier in retirePersistedOperation (after the early return
when !operation) operation is always truthy; remove the ternary and set
operation unconditionally (e.g., operation: { id: operation.id, operationId:
operation.operationId }) so the response is simpler and avoids unreachable
branches—update the return in retirePersistedOperation accordingly.
- Around line 14-76: The handler retirePersistedOperation currently returns
success without recording an audit trail; after the call to
OperationsRepository.retirePersistedOperation and once operation is confirmed
non-null, emit an audit log entry (following the existing audit pattern used by
other persisted-operation handlers) before returning: include actor info from
authContext, headers from ctx.requestHeader, target info (federatedGraph.id and
req.fedGraphName), the operation identifiers (operation.id and
operation.operationId), and result status EnumStatusCode.OK; place this call
immediately after the operation variable is set and before the final return so
successful retires are recorded.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f271ff2 and 30f4a7b.

📒 Files selected for processing (12)
  • connect/src/wg/cosmo/platform/v1/platform-PlatformService_connectquery.ts
  • connect/src/wg/cosmo/platform/v1/platform_connect.ts
  • connect/src/wg/cosmo/platform/v1/platform_pb.ts
  • controlplane/README.md
  • controlplane/package.json
  • controlplane/src/core/bufservices/PlatformService.ts
  • controlplane/src/core/bufservices/persisted-operation/publishPersistedOperations.ts
  • controlplane/src/core/bufservices/persisted-operation/retirePersistedOperation.ts
  • controlplane/src/core/repositories/OperationsRepository.ts
  • controlplane/test/persisted-operations.test.ts
  • proto/wg/cosmo/platform/v1/platform.proto
  • studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/clients.tsx

@comatory comatory force-pushed the ondrej/eng-8830-controlplane-cli-delete-persistent-operation branch from 30f4a7b to fb26429 Compare February 25, 2026 12:09
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (2)
controlplane/src/core/bufservices/persisted-operation/retirePersistedOperation.ts (1)

51-54: Blob storage cleanup is still missing on retirement.

The handler deletes the DB record but leaves the blob at ${organizationId}/${federatedGraph.id}/operations/${clientName}/${operationId}.json orphaned. opts.blobStorage is available via RouterOptions and BlobStorage exposes deleteObject().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@controlplane/src/core/bufservices/persisted-operation/retirePersistedOperation.ts`
around lines 51 - 54, The DB delete leaves an orphaned blob—after calling
OperationsRepository.retirePersistedOperation, call
opts.blobStorage.deleteObject(...) to remove the JSON blob at the same key used
to store it (use the template
`${federatedGraph.organizationId}/${federatedGraph.id}/operations/${req.clientName}/${req.operationId}.json`);
implement this in retirePersistedOperation handler (after the
operationsRepo.retirePersistedOperation call), await the delete, and handle/log
any errors from deleteObject so failures don't crash the handler.
controlplane/test/persisted-operations.test.ts (1)

261-261: Typo: "blog storage" should be "blob storage".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controlplane/test/persisted-operations.test.ts` at line 261, The test title
contains a typo: change "blog storage" to "blob storage" in the test description
string for the test named "Should escape persistent operation client name before
storing to blog storage" in controlplane/test/persisted-operations.test.ts so it
reads "Should escape persistent operation client name before storing to blob
storage"; scan for and update any other occurrences of the same typo in related
test names or messages (e.g., within the same test function or nearby
assertions) to keep wording consistent.
🧹 Nitpick comments (1)
controlplane/src/core/bufservices/persisted-operation/retirePersistedOperation.ts (1)

65-75: Redundant ternary — operation is always truthy here.

The !operation guard on line 56 returns early, so operation is guaranteed truthy at line 69. The ? ... : undefined branch is dead code.

♻️ Proposed fix
     return {
       response: {
         code: EnumStatusCode.OK,
       },
-      operation: operation
-        ? {
-            id: operation.id,
-            operationId: operation.operationId,
-          }
-        : undefined,
+      operation: {
+        id: operation.id,
+        operationId: operation.operationId,
+      },
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@controlplane/src/core/bufservices/persisted-operation/retirePersistedOperation.ts`
around lines 65 - 75, The ternary around operation in retirePersistedOperation
is dead code because operation is guaranteed truthy earlier; replace the
conditional expression with a direct object using operation.id and
operation.operationId (i.e., remove the ? ... : undefined and always include the
operation object) so the returned shape is simpler and avoids unreachable
branches.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@controlplane/src/core/bufservices/persisted-operation/publishPersistedOperations.ts`:
- Around line 105-113: In publishPersistedOperations.ts update the
authorizer.authorize call to use the federatedGraph.targetId field instead of
federatedGraph.id; locate the authorize invocation (the call on
opts.authorizer.authorize with graph: { targetId: ..., targetType:
'federatedGraph' }) and replace federatedGraph.id with federatedGraph.targetId
so it matches other usages like retirePersistedOperation.ts and
updateFederatedGraph.ts.

In
`@controlplane/src/core/bufservices/persisted-operation/retirePersistedOperation.ts`:
- Around line 29-38: The handler in retirePersistedOperation.ts calls
fedRepo.byName(req.fedGraphName, req.namespace) without applying the
DefaultNamespace fallback used in publishPersistedOperations, causing
missing-namespace requests to return ERR_NOT_FOUND; update the code that reads
req.namespace (used when calling FederatedGraphRepository.byName) to apply the
same fallback (req.namespace = req.namespace || DefaultNamespace) before calling
fedRepo.byName so omitted namespaces default correctly.

In `@controlplane/test/persisted-operations.test.ts`:
- Around line 98-118: The test calls SetupTest without enableMultiUsers so
users[TestUser.viewerTimCompanyA] is undefined and changeUserWithSuppliedContext
receives a context missing userId, causing authentication to fail before RBAC is
checked; update the test that calls SetupTest in the 'Should NOT be able to
publish persisted operations in a viewer role' case to pass enableMultiUsers:
true (same pattern as the retirement viewer test) so
users[TestUser.viewerTimCompanyA] is populated and changeUserWithSuppliedContext
exercises the RBAC path used by publishPersistedOperations.
- Around line 351-365: The test accesses
publishPersistedOperations().operations[0].id without verifying the publish call
succeeded or that operations is non-empty; update each call site (the
publishPersistedOperations calls used before retirePersistedOperation) to first
assert the publish response succeeded and that publishOperationsResp.operations
is defined/not-empty (e.g.,
expect(publishOperationsResp.response?.code).toBe(EnumStatusCode.OK) and
expect(publishOperationsResp.operations.length).toBeGreaterThan(0)) or throw a
clear error including publishOperationsResp when empty, then use
publishOperationsResp.operations[0].id for retirePersistedOperation; apply this
check to all three locations mentioned.

---

Duplicate comments:
In
`@controlplane/src/core/bufservices/persisted-operation/retirePersistedOperation.ts`:
- Around line 51-54: The DB delete leaves an orphaned blob—after calling
OperationsRepository.retirePersistedOperation, call
opts.blobStorage.deleteObject(...) to remove the JSON blob at the same key used
to store it (use the template
`${federatedGraph.organizationId}/${federatedGraph.id}/operations/${req.clientName}/${req.operationId}.json`);
implement this in retirePersistedOperation handler (after the
operationsRepo.retirePersistedOperation call), await the delete, and handle/log
any errors from deleteObject so failures don't crash the handler.

In `@controlplane/test/persisted-operations.test.ts`:
- Line 261: The test title contains a typo: change "blog storage" to "blob
storage" in the test description string for the test named "Should escape
persistent operation client name before storing to blog storage" in
controlplane/test/persisted-operations.test.ts so it reads "Should escape
persistent operation client name before storing to blob storage"; scan for and
update any other occurrences of the same typo in related test names or messages
(e.g., within the same test function or nearby assertions) to keep wording
consistent.

---

Nitpick comments:
In
`@controlplane/src/core/bufservices/persisted-operation/retirePersistedOperation.ts`:
- Around line 65-75: The ternary around operation in retirePersistedOperation is
dead code because operation is guaranteed truthy earlier; replace the
conditional expression with a direct object using operation.id and
operation.operationId (i.e., remove the ? ... : undefined and always include the
operation object) so the returned shape is simpler and avoids unreachable
branches.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 30f4a7b and fb26429.

📒 Files selected for processing (3)
  • controlplane/src/core/bufservices/persisted-operation/publishPersistedOperations.ts
  • controlplane/src/core/bufservices/persisted-operation/retirePersistedOperation.ts
  • controlplane/test/persisted-operations.test.ts

@codecov
Copy link
Copy Markdown

codecov bot commented Feb 25, 2026

Codecov Report

❌ Patch coverage is 39.81337% with 387 lines in your changes missing coverage. Please review.
✅ Project coverage is 36.94%. Comparing base (3168ab0) to head (70bd866).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
[...nizationSlug]/[namespace]/graph/[slug]/clients.tsx](https://app.codecov.io/gh/wundergraph/cosmo/pull/2553?src=pr&el=tree&filepath=studio%2Fsrc%2Fpages%2F%5BorganizationSlug%5D%2F%5Bnamespace%5D%2Fgraph%2F%5Bslug%5D%2Fclients.tsx&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wundergraph#diff-c3R1ZGlvL3NyYy9wYWdlcy9bb3JnYW5pemF0aW9uU2x1Z10vW25hbWVzcGFjZV0vZ3JhcGgvW3NsdWddL2NsaWVudHMudHN4) 0.00% 240 Missing and 1 partial ⚠️
...ents/clients/delete-persisted-operation-dialog.tsx 0.00% 70 Missing and 1 partial ⚠️
...lane/src/core/repositories/OperationsRepository.ts 71.69% 30 Missing ⚠️
...ted-operation/check-persisted-operation-traffic.ts 80.00% 15 Missing ⚠️
...es/persisted-operation/deletePersistedOperation.ts 84.14% 13 Missing ⚠️
...c/core/repositories/analytics/MetricsRepository.ts 67.64% 11 Missing ⚠️
controlplane/src/core/blobstorage/index.ts 50.00% 3 Missing ⚠️
...core/bufservices/check/getNamespaceChecksConfig.ts 0.00% 1 Missing ⚠️
...e/bufservices/check/updateNamespaceChecksConfig.ts 0.00% 1 Missing ⚠️
...ane/src/core/repositories/SchemaCheckRepository.ts 50.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2553      +/-   ##
==========================================
- Coverage   42.53%   36.94%   -5.59%     
==========================================
  Files        1014      964      -50     
  Lines      140682   126281   -14401     
  Branches     8088     5153    -2935     
==========================================
- Hits        59836    46657   -13179     
+ Misses      79147    77981    -1166     
+ Partials     1699     1643      -56     
Files with missing lines Coverage Δ
...ntrolplane/src/core/bufservices/PlatformService.ts 82.04% <100.00%> (+0.20%) ⬆️
.../persisted-operation/publishPersistedOperations.ts 66.04% <100.00%> (+1.76%) ⬆️
.../src/core/bufservices/persisted-operation/utils.ts 100.00% <100.00%> (ø)
...c/core/bufservices/subgraph/checkSubgraphSchema.ts 58.56% <100.00%> (ø)
controlplane/src/core/constants.ts 100.00% <100.00%> (ø)
...ntrolplane/src/core/repositories/analytics/util.ts 48.33% <ø> (ø)
controlplane/src/types/index.ts 100.00% <ø> (ø)
...core/bufservices/check/getNamespaceChecksConfig.ts 5.00% <0.00%> (ø)
...e/bufservices/check/updateNamespaceChecksConfig.ts 5.00% <0.00%> (ø)
...ane/src/core/repositories/SchemaCheckRepository.ts 67.51% <50.00%> (ø)
... and 7 more

... and 71 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (6)
controlplane/src/core/bufservices/persisted-operation/retirePersistedOperation.ts (2)

60-63: Blob storage not cleaned up after retirement.

Already flagged in a previous review: the handler removes the DB record but leaves the blob at ${organizationId}/${federatedGraph.id}/operations/${clientName}/${operationId}.json. opts.blobStorage.deleteObject() should be called after the successful retirement, mirroring how deleteFederatedGraph uses blobStorage.removeDirectory().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@controlplane/src/core/bufservices/persisted-operation/retirePersistedOperation.ts`
around lines 60 - 63, The retirePersistedOperation handler currently deletes the
DB record but doesn’t remove the corresponding blob; after you successfully
retire the operation (use the existing OperationsRepository logic that finds
operation via getPersistedOperation and calls the retire method), call
opts.blobStorage.deleteObject(...) with the key constructed as
`${organizationId}/${federatedGraph.id}/operations/${clientName}/${operationId}.json`
(use the same organizationId, federatedGraph.id, clientName and operationId
available in the function) and only after the DB retire succeeds; mirror
deleteFederatedGraph’s pattern (which uses blobStorage.removeDirectory) to
ensure blob cleanup and handle/report any blobStorage errors appropriately.

38-39: Missing DefaultNamespace fallback for req.namespace.

Already flagged in a previous review: fedRepo.byName(req.fedGraphName, req.namespace) will return not-found for the common case where namespace is omitted by the caller, because publishPersistedOperations applies req.namespace = req.namespace || DefaultNamespace before this call.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@controlplane/src/core/bufservices/persisted-operation/retirePersistedOperation.ts`
around lines 38 - 39, The call to fedRepo.byName uses req.namespace directly and
will miss the common default; before calling FederatedGraphRepository.byName
(fedRepo.byName) ensure req.namespace falls back to DefaultNamespace (the same
behavior used in publishPersistedOperations) so the lookup uses req.namespace ||
DefaultNamespace; update the code path around FederatedGraphRepository
instantiation / byName invocation to pass the normalized namespace value.
controlplane/test/persisted-operations.test.ts (3)

99-119: ⚠️ Potential issue | 🟠 Major

Viewer-role publish test still uses the wrong setup — it exercises auth failure, not RBAC.

SetupTest is called without enableMultiUsers: true, so users[TestUser.viewerTimCompanyA] is undefined. Spreading undefined yields {}, meaning changeUserWithSuppliedContext receives a context with no userId. The userId guard in the handler fires first and returns ERROR_NOT_AUTHENTICATED — the same code the assertion checks — but the RBAC path is never reached. The expected status code should be ERROR_NOT_AUTHORIZED once the setup is corrected (same pattern as the viewer retirement test at lines 417–421).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controlplane/test/persisted-operations.test.ts` around lines 99 - 119, The
test is exercising auth failure because SetupTest was called without
enableMultiUsers, so users[TestUser.viewerTimCompanyA] is undefined; update the
test to call SetupTest({ dbname, chClient, enableMultiUsers: true }) so
TestUser.viewerTimCompanyA is populated, keep the changeUserWithSuppliedContext
usage, and then assert the publishPersistedOperations response code is
EnumStatusCode.ERROR_NOT_AUTHORIZED (not ERROR_NOT_AUTHENTICATED) to verify RBAC
denial for the viewer role.

352-366: ⚠️ Potential issue | 🟡 Minor

Publish response unchecked before accessing operations[0].id.

If publishPersistedOperations fails the response code check, operations will be empty and operations[0].id will throw, making the true failure hard to diagnose. The same omission is present at lines 385–390 and 429–434.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controlplane/test/persisted-operations.test.ts` around lines 352 - 366, The
test accesses publishOperationsResp.operations[0].id without asserting the
publishPersistedOperations result succeeded; update the test to first assert
publishOperationsResp.response?.code === EnumStatusCode.OK (or that
publishOperationsResp.operations.length > 0) before using
publishOperationsResp.operations[0].id, and apply the same guard/assertion at
the other occurrences noted (the blocks around lines 385–390 and 429–434) so
retirePersistedOperation is only called with a valid operation id.

262-262: ⚠️ Potential issue | 🟡 Minor

Typo: "blog storage" should be "blob storage".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controlplane/test/persisted-operations.test.ts` at line 262, Fix the typo in
the test description and any related strings: replace "blog storage" with "blob
storage" in the test titled "Should escape persistent operation client name
before storing to blog storage" (the test callback passed to test(...)), and
update any other occurrences in that test or nearby assertions/messages so they
consistently reference "blob storage" instead of "blog storage".
proto/wg/cosmo/platform/v1/platform.proto (1)

1285-1290: ⚠️ Potential issue | 🟠 Major

RetirePersistedOperationRequest is still missing a client identity field.

operationId is user-supplied (see PersistedOperation.id) and is not globally unique — it is only unique within a client scope. Every other persisted-operation RPC (GetPersistedOperations, PublishPersistedOperations) carries explicit client identity. Without a clientId/clientName field the retirement handler cannot deterministically target a single record when the same operationId string exists under two different clients in the same federated graph.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@proto/wg/cosmo/platform/v1/platform.proto` around lines 1285 - 1290,
RetirePersistedOperationRequest lacks client identity; add a client identifier
field (e.g., string clientId = 5; or string clientName = 5;) to the message to
match other persisted-operation RPCs, regenerate protobuf artifacts, and update
any code that constructs/parses RetirePersistedOperationRequest and the retire
handler/query logic (the server-side retirePersistedOperation handler and any
DAO/query that currently matches only
RetirePersistedOperationRequest.operationId) to include the new clientId when
locating the persisted operation so retirements target a single client's record.
🧹 Nitpick comments (1)
controlplane/test/persisted-operations.test.ts (1)

368-399: Graph setup runs under the dev role, which could cause misleading failures.

authenticator.changeUserWithSuppliedContext is called at lines 377–380 before setupFederatedGraph at line 382, so graph creation, subgraph publish, and persisted-operation publish all execute as the dev user. If the dev role ever lacks graph-creation rights the setupFederatedGraph assertions will produce an opaque error rather than a test-design hint. Consider switching to the dev role only after the shared graph setup, consistent with the viewer retirement test pattern (lines 426–439).

♻️ Proposed fix
       const { client, server, users, authenticator } = await SetupTest({
         dbname,
         chClient,
         enableMultiUsers: true,
       });

       testContext.onTestFinished(() => server.close());

-      authenticator.changeUserWithSuppliedContext({
-        ...users[TestUser.devJoeCompanyA]!,
-        rbac: createTestRBACEvaluator(createTestGroup({ role: 'organization-developer' })),
-      });
-
       const fedGraphName = genID('fedGraph');
       await setupFederatedGraph(fedGraphName, client);

+      authenticator.changeUserWithSuppliedContext({
+        ...users[TestUser.devJoeCompanyA]!,
+        rbac: createTestRBACEvaluator(createTestGroup({ role: 'organization-developer' })),
+      });
+
       const publishOperationsResp = await client.publishPersistedOperations({
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controlplane/test/persisted-operations.test.ts` around lines 368 - 399, The
test "Should be able to retire a persisted operation in dev role" switches the
user context via authenticator.changeUserWithSuppliedContext before calling
setupFederatedGraph, which causes graph creation/publish to run as the dev user
and can hide permission-related failures; move the
authenticator.changeUserWithSuppliedContext call to after setupFederatedGraph
(and after publishPersistedOperations), so setupFederatedGraph and initial
publishes run under the original privileged context and only the
retirePersistedOperation call runs under the developer RBAC context referenced
by createTestGroup({ role: 'organization-developer' }).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@controlplane/src/core/bufservices/persisted-operation/retirePersistedOperation.ts`:
- Around line 23-30: The early-return guard that checks !opts.chClient before
authentication incorrectly leaks analytics-enabled state, returns the wrong
shape (includes filters: [] not part of RetirePersistedOperationResponse), and
blocks DB retirement unnecessarily; instead, move authentication to run before
any chClient check, remove the early return that returns filters, and change
logic in retirePersistedOperation (the branch around opts.chClient and the
traffic-safety/metrics check) to treat missing opts.chClient as "no traffic
data" (skip ClickHouse metric validation, log or annotate a warning in the
response) while still proceeding to call OperationsRepository.retire (Postgres)
to perform the retirement; ensure the function returns a valid
RetirePersistedOperationResponse shape in all paths and keeps use of
EnumStatusCode for errors.

In `@controlplane/src/core/repositories/analytics/MetricsRepository.ts`:
- Around line 1348-1366: The query is summing only the first 5-minute bucket;
change the SQL in MetricsRepository to aggregate across buckets by using
SUM(TotalRequests) AS TotalRequests from operation_request_metrics_5_30 (instead
of selecting TotalRequests directly), update the parameter name from graphId to
federatedGraphId to match other methods, call this.client.queryPromise with the
new federatedGraphId param, and when reading the result use a safe optional
access like results[0]?.TotalRequests and coerce to a number (e.g.,
Number(results[0]?.TotalRequests || 0)) before returning totalRequests.

In `@controlplane/test/persisted-operations.test.ts`:
- Around line 456-472: The test accesses publishOperationsResp.operations[0].id
without first verifying the publish succeeded; update both call sites that use
publishPersistedOperations (the variables publishOperationsResp at lines near
the retirePersistedOperation calls) to assert
publishOperationsResp.response?.code === EnumStatusCode.OK (or throw/assert with
a helpful message) before reading operations[0].id, so a publish failure yields
a clear test assertion instead of an unhelpful crash; apply the same check at
the second occurrence as well.

---

Duplicate comments:
In
`@controlplane/src/core/bufservices/persisted-operation/retirePersistedOperation.ts`:
- Around line 60-63: The retirePersistedOperation handler currently deletes the
DB record but doesn’t remove the corresponding blob; after you successfully
retire the operation (use the existing OperationsRepository logic that finds
operation via getPersistedOperation and calls the retire method), call
opts.blobStorage.deleteObject(...) with the key constructed as
`${organizationId}/${federatedGraph.id}/operations/${clientName}/${operationId}.json`
(use the same organizationId, federatedGraph.id, clientName and operationId
available in the function) and only after the DB retire succeeds; mirror
deleteFederatedGraph’s pattern (which uses blobStorage.removeDirectory) to
ensure blob cleanup and handle/report any blobStorage errors appropriately.
- Around line 38-39: The call to fedRepo.byName uses req.namespace directly and
will miss the common default; before calling FederatedGraphRepository.byName
(fedRepo.byName) ensure req.namespace falls back to DefaultNamespace (the same
behavior used in publishPersistedOperations) so the lookup uses req.namespace ||
DefaultNamespace; update the code path around FederatedGraphRepository
instantiation / byName invocation to pass the normalized namespace value.

In `@controlplane/test/persisted-operations.test.ts`:
- Around line 99-119: The test is exercising auth failure because SetupTest was
called without enableMultiUsers, so users[TestUser.viewerTimCompanyA] is
undefined; update the test to call SetupTest({ dbname, chClient,
enableMultiUsers: true }) so TestUser.viewerTimCompanyA is populated, keep the
changeUserWithSuppliedContext usage, and then assert the
publishPersistedOperations response code is EnumStatusCode.ERROR_NOT_AUTHORIZED
(not ERROR_NOT_AUTHENTICATED) to verify RBAC denial for the viewer role.
- Around line 352-366: The test accesses publishOperationsResp.operations[0].id
without asserting the publishPersistedOperations result succeeded; update the
test to first assert publishOperationsResp.response?.code === EnumStatusCode.OK
(or that publishOperationsResp.operations.length > 0) before using
publishOperationsResp.operations[0].id, and apply the same guard/assertion at
the other occurrences noted (the blocks around lines 385–390 and 429–434) so
retirePersistedOperation is only called with a valid operation id.
- Line 262: Fix the typo in the test description and any related strings:
replace "blog storage" with "blob storage" in the test titled "Should escape
persistent operation client name before storing to blog storage" (the test
callback passed to test(...)), and update any other occurrences in that test or
nearby assertions/messages so they consistently reference "blob storage" instead
of "blog storage".

In `@proto/wg/cosmo/platform/v1/platform.proto`:
- Around line 1285-1290: RetirePersistedOperationRequest lacks client identity;
add a client identifier field (e.g., string clientId = 5; or string clientName =
5;) to the message to match other persisted-operation RPCs, regenerate protobuf
artifacts, and update any code that constructs/parses
RetirePersistedOperationRequest and the retire handler/query logic (the
server-side retirePersistedOperation handler and any DAO/query that currently
matches only RetirePersistedOperationRequest.operationId) to include the new
clientId when locating the persisted operation so retirements target a single
client's record.

---

Nitpick comments:
In `@controlplane/test/persisted-operations.test.ts`:
- Around line 368-399: The test "Should be able to retire a persisted operation
in dev role" switches the user context via
authenticator.changeUserWithSuppliedContext before calling setupFederatedGraph,
which causes graph creation/publish to run as the dev user and can hide
permission-related failures; move the
authenticator.changeUserWithSuppliedContext call to after setupFederatedGraph
(and after publishPersistedOperations), so setupFederatedGraph and initial
publishes run under the original privileged context and only the
retirePersistedOperation call runs under the developer RBAC context referenced
by createTestGroup({ role: 'organization-developer' }).

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fb26429 and 4e0d13a.

📒 Files selected for processing (7)
  • connect/src/wg/cosmo/common/common_pb.ts
  • connect/src/wg/cosmo/platform/v1/platform_pb.ts
  • controlplane/src/core/bufservices/persisted-operation/retirePersistedOperation.ts
  • controlplane/src/core/repositories/analytics/MetricsRepository.ts
  • controlplane/test/persisted-operations.test.ts
  • proto/wg/cosmo/common/common.proto
  • proto/wg/cosmo/platform/v1/platform.proto

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
controlplane/test/persisted-operations.test.ts (3)

262-262: ⚠️ Potential issue | 🟡 Minor

Typo in test title: “blog storage” → “blob storage”.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controlplane/test/persisted-operations.test.ts` at line 262, The test title
string in the test case 'Should escape persistent operation client name before
storing to blog storage' has a typo; change "blog storage" to "blob storage" in
that test declaration (the test(...) in persisted-operations.test.ts) so the
title reads 'Should escape persistent operation client name before storing to
blob storage'.

352-363: ⚠️ Potential issue | 🟡 Minor

Guard publish success before reading operations[0].id.

These tests access publishOperationsResp.operations[0].id without asserting publish succeeded. If publish fails, you get an unhelpful crash instead of a clear assertion.

🛡️ Proposed fix pattern (apply to each call site)
       const publishOperationsResp = await client.publishPersistedOperations({
         fedGraphName,
         namespace: 'default',
         clientName: 'curl',
         operations: [{ id: genID('hello'), contents: `query { hello }` }],
       });
+      expect(publishOperationsResp.response?.code).toBe(EnumStatusCode.OK);
+      expect(publishOperationsResp.operations.length).toBeGreaterThan(0);

       const retireOperationsResp = await client.retirePersistedOperation({
         fedGraphName,
         namespace: 'default',
         operationId: publishOperationsResp.operations[0].id,
       });

Also applies to: 385-396, 429-445, 456-472, 496-513

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controlplane/test/persisted-operations.test.ts` around lines 352 - 363, Guard
against failed publishes before indexing into publish response arrays: for each
call site using publishPersistedOperations and then reading
publishOperationsResp.operations[0].id (e.g., the block that calls
client.publishPersistedOperations and then client.retirePersistedOperation),
first assert the publish succeeded by checking the response status (e.g., a
success flag, no errors, and that operations array length > 0) and fail the test
with a clear message if not; only after that safe assertion use
publishOperationsResp.operations[0].id. Apply the same pattern to all similar
sites (lines around the other publish/retire calls).

99-119: ⚠️ Potential issue | 🟠 Major

Viewer-role publish test is still validating the wrong failure path.

This case is titled as RBAC denial, but setup still does not enable multi-user context, so it can fail as unauthenticated instead of unauthorized. That gives false confidence for role enforcement.

🐛 Proposed fix
-      const { client, server, authenticator, users } = await SetupTest({ dbname, chClient });
+      const { client, server, authenticator, users } = await SetupTest({ dbname, chClient, enableMultiUsers: true });
@@
-      expect(publishOperationsResp.response?.code).toBe(EnumStatusCode.ERROR_NOT_AUTHENTICATED);
+      expect(publishOperationsResp.response?.code).toBe(EnumStatusCode.ERROR_NOT_AUTHORIZED);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controlplane/test/persisted-operations.test.ts` around lines 99 - 119, The
test is asserting an authentication failure but the test setup doesn't enable
multi-user mode so it can fail as unauthenticated; update the SetupTest call to
enable multi-user context (e.g., SetupTest({ dbname, chClient, multiUser: true
})) so the authenticator.changeUserWithSuppliedContext actually sets the active
user, then change the expected assertion from
EnumStatusCode.ERROR_NOT_AUTHENTICATED to the RBAC denial code (e.g.,
EnumStatusCode.ERROR_NOT_AUTHORIZED) after calling
client.publishPersistedOperations, keeping references to SetupTest,
authenticator.changeUserWithSuppliedContext,
createTestRBACEvaluator/createTestGroup, client.publishPersistedOperations and
EnumStatusCode.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@controlplane/test/persisted-operations.test.ts`:
- Around line 166-180: The failing tests omit the required clientName so
validation fails early; update both tests that call
client.publishPersistedOperations (the one named "Should not publish persisted
operations with invalid queries" and the similar test for invalid federated
graph) to include a valid clientName property in the request payload (e.g.,
clientName: genID('testClient')) so the request passes initial validation and
exercises the intended checks; ensure the call to
client.publishPersistedOperations({ fedGraphName, namespace: 'default',
operations: [...], clientName }) includes the new field.

---

Duplicate comments:
In `@controlplane/test/persisted-operations.test.ts`:
- Line 262: The test title string in the test case 'Should escape persistent
operation client name before storing to blog storage' has a typo; change "blog
storage" to "blob storage" in that test declaration (the test(...) in
persisted-operations.test.ts) so the title reads 'Should escape persistent
operation client name before storing to blob storage'.
- Around line 352-363: Guard against failed publishes before indexing into
publish response arrays: for each call site using publishPersistedOperations and
then reading publishOperationsResp.operations[0].id (e.g., the block that calls
client.publishPersistedOperations and then client.retirePersistedOperation),
first assert the publish succeeded by checking the response status (e.g., a
success flag, no errors, and that operations array length > 0) and fail the test
with a clear message if not; only after that safe assertion use
publishOperationsResp.operations[0].id. Apply the same pattern to all similar
sites (lines around the other publish/retire calls).
- Around line 99-119: The test is asserting an authentication failure but the
test setup doesn't enable multi-user mode so it can fail as unauthenticated;
update the SetupTest call to enable multi-user context (e.g., SetupTest({
dbname, chClient, multiUser: true })) so the
authenticator.changeUserWithSuppliedContext actually sets the active user, then
change the expected assertion from EnumStatusCode.ERROR_NOT_AUTHENTICATED to the
RBAC denial code (e.g., EnumStatusCode.ERROR_NOT_AUTHORIZED) after calling
client.publishPersistedOperations, keeping references to SetupTest,
authenticator.changeUserWithSuppliedContext,
createTestRBACEvaluator/createTestGroup, client.publishPersistedOperations and
EnumStatusCode.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4e0d13a and 68a691c.

📒 Files selected for processing (1)
  • controlplane/test/persisted-operations.test.ts

@comatory comatory force-pushed the ondrej/eng-8830-controlplane-cli-delete-persistent-operation branch 2 times, most recently from 6141440 to c1bfaf0 Compare February 26, 2026 08:11
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (13)
controlplane/README.md (1)

78-78: ⚠️ Potential issue | 🟡 Minor

Grammar error persists from previous review.

Line 78 still contains the grammar error that was previously flagged: "ensure" should be "ensures" for correct subject-verb agreement.

✏️ Proposed grammar fix
-__Summary: If the frontend ensure that the session endpoint `/v1/auth/session` is called on focus and load. The user might never be logged out again.__
+__Summary: If the frontend ensures that the session endpoint `/v1/auth/session` is called on focus and load, the user might never be logged out again.__
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controlplane/README.md` at line 78, Fix the grammar in the README sentence:
change "If the frontend ensure that the session endpoint `/v1/auth/session` is
called on focus and load. The user might never be logged out again." to use
correct subject-verb agreement by replacing "ensure" with "ensures" so the
sentence reads "If the frontend ensures that the session endpoint
`/v1/auth/session` is called on focus and load, the user might never be logged
out again."
controlplane/src/core/repositories/analytics/MetricsRepository.ts (1)

1348-1368: ⚠️ Potential issue | 🔴 Critical

Missing SQL aggregation — query returns per-bucket values instead of total.

The operation_request_metrics_5_30 table contains one row per 5-minute bucket. The current query returns multiple rows for operations with traffic across buckets, but only results[0].TotalRequests is read — yielding a single bucket's count instead of the aggregate total. This defeats the purpose of the traffic guard for destructive operations.

🐛 Proposed fix — aggregate with SUM
     const query = `
-    SELECT TotalRequests
+    SELECT sum(TotalRequests) AS TotalRequests
     FROM ${this.client.database}.operation_request_metrics_5_30
     WHERE OrganizationID = {organizationId:String}
-      AND FederatedGraphID = {graphId:String}
+      AND FederatedGraphID = {federatedGraphId:String}
       AND OperationPersistedID = {id:String}`;
 
     const results = await this.client.queryPromise<{
       TotalRequests: number;
     }>(query, {
       id,
       organizationId,
-      graphId,
+      federatedGraphId: graphId,
     });
 
-    return results.length > 0
-      ? {
-          totalRequests: Number(results[0].TotalRequests),
-        }
-      : null;
+    const total = Number(results[0]?.TotalRequests ?? 0);
+    return total > 0 ? { totalRequests: total } : null;

Also aligns parameter naming to federatedGraphId for consistency with other methods in this repository.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controlplane/src/core/repositories/analytics/MetricsRepository.ts` around
lines 1348 - 1368, The query in MetricsRepository (method reading
operation_request_metrics_5_30) returns per-5-minute rows but only reads
results[0], so it must aggregate; change the SQL to SUM(TotalRequests) (e.g.,
SELECT SUM(TotalRequests) AS TotalRequests FROM ... WHERE OrganizationID =
{organizationId:String} AND FederatedGraphID = {federatedGraphId:String} AND
OperationPersistedID = {id:String}) and update the parameter name passed into
this.client.queryPromise to use federatedGraphId instead of graphId so the WHERE
clause matches other methods; keep reading Number(results[0].TotalRequests) but
it will now be the summed total (or handle null to return null).
proto/wg/cosmo/platform/v1/platform.proto (1)

1285-1290: ⚠️ Potential issue | 🟠 Major

Add client scope to retirement request to avoid ambiguous targeting.

operationId alone is not sufficient to uniquely identify a persisted operation across clients. This can retire the wrong record when IDs overlap between clients.

🧩 Proposed API shape update
 message RetirePersistedOperationRequest {
   string fedGraphName = 1;
   string namespace = 2;
   string operationId = 3;
   bool force = 4;
+  string clientId = 5;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@proto/wg/cosmo/platform/v1/platform.proto` around lines 1285 - 1290, The
RetirePersistedOperationRequest message currently only uses operationId (in
message RetirePersistedOperationRequest) and risks ambiguous retirement across
clients; add a client-scoping field (e.g., string clientId or string
clientScope) to RetirePersistedOperationRequest, update any server-side handlers
that accept RetirePersistedOperationRequest to require and validate the new
client identifier when locating persisted operations, and ensure related client
code constructs/populates the new field when sending retire requests so deletion
targets are disambiguated by both operationId and client scope.
controlplane/test/persisted-operations.test.ts (4)

352-363: ⚠️ Potential issue | 🟡 Minor

Assert publish success before dereferencing operations[0] in retirement tests.

These paths access publishOperationsResp.operations[0].id without confirming the publish step succeeded, which can produce noisy failures.

Also applies to: 385-396, 429-445, 456-472, 496-512

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controlplane/test/persisted-operations.test.ts` around lines 352 - 363, The
test dereferences publishOperationsResp.operations[0].id before verifying the
publish succeeded; add an assertion that publishPersistedOperations returned a
successful result and that publishOperationsResp.operations is an array with at
least one element (e.g., assert publishOperationsResp.success/ok and
publishOperationsResp.operations.length > 0) before calling
client.retirePersistedOperation. Apply the same guard/assertion pattern for
every test block that publishes then retires (the publishPersistedOperations =>
retirePersistedOperation sequences around the existing dereferences).

173-177: ⚠️ Potential issue | 🟠 Major

Negative publish tests omit clientName, so they can fail before the intended validation.

Both tests should include a valid clientName to ensure they exercise invalid-query / invalid-graph behavior rather than request-shape validation.

Also applies to: 217-221

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controlplane/test/persisted-operations.test.ts` around lines 173 - 177, The
negative tests calling client.publishPersistedOperations currently omit
clientName so they can fail on request-shape validation instead of the intended
invalid-query/invalid-graph logic; update both calls to
client.publishPersistedOperations (the one using genID('hello') at the shown
block and the other at the 217-221 block) to include a valid clientName (e.g.,
clientName: 'test-client') alongside fedGraphName, namespace and operations so
the tests exercise invalid-query/invalid-graph behavior rather than failing
earlier.

262-262: ⚠️ Potential issue | 🟡 Minor

Fix test title typo: “blog storage” → “blob storage”.

This is minor, but it makes test intent clearer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controlplane/test/persisted-operations.test.ts` at line 262, The test title
contains a typo: change "blog storage" to "blob storage" in the test named
'Should escape persistent operation client name before storing to blog storage'
(the test declaration starting with test('Should escape persistent operation
client name before storing to blog storage', ...)). Update the string to read
"Should escape persistent operation client name before storing to blob storage"
so the intent is clear.

99-119: ⚠️ Potential issue | 🟠 Major

Viewer-role publish test is exercising unauthenticated path, not RBAC denial.

enableMultiUsers is missing, so viewer context can be undefined and assertion may pass for the wrong reason (ERROR_NOT_AUTHENTICATED instead of authorization failure).

🐛 Proposed fix
-const { client, server, authenticator, users } = await SetupTest({ dbname, chClient });
+const { client, server, authenticator, users } = await SetupTest({ dbname, chClient, enableMultiUsers: true });
@@
-expect(publishOperationsResp.response?.code).toBe(EnumStatusCode.ERROR_NOT_AUTHENTICATED);
+expect(publishOperationsResp.response?.code).toBe(EnumStatusCode.ERROR_NOT_AUTHORIZED);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controlplane/test/persisted-operations.test.ts` around lines 99 - 119, The
test is currently exercising an unauthenticated path because SetupTest is
missing enableMultiUsers, so set enableMultiUsers: true when calling SetupTest({
dbname, chClient }) to ensure the viewer context is actually provided; then
update the assertion for publishPersistedOperations to expect the RBAC denial
status (use the project RBAC error constant, e.g.,
EnumStatusCode.ERROR_FORBIDDEN or your codebase's RBAC-specific error) instead
of ERROR_NOT_AUTHENTICATED so the test verifies authorization denial when using
authenticator.changeUserWithSuppliedContext and publishPersistedOperations.
controlplane/src/core/bufservices/persisted-operation/retirePersistedOperation.ts (3)

23-30: ⚠️ Potential issue | 🔴 Critical

Authenticate before any analytics capability check; current guard is unsafe and over-restrictive.

This branch runs before auth, returns a non-schema field (filters), and blocks retirement even though DB retirement does not require ClickHouse.

🐛 Proposed fix
-    if (!opts.chClient) {
-      return {
-        response: {
-          code: EnumStatusCode.ERR_ANALYTICS_DISABLED,
-        },
-        filters: [],
-      };
-    }
     const authContext = await opts.authenticator.authenticate(ctx.requestHeader);
     logger = enrichLogger(ctx, logger, authContext);
@@
-    const metricsRepository = new MetricsRepository(opts.chClient);
-    const operationMetrics = req.force
-      ? null
-      : await metricsRepository.getPersistedOperationMetrics({
+    const operationMetrics =
+      req.force || !opts.chClient
+        ? null
+        : await new MetricsRepository(opts.chClient).getPersistedOperationMetrics({
           organizationId: authContext.organizationId,
           graphId: federatedGraph.id,
           id: operation.hash,
         });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@controlplane/src/core/bufservices/persisted-operation/retirePersistedOperation.ts`
around lines 23 - 30, The current early return in retirePersistedOperation that
checks opts.chClient is misplaced: authentication must run before any analytics
capability gating, and DB retirement should not be blocked when ClickHouse
client is absent; update retirePersistedOperation to perform auth first, remove
or avoid returning the non-schema field "filters" in the error response, and
instead return the proper schema error (e.g., only response with code
EnumStatusCode.ERR_ANALYTICS_DISABLED or an auth error) so that retirement logic
that does not require opts.chClient can proceed when authenticated.

38-40: ⚠️ Potential issue | 🟠 Major

Apply default namespace fallback before graph lookup.

Missing fallback can return false ERR_NOT_FOUND for requests relying on default namespace behavior.

🐛 Proposed fix
+import { DefaultNamespace } from '../../repositories/NamespaceRepository.js';
@@
-    const fedRepo = new FederatedGraphRepository(logger, opts.db, authContext.organizationId);
-    const federatedGraph = await fedRepo.byName(req.fedGraphName, req.namespace);
+    req.namespace = req.namespace || DefaultNamespace;
+    const fedRepo = new FederatedGraphRepository(logger, opts.db, authContext.organizationId);
+    const federatedGraph = await fedRepo.byName(req.fedGraphName, req.namespace);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@controlplane/src/core/bufservices/persisted-operation/retirePersistedOperation.ts`
around lines 38 - 40, The lookup calls FederatedGraphRepository.byName with
req.namespace directly which can be undefined and cause ERR_NOT_FOUND; before
creating/using fedRepo/byName, compute a namespace variable that applies the
default namespace fallback (e.g., const namespace = req.namespace ??
DEFAULT_NAMESPACE or use the module's configured default) and then call
fedRepo.byName(req.fedGraphName, namespace); update references from
req.namespace to that namespace so requests relying on default namespace
behavior succeed.

92-106: ⚠️ Potential issue | 🟠 Major

Retirement should also clean up blob storage for the retired operation.

The DB row is removed, but the persisted operation blob remains, leaving orphaned objects.

🧹 Proposed fix
     const retiredOperation = await operationsRepo.retirePersistedOperation({
       operationId: req.operationId,
     });
+
+    if (retiredOperation?.filePath) {
+      try {
+        await opts.blobStorage.deleteObject({ key: retiredOperation.filePath });
+      } catch (error) {
+        logger.warn({ error, filePath: retiredOperation.filePath }, 'Failed to delete retired operation blob');
+      }
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@controlplane/src/core/bufservices/persisted-operation/retirePersistedOperation.ts`
around lines 92 - 106, The DB row is removed by
operationsRepo.retirePersistedOperation but its persisted blob is left behind;
after calling operationsRepo.retirePersistedOperation and receiving
retiredOperation, call the blob storage cleanup using the retiredOperation's
blob key (e.g., retiredOperation.storageKey / blobKey) to delete the object from
blob storage, handle and log any deletion errors (but don't abort the response),
and ensure the deletion runs after the DB retire call; update
retirePersistedOperation handler to perform that delete and return the same
response shape.
controlplane/src/core/repositories/OperationsRepository.ts (1)

154-176: ⚠️ Potential issue | 🔴 Critical

Retirement delete is still under-scoped and can remove unintended rows.

The lookup is graph-scoped, but delete is where(operationId = ...) only. This can delete across clients/graphs sharing the same operation ID.

🚨 Proposed fix
     await this.db
       .delete(federatedGraphPersistedOperations)
-      .where(eq(federatedGraphPersistedOperations.operationId, operationId));
+      .where(eq(federatedGraphPersistedOperations.id, operationResult.id));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controlplane/src/core/repositories/OperationsRepository.ts` around lines 154
- 176, The delete in retirePersistedOperation is under-scoped (only filters by
operationId) and can remove rows across graphs; update the delete call on
federatedGraphPersistedOperations to include the same federatedGraphId predicate
used in the initial lookup (i.e., combine
eq(federatedGraphPersistedOperations.operationId, operationId) AND
eq(federatedGraphPersistedOperations.federatedGraphId, this.federatedGraphId)),
so the deletion is scoped to the current graph and matches the lookup logic.
studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/clients.tsx (2)

228-243: ⚠️ Potential issue | 🟠 Major

Refresh persisted operations after a successful retirement.

Success toasts are shown, but the list is not refetched, so retired operations stay visible until manual refresh/navigation.

🐛 Proposed fix
   const { mutate, isPending } = useMutation(retirePersistedOperation, {
     onSuccess(data) {
       if (data.response?.code !== EnumStatusCode.OK) {
         toast({
           variant: "destructive",
           title: "Could not retire the operation",
           description: data.response?.details ?? "Please try again",
         });
         return;
       }

       toast({
         title: "Operation retired successfully",
       });
+      refetch();
     },
   });
@@
                   {
-                    onSuccess: () => setPersistedOperationIdToRetire(null),
+                    onSuccess: () => {
+                      setPersistedOperationIdToRetire(null);
+                      refetch();
+                    },
                   },
                 );
               }

Also applies to: 550-566

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@studio/src/pages/`[organizationSlug]/[namespace]/graph/[slug]/clients.tsx
around lines 228 - 243, The success handler for retirePersistedOperation (inside
useMutation where mutate/isPending are declared) currently only shows toasts and
does not refresh the persisted-operations list; update the onSuccess callback to
trigger a refetch of the persisted operations (for example call the existing
refetch function or use your query client to invalidate/refetch the query for
the persisted operations query key such as "persistedOperations") so the retired
item is removed from the UI; apply the same fix to the other similar mutation
block around lines 550-566.

383-414: ⚠️ Potential issue | 🔴 Critical

Use a real button element for retire action; current composition breaks disabled/accessibility semantics.

The click handler is attached to TrashIcon and Button is rendered with asChild, so you lose reliable button behavior.

🐛 Proposed fix
-<Button
-  variant="outline"
-  size="icon"
-  asChild
-  disabled={isPending}
->
-  <TrashIcon
-    height={20}
-    onClick={() => {
-      mutate(/* ... */);
-    }}
-  />
-</Button>
+<Button
+  variant="outline"
+  size="icon"
+  disabled={isPending}
+  onClick={() => {
+    mutate(/* ... */);
+  }}
+>
+  <TrashIcon height={20} />
+</Button>
#!/bin/bash
# Verify local Button implementation and the retire call site wiring.
fd button.tsx studio/src/components/ui --exec sed -n '1,180p' {}
rg -n -C3 "asChild|TrashIcon|Retire the operation|onClick" 'studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/clients.tsx'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@studio/src/pages/`[organizationSlug]/[namespace]/graph/[slug]/clients.tsx
around lines 383 - 414, The retire action currently attaches the click handler
to TrashIcon while Button is rendered with asChild, breaking native button
semantics and disabled/accessibility behavior; move the onClick handler onto the
Button (or remove asChild so Button renders its own <button> element) and render
TrashIcon purely as a visual child, ensure the Button uses disabled={isPending},
provides an aria-label (e.g., "Retire operation"), and calls mutate with
operationId: op.id, namespace and fedGraphName: slug; keep
setPersistedOperationIdToRetire and the existing onSuccess logic unchanged.
🧹 Nitpick comments (1)
studio/src/components/clients/retire-persisted-operation-dialog.tsx (1)

42-49: Expose a submitting state and disable the destructive action while pending.

This button can be clicked multiple times while the mutation is in-flight. Consider accepting an isSubmitting prop and disabling the action to prevent duplicate retire attempts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@studio/src/components/clients/retire-persisted-operation-dialog.tsx` around
lines 42 - 49, The Retire button (Button with onClick={onSubmitButtonClick})
allows duplicate clicks while the retire mutation is in-flight; add an
isSubmitting boolean prop to the RetirePersistedOperationDialog component,
thread it to the button, and disable the destructive action when true (e.g.,
<Button disabled={isSubmitting} onClick={onSubmitButtonClick}>), optionally
adding aria-busy or a loading indicator; ensure callers pass the mutation
loading state into isSubmitting so the button is disabled while the mutation
runs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@controlplane/README.md`:
- Line 78: Fix the grammar in the README sentence: change "If the frontend
ensure that the session endpoint `/v1/auth/session` is called on focus and load.
The user might never be logged out again." to use correct subject-verb agreement
by replacing "ensure" with "ensures" so the sentence reads "If the frontend
ensures that the session endpoint `/v1/auth/session` is called on focus and
load, the user might never be logged out again."

In
`@controlplane/src/core/bufservices/persisted-operation/retirePersistedOperation.ts`:
- Around line 23-30: The current early return in retirePersistedOperation that
checks opts.chClient is misplaced: authentication must run before any analytics
capability gating, and DB retirement should not be blocked when ClickHouse
client is absent; update retirePersistedOperation to perform auth first, remove
or avoid returning the non-schema field "filters" in the error response, and
instead return the proper schema error (e.g., only response with code
EnumStatusCode.ERR_ANALYTICS_DISABLED or an auth error) so that retirement logic
that does not require opts.chClient can proceed when authenticated.
- Around line 38-40: The lookup calls FederatedGraphRepository.byName with
req.namespace directly which can be undefined and cause ERR_NOT_FOUND; before
creating/using fedRepo/byName, compute a namespace variable that applies the
default namespace fallback (e.g., const namespace = req.namespace ??
DEFAULT_NAMESPACE or use the module's configured default) and then call
fedRepo.byName(req.fedGraphName, namespace); update references from
req.namespace to that namespace so requests relying on default namespace
behavior succeed.
- Around line 92-106: The DB row is removed by
operationsRepo.retirePersistedOperation but its persisted blob is left behind;
after calling operationsRepo.retirePersistedOperation and receiving
retiredOperation, call the blob storage cleanup using the retiredOperation's
blob key (e.g., retiredOperation.storageKey / blobKey) to delete the object from
blob storage, handle and log any deletion errors (but don't abort the response),
and ensure the deletion runs after the DB retire call; update
retirePersistedOperation handler to perform that delete and return the same
response shape.

In `@controlplane/src/core/repositories/analytics/MetricsRepository.ts`:
- Around line 1348-1368: The query in MetricsRepository (method reading
operation_request_metrics_5_30) returns per-5-minute rows but only reads
results[0], so it must aggregate; change the SQL to SUM(TotalRequests) (e.g.,
SELECT SUM(TotalRequests) AS TotalRequests FROM ... WHERE OrganizationID =
{organizationId:String} AND FederatedGraphID = {federatedGraphId:String} AND
OperationPersistedID = {id:String}) and update the parameter name passed into
this.client.queryPromise to use federatedGraphId instead of graphId so the WHERE
clause matches other methods; keep reading Number(results[0].TotalRequests) but
it will now be the summed total (or handle null to return null).

In `@controlplane/src/core/repositories/OperationsRepository.ts`:
- Around line 154-176: The delete in retirePersistedOperation is under-scoped
(only filters by operationId) and can remove rows across graphs; update the
delete call on federatedGraphPersistedOperations to include the same
federatedGraphId predicate used in the initial lookup (i.e., combine
eq(federatedGraphPersistedOperations.operationId, operationId) AND
eq(federatedGraphPersistedOperations.federatedGraphId, this.federatedGraphId)),
so the deletion is scoped to the current graph and matches the lookup logic.

In `@controlplane/test/persisted-operations.test.ts`:
- Around line 352-363: The test dereferences
publishOperationsResp.operations[0].id before verifying the publish succeeded;
add an assertion that publishPersistedOperations returned a successful result
and that publishOperationsResp.operations is an array with at least one element
(e.g., assert publishOperationsResp.success/ok and
publishOperationsResp.operations.length > 0) before calling
client.retirePersistedOperation. Apply the same guard/assertion pattern for
every test block that publishes then retires (the publishPersistedOperations =>
retirePersistedOperation sequences around the existing dereferences).
- Around line 173-177: The negative tests calling
client.publishPersistedOperations currently omit clientName so they can fail on
request-shape validation instead of the intended invalid-query/invalid-graph
logic; update both calls to client.publishPersistedOperations (the one using
genID('hello') at the shown block and the other at the 217-221 block) to include
a valid clientName (e.g., clientName: 'test-client') alongside fedGraphName,
namespace and operations so the tests exercise invalid-query/invalid-graph
behavior rather than failing earlier.
- Line 262: The test title contains a typo: change "blog storage" to "blob
storage" in the test named 'Should escape persistent operation client name
before storing to blog storage' (the test declaration starting with test('Should
escape persistent operation client name before storing to blog storage', ...)).
Update the string to read "Should escape persistent operation client name before
storing to blob storage" so the intent is clear.
- Around line 99-119: The test is currently exercising an unauthenticated path
because SetupTest is missing enableMultiUsers, so set enableMultiUsers: true
when calling SetupTest({ dbname, chClient }) to ensure the viewer context is
actually provided; then update the assertion for publishPersistedOperations to
expect the RBAC denial status (use the project RBAC error constant, e.g.,
EnumStatusCode.ERROR_FORBIDDEN or your codebase's RBAC-specific error) instead
of ERROR_NOT_AUTHENTICATED so the test verifies authorization denial when using
authenticator.changeUserWithSuppliedContext and publishPersistedOperations.

In `@proto/wg/cosmo/platform/v1/platform.proto`:
- Around line 1285-1290: The RetirePersistedOperationRequest message currently
only uses operationId (in message RetirePersistedOperationRequest) and risks
ambiguous retirement across clients; add a client-scoping field (e.g., string
clientId or string clientScope) to RetirePersistedOperationRequest, update any
server-side handlers that accept RetirePersistedOperationRequest to require and
validate the new client identifier when locating persisted operations, and
ensure related client code constructs/populates the new field when sending
retire requests so deletion targets are disambiguated by both operationId and
client scope.

In `@studio/src/pages/`[organizationSlug]/[namespace]/graph/[slug]/clients.tsx:
- Around line 228-243: The success handler for retirePersistedOperation (inside
useMutation where mutate/isPending are declared) currently only shows toasts and
does not refresh the persisted-operations list; update the onSuccess callback to
trigger a refetch of the persisted operations (for example call the existing
refetch function or use your query client to invalidate/refetch the query for
the persisted operations query key such as "persistedOperations") so the retired
item is removed from the UI; apply the same fix to the other similar mutation
block around lines 550-566.
- Around line 383-414: The retire action currently attaches the click handler to
TrashIcon while Button is rendered with asChild, breaking native button
semantics and disabled/accessibility behavior; move the onClick handler onto the
Button (or remove asChild so Button renders its own <button> element) and render
TrashIcon purely as a visual child, ensure the Button uses disabled={isPending},
provides an aria-label (e.g., "Retire operation"), and calls mutate with
operationId: op.id, namespace and fedGraphName: slug; keep
setPersistedOperationIdToRetire and the existing onSuccess logic unchanged.

---

Nitpick comments:
In `@studio/src/components/clients/retire-persisted-operation-dialog.tsx`:
- Around line 42-49: The Retire button (Button with
onClick={onSubmitButtonClick}) allows duplicate clicks while the retire mutation
is in-flight; add an isSubmitting boolean prop to the
RetirePersistedOperationDialog component, thread it to the button, and disable
the destructive action when true (e.g., <Button disabled={isSubmitting}
onClick={onSubmitButtonClick}>), optionally adding aria-busy or a loading
indicator; ensure callers pass the mutation loading state into isSubmitting so
the button is disabled while the mutation runs.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 68a691c and c1bfaf0.

📒 Files selected for processing (16)
  • connect/src/wg/cosmo/common/common_pb.ts
  • connect/src/wg/cosmo/platform/v1/platform-PlatformService_connectquery.ts
  • connect/src/wg/cosmo/platform/v1/platform_connect.ts
  • connect/src/wg/cosmo/platform/v1/platform_pb.ts
  • controlplane/README.md
  • controlplane/package.json
  • controlplane/src/core/bufservices/PlatformService.ts
  • controlplane/src/core/bufservices/persisted-operation/publishPersistedOperations.ts
  • controlplane/src/core/bufservices/persisted-operation/retirePersistedOperation.ts
  • controlplane/src/core/repositories/OperationsRepository.ts
  • controlplane/src/core/repositories/analytics/MetricsRepository.ts
  • controlplane/test/persisted-operations.test.ts
  • proto/wg/cosmo/common/common.proto
  • proto/wg/cosmo/platform/v1/platform.proto
  • studio/src/components/clients/retire-persisted-operation-dialog.tsx
  • studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/clients.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
  • controlplane/src/core/bufservices/PlatformService.ts
  • connect/src/wg/cosmo/platform/v1/platform-PlatformService_connectquery.ts
  • connect/src/wg/cosmo/platform/v1/platform_connect.ts
  • controlplane/package.json

@comatory comatory force-pushed the ondrej/eng-8830-controlplane-cli-delete-persistent-operation branch 3 times, most recently from 4fc1479 to 3bf7236 Compare February 26, 2026 14:30
@comatory comatory changed the title ENG-8830: retire persisted operation using studio feat(studio): Retire persisted operations Feb 26, 2026
@comatory comatory changed the title feat(studio): Retire persisted operations feat(studio): retire persisted operations Feb 26, 2026
@comatory comatory force-pushed the ondrej/eng-8830-controlplane-cli-delete-persistent-operation branch from ca4e34f to 77f031b Compare February 26, 2026 19:18
@comatory comatory force-pushed the ondrej/eng-8830-controlplane-cli-delete-persistent-operation branch from 77f031b to b4213c8 Compare February 26, 2026 19:32
@comatory comatory force-pushed the ondrej/eng-8830-controlplane-cli-delete-persistent-operation branch from b4213c8 to 3c709d4 Compare February 26, 2026 19:42
comatory added a commit to wundergraph/cosmo-docs that referenced this pull request Feb 27, 2026
@comatory
Copy link
Copy Markdown
Contributor Author

@StarpTech ready for re-review

@comatory
Copy link
Copy Markdown
Contributor Author

Docs are here btw wundergraph/cosmo-docs#255

@comatory comatory changed the title feat(studio): retire persisted operations feat(studio): delete persisted operations Feb 27, 2026
@comatory
Copy link
Copy Markdown
Contributor Author

comatory commented Feb 27, 2026

@StarpTech please re-review, I believe you wanted this one addressed?

@comatory comatory requested a review from StarpTech February 27, 2026 15:22
@comatory
Copy link
Copy Markdown
Contributor Author

@StarpTech copy fixed

@comatory comatory self-assigned this Feb 27, 2026
comatory and others added 2 commits February 27, 2026 16:45
I did not like the idea of instantiating the class inside of itself,
that could have strange side-effects in future, let's just swap
the `db` propery.
Copy link
Copy Markdown
Contributor

@StarpTech StarpTech left a comment

Choose a reason for hiding this comment

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

LGTM

comatory and others added 2 commits March 1, 2026 19:23
Intended to be fixed in `ENG-9057`. The vulnerability happens on
macOS/Darwin, as most images are used on Linux machines, we're ok
with making this compromise. This was discussed with the team
and OK'ed.
@comatory comatory requested review from a team as code owners March 2, 2026 10:11
@comatory comatory requested a review from ysmolski March 2, 2026 10:11
@comatory comatory merged commit 1adf02c into main Mar 2, 2026
45 checks passed
@comatory comatory deleted the ondrej/eng-8830-controlplane-cli-delete-persistent-operation branch March 2, 2026 11:40
comatory added a commit to wundergraph/cosmo-docs that referenced this pull request Mar 2, 2026
* feat(studio): documentation for retiring persisted operations

wundergraph/cosmo#2553

* Update docs/router/persisted-queries/persisted-operations.mdx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* fix: adjust naming, use delete instead of retire

* fix: make language similar to studio dialog

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants