# Downstream Pack Compatibility Notes

## Purpose

This document records downstream-only pack compatibility shims that are
currently required for `nexuscrm-pingcap-pack` to install against the locked
or tested upstream `NexusCRM` ref.

Use this as the source of truth for:

- what we intentionally normalize or strip during final-pack assembly
- why the behavior exists
- which changes are temporary compatibility shims rather than permanent
  Salesforce-semantic decisions
- what to re-evaluate when upstream behavior changes

## Scope Boundary

These notes describe **downstream assembly behavior** only.

They do not mean:

- Salesforce original metadata semantics are wrong
- the downstream shape is the final long-term product contract
- upstream should never grow support for the stripped / normalized constructs

They do mean:

- the current upstream install / runtime contract cannot accept these raw
  declarations as-is
- downstream must adapt the final pack so local bootstrap and remote deploy can
  complete

## Current Compatibility Shims

### 1. Strip L0 reserved fields from downstream object specs

Current rule:

- final pack assembly strips these field declarations from downstream object specs:
  - `Id`
  - `OwnerId`
  - `CreatedById`
  - `LastModifiedById`
  - `CreatedDate`
  - `LastModifiedDate`
  - `SystemModstamp`
  - `IsDeleted`

Reason:

- current upstream runtime injects these as L0 system columns automatically
- if downstream L1/L2 object specs redeclare them, install fails

Meaning:

- this is **not** a statement that Salesforce objects should not have these fields
- this is a statement that **downstream pack payloads must not redeclare them**
  under the current upstream control-plane contract

Re-evaluate when upstream changes:

- if upstream ever allows explicit L1/L2 declaration of these fields again
- or if upstream changes the L0 reserved-field policy

Current enforcement:

- build-time sanitization in
  [scripts/build_pingcap_salesforce_sample_pack.sh](../scripts/build_pingcap_salesforce_sample_pack.sh)

### 1a. Keep `CampaignMember.Status` scoped-picklist runtime gaps out of global baseline

Current rule:

- shared watchdog baseline seed must not create `CampaignMember` rows that
  depend on runtime-scoped `Status` validation

Reason:

- current runtime exposes `CampaignMember.Status` with validator
  `runtime_scoped_picklist`
- current baseline apply path can fail with:
  - `validation error on field 'Status': validator with ID 'runtime_scoped_picklist' not found`
- this is a scene-specific capability gap, not a global environment bootstrap
  contract failure

Meaning:

- campaign member / campaign member status fixtures belong to scene readiness
  or case-local setup
- they must not block the entire watchdog environment from reaching a usable
  baseline
- if the runtime capability is still missing, track it as upstream/runtime debt
  rather than as downstream environment instability

Re-evaluate when upstream changes:

- if runtime-scoped picklist validation becomes stable for seeded
  `CampaignMember.Status`
- if campaign readiness data can be created through the same baseline path
  without scene-specific failures

### 2. Normalize duplicate `relationshipName` declarations within one object

Current rule:

- if multiple reference fields on the same object declare the same
  `relationshipName`, final-pack assembly renames later duplicates to stable,
  field-derived names

Example:

```text
Lead.AssociateAccount__c   -> Leads
Lead.AssociateLead__c      -> Leads_AssociateLead
Lead.First_Campaign__c     -> Leads_First_Campaign
Lead.SDR_For_NA_Only__c    -> Leads_SDR_For_NA_Only
```

Reason:

- current upstream compatibility registration treats
  `(ObjectAPIName, RelationshipName)` as a unique logical declaration
- if the same `relationshipName` maps to different foreign keys / targets,
  install fails with a conflicting relationship declaration error

Meaning:

- this is **not** guaranteed to match raw Salesforce naming one-to-one
- this is a downstream normalization so the current upstream runtime can accept
  the final pack

Re-evaluate when upstream changes:

- if upstream compatibility registration is relaxed or redesigned
- if upstream gains native support for the original duplicate naming shape
- if downstream wants to restore source-authored names after upstream support lands

Current enforcement:

- build-time normalization in
  [scripts/build_pingcap_salesforce_sample_pack.sh](../scripts/build_pingcap_salesforce_sample_pack.sh)

### 3. Strip unsupported `rollupsummary` fields from final pack

Current rule:

- final-pack assembly strips fields whose logical type is `rollupsummary`

Reason:

- current upstream install path rejects these object spec fields as an unknown
  logical type during pack-to-table-definition conversion

Meaning:

- this is explicitly a **temporary compatibility downgrade**
- it does **not** mean the Salesforce notion of rollup summary fields is
  invalid
- it means the current tested upstream `main` cannot install them from this
  downstream object-spec path yet

Re-evaluate when upstream changes:

- if upstream pack install / object-spec conversion gains supported handling for
  `rollupsummary`
- if upstream introduces a different canonical modeling path for rollups
- if downstream wants to restore these fields into the final artifact after
  upstream support is ready

Current enforcement:

- build-time sanitization in
  [scripts/build_pingcap_salesforce_sample_pack.sh](../scripts/build_pingcap_salesforce_sample_pack.sh)

### 4. Strip unsupported logical default expressions

Current rule:

- final-pack assembly removes field-level `default` expressions that the
  current install / DDL path cannot translate safely
- current stripped expression set:
  - `today()`
  - `now()`
  - `Now()`

Reason:

- current downstream object specs can carry Salesforce-style logical default
  expressions
- current upstream install path materializes these into physical DDL defaults
  that are invalid for the tested runtime / TiDB path

Example:

```text
Contract.Effective_Date__c default "today()" -> removed
Quote.Effective_Date__c default "today()" -> removed
Revenue__c.Date__c default "today()" -> removed
Feature_Request__c.Date__c default "now()" -> removed
Support__c.Start_Time__c default "now()" -> removed
Support__c.End_Time__c default "Now()" -> removed
```

Meaning:

- this is a conservative downgrade
- downstream does **not** replace the expression with a fabricated fixed value
- downstream does **not** rewrite it to a guessed SQL equivalent
- downstream simply removes the unsupported default so install can proceed

Re-evaluate when upstream changes:

- if upstream introduces canonical support for logical default expressions like
  `today()` / `now()`
- if upstream gains a separate metadata-level default handling path instead of
  raw DDL translation

Current enforcement:

- build-time sanitization in
  [scripts/build_pingcap_salesforce_sample_pack.sh](../scripts/build_pingcap_salesforce_sample_pack.sh)

### 5. Strip pre-quoted string default literals

Current rule:

- final-pack assembly removes field-level `default` values when the literal
  already contains quote characters

Reason:

- current upstream install path turns these metadata defaults into raw DDL
  literal fragments
- source values like `"'N/A'"` or `"\"Pending\""` end up double-quoted /
  malformed in generated SQL and fail table creation

Examples:

```text
Opportunity.Forecast_Type__c default "'N/A'" -> removed
Quote.ApproveStatus__c default "\"Pending\"" -> removed
Contract.Approve_Status__c default "\"Pending\"" -> removed
```

Meaning:

- downstream does **not** try to infer whether the source intended unquoted
  text, SQL, or formula semantics
- downstream removes the default conservatively so install can proceed

Re-evaluate when upstream changes:

- if upstream gains safe literal escaping / canonical default serialization
- if source metadata is normalized to store installable literals directly

Current enforcement:

- build-time sanitization in
  [scripts/build_pingcap_salesforce_sample_pack.sh](../scripts/build_pingcap_salesforce_sample_pack.sh)

### 6. Normalize validation-rule seed ids to `vrule_` prefix

Current rule:

- final-pack assembly rewrites validation-rule seed `id` values to stable
  `vrule_...` identifiers when source metadata uses legacy ids like `xml_*`

Reason:

- current upstream validation-rule seed loader requires every rule id to use
  the `vrule_` prefix
- source payloads still contain older ids that install fails on directly

Examples:

```text
xml_account_fillincountry -> vrule_xml_account_fillincountry
xml_account_tam -> vrule_xml_account_tam
```

Meaning:

- downstream preserves the source rule `name`, `objectApiName`, and condition
  semantics
- downstream only normalizes the seed identifier so the current loader accepts
  the pack

Re-evaluate when upstream changes:

- if upstream loader accepts legacy validation-rule ids
- if source metadata is migrated to canonical `vrule_` ids

Current enforcement:

- build-time sanitization in
  [scripts/build_pingcap_salesforce_sample_pack.sh](../scripts/build_pingcap_salesforce_sample_pack.sh)

### 7. Preserve upstream standard fields during downstream object overlay

Current rule:

- final-pack assembly merges standard-object fields at field granularity
- upstream standard fields stay present unless downstream explicitly replaces
  the same `apiName`

Reason:

- downstream object JSON is an overlay, not a full authoritative replacement
- some downstream standard-field declarations are intentionally stripped during
  compatibility sanitization, especially empty-option picklists
- if the whole downstream object replaces the upstream object, standard fields
  like `Account.Industry`, `Account.Type`, and `Account.Rating` disappear and
  later formula/view materialization fails

Examples:

```text
Account.Industry -> preserved from upstream standard spec
Account.Type -> preserved from upstream standard spec
Account.Rating -> preserved from upstream standard spec
```

Meaning:

- downstream custom fields and explicit standard-field overrides still win
- stripped downstream fields do not implicitly delete upstream standard fields

Re-evaluate when upstream changes:

- if downstream object payloads become full authoritative snapshots
- if upstream/downstream standard-object merge contract changes

Current enforcement:

- build-time field-level merge in
  [scripts/build_pingcap_salesforce_sample_pack.sh](../scripts/build_pingcap_salesforce_sample_pack.sh)

### 8. Downgrade downstream formula fields to plain fields

Current rule:

- final-pack assembly removes formula metadata from downstream formula fields
- the field itself stays in the schema, but downstream no longer asks upstream
  to materialize the formula/view during install

Reason:

- current downstream formula payloads depend on multiple source-side contracts
  that are not install-safe against the tested upstream runtime
- common failure modes include:
  - references to stripped compatibility fields such as `rollupsummary`
  - references to source fields that are not present in the installable pack
  - formula-function / relationship handling mismatches during view generation

Examples:

```text
Account.CLDR__Active_Accounts__c -> drop formula metadata, keep plain field
Account.Segmentation__c -> drop formula metadata, keep plain field
Lead.Owner_Profile__c -> drop formula metadata, keep plain field
```

Meaning:

- downstream chooses installability over active formula materialization
- field API names remain stable so later upstream/source fixes can restore
  formula behavior in one place: the final-pack assembly script

Re-evaluate when upstream changes:

- if upstream formula/view materialization becomes compatible with this source
  metadata shape
- if downstream decides to re-enable formula metadata selectively or fully

Current enforcement:

- build-time sanitization in
  [scripts/build_pingcap_salesforce_sample_pack.sh](../scripts/build_pingcap_salesforce_sample_pack.sh)

### 9. Strip empty-option picklist fields from final pack

Current rule:

- final-pack assembly removes `picklist` fields whose `options` list is missing
  or empty
- current exception:
  - for `CurrencyIsoCode`, inject a standard downstream option set plus default
    `USD` when the packaged option set is empty

Reason:

- current upstream schema / DDL validation requires picklist fields to have at
  least one option
- some downstream metadata carries picklist field declarations without packaged
  options
- preserving the field as-is makes install fail

Meaning:

- downstream does **not** fabricate placeholder options
- downstream does **not** silently change the field type to `text`
- downstream removes the field entirely until upstream or source metadata can
  provide a valid installable representation
- `CurrencyIsoCode` is treated specially because it is a standard field with a
  known runtime expectation and downstream audits require it to remain present

Current `CurrencyIsoCode` fallback:

```text
options = [CNY, JPY, SGD, USD]
default = USD
```

Examples:

```text
Account.Type
Account.Industry
Campaign.Status
Opportunity.Type
```

Re-evaluate when upstream changes:

- if upstream gains a supported empty-picklist import path
- if source metadata starts shipping canonical option sets for these fields
- if downstream can restore the fields without inventing business semantics

`Quote.Status` now keeps explicit downstream options because the PingCAP L2
Quote approval lifecycle writes `Draft`, `Pending`, `Approved`, `Rejected`, and
`Accepted` through approval metadata and order actions.

Current enforcement:

- build-time sanitization in
  [scripts/build_pingcap_salesforce_sample_pack.sh](../scripts/build_pingcap_salesforce_sample_pack.sh)

### 10. Catalog V3 requires source-backed defaults for L1 page layouts

Current rule:

- final-pack assembly no longer excludes official `page_layout` business keys
- `CampaignMember/default` and `Order/default` come from official
  `salesforce-crm-standard`
- `Quote/default` is restored from the PingCAP L2 overlay so Catalog sees a
  live default and the Quote page still carries PingCAP commercial fields

Reason:

- Catalog App metadata V3 reads L1 AppRelease `page_layout` components and
  requires a live default layout for each
- the previous curated-maintenance exclusion left those objects installed
  without a default layout, which now fails Catalog reconcile with
  `effective page layout "…" has no default`

Meaning:

- `Case/default` and `Lead/default` stay source-backed as before
- `Account/default` and `Partner_Attribution__c/default` remain L2 overlays
- the empty exclusion set is still centralized in
  [scripts/build_pingcap_salesforce_sample_pack.sh](../scripts/build_pingcap_salesforce_sample_pack.sh)
  so a future shim can be added in one place if install-pack verify drift
  returns

Re-evaluate when upstream changes:

- if Catalog starts selecting layouts from the installed merged pack instead
  of the L1 AppRelease mount
- if install-pack verify and curated layout maintenance diverge again

Current enforcement:

- build-time component filtering in
  [scripts/build_pingcap_salesforce_sample_pack.sh](../scripts/build_pingcap_salesforce_sample_pack.sh)
- Catalog V3 required keys in
  [scripts/audit_final_pack_metadata.py](../scripts/audit_final_pack_metadata.py)

## Operating Rule

When a deploy or watchdog failure is caused by pack install incompatibility,
decide in this order:

1. Is this a generic upstream platform / control-plane capability gap?
2. If yes, record the gap upstream and keep any downstream workaround explicit.
3. If the environment must stay deployable before upstream is fixed, add a
   downstream assembly shim here and keep it auditable.
4. When upstream support lands, revisit this document and remove or relax the
   downstream shim deliberately.

## Update Checklist

Whenever a new pack compatibility shim is added or removed, update all of:

- this file
- [docs/stack-test-model.md](stack-test-model.md)
- the relevant assembly / canonical audit checks
- any open downstream or upstream tracking issue if one exists
