Salesforce Org-to-Org Diff
Comparison specialist for two live Salesforce orgs reached through two MCP
connectors. Inventory both sides cheaply, diff the inventories, fetch bodies
only for the narrow shortlist that actually differs, and hand back a drift
report that says what changed, which direction, and what to do about it.
READ-ONLY, always, on both orgs. This skill never writes to either org —
no DML, no metadata deploys, no Tooling POSTs. Reconciliation is a plan
handed to sf-metadata, never an action taken here. There is no mode or
argument that relaxes this.
Dispatch
| First argument or intent |
Workflow |
drift, "what drifted", "sandbox vs production", "out of sync" |
Drift Check |
release, "did the release deploy everything", "post-deploy diff" |
Release Verification |
baseline, "compare against baseline", "customer org vs our demo" |
Baseline Comparison |
| Two connector names with no mode word |
Ask which mode — the report reads differently in each |
All three modes share the same engine (Phases 0–4 below); the mode changes
which differences are findings and which are noise. Never guess the
mode: a prod-only Apex class is a red flag in drift and expected
background in baseline.
Two connectors, one direction
Every run names two connectors explicitly:
- Source — the org whose state is the "newer" or "reference" side
(sandbox in drift, the org you deployed FROM in release, the reference/demo
org in baseline).
- Target — the org being measured against it (production in drift, the
org you deployed TO in release, the customer org in baseline).
A reversed diff is a wrong diff — "5 flows newer in sandbox" and "5 flows
newer in production" demand opposite actions. That is why Phase 0 is not
skippable.
Execution modes
See references/execution-modes.md — including the Tool-name mapping
preamble (the tool names below are capability conventions) and the headless
rule. This skill runs the init/verify step twice, once per connector,
and every subsequent call must state which connector it targets. In headless
runs the whole skill proceeds without gates except identity confirmation:
if the two connectors cannot both be identity-verified (Phase 0), stop and
report — a headless diff against unverified orgs is worse than no diff.
Phase 0 — Identity confirmation (mandatory)
Query each connector before any inventory work:
SELECT Id, Name, InstanceName, IsSandbox, OrganizationType,
NamespacePrefix, TrialExpirationDate
FROM Organization
Then present both identities side by side and confirm the direction:
|
Source |
Target |
| Connector |
my-sandbox |
my-production |
| Org Name / Id |
... / 00D... |
... / 00D... |
| Instance / IsSandbox |
... / true |
... / false |
Hard checks, in order:
- Same-org-twice: identical
Organization.Id on both connectors means
the user pointed both arguments at the same org (or two connectors share
credentials). Stop immediately — a self-diff always reports "no drift"
and gives false confidence.
- Direction sanity: in
drift mode, if the source has
IsSandbox = false and the target IsSandbox = true, ask before
proceeding — the user probably reversed the arguments. Ask, don't assume:
prod-as-source is legitimate in some workflows (seeding a fresh sandbox).
- API-version skew: fetch
GET /services/data/ (versions list) via the
connector's generic REST tool if it has one (e.g. restful). Note:
tooling_api_query-style tools are rooted at the Tooling base path and
cannot reach /services/data/ — do not attempt the check through
them. If no generic REST tool is available, record "version skew
unverified" in the report header and proceed at the lower assumed
version. When versions are known to differ, run all comparison queries
at the lower common version so both sides see the same field
surface — a field that exists only at the newer API version would
otherwise show up as fake drift.
- Interactive confirmation: in interactive runs, get an explicit "yes,
that direction" from the user before Phase 1. Headless: verify 1–3,
record the assumed direction in the report header, and continue.
Every report begins with this identity table plus the run timestamp (UTC).
A diff without provenance cannot be trusted a week later.
Aborted-run report shape. When Phase 0 fails (one connector cannot be
identity-verified, or a hard check trips), still emit a report — this exact
shape, nothing improvised:
## Org Diff: ABORTED at Phase 0 (<mode>, <UTC timestamp>)
<identity table — render the failed side's cells as UNVERIFIED>
## Attention flags
| CRITICAL | Run integrity | <which connector failed and how (e.g.
auth/session error on target connector 'my-production'); no comparison
was performed> |
Remediation: re-authenticate the failed connector, then rerun the exact
same command. Never substitute a different connector for the same org.
Phase 1 — Scope
Default domains and the user's controls:
| Domain |
Default in drift/release |
Default in baseline |
Scope keyword |
| Apex classes + triggers |
yes |
yes |
apex |
| Flows (versions + status) |
yes |
yes |
flows |
| Objects + fields |
yes |
yes |
objects |
| Validation rules |
yes |
yes |
validation |
| Permission sets |
yes |
no (usually customer-specific) |
permissions |
| Layouts + FlexiPages (counts/names) |
yes |
no |
layouts |
| Custom labels |
yes |
no |
labels |
| Remote sites + named credentials |
yes |
yes |
endpoints |
| Installed packages (versions) |
yes |
yes |
packages |
scope in the arguments (e.g. /sf-orgdiff drift dev prod apex,flows)
limits the run to those domains. Honor it strictly — a scoped run that
"helpfully" scans everything burns API calls on large orgs and buries the
answer the user asked for.
Phase 2 — Inventory (both orgs, cheap fields only)
Follow references/diff-method.md for the per-domain queries. The
discipline that makes this skill viable on real orgs:
- Counts before rows.
COUNT() each domain on each org first. The
counts alone are a first-cut diff signal and they size the pagination.
- Never fetch bodies during inventory. Names, versions, lengths, and
dates are enough to classify 95%+ of items. Bodies wait for Phase 4.
- Paginate defensively. Domains over ~200 rows:
ORDER BY Name with
keyset pagination (WHERE Name > :last). Tooling OFFSET caps at 2000 —
keyset works at any size, so prefer it from the start.
- Interleave, don't serialize. Query domain N on source and target
back to back so partial results are still comparable if the run is cut
short.
Phase 3 — Compare inventories
Join each domain on its identity key (NamespacePrefix + API name — see
diff-method) and bucket every item:
| Bucket |
Meaning |
Cost to produce |
| Only in source |
Exists in source, absent in target |
Free (set difference) |
| Only in target |
Exists in target, absent in source |
Free |
| Both, different |
Present in both; a checksum-ish field differs (LengthWithoutComments, ApiVersion, active version number, field count, Active flag, endpoint URL, package version) |
Free |
| Both, touched |
Only LastModifiedDate differs |
Free — report as low-signal |
| Identical |
All compared fields match |
Not listed individually |
Managed packages: compare versions, never contents. Anything with a
NamespacePrefix belonging to an installed package is compared solely via
the package-version table. Managed bodies are hidden or irrelevant, and
"drift" inside a managed namespace just means different installed versions
— say that in one line instead of fifty.
Phase 4 — Body fetch (shortlist only)
Only for Both, different items that are unmanaged, and only where a body
diff changes the recommendation (Apex, validation rule formulas, labels).
Cap at ~20 bodies per run before checking in with the user — beyond that the
right deliverable is the shortlist itself, handed to sf-metadata or a CLI
retrieve for a file-level diff. Some connectors redact Body in Tooling
query results; fetch via GET .../tooling/sobjects/<Type>/<Id> per the
mapping preamble before concluding a body is unavailable.
Drift report
Always this shape:
## Org Diff: <source name> → <target name> (<mode>, <UTC timestamp>)
<identity table from Phase 0>
## Summary
| Domain | Only in source | Only in target | Modified | Touched |
| ... one row per scoped domain, zeros included ...
## <Domain> detail (one section per domain with findings)
<table: item, key fields both sides, what differs>
<the actionable read, e.g. "5 flows have higher active versions in
sandbox — likely pending deployment" or "2 validation rules active in
prod only — created directly in prod?">
## Attention flags
<severity-ranked list, see below>
## What to do
<3-6 concrete next steps with skill handoffs>
Severity model — direction is what makes a difference dangerous:
| Flag |
Pattern |
Why it ranks high |
| CRITICAL |
Target(prod)-only or prod-newer changes in drift mode |
Hotfix drift: changes made directly in prod will be overwritten by the next deployment unless back-promoted |
| CRITICAL |
Expected release item absent in target in release mode |
The deploy silently didn't land it |
| HIGH |
Endpoint drift (RemoteSite/NamedCredential URLs differ) |
Integrations point at different systems — data goes to the wrong place |
| HIGH |
Permission set present/different across orgs |
Access drift is a security finding, not a hygiene one → sf-security |
| MEDIUM |
Managed package version skew |
Behavioral differences with no visible metadata diff |
| MEDIUM |
Flow active-version skew, source newer |
Pending deployment — expected in drift, a finding in release |
| LOW |
Touched-only items (dates differ, content signals equal) |
Often no-op saves, comment edits, or sandbox-refresh artifacts |
Prod-only changes are called out by name every time. They are the dangerous
kind of drift precisely because everyone's deployment tooling looks the
other way.
Mode-specific reads
- Drift: the question is "what will the next deployment overwrite, and
what hasn't been promoted yet?" Source-newer = pending deployment;
target-newer/target-only = hotfix drift. If the sandbox was recently
refreshed, say so — a refresh copies prod's timestamps, so only
differences dated after the refresh are true drift.
- Release: run twice around a deploy (or once after, against the source
org the release was built in). The report pivots to a checklist: every
item in the release scope must appear in target with matching
version/length, and flows must be not just present but active at the
expected version — "deployed but latest version inactive" is the classic
half-landed release.
- Baseline: customer org vs reference. Only-in-target items are usually
legitimate customer customization — inventory them neutrally. Findings are
reference items missing or modified in the customer org, and package
version gaps. Recommend, don't scold: this mode feeds an upgrade or
enablement conversation.
Pitfalls
| Pitfall |
Handling |
| Same org behind both connectors |
Phase 0 hard stop on identical Org Ids |
| Connectors pinned to different API versions |
Query both at the lower common version (Phase 0 §3) |
LastModifiedDate timezone confusion |
API datetimes are UTC on both sides — compare raw; never render them in local time inside comparison tables without labeling the zone |
Tooling object not queryable on a connector (e.g. InstalledSubscriberPackage, EntityDefinition quirks) |
Fall back per the Tool-name mapping preamble in references/execution-modes.md; diff-method lists a fallback per domain. Degrade the domain to "partial" in the summary rather than dropping it silently |
EntityDefinition doesn't support queryMore/deep OFFSET |
Keyset-paginate on QualifiedApiName (see diff-method); plain COUNT() generally works and can drive the counts-first pass |
| Connector auth failure / expired session on ONE side |
Hard stop (Phase 0 identity gate). Emit the aborted-run report: identity table with UNVERIFIED on the failed side, a CRITICAL run-integrity flag naming which connector failed, and the remediation line (re-authenticate that connector, rerun the same command). Never substitute a different connector that reaches the same org — it silently breaks the run's org rules |
| Equal lengths, different dates |
Classify as Touched, not Modified — LengthWithoutComments unchanged usually means comments/no-op |
| Huge orgs (10k+ fields, 1k+ classes) |
Counts-first guard in Phase 2; offer to narrow scope before inventorying a domain whose count exceeds ~2,000 rows per side |
Document deliverables
When the user asks for a document (HTML/DOCX/XLSX) rather than a chat
report, follow the sf-audit report template — sections 7 (Visualization
Requirements) and 8 (HTML Deliverable Standards) — at
../sf-audit/references/report-template.md. The summary table becomes the
category-breakdown visual; attention flags become the severity distribution
and top-N offenders.
Cross-skill handoffs
- Deep quality scoring of either org's code/flows → sf-audit
- Deploying the reconciliation (promote source-newer items, back-promote
hotfixes) → sf-metadata
- Permission-set drift → sf-security (treat as a security review input)
- "Why does this class differ" root-causing → sf-apex / sf-flow
References
| File |
Read when |
references/diff-method.md |
Phase 2 onward — per-domain queries, keys, comparison fields, fallbacks |
references/execution-modes.md |
Start of session — tool mapping, headless rule |
1---2name: sf-orgdiff3description: Compares two connected Salesforce orgs through their MCP connectors — inventory-first metadata diffing across Apex, Flows, objects/fields, validation rules, permission sets, layouts, labels, endpoints, and installed packages — producing a drift report with severity flags and a reconciliation plan. Strictly read-only on both orgs. Use when the user asks "what drifted", "compare orgs", "sandbox vs production", "did the release deploy everything", "compare against baseline", pre/post-release verification, or any question about differences between two orgs. Do NOT use for auditing a single org's quality (use sf-audit), creating or deploying metadata (use sf-metadata), or vulnerability scanning (use sf-security). Usage: /sf-orgdiff [drift|release|baseline] {source-connector} {target-connector} [scope] ...4---56# Salesforce Org-to-Org Diff78Comparison specialist for two live Salesforce orgs reached through two MCP9connectors. Inventory both sides cheaply, diff the inventories, fetch bodies10only for the narrow shortlist that actually differs, and hand back a drift11report that says what changed, which direction, and what to do about it.1213**READ-ONLY, always, on both orgs.** This skill never writes to either org —14no DML, no metadata deploys, no Tooling POSTs. Reconciliation is a *plan*15handed to sf-metadata, never an action taken here. There is no mode or16argument that relaxes this.1718## Dispatch1920| First argument or intent | Workflow |21| ----------------------------------------------------------------- | -------- |22| `drift`, "what drifted", "sandbox vs production", "out of sync" | Drift Check |23| `release`, "did the release deploy everything", "post-deploy diff" | Release Verification |24| `baseline`, "compare against baseline", "customer org vs our demo" | Baseline Comparison |25| Two connector names with no mode word | Ask which mode — the report reads differently in each |2627All three modes share the same engine (Phases 0–4 below); the mode changes28which differences are *findings* and which are *noise*. Never guess the29mode: a prod-only Apex class is a red flag in `drift` and expected30background in `baseline`.3132## Two connectors, one direction3334Every run names two connectors explicitly:3536- **Source** — the org whose state is the "newer" or "reference" side37 (sandbox in drift, the org you deployed FROM in release, the reference/demo38 org in baseline).39- **Target** — the org being measured against it (production in drift, the40 org you deployed TO in release, the customer org in baseline).4142A reversed diff is a wrong diff — "5 flows newer in sandbox" and "5 flows43newer in production" demand opposite actions. That is why Phase 0 is not44skippable.4546## Execution modes4748See `references/execution-modes.md` — including the Tool-name mapping49preamble (the tool names below are capability conventions) and the headless50rule. This skill runs the init/verify step **twice**, once per connector,51and every subsequent call must state which connector it targets. In headless52runs the whole skill proceeds without gates *except* identity confirmation:53if the two connectors cannot both be identity-verified (Phase 0), stop and54report — a headless diff against unverified orgs is worse than no diff.5556---5758## Phase 0 — Identity confirmation (mandatory)5960Query **each** connector before any inventory work:6162```sql63SELECT Id, Name, InstanceName, IsSandbox, OrganizationType,64 NamespacePrefix, TrialExpirationDate65FROM Organization66```6768Then present both identities side by side and confirm the direction:6970| | Source | Target |71| --- | --- | --- |72| Connector | my-sandbox | my-production |73| Org Name / Id | ... / 00D... | ... / 00D... |74| Instance / IsSandbox | ... / true | ... / false |7576Hard checks, in order:77781. **Same-org-twice:** identical `Organization.Id` on both connectors means79 the user pointed both arguments at the same org (or two connectors share80 credentials). Stop immediately — a self-diff always reports "no drift"81 and gives false confidence.822. **Direction sanity:** in `drift` mode, if the *source* has83 `IsSandbox = false` and the *target* `IsSandbox = true`, ask before84 proceeding — the user probably reversed the arguments. Ask, don't assume:85 prod-as-source is legitimate in some workflows (seeding a fresh sandbox).863. **API-version skew:** fetch `GET /services/data/` (versions list) via the87 connector's **generic REST tool** if it has one (e.g. `restful`). Note:88 `tooling_api_query`-style tools are rooted at the Tooling base path and89 **cannot** reach `/services/data/` — do not attempt the check through90 them. If no generic REST tool is available, record "version skew91 unverified" in the report header and proceed at the lower assumed92 version. When versions are known to differ, run all comparison queries93 at the **lower** common version so both sides see the same field94 surface — a field that exists only at the newer API version would95 otherwise show up as fake drift.964. **Interactive confirmation:** in interactive runs, get an explicit "yes,97 that direction" from the user before Phase 1. Headless: verify 1–3,98 record the assumed direction in the report header, and continue.99100Every report begins with this identity table plus the run timestamp (UTC).101A diff without provenance cannot be trusted a week later.102103**Aborted-run report shape.** When Phase 0 fails (one connector cannot be104identity-verified, or a hard check trips), still emit a report — this exact105shape, nothing improvised:106107```108## Org Diff: ABORTED at Phase 0 (<mode>, <UTC timestamp>)109<identity table — render the failed side's cells as UNVERIFIED>110111## Attention flags112| CRITICAL | Run integrity | <which connector failed and how (e.g.113 auth/session error on target connector 'my-production'); no comparison114 was performed> |115116Remediation: re-authenticate the failed connector, then rerun the exact117same command. Never substitute a different connector for the same org.118```119120## Phase 1 — Scope121122Default domains and the user's controls:123124| Domain | Default in drift/release | Default in baseline | Scope keyword |125| --- | --- | --- | --- |126| Apex classes + triggers | yes | yes | `apex` |127| Flows (versions + status) | yes | yes | `flows` |128| Objects + fields | yes | yes | `objects` |129| Validation rules | yes | yes | `validation` |130| Permission sets | yes | no (usually customer-specific) | `permissions` |131| Layouts + FlexiPages (counts/names) | yes | no | `layouts` |132| Custom labels | yes | no | `labels` |133| Remote sites + named credentials | yes | yes | `endpoints` |134| Installed packages (versions) | yes | yes | `packages` |135136`scope` in the arguments (e.g. `/sf-orgdiff drift dev prod apex,flows`)137limits the run to those domains. Honor it strictly — a scoped run that138"helpfully" scans everything burns API calls on large orgs and buries the139answer the user asked for.140141## Phase 2 — Inventory (both orgs, cheap fields only)142143Follow `references/diff-method.md` for the per-domain queries. The144discipline that makes this skill viable on real orgs:145146- **Counts before rows.** `COUNT()` each domain on each org first. The147 counts alone are a first-cut diff signal and they size the pagination.148- **Never fetch bodies during inventory.** Names, versions, lengths, and149 dates are enough to classify 95%+ of items. Bodies wait for Phase 4.150- **Paginate defensively.** Domains over ~200 rows: `ORDER BY Name` with151 keyset pagination (`WHERE Name > :last`). Tooling OFFSET caps at 2000 —152 keyset works at any size, so prefer it from the start.153- **Interleave, don't serialize.** Query domain N on source and target154 back to back so partial results are still comparable if the run is cut155 short.156157## Phase 3 — Compare inventories158159Join each domain on its identity key (`NamespacePrefix` + API name — see160diff-method) and bucket every item:161162| Bucket | Meaning | Cost to produce |163| --- | --- | --- |164| **Only in source** | Exists in source, absent in target | Free (set difference) |165| **Only in target** | Exists in target, absent in source | Free |166| **Both, different** | Present in both; a checksum-ish field differs (`LengthWithoutComments`, `ApiVersion`, active version number, field count, `Active` flag, endpoint URL, package version) | Free |167| **Both, touched** | Only `LastModifiedDate` differs | Free — report as low-signal |168| **Identical** | All compared fields match | Not listed individually |169170**Managed packages: compare versions, never contents.** Anything with a171`NamespacePrefix` belonging to an installed package is compared solely via172the package-version table. Managed bodies are hidden or irrelevant, and173"drift" inside a managed namespace just means different installed versions174— say that in one line instead of fifty.175176## Phase 4 — Body fetch (shortlist only)177178Only for **Both, different** items that are unmanaged, and only where a body179diff changes the recommendation (Apex, validation rule formulas, labels).180Cap at ~20 bodies per run before checking in with the user — beyond that the181right deliverable is the shortlist itself, handed to sf-metadata or a CLI182retrieve for a file-level diff. Some connectors redact `Body` in Tooling183*query* results; fetch via `GET .../tooling/sobjects/<Type>/<Id>` per the184mapping preamble before concluding a body is unavailable.185186---187188## Drift report189190Always this shape:191192```193## Org Diff: <source name> → <target name> (<mode>, <UTC timestamp>)194<identity table from Phase 0>195196## Summary197| Domain | Only in source | Only in target | Modified | Touched |198| ... one row per scoped domain, zeros included ...199200## <Domain> detail (one section per domain with findings)201<table: item, key fields both sides, what differs>202<the actionable read, e.g. "5 flows have higher active versions in203 sandbox — likely pending deployment" or "2 validation rules active in204 prod only — created directly in prod?">205206## Attention flags207<severity-ranked list, see below>208209## What to do210<3-6 concrete next steps with skill handoffs>211```212213**Severity model** — direction is what makes a difference dangerous:214215| Flag | Pattern | Why it ranks high |216| --- | --- | --- |217| CRITICAL | Target(prod)-only or prod-newer changes in drift mode | Hotfix drift: changes made directly in prod will be **overwritten by the next deployment** unless back-promoted |218| CRITICAL | Expected release item absent in target in release mode | The deploy silently didn't land it |219| HIGH | Endpoint drift (RemoteSite/NamedCredential URLs differ) | Integrations point at different systems — data goes to the wrong place |220| HIGH | Permission set present/different across orgs | Access drift is a security finding, not a hygiene one → sf-security |221| MEDIUM | Managed package version skew | Behavioral differences with no visible metadata diff |222| MEDIUM | Flow active-version skew, source newer | Pending deployment — expected in drift, a finding in release |223| LOW | Touched-only items (dates differ, content signals equal) | Often no-op saves, comment edits, or sandbox-refresh artifacts |224225Prod-only changes are called out by name every time. They are the dangerous226kind of drift precisely because everyone's deployment tooling looks the227other way.228229## Mode-specific reads230231- **Drift:** the question is "what will the next deployment overwrite, and232 what hasn't been promoted yet?" Source-newer = pending deployment;233 target-newer/target-only = hotfix drift. If the sandbox was recently234 refreshed, say so — a refresh copies prod's timestamps, so only235 differences dated *after* the refresh are true drift.236- **Release:** run twice around a deploy (or once after, against the source237 org the release was built in). The report pivots to a checklist: every238 item in the release scope must appear in target with matching239 version/length, and flows must be not just present but **active** at the240 expected version — "deployed but latest version inactive" is the classic241 half-landed release.242- **Baseline:** customer org vs reference. Only-in-target items are usually243 legitimate customer customization — inventory them neutrally. Findings are244 reference items *missing* or *modified* in the customer org, and package245 version gaps. Recommend, don't scold: this mode feeds an upgrade or246 enablement conversation.247248## Pitfalls249250| Pitfall | Handling |251| --- | --- |252| Same org behind both connectors | Phase 0 hard stop on identical Org Ids |253| Connectors pinned to different API versions | Query both at the lower common version (Phase 0 §3) |254| `LastModifiedDate` timezone confusion | API datetimes are UTC on both sides — compare raw; never render them in local time inside comparison tables without labeling the zone |255| Tooling object not queryable on a connector (e.g. `InstalledSubscriberPackage`, `EntityDefinition` quirks) | Fall back per the Tool-name mapping preamble in `references/execution-modes.md`; diff-method lists a fallback per domain. Degrade the domain to "partial" in the summary rather than dropping it silently |256| `EntityDefinition` doesn't support queryMore/deep OFFSET | Keyset-paginate on `QualifiedApiName` (see diff-method); plain `COUNT()` generally works and can drive the counts-first pass |257| Connector auth failure / expired session on ONE side | Hard stop (Phase 0 identity gate). Emit the aborted-run report: identity table with UNVERIFIED on the failed side, a CRITICAL run-integrity flag naming which connector failed, and the remediation line (re-authenticate that connector, rerun the same command). **Never substitute a different connector that reaches the same org** — it silently breaks the run's org rules |258| Equal lengths, different dates | Classify as Touched, not Modified — `LengthWithoutComments` unchanged usually means comments/no-op |259| Huge orgs (10k+ fields, 1k+ classes) | Counts-first guard in Phase 2; offer to narrow scope before inventorying a domain whose count exceeds ~2,000 rows per side |260261## Document deliverables262263When the user asks for a document (HTML/DOCX/XLSX) rather than a chat264report, follow the sf-audit report template — sections 7 (Visualization265Requirements) and 8 (HTML Deliverable Standards) — at266../sf-audit/references/report-template.md. The summary table becomes the267category-breakdown visual; attention flags become the severity distribution268and top-N offenders.269270## Cross-skill handoffs271272- Deep quality scoring of either org's code/flows → **sf-audit**273- Deploying the reconciliation (promote source-newer items, back-promote274 hotfixes) → **sf-metadata**275- Permission-set drift → **sf-security** (treat as a security review input)276- "Why does this class differ" root-causing → **sf-apex** / **sf-flow**277278## References279280| File | Read when |281| --- | --- |282| `references/diff-method.md` | Phase 2 onward — per-domain queries, keys, comparison fields, fallbacks |283| `references/execution-modes.md` | Start of session — tool mapping, headless rule |