Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/conductorone/baton-github
go 1.25.2

require (
github.com/conductorone/baton-sdk v0.24.6
github.com/conductorone/baton-sdk v0.25.1-0.20260825204020-991ca45253a7

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: two things on this bump.

  1. It pins an unreleased pseudo-version (v0.25.1-0.20260825204020-991ca45253a7). The PR already commits to swapping to the tagged release before undrafting — flagging so it isn't lost.
  2. The bump is scoped in the description to "adds the two PENDING enum values", but v0.24.6 → v0.25.x also changes runtime behavior the connector inherits: the default dotc1z storage engine flips from SQLite to Pebble for new files (pkg/dotc1z/engine_registry.go, pkg/field/defaults.go), and the --diff-syncs / --base-sync-id / --applied-sync-id flags are removed along with diff-sync support. Nothing in this repo references those flags, so there is no build or CI break, but the storage-engine default change affects the artifact this connector produces and is worth calling out in the PR description and release notes rather than shipping silently.

github.com/deckarep/golang-set/v2 v2.9.0
github.com/ennyjfrick/ruleguard-logfatal v0.0.2
github.com/golang-jwt/jwt/v5 v5.2.2
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ github.com/cockroachdb/swiss v0.0.0-20251224182025-b0f6560f979b h1:VXvSNzmr8hMj8
github.com/cockroachdb/swiss v0.0.0-20251224182025-b0f6560f979b/go.mod h1:yBRu/cnL4ks9bgy4vAASdjIW+/xMlFwuHKqtmh3GZQg=
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo=
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ=
github.com/conductorone/baton-sdk v0.24.6 h1:mORfZrBdsxXSYqZxlGMEQTFf6I2fu2/PBF+0c7a73KU=
github.com/conductorone/baton-sdk v0.24.6/go.mod h1:SKm95z4KkQ23Tufo2ys88lVzbwKb0AQEbKee5GE0Lig=
github.com/conductorone/baton-sdk v0.25.1-0.20260825204020-991ca45253a7 h1:3exONVa6aKJ1pN2sJlTFQbtoWbJeluEvOOQYaaIeN1w=
github.com/conductorone/baton-sdk v0.25.1-0.20260825204020-991ca45253a7/go.mod h1:SKm95z4KkQ23Tufo2ys88lVzbwKb0AQEbKee5GE0Lig=
github.com/conductorone/dpop v0.2.6 h1:fakwai/Xm2b/fcDUwJN41WtcSI/2UhQOyRIVvnnrrNA=
github.com/conductorone/dpop v0.2.6/go.mod h1:gyo8TtzB9SCFCsjsICH4IaLZ7y64CcrDXMOPBwfq/3s=
github.com/conductorone/dpop/integrations/dpop_grpc v0.2.4 h1:lYxYi9/WTSL9sE96CO0QF2BY3kehs8dTTApI134TGCA=
Expand Down
2 changes: 1 addition & 1 deletion pkg/connector/connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ var (
resourceTypeInvitation = &v2.ResourceType{
Id: "invitation",
DisplayName: "Invitation",
// Invitations emit TRAIT_USER with UserTrait_Status_STATUS_UNSPECIFIED.
// Invitations emit TRAIT_USER with STATUS_PENDING.
// Accepted members from user.go emit STATUS_ENABLED.
Traits: []v2.ResourceType_Trait{
v2.ResourceType_TRAIT_USER,
Expand Down
18 changes: 8 additions & 10 deletions pkg/connector/invitation.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,19 +64,17 @@ func invitationToUserResource(invitation *github.Invitation, status string) (*v2
invitation.GetID(),
[]resourceSdk.UserTraitOption{
resourceSdk.WithEmail(invitation.GetEmail(), true),
// An invitation is a pending/expired user that must not be
// reported as enabled. WithResourceStatus cannot express this:
// NewUserTrait force-defaults an unset trait status to ENABLED, so
// migrating this line would flip the emitted status from
// UNSPECIFIED to ENABLED. Keep the deprecated trait option (which
// also mirrors UNSPECIFIED to the resource level) to preserve the
// exact status semantics.
//nolint:staticcheck // deliberate: WithResourceStatus would force the trait status to ENABLED; UNSPECIFIED must be preserved for invitations.
resourceSdk.WithStatus(v2.UserTrait_Status_STATUS_UNSPECIFIED),
// Set explicitly: NewUserTrait defaults an unset trait status to
// ENABLED, which an unaccepted invitation is not.
//nolint:staticcheck // trait status is deprecated but must be set to override the ENABLED default.
resourceSdk.WithDetailedStatus(v2.UserTrait_Status_STATUS_PENDING, status),
resourceSdk.WithUserLogin(login),
},
// profile has moved from UserTrait to a Resource-level attribute.
// profile and status have moved from UserTrait to Resource-level
// attributes. Expired invitations stay PENDING - they are still not a
// usable account - and carry the distinction in the status details.
resourceSdk.WithResourceProfile(profile),
resourceSdk.WithResourceStatus(v2.Status_RESOURCE_STATUS_PENDING, status),

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: docs/connector.mdx:26 still says "if an invitation is pending, the account status will be shown as Unspecified", which this change makes wrong. Update it to Pending in this PR so the docs don't lag the emitted status.

Separately, both status fields carry defined_only enum validation, so a reader still on the pre-PENDING proto descriptor would reject enum value 4 rather than fall back to unspecified. The PR description covers the c1-first rollout ordering, but nothing in the connector enforces it — worth confirming that ordering is tracked before this leaves draft, since there is no config gate to fall back to.

)
if err != nil {
return nil, err
Expand Down
24 changes: 24 additions & 0 deletions pkg/connector/invitation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ func TestInvitationListPagination(t *testing.T) {
pendingCreated1.Add(invitationLifetime).UTC().Format(time.RFC3339),
aliceProfile["invitation_expires_at"],
)
requireInvitationPending(t, byID["1001"], invitationStatusPendingAcceptance)

// Expired resources carry status=expired and expires_at = failed_at.
daveProfile := invitationProfile(t, byID["2001"])
Expand All @@ -207,6 +208,9 @@ func TestInvitationListPagination(t *testing.T) {
expiredFailedAt1.UTC().Format(time.RFC3339),
daveProfile["invitation_expires_at"],
)
// An expired invitation is still not a usable account, so it stays
// PENDING at both levels; only the details distinguish it.
requireInvitationPending(t, byID["2001"], invitationStatusExpired)
})

t.Run("pending 404 falls through to failed", func(t *testing.T) {
Expand All @@ -222,6 +226,7 @@ func TestInvitationListPagination(t *testing.T) {
require.Equal(t, "2001", got[0].Id.Resource)
require.Equal(t, invitationStatusExpired,
invitationProfile(t, got[0])["invitation_status"])
requireInvitationPending(t, got[0], invitationStatusExpired)
})

t.Run("failed 404 terminates cleanly", func(t *testing.T) {
Expand All @@ -236,6 +241,7 @@ func TestInvitationListPagination(t *testing.T) {
require.Len(t, got, 2)
require.Equal(t, invitationStatusPendingAcceptance,
invitationProfile(t, got[0])["invitation_status"])
requireInvitationPending(t, got[0], invitationStatusPendingAcceptance)
})

t.Run("both endpoints empty terminates without API errors", func(t *testing.T) {
Expand All @@ -262,3 +268,21 @@ func invitationProfile(t *testing.T, r *v2.Resource) map[string]any {
require.NotNil(t, profile)
return profile.AsMap()
}

// requireInvitationPending asserts that an invitation resource reports PENDING
// at both the resource level and the (deprecated) user-trait level, with
// details naming which flavor of pending it is.
func requireInvitationPending(t *testing.T, r *v2.Resource, wantDetails string) {
t.Helper()

require.Equal(t, v2.Status_RESOURCE_STATUS_PENDING, r.GetStatus().GetStatus())
require.Equal(t, wantDetails, r.GetStatus().GetDetails())

ut, err := resourceSdk.GetUserTrait(r)
require.NoError(t, err)
require.NotNil(t, ut)
//nolint:staticcheck // asserting the deprecated trait status is the point of this check.
require.Equal(t, v2.UserTrait_Status_STATUS_PENDING, ut.GetStatus().GetStatus())
//nolint:staticcheck // asserting the deprecated trait status is the point of this check.
require.Equal(t, wantDetails, ut.GetStatus().GetDetails())
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading