Repository navigation
CXP-481 Support for Incremental Sync - #120
JavierCarnelli-ConductorOne wants to merge 21 commits into
Conversation
…n all examples All resource-level incremental_sync pagination blocks were missing the required primary_key field, which caused "primary key not found in query results" errors when detecting new or modified resources via event feed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nt feed First-run sources derive `since` from the default lookback window. The previous code unconditionally applied earliestEvent (which reflects the last processed event from another source) as a lower bound, pushing `since` forward past the lookback and permanently hiding events — including revokes — that occurred before the first cycle. sinceForSource now returns (time.Time, bool); earliestEvent is only applied when hasCommittedCursor is true. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix WordPress incremental_sync query missing ?<since> placeholder - Log debug message when event feed syncer creation fails instead of silently dropping the error - Refactor hasSince/hasID loops into queryUsesVar helper to reduce duplication - Validate IncrementalSyncConfig.default_lookback at startup - Fix event ID collisions for grants sharing the same (resource, principal, timestamp) by appending the pagination primary key - Fix SAP HANA YAML indentation: get and incremental_sync were siblings of user instead of children - Remove debug fmt.Println statements from event_feed.go Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Claude finished @JavierCarnelli-ConductorOne's task in 4m 19s —— View job PR Review: Incremental Sync Support
OverviewThis PR adds incremental sync (event feed) support to baton-sql, enabling efficient change detection via timestamp-based queries instead of full re-syncs. The implementation supports three event types:
The architecture is well-designed: each "source" (resource type or grant query) maintains its own independent cursor, and a round-robin scheduler processes one page per source per What works well
Issues foundMust-fix
Should-fix
Nits / suggestions
SummaryThe core design is solid — the multi-source cursor state machine, the separation of committed vs in-flight state, and the reuse of existing query infrastructure are all well-thought-out. The main gap is test coverage: the cursor state machine and validation logic have enough complexity to warrant dedicated unit tests before merging. The |
- Add resource_id field to ResourceIncrementalSync to decouple event
feed ID extraction from list.map.id, mirroring the existing pattern
in GrantsIncrementalSync; falls back to list.map.id when not set
- Add bcel.Env.Compile for startup CEL expression validation; use it
to validate resource_id at config validation time
- Fix grantRowKey to format int64/float64 primary keys with strconv
instead of fmt.Sprintf("%v") which produces scientific notation for
large IDs
- Document why earliestEvent is intentionally ignored in ListEvents
- Document why EventFeeds re-creates syncers separately from
ResourceSyncers and why this is safe
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Claude finished @JavierCarnelli-ConductorOne's task in 2m 8s —— View job PR Review: Incremental Sync Support
OverviewThis PR adds incremental sync (event feed) support to baton-sql. Instead of full re-syncs, the connector can now detect changes via timestamp-based SQL queries and emit Architecture AssessmentThe design is well-structured:
Issues1. No tests for new code (must-fix)
Key areas to test:
The validation additions in 2. Potential data loss:
|
|
Claude finished @JavierCarnelli-ConductorOne's task in 2m 59s —— View job PR Review: Incremental Sync Support
OverviewThis PR adds incremental sync (event feed) support to baton-sql. Instead of full re-syncs, the connector can now detect changes via timestamp-based SQL queries and emit Also adds ArchitectureThe design is solid:
The latest commit ( Issues1.
|
Rename the global, resource-level and grants-level `incremental_sync` config keys to `event_feed`, along with the matching Go types, fields, validation errors and example configs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ResourceSyncers and EventFeeds now share a cached set of syncers. A build failure is logged at Error and still surfaced by Validate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Event feed queries must now configure pagination with a primary key and use ?<Limit>, so each page is bounded. Using ?<Limit>, ?<Offset> or ?<Cursor> without pagination now returns an error instead of panicking. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each poll now starts reading at the committed cursor minus a configurable `event_feed.overlap` (default 60s), so rows with tied or late-committed timestamps are not skipped. The committed cursor never moves backward and re-read rows keep deterministic event IDs. Example queries now use `>= ?<since>` and order by the cursor column plus the primary key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Move the expandable-annotation logic into a helper shared by full sync and the event feed, and attach the grant annotations to CreateGrantEvent and CreateRevokeEvent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Superseded — see the current review report for commit
|
|
|
||
| // validateEventFeedPagination requires offset pagination on event feed queries so each page stays bounded. | ||
| // Cursor paging resumes from the last primary key, which skips rows when results are ordered by timestamp. | ||
| func validateEventFeedPagination(field string, p *Pagination, usedVars []string) error { |
There was a problem hiding this comment.
🟡 Suggestion (medium confidence): Offset pagination over ORDER BY updated_at, id with a fixed since can skip rows. If a row already returned on page k is updated before page k+1 is fetched, it moves to the end of the result set. Every later row shifts back one position, so the row at the page boundary is never returned. If that row's timestamp is older than maxSeen - overlap, the next cycle won't re-read it either, and the change is missed until the next full sync. A keyset cursor on (cursor_column, primary_key) would close this gap. Otherwise, document the limitation.
Superseded — see the current review report for commit
|
There was a problem hiding this comment.
No blocking issues found — see the full review report
A missing or NULL cursor column now returns a FailedPrecondition error instead of silently reusing the since timestamp, which kept the cursor from advancing and changed event IDs on every poll. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pt numeric epochs String cursor values now use the engine-specific time formats, and integer or float values are read as Unix timestamps with the same seconds-or-milliseconds rule used for numeric strings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Superseded — see the current review report for commit
|
Connector PR Review: CXH-1390 CXP-481 Support for Incremental SyncBlocking Issues: 0 | Suggestions: 2 | Threads Resolved: 0 Review SummaryThis commit changes how the event feed reads cursor values:
The new behavior has tests: missing cursor columns on both the resource feed and the grant feed, epoch types and ranges, and the invalid-CEL case. I scanned the full PR diff for security and correctness issues and found no injection, nil-safety, or swallowed-error problems. I applied the repo-local criteria to the event-feed paths:
Coverage note: earlier PR revisions were reviewed in previous runs. In this run I examined the new commit line by line and pattern-scanned the rest of the diff. Security IssuesNone found. Correctness IssuesNone found. Suggestions
Resolved prior findings
Prompt for AI agentsReviewed commit: |
There was a problem hiding this comment.
No blocking issues found — see the full review report
ggreer
left a comment
There was a problem hiding this comment.
I noticed a couple of minor things, but this looks good to merge. Feel free to address them in this PR, a follow-up PR, or not at all.
| // ResourceSyncers returns a ResourceSyncer for each resource type that should be synced from the upstream service. | ||
| func (c *Connector) ResourceSyncers(ctx context.Context) []connectorbuilder.ResourceSyncer { | ||
| syncers, err := c.config.GetSQLSyncers(ctx, c.dbs, c.dbEngine, c.celEnv) | ||
| syncers, err := c.getOrSetSQLSyncers(ctx) |
There was a problem hiding this comment.
Could we call this in New()? Then we could return the error and avoid the mutex + bool.
| type EventFeedConfig struct { | ||
| // DefaultLookback is the maximum time window to look back when initializing a cursor for the | ||
| // first time. Accepts any Go time.Duration string (e.g., "3h", "30m", "24h"). Defaults to 3h. | ||
| DefaultLookback string `yaml:"default_lookback" json:"default_lookback"` |
There was a problem hiding this comment.
Nitpick: If this is the maximum time to look back, should it be called MaxLookback?
Description
Adds incremental sync support through the baton event feed (
ListEvents) and targeted resource sync (Get).event_feedquery emitsResourceChangeEvents, and C1 re-syncs those resources through the resource type'sgetquery.event_feed(changes_query, plus an optionalrevokes_query) emitsCreateGrantEvent/CreateRevokeEvent. Grants keepexpandableannotations.overlapwindow (default60s) so rows with tied or late-committed timestamps are not missed; re-sent events keep the same ID, so C1 skips them.paginationwithstrategy: offsetand use?<Limit>/?<Offset>. They mustORDER BY <cursor column>, <primary key>.GetreturnsUnimplementedwhen a resource type has nogetquery.See the "Event Feed" section in the README and
examples/for configuration.Breaking changes vs. earlier revisions of this PR
incremental_syncconfig keys were renamed toevent_feed.?<id>placeholder ingetqueries was renamed to?<resource_id>.pagination(offset only) and?<Limit>.Notes
Useful links:
🤖 Generated with Claude Code