feat: portable resource-deleter idempotency contract (already-gone = success)#1033
feat: portable resource-deleter idempotency contract (already-gone = success)#1033c1-squire-dev[bot] wants to merge 1 commit into
Conversation
Formalize an already-gone-is-success contract for ResourceDeleter/ ResourceDeleterV2.Delete, mirroring the existing GrantAlreadyRevoked idiom used by Revoke. When a connector's delete (or get) authoritatively confirms the addressed resource is already absent at the provider, Delete must return a nil error with a ResourceDoesNotExist annotation instead of an error, so a delete retried after a crash or lost response is safe to treat as success. This is normative for connector authors; baton-sdk is the contract layer only (proto message and generated pb already existed and are unchanged) — the C1 backend is the consumer. Adds ResourceDoesNotExistAnnotations() in pkg/types/resource as a discoverability helper so connectors don't hand-roll the annotation, and tests locking the plumbing through both the DeleteResource (V1) and DeleteResourceV2 RPC handlers: the annotation survives end-to-end, a normal successful delete carries no such annotation, and a genuine delete error still surfaces as an error. Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
General PR Review: feat: portable resource-deleter idempotency contract (already-gone = success)Blocking Issues: 0 | Suggestions: 0 | Threads Resolved: 0 Review SummaryThe full PR diff was scanned for security and correctness. This change is purely additive: godoc documenting the idempotency / Security IssuesNone found. Correctness IssuesNone found. SuggestionsNone. |
Summary
Formalizes a small, portable contract that makes provider resource deletion safely retryable
after a crash, by wiring the already-declared
c1.connector.v2.ResourceDoesNotExistannotation asa typed "already-gone = success" outcome — the resource-plane analog of
GrantAlreadyRevoked.This is the SDK half of the durable-credential revocation lifecycle: when a delete's response is lost
(crash / transport failure after the provider mutation), a blind retry is only safe if the deleter
contract makes already-absent a typed success instead of an ambiguous
NotFounderror.No
.protochange.ResourceDoesNotExist {}already exists inproto/c1/connector/v2/annotation_resource.protobut was never produced or consumed. This PR makesit normative and gives connectors a helper to emit it. The change is purely additive and independent
of #941 (touches none of its files).
The contract
ResourceDeleter/ResourceDeleterV2DeleteMUST be idempotent.DeleteMUSTreturn
nilerror + aResourceDoesNotExistannotation (success-equivalent).DeleteMUST NOT emitResourceDoesNotExistwhen the not-found could instead mean a wrong/malformedresourceId,parentResourceID, or tenant — those return an error, so callers distinguish a realdeletion from a mis-addressed one.
NotFoundandconvert it (consistent with how
GrantAlreadyRevokedworks).ResourceDeleterV2: the V1→V2 shim dropsparentResourceID, which is often required to address the resource for absence confirmation.Why not blanket SDK NotFound→success
Empirically, connector
Delete404-handling is inconsistent (baton-aws IAM/SSO and baton-githubinvitation normalize to success; baton-okta group, baton-github/gitlab user do not). Only the connector
knows whether a provider 404 addresses the intended resource, so central
NotFound-swallowing wouldhide mis-addressing bugs and silently change existing behavior. The typed, connector-emitted annotation
lets C1 distinguish confirmed already-gone (⇒ revoked) from ambiguous NotFound (⇒ reconcile).
Changes
pkg/connectorbuilder/resource_manager.go— normative godoc onResourceDeleterLimited.DeleteandResourceDeleterV2Limited.Delete.pkg/types/resource/resource.go—ResourceDoesNotExistAnnotations()discoverability helper.pkg/connectorbuilder/resource_manager_test.go— tests locking the annotation round-trip throughboth delete RPC handlers, a normal-delete negative test, an error-still-surfaces test, and a helper
unit test.
Compatibility
DeleteResource(V2)Responseshape unchanged.buf.yaml(no message/field added or removed).Validation
gofmt -lon changed files: clean.go build ./...: ok.go test ./pkg/connectorbuilder/... ./pkg/types/resource/... ./pkg/annotations/... -count=1: ok(new tests pass, no regressions).
golangci-lint runon changed packages: 0 issues.buf: no.protomodified, sobuf breakingis trivially clean.Follow-ups (not in this PR)
ResourceDoesNotExistin delete-outcome/error classification (currentlyNotFoundis terminal) so already-gone maps to a revoked/success outcome and opaque NotFound stays terminal.
Deleteon confirmed absence (start with those thatalready normalize 404).
retry for non-conforming deleters.
Draft: opened for design review of the SDK contract before connector/backend rollout.