---
apiVersion: data-spec/v1
id: lead-required-fields-20260415
description: "Backfill _System_Field.Required=1 for Lead.Name/LastName/Company/Status (issue #549)"
created: 2026-04-15
applied: 2026-04-15
scope: production-only
status: applied
applied_to:
  - staging (gateway01.us-east-1.prod.aws.tidbcloud.com:4000/nexuscrm)
tables: [_System_Field]
depends_on: []
---

# Lead Required Fields Backfill — Data Spec

## Summary

Issue #549: The Lead New form on staging allows saving with no values — no `*`
markers, no client validation, no server validation — and the resulting row has
`LastName`/`Company`/`Status` all `NULL`. This pollutes the Lead table and
violates Salesforce parity.

Root cause: `_System_Field.Required` is `0` for `Lead.LastName`, `Lead.Company`,
and `Lead.Status` in the staging metadata registry. The describe endpoint
translates `Required → Nillable` via
`backend/internal/interfaces/rest/sobject_field_helpers.go:isNillable`, so a
field with `Required=0` is emitted as `nillable: true`. The frontend
`MetadataRecordForm` then omits the required marker and the `required` rule on
the field, and the backend `CreateRecord` handler has no
required-field validation of its own.

This is the same discover-path seeding defect fixed for `Campaign.Name` under
issue #300, which lives as an idempotent override in
`backend/internal/bootstrap/discover_tables.go:reconcileDiscoveredFieldOverrides`.
The companion PR for this spec adds Lead entries to that override list so a
fresh bootstrap produces the correct state. This data spec applies the same
correction to environments that were bootstrapped before that change and will
not re-run the override path until the next full backend restart.

Salesforce reference: standard Salesforce Lead has `LastName` and `Company`
required with no default; `Status` is required with a server-side default of
`"Open - Not Contacted"`. The describe emits `nillable=false` for all three;
`Status` additionally has `defaultedOnCreate=true`. See
`backend/internal/bootstrap/standard_objects.json` — those facts are already
encoded in the JSON source of truth; only the DB row is out of sync.

## Changes

### 1. Set Required=1 on Lead.LastName/Company/Status (idempotent)

```sql
UPDATE _System_Field f
JOIN _System_Object o ON o.Id = f.ObjectId
SET f.Required = 1
WHERE o.ApiName = 'Lead'
  AND f.ApiName IN ('Name', 'LastName', 'Company', 'Status')
  AND f.Required = 0;
```

**Note on `Name` vs `LastName`:** Salesforce standard Lead has no physical
`Name` column — `Name` is a compound formula of Salutation/FirstName/LastName,
and `LastName` is the required name field (`nameField=true`). Some NexusCRM
environments (including this staging TiDB) were seeded by the discover path
with a physical `Lead.Name` column marked `IsNameField=1`, and the default
page layout references `Name` rather than `LastName`. Both `Name` and
`LastName` are therefore marked `Required=1` by this spec so the fix takes
effect regardless of which flavor the environment uses; the backend
`validateRequiredForCreate` only checks fields that appear in the in-memory
schema, so marking an absent field required is a no-op on that environment.

**Idempotency:** the `f.Required = 0` guard means re-applying the spec on an
environment that already has the correct value is a no-op (0 rows affected).
No deterministic `Id` is needed because we are updating existing rows, not
inserting new ones.

**Safety:** this only flips a boolean column on three metadata rows. It does
not touch any user data, does not change the physical `Lead` table schema
(those columns remain `NULL`-able at the MySQL/TiDB level — the enforcement is
application-layer via the describe-driven frontend + the new
`validateRequiredForCreate` server-side check added in the companion PR). No
existing Lead rows are modified.

Existing `Lead` rows with `NULL` values in those columns continue to exist
untouched — the new Required flag only affects future Creates.

## Rollback

```sql
UPDATE _System_Field f
JOIN _System_Object o ON o.Id = f.ObjectId
SET f.Required = 0
WHERE o.ApiName = 'Lead'
  AND f.ApiName IN ('LastName', 'Company', 'Status');
```

Rolling back re-opens the bug: the Lead form will again allow empty saves and
the backend `validateRequiredForCreate` will no longer fire for Lead (because
`f.Required` is what it reads from the in-memory schema).

## Verification

Before apply — capture current state:

```sql
SELECT f.ApiName, f.Required, f.Label
FROM _System_Field f
JOIN _System_Object o ON o.Id = f.ObjectId
WHERE o.ApiName = 'Lead'
  AND f.ApiName IN ('LastName', 'Company', 'Status')
ORDER BY f.ApiName;
```

Expected before: three rows, all with `Required = 0`.

After apply — same query should show all three rows with `Required = 1`.

Then verify end-to-end in the browser (captured in issue #549 evidence folder):

1. `/object/lead/new` — required `*` markers appear on LastName, Company, Status.
2. Clicking **Save** with an empty form shows inline validation errors and
   does NOT POST to the backend.
3. A direct `POST /services/data/v63.0/sobjects/Lead` with an empty body
   returns `400 REQUIRED_FIELD_MISSING` naming LastName and Company (Status is
   defaulted by the persistence layer, so it is not in the error list).
4. Filling the three fields and saving creates the Lead and navigates to its
   detail page.
