# Salesforce Order CRM Persistence Contract Workflow

## Decision

`.github/workflows/nexuscrm-salesforce-order-crm-source-contract.yml` is the selected-fixture diagnostic. The main-only staging workflow `.github/workflows/provision-nexuscrm-salesforce-order-crm-source-contract-fixture.yml` is the CRM-side replacement acceptance gate for the Salesforce Order downstream. Its field-value oracle is the target environment's committed CRM TiDB state, not a Salesforce-compatible REST query response.

The selected fresh fixture retains its narrower passing verdict:

```text
CRM_PERSISTENCE_CONTRACT_PASS
```

The staging replacement workflow passes only with:

```text
SALESFORCE_ORDER_REPLACEMENT_PASS
```

That verdict requires all three gates: a fresh CRM create/save graph, the full staging Prod-copy snapshot, and the in-process raw→final→46-field `salesforce.order` projection.

The versioned field registry is the source of truth:

`staging-inspection/config/contracts/salesforce-order-crm-source-contract.v1.json` (contract version `8`), together with `staging-inspection/config/contracts/crm-data-platform-view-projection-contract.v1.json` (view projection contract version `1`).

The legacy filename is retained to avoid an unsafe split of the established workflow asset; its contract ID, pass marker, artifacts, and workflow names identify the persistence contract.

`OrderItem` is the only line-item authority. The executable contract does not query, join, union, or fall back to the retired custom line-item object. `OrderItem.OrderProduct__c` remains required on the fresh controlled fixture, but it is conditional for the existing snapshot because it is not a relationship used to produce `salesforce.order`.

## Oracle and execution model

The contract deliberately uses two read paths, with separate purposes:

| Check | Read path | Passing evidence |
|---|---|---|
| Field existence and declared type | NexusCRM `describe` API | Required field is present with an accepted type. |
| Rollup authority metadata | NexusCRM `_System_Object` / `_System_Field` API | The five Order rollups name `OrderItem`, `OrderId`, the expected source field, and aggregation. |
| Physical required-field values, cross-object references, and rollup values | Direct `mysql`/`mariadb` read of the active target CRM TiDB physical tables | The committed row is non-null/type-correct and matches the deterministic oracle. |
| Virtual formula semantics | The committed TiDB inputs plus a NexusCRM runtime query | The formula's direct inputs are committed and valid; the independent live formula output matches the deterministic oracle. The API result is output evidence only, never persistence evidence. |
| Historical retained-row completeness | Aggregate direct `mysql`/`mariadb` reads across every non-deleted CRM object row, plus bounded runtime formula-null probes | Every required physical export field has zero null/blank rows, every required virtual formula has no null runtime output, and the report exposes only aggregate counts. |
| Existing-data readiness | Direct TiDB reads of every non-deleted `Order` selected by the raw export and its Account, Opportunity, Quote, QuoteLineItem, Product2, and OrderItem graph | `Order.OpportunityId` and `Order.QuoteId` are always present and resolve. Fields that were non-blank in every matching active Salesforce historical baseline row are also always required. Other checks are object- or business-condition scoped: present values are type-correct, activated Orders have OrderItems, and deterministic reference, mapping, formula, arithmetic, and rollup oracles match whenever their inputs produce a value of the declared type. |
| Existing-data projection | Pure Node in-memory materialization | Every committed Order graph evaluates all 46 `salesforce.order` fields against the explicit requiredness policy without BigQuery, an external raw/final dataset, or `salesforce_order_compatible`. `OpportunityId` and `QuoteId` are always required. |

The full-snapshot requiredness policy is metadata-owned in `snapshotRequiredness`. It deliberately differs from the discriminating fresh fixture: the fresh fixture must populate every declared field so that every calculation path is exercised. In the existing snapshot, missing `Order.OpportunityId`, `Order.QuoteId`, projected `opportunity_id`, or projected `quote_id` always blocks by explicit product requirement. Fields observed non-blank in 100% of their matching active Salesforce reference cohort are also always required. Other fields are scoped to the object or business condition that uses them: if a value is present its type must be valid. An exact/formula/mapping/rollup comparison becomes applicable as soon as its oracle can produce a value of the declared type; a blank output cannot turn that check into not-applicable. Only a genuinely unavailable or inapplicable oracle input is reported as not applicable. `Product2.IsActive=true` is a fresh-fixture precondition only; a historical OrderItem may validly reference an inactive catalog product.

## Historical Salesforce calibration

The 2026-07-29 calibration used the five historical Salesforce datasets only as an offline reference oracle:

- `salesforce.order` → CRM `Order`, joined by `id = Id`;
- `salesforce.quote` → CRM `Quote`, joined by `id = Id`;
- `salesforce.account` → CRM `Account`, joined by `id = Id`;
- `salesforce.opportunity` → CRM `Opportunity`, joined by `id = Id`;
- `salesforce.order_product_c` → CRM `OrderItem`, joined by `id = OrderProduct__c`, with `order_c = OrderId` as the parent check.

The CRM workflow does not query BigQuery or any Salesforce dataset. The offline comparison calibrates requiredness and supplies migration evidence; the fresh CRM-native fixture remains the write-path oracle. A blank Salesforce reference value does not make the CRM field required. When keys and `SystemModstamp` both match, a non-blank Salesforce value missing or different in CRM is same-version preservation evidence.

The aggregate-only comparison produced this point-in-time result:

| Object | Compared fields | Non-blank Salesforce positions | Exact CRM matches | Missing CRM values | Different non-blank values |
|---|---:|---:|---:|---:|---:|
| Account | 6 | 3,424 | 3,417 | 1 | 6 |
| Opportunity | 7 | 2,869 | 2,853 | 0 | 16 |
| Quote | 10 | 5,957 | 3,746 | 2,206 | 5 |
| Order | 46 | 214,549 | 176,743 | 37,798 | 8 |
| OrderItem | 10 | 48,200 | 48,172 | 23 | 5 |

For `Order`, 5,475 of 5,476 matched rows also had the same second-level `SystemModstamp`; within that same-version cohort there were 37,790 missing values and 6 different values. The missing-value fields were:

| `salesforce.order` field | Salesforce value present but CRM value blank |
|---|---:|
| `account_id_c` | 235 |
| `account_name_for_email_template_c` | 249 |
| `acv_amount_c` | 229 |
| `count_product_lines_c` | 5,245 |
| `end_date` | 5,454 |
| `exchange_rate_to_use_c` | 235 |
| `is_nonstandard_order_c` | 15 |
| `name` | 5,013 |
| `net_fees_c` | 5,247 |
| `opportunity_id` | 3 |
| `order_end_date_c` | 11 |
| `order_record_type_c` | 12 |
| `overall_end_date_c` | 5,193 |
| `overall_start_date_c` | 5,193 |
| `partner_name_c` | 41 |
| `payment_term_c` | 3 |
| `payment_type_c` | 796 |
| `ping_cap_contracting_entity_c` | 235 |
| `product_family_c` | 7 |
| `real_booking_date_c` | 235 |
| `region_c` | 849 |
| `tax_c` | 2 |
| `tcv_amount_c` | 229 |
| `term_c` | 231 |
| `total_amount` | 10 |
| `total_fees_c` | 2,826 |

Fields not listed had no missing CRM value in the matched Order cohort. The important line-item result was: 5,303 active Salesforce order-product rows under active Orders, 5,272 matched CRM OrderItems, 31 missing CRM OrderItems, 23 matched rows missing `Net_fees__c`, 4 `ListPrice` differences, 1 derived `Quantity × UnitPrice` versus `TotalPrice` difference, and 2 duplicated non-blank `OrderProduct__c` keys in CRM. Product, quantity, unit price, currency, service dates, and parent Order matched on every matched row where the Salesforce reference value was present.

The source tables are not equally fresh. `salesforce.order` stopped at 2026-07-24 08:57 UTC versus CRM Order at 2026-07-25 03:40 UTC; `salesforce.quote` stopped at 2026-05-28; and `salesforce.order_product_c` stopped at 2026-06-03. Therefore Account, Opportunity, Quote, and OrderItem non-version-matched differences remain drift signals rather than automatic correctness failures. The Order same-version result is the high-confidence historical preservation blocker.

The CRM snapshot contained two Orders absent from the Salesforce Order snapshot, created on 2026-07-24 and 2026-07-25. Both had Account, Opportunity, Quote, and one-or-more OrderItems, but both had 27 of the 46 projection source fields blank, including `Product_Family__c`, `TotalAmount`, and `Term__c`. This is evidence for the 2026-07-25 CRM-final snapshot only, not proof of the current staging runtime after later fixes.

### Xiaopeng version-8 read-only dry-run

At 2026-07-29T10:51:13Z, contract version 8 was run read-only against the current Xiaopeng CRM TiDB snapshot. The result is bound to source worktree head `7a634757ab49fc0802999a45fc81f0cf01596c09`, contract SHA-256 `061b801f19fb60d7f93a78e77e0710b11cdd289988b376511c409c270e28936f`, and snapshot evaluator SHA-256 `f4b10ad761c7bb6a7097d7f0b66bcd85c92158b985943aad99a5951a67a7f9a1`.

```text
CRM_PROD_SNAPSHOT_READINESS_BLOCKED
Orders: 0 / 5,478 fully passed
salesforce.order projection positions: 219,887 / 251,988 passed
aggregate violations: 76,346
```

The 46-field projection failures were:

| Projected field | Applicable | Correct | Incorrect | Not applicable |
|---|---:|---:|---:|---:|
| `order_end_date_c` | 5,470 | 5,461 | 9 | 8 |
| `opportunity_id` | 5,478 | 818 | 4,660 | 0 |
| `quote_id` | 5,478 | 819 | 4,659 | 0 |
| `account_name_for_email_template_c` | 5,478 | 5,227 | 251 | 0 |
| `product_family_c` | 5,478 | 5,469 | 9 | 0 |
| `partner_name_c` | 108 | 101 | 7 | 5,370 |
| `total_amount` | 5,473 | 5,460 | 13 | 5 |
| `net_fees_c` | 5,449 | 223 | 5,226 | 29 |
| `overall_start_date_c` | 5,413 | 220 | 5,193 | 65 |
| `overall_end_date_c` | 5,413 | 220 | 5,193 | 65 |
| `count_product_lines_c` | 5,472 | 227 | 5,245 | 6 |
| `exchange_rate_to_use_c` | 5,478 | 4,641 | 837 | 0 |
| `payment_term_c` | 5,258 | 5,255 | 3 | 220 |
| `payment_type_c` | 814 | 18 | 796 | 4,664 |

The other 32 projected fields had zero incorrect positions under their required/applicable policy. A non-blank projection can still be semantically wrong: the OrderItem-backed exact checks found 230 incorrect `TotalAmount` rollups, 5,226 incorrect `Net_Fees__c` rollups, 5,248 incorrect line-count rollups, and 5,193 incorrect values for each service-period rollup. Other blockers included 810 of 819 referenced Quote occurrences missing `Product_Family__c`, 731 Order-to-Account name mismatches, 27 OrderItem occurrences missing `Net_fees__c`, 35 OrderItem-to-Account mismatches, 3 OrderItem arithmetic mismatches, 11 referenced Account occurrences missing `Name`, and 3 referenced Opportunity occurrences missing `Rate__c`.

`term_c` was applicable and correct for 5,461 Orders, with 17 not applicable because the required date oracle was unavailable; it had zero incorrect projection positions. Therefore the current Xiaopeng blocker is not `Term__c`.

For staging, the workflow shares the repository's `deploy-staging-blue-green-environment` concurrency lock with deployment and runtime validation; test targets share their matching `deploy-test-multi-slot-environment-<slot>` lock. It then calls `/health`, reads `X-NexusCRM-Backend-Port`, and maps that port to exactly one configured blue/green slot before it reads TiDB. The workflow fails closed if the route cannot be bound to one slot. TiDB command failures contain only a generic exit/code classification, never database endpoint, user, password, SQL, fixture IDs, or query results.

Both workflows preflight a `mysql` or `mariadb` client before any secret-bearing direct read or fixture mutation. On GitHub's Ubuntu runner they install `default-mysql-client` only when neither executable is already present; failure to provide a client stops the run before CRM data is changed.

The normal acceptance workflow is read-only with respect to CRM records. The separate main-only fixture workflow `.github/workflows/provision-nexuscrm-salesforce-order-crm-source-contract-fixture.yml` is restricted to staging and is the controlled write/update proof. Each workflow attempt passes its immutable GitHub `run_id-run_attempt` to the fixture provisioner. That namespace is new for every attempt; if its Account marker already exists, the workflow blocks instead of reusing it. It:

1. creates one fresh CRM-native Account → Opportunity → Quote → Order → two-or-more-OrderItem graph, together with its Org, Cloud CSA, Invoice Collection, and Revenue inputs; it never selects a historical Account, Order, or OrderItem as a passing fixture;
2. writes a known direct OrderItem `Net_fees__c` baseline of `900`;
3. changes it through the normal CRM object API to `901`;
4. reads the same OrderItem directly from the active staging CRM TiDB and requires the changed value and a non-blank `SystemModstamp`;
5. runs the full committed-state contract, including recalculated OrderItem-backed rollups.

Thus a fixture provisioning PASS proves a real normal CRM create/write/update reached the CRM TiDB for a graph that cannot be a historical Salesforce import. A normal contract PASS proves every physical required source field and every virtual-formula input on the selected fixture remains correct in that committed TiDB state. The replacement workflow then scans every existing non-deleted Order in the staging Prod-copy database; imported history and CRM-native rows are held to the same export-readiness rules. Runtime API formula results are used only as independent evidence of virtual-formula evaluation; they are never evidence that a value persisted. `Product2`, `Pricebook2`, `PricebookEntry`, and `RecordType` are deliberately shared reference metadata, not historical business-record evidence.

NexusCRM formula fields are virtual and intentionally have no physical object-table column. The registry marks each active formula with `persistenceMode: "virtual_formula"`; the TiDB reader never selects it. The gate reads its output through a CRM runtime query and compares it with an explicit deterministic oracle whose inputs were directly read from committed TiDB. Static tests fail if an active L2 formula is not marked virtual, or if a marked virtual formula has no deterministic oracle. Rollups remain physical committed columns and are read directly.

## Object cases and lineage labels

Dataset names are lineage labels in the report; this gate does not read BigQuery.

| Case | Object API | Required export fields | Formula/projection inputs | Raw dataset label | Final dataset label |
|---|---|---:|---:|---|---|
| `crm-source-account` | `Account` | 10 | 29 | `nexuscrm_raw.nexuscrm_account_raw` | `nexuscrm_final.nexuscrm_account` |
| `crm-source-user` | `User` | 5 | 0 | `nexuscrm_raw.nexuscrm_user_raw` | `nexuscrm_final.nexuscrm_user` |
| `crm-source-org` | `Org__c` | 11 | 11 | `nexuscrm_raw.nexuscrm_org_c_raw` | `nexuscrm_final.nexuscrm_org_c` |
| `crm-source-opportunity` | `Opportunity` | 6 | 2 | `nexuscrm_raw.nexuscrm_opportunity_raw` | `nexuscrm_final.nexuscrm_opportunity` |
| `crm-source-quote` | `Quote` | 11 | 0 | `nexuscrm_raw.nexuscrm_quote_raw` | `nexuscrm_final.nexuscrm_quote` |
| `crm-source-product2` | `Product2` | 6 | 0 | `nexuscrm_raw.nexuscrm_product2_raw` | `nexuscrm_final.nexuscrm_product2` |
| `crm-source-order-item` | `OrderItem` | 20 | 0 | `nexuscrm_raw.nexuscrm_orderitem_raw` | `nexuscrm_final.nexuscrm_orderitem` |
| `crm-source-order` | `Order` | 54 | 13 | `nexuscrm_raw.nexuscrm_order_raw` | `nexuscrm_final.nexuscrm_order` |

The registry lists every field name, accepted describe type, persisted value type, and validation mode. It includes the Account country/install-base/segmentation formulas, the Account owner `User`, `Org__c` identity/account/owner values, `Product2.Description`, `Account.BillingCountryCode`, `Opportunity.Rate__c`, `Opportunity.PingCAP_Contracting_Entity_EMEA_only__c`, and the 13 required `Order` formula/projection inputs. `RecordType.Name` is resolved from committed TiDB through `RecordTypeId` and the `_System_RecordType` table.

## Passing standard

All eight object cases and all three CRM-derived view cases must pass all layers:

1. Schema: every required field is present in `describe`, with an accepted type.
2. Committed value shape: every physical required export field and every virtual-formula input is read from CRM TiDB. Strings and references are non-blank; dates use `YYYY-MM-DD`; datetimes are parseable ISO timestamps; numbers are finite; booleans are actual booleans after TiDB normalization. `false` is a valid non-null value.
3. Exact business semantics: references must stay within the controlled graph; a runtime query must return each virtual formula value, and it must match the versioned deterministic oracle built from committed inputs; the metadata and committed values of all five required rollups must be correct.
4. Fresh CRM origin (fixture workflow only): the certification must contain the exact workflow run/attempt namespace and the complete list of newly created business objects. A historical Salesforce-imported row, a prior controlled fixture, or an object graph reused from a previous run cannot pass.
5. CRM-only view projection: the 72 CRM-derived fields of `account_view` (33), `org_view` (34), and `product_view` (5) must be non-blank/type-correct when required and must preserve the committed physical raw source or separately observed virtual-formula value through the in-memory final and view-shaped projection. `org_view.account_alias` is intentionally a downstream SQL NULL, not a CRM-derived field.
6. Historical retained-row coverage: across all non-deleted rows in the eight CRM object tables, every required physical export value must be present. Required virtual-formula outputs are separately queried for nulls. The result is an aggregate-only check: it does not log record IDs or field values.
7. Full existing Order snapshot (replacement workflow only): every non-deleted Order selected by the direct TiDB export must have non-null, resolvable Opportunity and Quote references. An activated Order must have at least one committed OrderItem. `OrderItem/OrderId` is the rollup authority. QuoteLineItem mapping is checked only when an OrderItem explicitly carries that QuoteLineItem ID; an unlinked historical QuoteLineItem is not used as rollup authority. Product, price, quantity, service dates, currency, net fees, OrderItem arithmetic, authoritative line-derived rollups, and all 46 output values are evaluated under the explicit required/conditional policy.

`account_view.id`, intentional SQL NULLs, exporter load time, customer surrogate keys/match method, and the non-CRM fallback rows are explicitly outside this CRM contract. `org_view.account_surrogate_key` and its non-CRM tenant fallback are also outside the CRM contract. The workflow does not read BigQuery.

The critical committed `OrderItem` rollups are:

| Order field | Exact oracle |
|---|---|
| `Count_Product_Lines__c` | Count of non-deleted `OrderItem` rows. |
| `Net_Fees__c` | Sum of `OrderItem.Net_fees__c`. |
| `TotalAmount` | Sum of `OrderItem.TotalPrice`. |
| `Overall_Start_Date__c` | Minimum `OrderItem.ServiceDate`. |
| `Overall_End_Date__c` | Maximum `OrderItem.EndDate`. |

`OrderItem.Net_fees__c` is required to be a committed finite number, but is intentionally not forced to equal `Quantity × UnitPrice`; quote-line and fee semantics can differ from display-price multiplication. The gate also checks `Discount__c`, `Term__c`, `Real_Booking_Date__c`, `Cloud_Order_Type__c`, `TCV_Amount__c`, `ACV_Amount__c`, `Total_Fees__c`, exchange-rate selection, revenue/cash fields, USD fields, contracting entity, prepayment, nonstandard-order status, and cross-object inheritance.

Any missing fresh fixture, reused run scope, missing always-required field, wrong non-null type, malformed non-null date, missing required related row, applicable QuoteLineItem mapping mismatch, applicable reference/formula/rollup mismatch, incorrect rollup metadata, missing TiDB credential, route-to-slot ambiguity, or failed TiDB readback blocks the relevant gate. The final workflow verdict is then `SALESFORCE_ORDER_REPLACEMENT_BLOCKED`.

## Controlled fixture and credentials

The read-only contract uses `NEXUSCRM_ORDER_SOURCE_CONTRACT_FIXTURES_JSON` only to select opaque controlled fixture IDs. The fixture workflow emits an opaque fixture manifest and always uploads its certification artifact once artifact generation is reached, including on a blocked run. Never put fixture IDs, credentials, connection details, or query results in documentation or logs.

TiDB passwords are scoped only to the direct-read execution steps after the workflow has passed its trusted-ref guard. The fixture workflow receives only the staging-slot TiDB credentials and rejects any target other than staging. The read-only workflow supports staging and test slots, and requires the corresponding configured TiDB credential. `CROSS_REPO_READ_TOKEN` remains a read-only provenance check for the supplied NexusCRM SHA; it does not certify the live runtime.

## Scope boundary and cutover predicate

`CRM_PERSISTENCE_CONTRACT_PASS` with `writePathProof=not_observed` proves that all retained historical source rows satisfy the required-field completeness check, that the selected controlled fixture has the required eight-object source state committed to the target CRM TiDB, and that the CRM-derived parts of the three data-platform views project correctly in the local raw-to-final simulation. It does not prove that a normal save/update reached TiDB. The staging-only fixture workflow attaches `writePathProof=controlled_fixture_mutation`, a passing `persistenceMutation` receipt, and `fixtureProvenance.mode=run_scoped_crm_api_create` after it creates the new graph, writes `Net_fees__c` through the normal CRM API, and reads both baseline and updated values directly from TiDB. Because `nexuscrm_raw` is a direct CRM TiDB export, that combined fixture receipt is the CRM-side persistence precondition for the raw export.

`SALESFORCE_ORDER_REPLACEMENT_PASS` means the CRM-side data contract no longer requires `salesforce_order_compatible`: the fresh create/save path, all existing exported rows in the staging Prod-copy, and the local raw→final→`salesforce.order` simulation all pass. It does not execute:

- delivery of the direct CRM TiDB export into `nexuscrm_raw`;
- `nexuscrm_raw`-to-`nexuscrm_final` materialization;
- historical backfill completeness;
- `data_platform.order_by_product_s` output parity;
- BigQuery execution, external customer-key enrichment, or non-CRM fallback rows in `account_view` and `org_view`;
- the downstream deployment/cutover or removal of compatibility code;
- repair of any existing row reported by the snapshot gate.

The CRM replacement predicate is:

```text
CRM_PERSISTENCE_CONTRACT_PASS
AND CRM_PROD_SNAPSHOT_READINESS_PASS
AND raw→final→salesforce.order projection PASS
=> SALESFORCE_ORDER_REPLACEMENT_PASS
```

Production certification is separately authorized and read-only: it must bind the production route/version and demonstrate committed persistence plus raw-export receipt. This staging fixture workflow never writes production data.

## Running and evidence

Run `NexusCRM Salesforce Order CRM Persistence Contract` manually from reviewed `main`, or an immutable `crm-persistence-contract/<workflow-sha>` tag reachable from `main`. Supply a full NexusCRM source SHA; it is recorded as source provenance only.

The contract artifact contains:

- `crm-persistence-contract-certification.json`;
- `crm-persistence-contract-report.md`;
- `crm-persistence-contract-summary.md`;
- `salesforce-order-replacement-certification.json` (replacement workflow);
- `salesforce-order-replacement-summary.md` (replacement workflow);
- `cli-output.json`.

The certification binds the result to the workflow SHA, asserted source SHA, target environment, active persistence target label, contract SHA-256, fixture fingerprint, run URL, timestamp, and non-sensitive run-scoped fixture provenance. It contains no raw fixture identifiers or credentials.

Both workflows publish `crm-persistence-contract-summary.md` to the GitHub Actions run Summary with `if: always()`. The replacement workflow additionally publishes a three-gate status table and aggregate existing-data blocker IDs/counts from `salesforce-order-replacement-summary.md`. Fixture identifiers, business record identifiers, check messages, and field values are redacted. A blocked certification is therefore directly visible in the run UI; the artifact remains the durable evidence bundle.
