Governing a Salesforce Org in an Afternoon: From describe.json to Your First Drift Audit
Somewhere in your org, last quarter, somebody edited a picklist. Nobody remembers who, the field history doesn't say why, and the report that broke three weeks later never mentioned it at all. Salesforce makes schema change wonderfully easy — and makes *remembering what the schema is supposed to mean* entirely your problem.
Governing a Salesforce Org in an Afternoon: From describe.json to Your First Drift Audit
Somewhere in your org, last quarter, somebody edited a picklist. Nobody remembers who, the field history doesn't say why, and the report that broke three weeks later never mentioned it at all. Salesforce makes schema change wonderfully easy — and makes remembering what the schema is supposed to mean entirely your problem.
CoreModels (by ARAMAI) solves the remembering half. It builds a governed model of your org — sObjects, fields, picklists, lookups — and then audits the live shape of the org against that model, on demand and continuously. This tutorial takes you from an empty CoreModels project to your first recorded drift audit. Nothing here requires giving CoreModels access to your org: you export metadata yourself and upload it.
What you need
- A CoreModels project and its 32-character hex project id (we use
$PROJECT_IDbelow). - A CoreModels login token (
$TOKEN). The import step needs the Admin role on the project; the audit step only needs Viewer. - The Salesforce CLI (
sf) authenticated against your org, plusjq. Any REST client works the same — the CLI just makes the loop short.
The API base URL is written as https://coremodels.example.com throughout; substitute your own.
Step 1 — Export the describes
The Salesforce connector consumes exactly one artifact, named describe: a JSON array of sObject describe results, the output of GET /services/data/vXX.0/sobjects/{name}/describe aggregated across the objects you care about. Describe has stayed stable across many Salesforce API versions, which is precisely why we built on it — it carries field types with their platform parameters, nillability, uniqueness, picklist value sets, lookup targets, and inline help text.
# Aggregate describes for the objects you want governed (Salesforce CLI):
sf sobject list --sobject all -o my-org > objects.txt
for OBJ in Account Contact Opportunity My_Object__c; do
sf api request rest "/services/data/v61.0/sobjects/$OBJ/describe" -o my-org
done | jq -s '.' > describe.json
Pick the objects that carry business meaning — you do not have to boil the whole org, and you can always import more later. The parser is tolerant about shape: a bare array, a single describe object, and the {"sobjects": [...]} wrapper all parse.
Note what did not happen in this step: no connected app, no OAuth grant to us, no stored credential. The export runs under your own login, and describe.json is the only thing CoreModels ever sees.
Step 2 — Import the org into the governed model
Import parses the describes and writes them into your project's graph. It is additive: if you import again later, existing governed nodes are never mutated or deleted — surfacing differences is the audit's job, and changing governed meaning stays a human act.
The artifact travels as a JSON string inside the request body, so let jq handle the escaping:
jq -n --rawfile describe describe.json \
'{artifacts: {describe: $describe}}' > import-request.json
curl -sS -X POST \
"https://coremodels.example.com/graph/integrations/salesforce/import/$PROJECT_ID" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
--data-binary @import-request.json
A successful import answers with counts and two honesty channels:
{ "success": true, "vendor": "salesforce", "projectName": "salesforce-org",
"datasetsAdded": 4, "datasetsSkippedExisting": 0, "fieldsAdded": 87,
"lineageEdgesAdded": 0, "lineageEdgesSkipped": 0, "nodesEnriched": 91,
"snapshotStored": true, "lossiness": [], "errors": [] }
Reading it: datasetsAdded counts sObjects that became governed Types; fieldsAdded counts fields that became Elements; nodesEnriched counts nodes that received vendor metadata. snapshotStored: true means the parsed org snapshot was persisted — that is what makes one-call re-audits possible later. lossiness lists anything the import approximated or dropped (a successful import can still be honest about its edges), while errors would mean it could not proceed at all.
What actually landed in the graph: each sObject is now a Type; each field an Element carrying its platform-real native type (string(255), currency(18,2)); every active picklist a Taxonomy; every single-target lookup a governed reference to the target object's Id; and inlineHelpText became the field description. The org's own semantics — nillable: false, unique, restricted-picklist flags — came along as governed checks and metadata.
Step 3 — Run your first audit
The audit compares fresh artifacts against the governed model and writes nothing. Passing recordHistory: true is the one opt-in piece of bookkeeping: it appends this run to the project's rolling drift trail, which later feeds the status badge.
jq -n --rawfile describe describe.json \
'{artifacts: {describe: $describe}, recordHistory: true}' > audit-request.json
curl -sS -X POST \
"https://coremodels.example.com/graph/integrations/salesforce/audit/$PROJECT_ID" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
--data-binary @audit-request.json
Since you imported thirty seconds ago, coverage will be complete and drift will be zero. What you will see, in most real orgs, are Conformance findings — Salesforce hygiene the connector checks on every run:
{ "section": "Conformance", "severity": "Warning", "code": "picklist-unrestricted",
"subject": "Invoice__c.Status__c",
"message": "Picklist is not restricted — Salesforce accepts values outside the set; the governed taxonomy is the only control." }
That finding is worth a pause, because it is the whole product in one sentence. An unrestricted picklist is a value set the platform does not enforce. The moment you imported it, the governed taxonomy in CoreModels became the only place that value set is actually defended — and the audit will tell you the instant the org and the taxonomy disagree.
Step 4 — Read the result
Every audit response carries the same fields, and one of them is the number that matters: errorCount. Errors mean the org violates governed meaning — a governed field gone, a type changed, a governed value set narrowed. Warnings and infos (like picklist-unrestricted, or field-no-help for custom fields missing help text) are visibility, not violations.
Findings arrive in three sections: Coverage (what exists but isn't mapped — codes like dataset-unmapped and field-unmapped), Drift (field-removed, field-type-drift, enum-narrowed, enum-widened, and friends), and Conformance (the Salesforce-specific rules). Alongside the machine-readable counts and the per-code codes tally, the response includes a markdown field — a complete human-readable report, ready to paste into a pull request or a wiki page — and a fingerprint of the artifact content, so identical exports are recognizably identical runs.
Step 5 — Watch it catch something
To see the other half, break something on purpose in a sandbox: deactivate a picklist value the governed taxonomy still contains, or change a custom field's type. Re-export describe.json, re-run the audit call from Step 3, and the response stops being quiet: the drift section names the exact object and field, severities turn to error where meaning changed, and driftedObjects lists the affected identities. That loop — export, audit, read — is the entire operating rhythm, and it is fast enough to run on every metadata change.
Step 6 — Put a badge on it
Because you passed recordHistory: true, the project now has a recorded run, and the badge route renders it as an SVG:
curl -sS -H "Authorization: Bearer $TOKEN" \
"https://coremodels.example.com/graph/integrations/salesforce/badge/$PROJECT_ID" \
-o salesforce-audit.svg
Green means clean, yellow means warnings, red means errors, gray means no recorded runs yet. One glance answers "does the org still match its governed meaning?"
Where this goes next
You now have a governed model and a repeatable audit. From here the same surface scales in four directions: a machine-to-machine v1 audit route that accepts user API keys, built for CI gates; a reaudit verb that re-checks the stored org snapshot whenever the governed model itself changes, no fresh export needed; a generate verb that emits Metadata-API CustomObject XML scaffolds back out of the governed model for review through your normal deployment process; and MCP tools that let your AI agents run the import, audit, and generate steps of this loop conversationally.
The self-contained Salesforce quickstart — this recipe plus every route and tool on one page — ships with the CoreModels docs under quickstarts/salesforce.