Skip to content

fix(ingestion): salesforce connector + dbt - #1288

Merged
aleksdotbar merged 4 commits into
mainfrom
feat/salesforce-sync-resilience
Jun 18, 2026
Merged

fix(ingestion): salesforce connector + dbt#1288
aleksdotbar merged 4 commits into
mainfrom
feat/salesforce-sync-resilience

Conversation

@aleksdotbar

@aleksdotbar aleksdotbar commented Jun 11, 2026

Copy link
Copy Markdown
Contributor
  • Rewrite the salesforce connector to REST /queryAll only; remove the Bulk API
    path.
  • Per-request OAuth token refresh — syncs longer than the SF session timeout no longer fail on expired tokens.
  • num_workers typed as string (K8s Secret stringData), default 1;
    stop_sync_on_stream_failure=false; drop salesforce_streams override.
  • Align salesforce dbt staging models with native bronze column types
    (DateTime64/Date32) and drop User fields the org never exposes.
  • Map only 003-prefixed WhoId to contact_id.
  • Per tenant+source incremental watermarks in salesforce staging and CRM silver
    classes; drop populated silver placeholders (row-count guard locked in stale
    schemas).

Summary by CodeRabbit

  • Changes

    • Salesforce ingestion is now REST /queryAll by default (Bulk API 2.0 behavior removed).
    • Default REST concurrency reduced to 1.
    • Incremental syncing now uses per-tenant/per-source high-water mark tracking for more reliable catch-up.
    • Stream selection now follows a curated connector-managed set (catalog-driven only).
  • Bug Fixes / Reliability

    • Long-running syncs refresh authentication tokens per request.
    • Improved handling for 401 and request-limit failures so individual stream errors don’t halt the entire sync.
  • Documentation

    • Updated connector configuration, concurrency/window settings, and build/deploy scripts.

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@aleksdotbar, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 34 minutes and 1 second. Learn how PR review limits work.

To continue reviewing without waiting, enable usage-based billing in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c1fa64b1-96a0-4cfc-9555-ef064074b6b3

📥 Commits

Reviewing files that changed from the base of the PR and between e3847208b1e57581b1db9596e654ce14ea8a42d3 and 8fcc767.

📒 Files selected for processing (24)
  • src/ingestion/connectors/crm/salesforce/README.md
  • src/ingestion/connectors/crm/salesforce/dbt/salesforce__crm_accounts.sql
  • src/ingestion/connectors/crm/salesforce/dbt/salesforce__crm_activities.sql
  • src/ingestion/connectors/crm/salesforce/dbt/salesforce__crm_contacts.sql
  • src/ingestion/connectors/crm/salesforce/dbt/salesforce__crm_deals.sql
  • src/ingestion/connectors/crm/salesforce/dbt/salesforce__crm_users.sql
  • src/ingestion/connectors/crm/salesforce/pyproject.toml
  • src/ingestion/connectors/crm/salesforce/source_salesforce/api.py
  • src/ingestion/connectors/crm/salesforce/source_salesforce/availability_strategy.py
  • src/ingestion/connectors/crm/salesforce/source_salesforce/constants.py
  • src/ingestion/connectors/crm/salesforce/source_salesforce/exceptions.py
  • src/ingestion/connectors/crm/salesforce/source_salesforce/rate_limiting.py
  • src/ingestion/connectors/crm/salesforce/source_salesforce/source.py
  • src/ingestion/connectors/crm/salesforce/source_salesforce/spec.json
  • src/ingestion/connectors/crm/salesforce/source_salesforce/streams.py
  • src/ingestion/dbt/dbt_project.yml
  • src/ingestion/dbt/macros/drop_silver_placeholders_at_start.sql
  • src/ingestion/reconcile-connectors/lib/cdk-build.sh
  • src/ingestion/secrets/connectors/salesforce.yaml.example
  • src/ingestion/silver/crm/class_crm_accounts.sql
  • src/ingestion/silver/crm/class_crm_activities.sql
  • src/ingestion/silver/crm/class_crm_contacts.sql
  • src/ingestion/silver/crm/class_crm_deals.sql
  • src/ingestion/silver/crm/class_crm_users.sql
📝 Walkthrough

Walkthrough

The Salesforce connector shifts from a hybrid REST + Bulk API 2.0 approach to a REST-only /queryAll architecture with per-request token refresh and incremental state management driven by per-tenant high-water marks across both Python source and dbt transformation layers.

Changes

REST-only Salesforce Connector Refactor

Layer / File(s) Summary
Configuration and dependency updates
src/ingestion/connectors/crm/salesforce/source_salesforce/spec.json, pyproject.toml, README.md, constants.py, secrets/connectors/salesforce.yaml.example
Spec schema redefined salesforce_num_workers from integer to string with default "1", pyproject.toml constrains airbyte-cdk to >=7.23.1,<8.0.0, README describes REST /queryAll streaming and updated build scripts, constants removes UNSUPPORTED_BULK_API_SALESFORCE_OBJECTS, and example secrets reflects REST concurrency config.
Authentication and stream discovery
src/ingestion/connectors/crm/salesforce/source_salesforce/api.py
New SalesforceAuthenticator fetches fresh tokens per request from SalesforceTokenProvider, get_validated_streams removed config parameter and always defaults to CRM_STREAMS when no catalog provided, eliminating prior config.salesforce_streams override path; SalesforceAvailabilityStrategy removed entirely.
Error handling simplification
src/ingestion/connectors/crm/salesforce/source_salesforce/rate_limiting.py, exceptions.py
Rate limiting returns transient errors with response text for HTTP 429, HTTP 401 handling extracts both error code and message with explicit remediation guidance, bulk-job error classification and private helpers removed, and BulkNotSupportedException and TmpFileIOError exception types eliminated.
Source orchestration
src/ingestion/connectors/crm/salesforce/source_salesforce/source.py
Docstring describes REST /queryAll describe-driven discovery for all streams, stop_sync_on_stream_failure set to False, _get_stream_type simplified to remove bulk-API selection, stream kwargs wired with message_repository and envelope context (tenant/source/custom fields).
Stream execution refactor
src/ingestion/connectors/crm/salesforce/source_salesforce/streams.py
Module restructured for REST-only incremental via ConcurrentCursor, SalesforceStream constructor drops job_tracker, get_start_date_from_state removed, all bulk slicing/orchestration classes deleted, new IncrementalRestSalesforceStream implements checkpointed reads with SOQL predicates and per-slice state clamping.
DBT staging model incremental refactoring
src/ingestion/connectors/crm/salesforce/dbt/salesforce__crm_accounts.sql, salesforce__crm_activities.sql, salesforce__crm_contacts.sql, salesforce__crm_deals.sql, salesforce__crm_users.sql
Five CRM staging models refactored with WITH src AS (...) CTEs, direct date mapping (removing best-effort parsing), _version computed via coalesce(toUnixTimestamp64Milli(SystemModstamp), 0), incremental logic changed from single global max to per-(tenant_id, source_id) high-water marks with left joins.
DBT silver layer incremental pattern
src/ingestion/silver/crm/class_crm_accounts.sql, class_crm_activities.sql, class_crm_contacts.sql, class_crm_deals.sql, class_crm_users.sql
Five CRM silver class models refactored with WITH src AS (...) CTEs wrapping union_by_tag(...), incremental logic changed from global max(_version) to per-(tenant_id, source_id) high-water marks via grouped subqueries and left joins, non-incremental paths simplified to direct SELECT * FROM src.
Supporting infrastructure
src/ingestion/dbt/macros/drop_silver_placeholders_at_start.sql, src/ingestion/reconcile-connectors/lib/cdk-build.sh
dbt placeholder-drop macro updated to two-factor detection requiring only marker comment presence and staging materialization (removing empty-table requirement), cdk-build.sh working-directory resolution adjusted to correct ingestion root path.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • mitasovr

Poem

🐰 Bulk API fades to REST we go,
Per-tenant marks now set the flow,
Token fresh on every call,
ClickHouse queries never stall,
Concurrent streams in sync we trust!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.81% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'fix(ingestion): salesforce connector + dbt' is vague and overly broad, failing to clearly specify the primary change across the extensive refactoring. Consider a more specific title that highlights the main architectural change, such as 'fix(ingestion): migrate salesforce connector from bulk api to rest-only' or 'fix(ingestion): implement per-tenant incremental watermarks for salesforce'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/salesforce-sync-resilience

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/ingestion/connectors/crm/salesforce/source_salesforce/streams.py (1)

476-616: ⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Critical: State regression bug in concurrent slice processing.

The _get_updated_state method (lines 582-616) has a state-management hazard in concurrent slicing scenarios. When a record slips past its slice boundary (latest_record_value > slice_max_value) and a later slice has already advanced global state beyond that slice's maximum, lines 600-602 return min(latest_record_value, slice_max_value) without comparing to current_stream_state, causing state to move backward.

Scenario:

  1. Slice B [2024-06-10, 2024-06-20) completes first → state advances to 2024-06-19
  2. Slice A [2024-06-01, 2024-06-10) processes a stray record at 2024-06-11 (past its boundary)
  3. Logic: latest=06-11 > slice_max=06-10 → returns min(06-11, 06-10) = 06-10
  4. State regresses from 06-1906-10, causing data loss on next sync (records in [06-10, 06-19) are re-read or skipped depending on cursor semantics)

Root cause: Line 602 ignores current_stream_state[self.cursor_field] when clamping.

🔧 Proposed fix
         slice_end = (self._slice or {}).get("end_date")
         if slice_end:
             slice_max_value: pendulum.DateTime = pendulum.parse(slice_end)
             max_possible_value = min(latest_record_value, slice_max_value)
             if current_stream_state.get(self.cursor_field):
+                existing_value = pendulum.parse(current_stream_state[self.cursor_field])
                 if latest_record_value > slice_max_value:
-                    return {self.cursor_field: max_possible_value.isoformat()}
+                    # Clamp to slice boundary, but never regress state from concurrent slices
+                    return {self.cursor_field: max(max_possible_value, existing_value).isoformat()}
                 max_possible_value = max(
                     latest_record_value,
-                    pendulum.parse(current_stream_state[self.cursor_field]),
+                    existing_value,
                 )
             return {self.cursor_field: max_possible_value.isoformat()}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ingestion/connectors/crm/salesforce/source_salesforce/streams.py` around
lines 476 - 616, The _get_updated_state method can regress global state when
clamping a latest_record_value > slice_max_value because it returns
slice_max_value without considering current_stream_state; modify
_get_updated_state (in the IncrementalRestSalesforceStream class) so that when
slice_end/slice_max_value exists and latest_record_value > slice_max_value you
compute the clamped_value = min(latest_record_value, slice_max_value) and then
compare it to current_stream_state.get(self.cursor_field) (if present) and
return the max of those two (as isoformat) to avoid moving state backwards;
ensure all other branches preserve existing logic and that comparisons use
pendulum-parsed datetimes.
🧹 Nitpick comments (2)
src/ingestion/connectors/crm/salesforce/source_salesforce/source.py (1)

182-210: ⚡ Quick win

Update stale comment to reflect REST-only implementation.

The comment on line 183 says "Choose proper stream class: syncMode (full_refresh/incremental), REST API, SubStream", listing "REST API" as if it's one of multiple API choices. Since the refactor removed Bulk API and now exclusively uses REST /queryAll, this phrasing is outdated.

📝 Suggested comment update
-    def prepare_stream(self, stream_name: str, json_schema, sobject_options, sf_object, authenticator, config):
-        """Choose proper stream class: syncMode (full_refresh/incremental), REST API, SubStream."""
+    def prepare_stream(self, stream_name: str, json_schema, sobject_options, sf_object, authenticator, config):
+        """Choose proper stream class based on sync mode (full_refresh/incremental) and whether it's a substream."""
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ingestion/connectors/crm/salesforce/source_salesforce/source.py` around
lines 182 - 210, Update the stale docstring in prepare_stream to reflect the
current REST-only implementation: replace "Choose proper stream class: syncMode
(full_refresh/incremental), REST API, SubStream" with a concise description
mentioning only selecting the stream class (full_refresh/incremental) and that
the connector uses the REST /queryAll API (no Bulk API). Ensure the updated
comment references prepare_stream (and that stream selection still uses
_get_stream_type and UNSUPPORTED_FILTERING_STREAMS) so readers understand the
function's purpose and REST-only behavior.
src/ingestion/connectors/crm/salesforce/source_salesforce/rate_limiting.py (1)

200-223: 💤 Low value

Minor inconsistency: mixing Tuple import with lowercase tuple annotation.

Line 14 imports Tuple from typing, but line 203 uses the built-in tuple[...] annotation (Python 3.9+ syntax). While both work in Python 3.9+, consider using consistent style throughout the file.

-    ) -> tuple[Optional[str], str]:
+    ) -> Tuple[Optional[str], str]:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ingestion/connectors/crm/salesforce/source_salesforce/rate_limiting.py`
around lines 200 - 223, The type annotation for _extract_error_code_and_message
mixes typing.Tuple with the built-in tuple[...] syntax; make them consistent by
switching the function return annotation to use typing.Tuple[Optional[str], str]
(or alternatively remove the Tuple import and use the built-in
tuple[Optional[str], str]) and update the imports accordingly so the file
consistently uses one style (refer to the _extract_error_code_and_message
function and the Tuple import).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/ingestion/dbt/macros/drop_silver_placeholders_at_start.sql`:
- Around line 6-7: Update the stale comment in dbt_project.yml to reflect the
actual behavior of the macro drop_silver_placeholders_at_start.sql: replace the
phrase "Three-factor detection (placeholder marker + 0 rows + at least one
staging model materialised...)" with "Two-factor detection (placeholder marker +
staging materialised for the matching `silver:<id>` tag)". Apply this same
wording adjustment to the other outdated comment blocks mentioned (around the
provided ranges) so the on-run-start documentation matches the macro's
two-factor detection logic.

---

Outside diff comments:
In `@src/ingestion/connectors/crm/salesforce/source_salesforce/streams.py`:
- Around line 476-616: The _get_updated_state method can regress global state
when clamping a latest_record_value > slice_max_value because it returns
slice_max_value without considering current_stream_state; modify
_get_updated_state (in the IncrementalRestSalesforceStream class) so that when
slice_end/slice_max_value exists and latest_record_value > slice_max_value you
compute the clamped_value = min(latest_record_value, slice_max_value) and then
compare it to current_stream_state.get(self.cursor_field) (if present) and
return the max of those two (as isoformat) to avoid moving state backwards;
ensure all other branches preserve existing logic and that comparisons use
pendulum-parsed datetimes.

---

Nitpick comments:
In `@src/ingestion/connectors/crm/salesforce/source_salesforce/rate_limiting.py`:
- Around line 200-223: The type annotation for _extract_error_code_and_message
mixes typing.Tuple with the built-in tuple[...] syntax; make them consistent by
switching the function return annotation to use typing.Tuple[Optional[str], str]
(or alternatively remove the Tuple import and use the built-in
tuple[Optional[str], str]) and update the imports accordingly so the file
consistently uses one style (refer to the _extract_error_code_and_message
function and the Tuple import).

In `@src/ingestion/connectors/crm/salesforce/source_salesforce/source.py`:
- Around line 182-210: Update the stale docstring in prepare_stream to reflect
the current REST-only implementation: replace "Choose proper stream class:
syncMode (full_refresh/incremental), REST API, SubStream" with a concise
description mentioning only selecting the stream class
(full_refresh/incremental) and that the connector uses the REST /queryAll API
(no Bulk API). Ensure the updated comment references prepare_stream (and that
stream selection still uses _get_stream_type and UNSUPPORTED_FILTERING_STREAMS)
so readers understand the function's purpose and REST-only behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 96dfd7a5-cb5a-4036-9b41-acefc0327ecf

📥 Commits

Reviewing files that changed from the base of the PR and between 6f2e166 and d6f881f.

📒 Files selected for processing (23)
  • src/ingestion/connectors/crm/salesforce/README.md
  • src/ingestion/connectors/crm/salesforce/dbt/salesforce__crm_accounts.sql
  • src/ingestion/connectors/crm/salesforce/dbt/salesforce__crm_activities.sql
  • src/ingestion/connectors/crm/salesforce/dbt/salesforce__crm_contacts.sql
  • src/ingestion/connectors/crm/salesforce/dbt/salesforce__crm_deals.sql
  • src/ingestion/connectors/crm/salesforce/dbt/salesforce__crm_users.sql
  • src/ingestion/connectors/crm/salesforce/pyproject.toml
  • src/ingestion/connectors/crm/salesforce/source_salesforce/api.py
  • src/ingestion/connectors/crm/salesforce/source_salesforce/availability_strategy.py
  • src/ingestion/connectors/crm/salesforce/source_salesforce/constants.py
  • src/ingestion/connectors/crm/salesforce/source_salesforce/exceptions.py
  • src/ingestion/connectors/crm/salesforce/source_salesforce/rate_limiting.py
  • src/ingestion/connectors/crm/salesforce/source_salesforce/source.py
  • src/ingestion/connectors/crm/salesforce/source_salesforce/spec.json
  • src/ingestion/connectors/crm/salesforce/source_salesforce/streams.py
  • src/ingestion/dbt/macros/drop_silver_placeholders_at_start.sql
  • src/ingestion/reconcile-connectors/lib/cdk-build.sh
  • src/ingestion/secrets/connectors/salesforce.yaml.example
  • src/ingestion/silver/crm/class_crm_accounts.sql
  • src/ingestion/silver/crm/class_crm_activities.sql
  • src/ingestion/silver/crm/class_crm_contacts.sql
  • src/ingestion/silver/crm/class_crm_deals.sql
  • src/ingestion/silver/crm/class_crm_users.sql
💤 Files with no reviewable changes (2)
  • src/ingestion/connectors/crm/salesforce/source_salesforce/availability_strategy.py
  • src/ingestion/connectors/crm/salesforce/source_salesforce/exceptions.py

Comment thread src/ingestion/dbt/macros/drop_silver_placeholders_at_start.sql
@mitasovr

Copy link
Copy Markdown
Contributor

Code review notes

Solid, well-motivated surgery — the Bulk→REST removal is clean (no dangling references to BulkNotSupportedException, availability_strategy, salesforce_streams, or job_tracker anywhere on the branch), and the per-tenant watermark + token-refresh fixes are real bugfixes. A few things to address before merge:

1. _get_updated_state is dead code — remove it rather than patching it

streams.py:582

CodeRabbit flagged a state-regression bug here, but the method is never called — state is owned by ConcurrentCursor via StreamFacade, so the regression is unreachable at runtime. The real problem is the opposite: this PR removed the _slice = None class attribute, yet line 596 still reads self._slice. If anything ever does call this method, it'll raise AttributeError. Please delete _get_updated_state entirely instead of applying CodeRabbit's suggested clamp fix.

2. Bronze type change needs a rollout plan for already-synced tenants

salesforce__crm_accounts.sql:33 (and the other staging models)

Staging now reads native bronze types (CreatedDate AS created_at, toUnixTimestamp64Milli(SystemModstamp), DurationInMinutes * 60 without toInt64OrNull). On any environment where bronze_salesforce tables were already created by the old Bulk/CSV path with String columns, these models will break. There's no migration or README note describing the cutover. What's the plan for tenants that already synced under the old connector — drop bronze_salesforce + full resync, or ALTER the columns?

3. coalesce(toUnixTimestamp64Milli(SystemModstamp), 0) can silently drop rows — what's the intended behavior?

salesforce__crm_accounts.sql:36 (same pattern in every staging + silver model)

A row with a NULL SystemModstamp gets _version = 0. After the first incremental run advances the high-water mark, that row can never satisfy _version > hwm again — it's silently excluded from all subsequent runs. In Salesforce SystemModstamp is effectively always populated, so the practical risk is low, but the failure mode is invisible.

Question: is 0 the deliberate choice here, or should a NULL modstamp fall back to something monotonic (e.g. CreatedDate/collected_at) so the row stays eligible? At minimum I'd add a comment documenting that _version = 0 rows are intentionally one-shot. How do you want to handle this?

4. _DEFAULT_CONCURRENCY = 1 on the concurrent CDK — watch the slice count

source.py:58

We've previously hit a concurrent-CDK self-deadlock at low concurrency with a large partition count (jira/confluence froze at default_concurrency:1 with ≥10k partitions — fingerprint "Records read: N" stuck). Here ~25 slices × 10 streams ≈ 250 partitions, which should be safe. But the risk profile returns if anyone shrinks salesforce_stream_slice_step (e.g. P30D → P1D produces thousands of slices) while workers stay at 1. The max(1, concurrency_level // 2) guard is correct and necessary — without it the default of 1 would pass 0 initial partitions. Just flagging the slice-step interaction as a documented gotcha.

6. Dropping the row-count guard in the placeholder macro — reasoning is sound

drop_silver_placeholders_at_start.sql:89

The rationale in the macro comment is convincing — a placeholder can fill mid-run (staging materializes after the hook has already run, the silver model inserts into the placeholder with on_schema_change=ignore), and contents are always rebuildable from staging, so a row-count guard would block the drop forever. The residual risk is that any populated table carrying the INSIGHT_PLACEHOLDER_v1 marker now gets dropped, but since only create-bronze-placeholders.sh ever sets that marker, this is acceptable. No change requested — just confirming the two-factor logic checks out.


Also: CI is red for process reasons unrelated to this PR — the E2E failure is the identity DB fixture (test_migrations_create_insight_database), fixed in #1287 which isn't in this branch's base, and DCO needs Signed-off-by. A git rebase --signoff onto main should clear both.

@aleksdotbar
aleksdotbar force-pushed the feat/salesforce-sync-resilience branch from d6f881f to e384720 Compare June 18, 2026 14:26
@aleksdotbar
aleksdotbar requested a review from a team as a code owner June 18, 2026 14:26
@aleksdotbar
aleksdotbar force-pushed the feat/salesforce-sync-resilience branch from e384720 to 78d5094 Compare June 18, 2026 14:51
aleksdotbar and others added 4 commits June 18, 2026 16:52
- per-request token refresh: syncs longer than the SF session
  timeout no longer fail on expired tokens
- num_workers typed as string (K8s Secret stringData), default 1
- drop salesforce_streams override; CRM_STREAMS is source of truth
- stop_sync_on_stream_failure=false: one stream fail no longer
  aborts the rest
- pin airbyte-cdk <8

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Aleksandr Barkhatov <pm@aleks.bar>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Aleksandr Barkhatov <pm@aleks.bar>
- map only 003-prefixed WhoId to contact_id; leads polluted half the links
- per tenant+source incremental watermark in salesforce staging and
  crm silver classes; global max dropped late-syncing sources' rows
- drop populated silver placeholders: row-count guard locked in stale schemas

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Aleksandr Barkhatov <pm@aleks.bar>
… macro doc

- Remove unused IncrementalRestSalesforceStream._get_updated_state: stream
  state is owned by ConcurrentCursor, and the method read a never-assigned
  self._slice attribute.
- Raise airbyte-cdk floor to >=7.23.1 and requests to >=2.34.2 (CVE-2024-35195).
- Fix the on-run-start comment in dbt_project.yml to describe the macro's
  two-factor placeholder detection (marker + staging materialised).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Aleksandr Barkhatov <pm@aleks.bar>
@aleksdotbar
aleksdotbar force-pushed the feat/salesforce-sync-resilience branch from 78d5094 to 8fcc767 Compare June 18, 2026 14:52
@aleksdotbar
aleksdotbar merged commit 225e319 into main Jun 18, 2026
12 checks passed
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.

2 participants