# SF describe → nexuscrm pack extractor (co-located with `salesforce-crm-standard-v66`)

Converts Salesforce REST `describeSObject` JSON dumps into nexuscrm
`object_spec` pack components. Produces one `spec.json` per object plus a
manifest `components` snippet, with information losses recorded against the
current pack `FieldSpec` schema.

The extractor is **co-located with the pack it generates**. Re-running it
produces fresh `spec.json` files into `/tmp/sfmeta/extracted/`; copying that
back over `../objects/` is how the pack gets refreshed from a new describe
dump. The leading `_` in the directory name signals "not a manifest
component" — `install-pack` only loads what `pack.json:components[].path`
points at, so this directory is invisible to the pack apply path.

## Inputs

```
/tmp/sfdescribe/describe/<ApiName>.json    # SF describe v66.0 dev-org dumps
```

Each input is the raw `sf sobject describe --target-org <alias> --json` output
(top-level `{"status":0,"result":{...describe...}}`).

## Outputs

```
/tmp/sfmeta/extracted/objects/<ApiName>/spec.json    # one ObjectSpec per object
/tmp/sfmeta/extracted/manifest_components.json       # 30 object_spec components, dependsOn topology
/tmp/sfmeta/extracted/conversion-loss.md             # information losses (also copied to last-run-loss.md)
```

## Run

```bash
# from repo root
python3 packs/salesforce-crm-standard-v66/_extractor/extract.py

# refresh the pack from a new describe dump
cp -R /tmp/sfmeta/extracted/objects packs/salesforce-crm-standard-v66/
```

No dependencies beyond stdlib Python 3.10+.

## Conversion rules (summary)

- Dropped: `Id` primary key, `custom: true` dev-sample fields,
  `deprecatedAndHidden`, platform-managed system columns
  (`OwnerId / CreatedById / LastModifiedById / CreatedDate / LastModifiedDate /
  SystemModstamp / IsDeleted / LastViewedDate / LastReferencedDate`),
  platform-managed objects (`User / Profile / Group / UserRole /
  PermissionSet / PermissionSetGroup`).
- Type map: SF `int`/`long` → pack `double` (loss); `address`/`base64` no
  mapping (omitted, recorded); `combobox` → `picklist`.
- Picklists: empty `picklistValues[]` → downgraded to `string` (L0 forbids
  empty options); inactive values kept with `tags:["inactive"]` for
  faithfulness.
- Multipicklist defaults dropped — L0 maps to JSON column, MySQL forbids
  DEFAULT on JSON/TEXT.
- dependsOn graph: master-detail relationships only (`relationshipOrder !=
  null`); plain lookups produce no component-level dependency.

See `last-run-loss.md` for the full per-field loss report from the most recent run.
