Skip to content

feat(invitation): emit ResourceDoesNotExist marker on confirmed-absent delete (SPEC-09a)#180

Open
c1-squire-dev[bot] wants to merge 2 commits into
mainfrom
paul.querna/nhi/resource-delete-idempotency-marker
Open

feat(invitation): emit ResourceDoesNotExist marker on confirmed-absent delete (SPEC-09a)#180
c1-squire-dev[bot] wants to merge 2 commits into
mainfrom
paul.querna/nhi/resource-delete-idempotency-marker

Conversation

@c1-squire-dev

@c1-squire-dev c1-squire-dev Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

What

Make the invitation ResourceDeleter emit the typed c1.connector.v2.ResourceDoesNotExist{} marker (with nil error) when the provider authoritatively confirms the addressed invitation is already absent, instead of returning an untyped success.

This is the second connector producer of the SPEC-09a already-absent delete outcome (after baton-aws#140), and the first non-AWS one.

Why

Per pqprime SPEC-09a (REQ-VPQ-009A) and conceptually the baton-sdk contract in baton-sdk#1033: a ResourceDeleter(V2) whose provider call authoritatively confirms the correctly-addressed resource is already gone should return annotations.New(&v2.ResourceDoesNotExist{}), nil — the resource-plane analogue of GrantAlreadyRevoked.

This lets C1 distinguish a confirmed already-gone delete (typed success ⇒ REVOKED) from an opaque / mis-addressed NotFound (untyped error ⇒ REVOKE_UNKNOWN), so a delete retried after a crash is classified correctly rather than ambiguously. There is no code dependency on #1033 — the marker type is already vendored in baton-sdk v0.19.1; this PR is independently mergeable.

Change

pkg/connector/invitation.goinvitationResourceType.Delete already treated a CancelInvite 404 as success (desired state achieved). The only new behavior: the marker is appended solely on the pure-absence success path — i.e. no active cancellation succeeded across the configured orgs, yet the invitation is considered removed because a well-formed invitation id returned 404 against a configured org.

  • Ordinary success (an active CancelInvite succeeded) remains success and carries no marker.
  • Every other provider/addressing error is unchanged: wrong resource type, unparseable invitation id, and the "failed to cancel invite" path all still return errors. No new NotFound is swallowed and no error path is broadened.
  • The multi-org loop is handled order-independently via a wasCancelled flag, so a real cancellation in one org is never masked by a 404 in another.

Intentionally excluded: the user deleter

userResourceType.Delete currently surfaces 404 as an error (wrapGitHubError, on both Users.GetByID and RemoveOrgMembership) and has no existing already-absent success branch. Adding the marker there would broaden behavior, which SPEC-09a forbids for this step; it is left for a separate, deliberate change.

No dependency churn

No go.mod / SDK bump. The resource.ResourceDoesNotExistAnnotations() helper from #1033 is unreleased (absent from vendored v0.19.1), so the marker is constructed directly (annotations.New/Append + &v2.ResourceDoesNotExist{}) — both aliases were already imported. A code comment notes swapping to the helper after a future SDK bump. No version files touched.

Tests

New pkg/connector/invitation_delete_test.go (go-github-mock inline harness, matching invitation_test.go):

  • confirmed absence (404) → annotations Contains(ResourceDoesNotExist), nil error
  • ordinary success (204) → no marker, nil error
  • other error (500) → error, nil annotations
  • exact addressing preserved → DELETE /orgs/test-org-12/invitations/5551212
  • malformed invitation id → error before any API call
  • wrong resource type → error

Verified: gofmt clean, go build ./..., go test -race ./pkg/connector/ green (focused + full package, no regressions), golangci-lint adds zero new issues.


Draft: opened as part of the SPEC-09a connector rollout for review before merge.

🤖 Generated with Claude Code

Comment thread pkg/connector/invitation.go Outdated
Comment on lines +370 to +381
if !wasCancelled {
// No active cancellation succeeded, yet the invitation is considered
// removed: the provider authoritatively reported it already absent
// (404) for a well-formed invitation id against a configured org.
// Emit the typed already-absent marker so a delete retried after a
// crash is classified as success rather than an ambiguous NotFound
// (SPEC-09a; baton-sdk#1033). An active cancellation is ordinary
// success and intentionally carries no marker. A future baton-sdk
// bump can replace this direct construction with
// resource.ResourceDoesNotExistAnnotations().
annotations.Append(&v2.ResourceDoesNotExist{})
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Suggestion: In a multi-org config, if one org returns 404 (isRemoved=true, wasCancelled stays false) while another returns a non-404 real error (e.g. 500), the loop still exits with isRemoved=true and emits ResourceDoesNotExist. That overstates the "authoritatively confirmed absent" semantics since one org's state was never determined. Low impact (invitation IDs are org-scoped and single-org is typical, and the pre-existing loop already ignored that 500), but you may want to only emit the marker when no non-404 error occurred across orgs. (confidence: low)

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Connector PR Review: feat(invitation): emit ResourceDoesNotExist marker on confirmed-absent delete (SPEC-09a)

Blocking Issues: 0 | Suggestions: 0 | Threads Resolved: 0
Criteria: Criteria status: loaded .claude/skills/ci-review.md from trusted base d1eefbb.
Review mode: full
View review run: https://github.com/ConductorOne/baton-github/actions/runs/30014346054

Review Summary

Scanned the full PR diff for security and correctness. The change adds the typed already-absent marker to invitationResourceType.Delete only on the pure-absence success path, gated by wasCancelled/hadRealError flags so it fires solely when every configured org authoritatively returned 404 with no active cancellation and no non-404 error. The marker logic is order-independent and correct, error paths and addressing are unchanged, helpers extractRateLimitData and isNotFoundError are nil-safe, no go.mod or SDK changes, and the new invitation_delete_test.go gives good coverage: confirmed absence, ordinary success, 500 error, exact addressing, malformed id, wrong type, and multi-org 404 plus 5xx. No new issues found.

Security Issues

None found.

Correctness Issues

None found.

Suggestions

None.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No blocking issues found.

@c1-squire-dev

c1-squire-dev Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

Thanks — addressed the multi-org suggestion in 72e0b835. Added a hadRealError flag so the ResourceDoesNotExist marker is emitted only when !wasCancelled && !hadRealError, i.e. a 404 with no competing non-404 error. The pre-existing success/error boundary is unchanged (a 404 still sets isRemoved → success); only the marker is now strictly gated on authoritative absence, matching SPEC-09a's no-false-positives rule. New multi-org test covers the 404+500 case: success preserved, no marker.

Note: the verify / lint failure is a pre-existing baseline on main (SA1019 trait-profile deprecations across ~7 untouched files, red since the 2026-07-21 dependency bump — last green Verify run predates it). This PR adds zero new lint issues.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No blocking issues found.

@c1-squire-dev

c1-squire-dev Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

Heads up: the red verify / lint on this PR is the pre-existing main SA1019 baseline (baton-sdk trait-profile deprecations from the 2026-07-21 dependency bump), not anything introduced here. #181 is a standalone prerequisite that repairs that baseline (CI-exact golangci-lint v2.11.4: 15 → 0 issues). Once #181 merges, this PR should rebase onto main to pick up the green lint — no changes to this branch are needed for the fix itself.

pquerna and others added 2 commits July 23, 2026 14:07
…t delete

The invitation ResourceDeleter already treats a provider 404 on CancelInvite
as success (desired state achieved). Per SPEC-09a (REQ-VPQ-009A), a deleter
that authoritatively confirms the correctly-addressed resource is already
absent should return the typed c1.connector.v2.ResourceDoesNotExist{} marker
(nil error) instead of an untyped success, so a delete retried after a crash
is classified as REVOKED rather than an ambiguous NotFound.

Only the confirmed-absence branch changes: the marker is emitted solely when
no active cancellation succeeded yet the invitation is considered removed
(a 404 for a well-formed invitation id against a configured org). An active
cancellation remains ordinary success and carries no marker; every other
provider/addressing error is unchanged.

The user deleter is intentionally excluded: it surfaces 404 as an error
(wrapGitHubError) and has no existing already-absent success branch, so adding
the marker there would broaden behavior.

No go.mod/SDK bump: the marker type is already vendored (baton-sdk v0.19.1);
the resource.ResourceDoesNotExistAnnotations() helper from baton-sdk#1033 is
unreleased, so the marker is constructed directly with a note to adopt the
helper after a future SDK bump.

Tests: new invitation_delete_test.go covers confirmed-absence -> marker,
ordinary success -> no marker, other error unchanged, exact org/invitation-id
addressing preserved, and malformed/wrong-type inputs.

Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
…n-404 error

Address the automated pr-review suggestion: in a multi-org config, a 404 in
one org combined with a non-404 error (e.g. 5xx) in another previously still
emitted ResourceDoesNotExist, slightly overstating "confirmed absent." Track a
hadRealError flag and emit the marker only when !wasCancelled && !hadRealError,
so the typed already-absent outcome strictly means the provider authoritatively
confirmed absence with no competing undetermined state — matching SPEC-09a's
no-false-positives rule.

The pre-existing success/error boundary is unchanged (a 404 still sets
isRemoved, so the call still returns success); only the marker emission is
tightened. Adds a multi-org test asserting the 404+500 case keeps success but
carries no marker.

Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
@c1-squire-dev
c1-squire-dev Bot force-pushed the paul.querna/nhi/resource-delete-idempotency-marker branch from 72e0b83 to 9437b89 Compare July 23, 2026 14:07

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No blocking issues found.

@c1-squire-dev
c1-squire-dev Bot marked this pull request as ready for review July 23, 2026 14:16
@c1-squire-dev
c1-squire-dev Bot requested a review from a team July 23, 2026 14:16
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