From de8855d2651258416beacba54c1851acb2f4900e Mon Sep 17 00:00:00 2001 From: Adrian Lansdown Date: Thu, 23 Jul 2026 18:27:30 +0100 Subject: [PATCH] Scope Salesforce agent guidance --- AGENTS.md | 2 +- app/jobs/salesforce/AGENTS.md | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 app/jobs/salesforce/AGENTS.md diff --git a/AGENTS.md b/AGENTS.md index 9358fcb0b..b0f64972d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -41,7 +41,7 @@ docker compose up - Sync writes to the `salesforce_connect` DB (not a Salesforce API). Pattern from editor-api PR #677. - Feature flag: `SALESFORCE_ENABLED=true`. - After deploy, backfill: `rails salesforce_sync:school`, `salesforce_sync:role`, `salesforce_sync:contact`, `salesforce_sync:school_class`, `salesforce_sync:class_teacher`, `salesforce_sync:lesson`. -- **Parent-sync race guard (required for any job using `__r__` external-ID lookups).** Heroku Connect rejects an INSERT permanently with `Foreign key external ID … not found` if the parent record isn't yet in Salesforce — the mirror row stays `FAILED` forever (no auto-retry). Call `ensure_parent_synced!(model, external_id_field, external_id, label)` on `Salesforce::SalesforceSyncJob` (the base class) before saving a child record; it checks the parent has a non-nil `sfid` in its Heroku Connect mirror and raises `SalesforceRecordNotFound` if not. The base job declares `retry_on SalesforceRecordNotFound, wait: :polynomially_longer, attempts: 10` so the job self-heals once parents land. See `Salesforce::RoleSyncJob` and `Salesforce::ClassTeacherSyncJob` for call-site examples. +- Jobs under `app/jobs/salesforce/**` have additional instructions in `app/jobs/salesforce/AGENTS.md`. ## Where to Look First - Routes: `config/routes.rb`. Auth: `config/initializers/omniauth.rb`, `app/helpers/authentication_helper.rb`, `app/controllers/concerns/identifiable.rb`. diff --git a/app/jobs/salesforce/AGENTS.md b/app/jobs/salesforce/AGENTS.md new file mode 100644 index 000000000..95fb19a27 --- /dev/null +++ b/app/jobs/salesforce/AGENTS.md @@ -0,0 +1,8 @@ +# Salesforce Sync Jobs + +## Parent-Sync Race Guard +- Before saving a child record that uses a `__r__` external-ID lookup, call `ensure_parent_synced!(model, external_id_field, external_id, label)` on the `Salesforce::SalesforceSyncJob` base class. +- The guard must confirm that the parent has a non-nil `sfid` in its Heroku Connect mirror. If it does not, raise `SalesforceRecordNotFound`. +- This guard is required because Heroku Connect permanently rejects the child INSERT with `Foreign key external ID … not found` when the parent has not reached Salesforce. The failed mirror row is not retried automatically. +- Let the base job's `retry_on SalesforceRecordNotFound, wait: :polynomially_longer, attempts: 10` retry the child after its parent lands. +- Follow `Salesforce::RoleSyncJob` and `Salesforce::ClassTeacherSyncJob` as call-site examples.