CXP-383 Support Last Log In w/ usage event feed for the Enterprise connector - #188
Conversation
Adds an opt-in usage event feed that streams each org's audit-log activity (web/API actions, excluding raw git operations and bot actors) as incremental usage events, letting the platform derive last-activity for members without a per-user API field to sync directly. The config field is intentionally hidden from this connector's CLI/GUI since it only applies to GitHub Enterprise audit-log access; the enterprise connector variant sets it directly on the shared config struct. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Connector PR Review: CXP-383 Support Last Log In w/ usage event feed for the Enterprise connectorBlocking Issues: 0 | Suggestions: 2 | Threads Resolved: 0 Review SummaryThe new commit is a pure rename: Security IssuesNone found. Correctness IssuesNone found. Suggestions
Prompt for AI agents |
newWithGithubApp built its GitHub struct without copying SyncLastActivity, so EventFeeds() always returned nil for App-authenticated connectors regardless of the config value, with no error or log. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Track the since-boundary explicitly instead of inferring it from whether an event was emitted, so a page of only filtered-out entries (bots, missing IDs) no longer ends an org's walk early and silently drops later pages of real activity. - Surface the tightest rate limit seen across a call's audit-log requests as an annotation, including on error responses, so the SDK can pace polling instead of hitting 429s. - Fall back to a synthesized event id (org/actor/timestamp/action) when GitHub omits _document_id, avoiding ambiguous dedup on empty ids. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…f them Skip-and-continue is now restricted to permanent per-org conditions (403/404), logged at Warn per this repo's log-level convention. Rate limits, 5xx, and any other error now abort the call instead of silently completing the pass and permanently losing the unfetched activity window on the next poll. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Use nanosecond precision for the synthesized event id and the persisted "since" cursor to avoid same-second id collisions and duplicate re-emitted events. Also check every audit entry's raw timestamp against the boundary, not just ones that pass the bot filter, so an all-filtered page doesn't stall pagination past "since". Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The page budget is a single counter shared across every org processed in a call, not a per-org cap as the comment claimed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Requires review. Bugs fixed
| return nil, nil, nil, wrapGitHubError(err, resp, | ||
| fmt.Sprintf("baton-github: failed to fetch audit log for org %s", orgName)) | ||
| case isNotFoundError(resp) || isPermissionError(resp): | ||
| l.Warn("org lacks audit-log access, skipping it for this pass", |
There was a problem hiding this comment.
This skip-and-continue 403/404 should be Debug, not Warn.
A missing audit-log permission is an expected per-org skip, not a connector failure — Warn just noise on every pass. Same shape as the rest of this connector: Debug for skippable 4xx, Error only for 5xx / bugs.
There was a problem hiding this comment.
Hey Tute! Thanks for the review.
In this case we want to keep it as Warn since it's an error condition, not an expected/normal data state.
You can see it in patterns-error-handling.md as Rule 4
GitHub orgs aren't a GitHub App install, so C1's usage uplift never picks up org-targeted usage events. Mimic baton-dropbox's pattern: sync a static TRAIT_APP resource with a real entitlement, gated behind sync-last-activity, and point usageEventFeed's UsageEvents at it instead of the org. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
| resourceSyncers = append(resourceSyncers, APITokenBuilder(gh.client, gh.orgCache)) | ||
| } | ||
|
|
||
| if gh.syncLastActivity { |
There was a problem hiding this comment.
Non-blocking suggestion — fine as a follow-up, not a condition for this PR.
usageAppBuilder is registered only while syncLastActivity is true, so flipping the enterprise toggle from true to false drops the whole usage-app resource type out of ResourceSyncers(), and C1 reads a previously synced type disappearing as a deletion. Registering the builder unconditionally and gating only EventFeeds() would keep the resource catalog stable across a config change.
Consumer config: https://github.com/ConductorOne/baton-github-enterprise/blob/52c1fbed32d34b623bb5aa80afb655b01714b9ee/pkg/config/config.go#L80-L113
There was a problem hiding this comment.
Deliberate tradeoff, not planning to change it: this entitlement never has grants (Grants() returns nil, resource type carries SkipGrants), so a toggle-triggered "deletion" is just catalog churn, not an access change. Registering it unconditionally would instead give every plain baton-github customer (sync-last-activity is hidden/CLI-only there) a permanent, unexplained "GitHub / access" resource with no function. Gating avoids that at the cost of occasional harmless churn for the few admins who actually flip the enterprise toggle.
| } | ||
|
|
||
| func (gh *GitHub) EventFeeds(_ context.Context) []connectorbuilder.EventFeed { | ||
| if !gh.syncLastActivity { |
There was a problem hiding this comment.
Non-blocking suggestion — worth a follow-up once the enterprise side lands.
The feat/cxp-383 branch wires SyncLastActivity, but DefaultCapabilitiesBuilder has no EventFeeds() and its generated capabilities contain neither usage-app nor CAPABILITY_EVENT_FEEDS; both new constructors here are private, so downstream cannot reuse them. Exporting or sharing the feed/resource capability path (and regenerating the enterprise metadata) would let the consumer advertise the feed.
Consumer builder: https://github.com/ConductorOne/baton-github-enterprise/blob/52c1fbed32d34b623bb5aa80afb655b01714b9ee/pkg/connector/connector.go#L31-L62
Adds an opt-in usage event feed that streams each org's audit-log activity (web/API actions, excluding raw git operations and bot actors) as incremental usage events, letting the platform derive last-activity for members without a per-user API field to sync directly. The config field is intentionally hidden from this connector's CLI/GUI since it only applies to GitHub Enterprise audit-log access; the enterprise connector variant sets it directly on the shared config struct.
This feature is build to be used by baton-github-enterprise which vendors this connector.