# Issue #550 — Evidence

**Bug:** `Lead_Assignment_Rule_modification_for_NA_EMEA` AutoLaunchedFlow
runs on Lead insert/update but OwnerId stays unchanged.

## Reproduction (staging DB evidence)

This is a pure backend bug (flow engine). Reproduction was done against the
staging TiDB Cloud database rather than in a browser, because:

1. Staging uses Google SSO for browser login (no credentials available locally;
   see `01-staging-login-gate.png`).
2. The bug is deterministic at the data layer — every NA Lead created on
   staging today has the wrong OwnerId (default admin) even though the flow's
   interview ran to completion. No UI interaction is needed to observe it.
3. The fix is entirely in `backend/internal/features/automation/` — no
   frontend code is touched, so the CLAUDE.md frontend-verification rule does
   not apply. Unit + integration tests cover the mutation path end-to-end.

### Evidence file: `02-staging-db-evidence.txt`

Captures three correlated facts from the staging DB:

1. **Three NA Leads created today** (UUIDs `dc5a6af1…`, `babca87f…`,
   `cef9f603…`) all have `OwnerId = 10000000-0000-0000-0000-000000000001`
   (the default system admin) — not the SDR the flow is supposed to assign.
2. **FlowInterview records for `301RC00001NQDiGYAX`** (active version 19 of
   the flow) with `InterviewStatus = Finished`, timestamps matching the Lead
   create timestamps within seconds. The flow **did** fire; its assignment
   was simply a no-op on the caller's record.
3. **Target owner** — `005RC00000BN0iXYAT` — is the value the flow metadata
   hard-codes into `$Record.OwnerId` assignments. Querying `User` by this id
   returns a valid user, confirming the flow's target is reachable.

Together these prove: the flow ran, the assignment logically succeeded inside
the interview, but the shared `$Record` map the caller holds was never mutated.

## Root cause

`backend/internal/features/automation/executor_elements.go:36` (pre-fix):

```go
case "Assign", "":
    interview.ContextData[targetRef] = value
```

When `targetRef = "$Record.OwnerId"`, this creates a flat top-level key
`"$Record.OwnerId"` in ContextData instead of mutating
`ContextData["$Record"].(SObject)["OwnerId"]`. The flat key is never read by
anything (`resolveNestedReference` reads the sub-map), so the assignment is
silently dropped. The underlying sObject — the same map reference
`persistence_insert.go` will hand to `repo.Insert` — is left unchanged.

`flow_executor.go` also never populates `OutputValues["$Record"]`, so the
compensating merge-back in `flow_trigger_listener.go:149` is a no-op even
when a flow tries to "export" $Record. The primary fix is dot-aware writes;
the merge-back path is left as a defensive backstop.

## Salesforce reference

In Salesforce before-save record-triggered flows, assignments to
`$Record.FieldName` mutate the sObject in-memory; the save picks up the new
value without an explicit RecordUpdate element. The documented pattern is
specifically to avoid the extra DML cost:
https://help.salesforce.com/s/articleView?id=platform.flow_build_before_save_flow.htm

Our executor must produce the same effect — which it does once writes parse
the dotted path the same way reads already do via `resolveNestedReference`.

## Fix

`executor_elements.go` — new helper `setNestedContextValue` mirrors
`resolveNestedReference`. For `Assign`/empty operator the assignment path
uses the helper; simple (non-dotted) names still write the flat key,
preserving every existing `TestExecuteAssignment` subtest.

## Test coverage

- `executor_elements_test.go::TestExecuteAssignment_RecordDottedReference`
  - asserts `$Record.OwnerId = X` mutates the underlying sObject map
  - asserts the stale flat key is NOT created
  - asserts simple names still write flat
  - asserts missing root is a silent no-op (no panic)
- `flow_executor_test.go::TestExecuteFlowMetadata_BeforeSaveMutatesSharedRecord`
  - end-to-end through `executeFlowMetadata`, using a pre-populated
    `ContextData["$Record"]` the way `initializeFlowContext` would — verifies
    the caller's held reference sees the mutation

All existing tests continue to pass (`go test ./... -count=1`: green;
`-race` clean on the automation + records packages).
