{
  "apiName": "Credits__c",
  "label": "Credits",
  "pluralLabel": "Credits",
  "tableType": "custom_object",
  "category": "crm",
  "sharingModel": "Private",
  "enableHierarchySharing": true,
  "description": "Credit engagement record. Distinguished by Credit_Type__c (PoC at launch; additional types — Trial, Marketing, CS, Partner — added as future programs adopt the system). Replaces the legacy PoC__c (PoC credits are USD-only at launch) object. See docs/object-proposals/poc-credits-refactor.md.",
  "fields": [
    {
      "apiName": "Name",
      "label": "Credits Name",
      "type": "string",
      "maxLength": 255,
      "isNameField": true,
      "required": false,
      "description": "Record display name. Auto-populated by the Credits — Pre-fill Snapshot Fields Flow (RecordBeforeSave Create) as `<Account Name> — <Credit Type> Credits`, so AE never types it. Required by the platform Related List / lookup-display contract: the frontend useRecordList hook unconditionally expands any lookup reference to `<rel>__r.Name`, so a child Related List on Credits (e.g. Top-ups) issues `Credits__r.Name` and 400s with INVALID_QUERY unless Credits__c exposes a Name field. Also drives the detail-page header (previously showed the raw record UUID)."
    },
    {
      "apiName": "Credit_Type__c",
      "label": "Credit Type",
      "type": "picklist",
      "required": true,
      "default": "PoC",
      "updateable": false,
      "options": [
        {
          "value": "PoC",
          "label": "PoC"
        }
      ],
      "description": "Discriminates credit programs. At launch only PoC is active; future types (Trial / Marketing / CS / Partner) added via Pack PR. Locked after create — switching type post-creation would invalidate approver stamping and business rules. See §3 snapshot rule."
    },
    {
      "apiName": "Account__c",
      "label": "Account",
      "type": "reference",
      "referenceTo": [
        "Account"
      ],
      "relationshipName": "Credits",
      "childRelationshipName": "Credits",
      "deleteConstraint": "Restrict",
      "required": true,
      "description": "Master context for the credit engagement. Required across all Credit_Types — every credit grant has a customer. childRelationshipName registers Credits as a child relationship in Account.describe so SOQL parent.child subqueries (`SELECT ..., (SELECT Id FROM Credits) FROM Account`) and referential integrity work; pair it with the `account-credits` rule in `standard_related_list_rules.json` (bucket=recommended) so the Account detail page's Related List Quick Links surface a 'Credits' entry — the UI is driven by the rules file, not by childRelationshipName alone."
    },
    {
      "apiName": "Account_Name__c",
      "label": "Account Name",
      "type": "string",
      "createable": false,
      "updateable": false,
      "description": "Snapshotted from Account.Name at create by Credits_Prefill_Snapshot_Fields before-save Flow. createable:false hides this field from the Create form — AE picks Account via the Account__c lookup picker, and the snapshot stamps automatically. Previously createable:true to support a ui_action prefill pattern that pre-filled this string from the Account-page entry context, but that path was redundant (the Flow already does the same stamp) and left a UX gap on the sidebar entry path where AE could type a freeform Account name with no matching FK. Forcing all reads through Account__c → snapshot Flow keeps the FK and the display name structurally consistent."
    },
    {
      "apiName": "Opportunity__c",
      "label": "Opportunity",
      "type": "reference",
      "referenceTo": [
        "Opportunity"
      ],
      "relationshipName": "Credits_FromOpp",
      "childRelationshipName": "Credits_FromOpp",
      "deleteConstraint": "Restrict",
      "required": false,
      "validator": "runtime_scoped_lookup",
      "validatorConfig": "{\"filterMappings\":[{\"recordField\":\"Account__c\",\"sourceField\":\"Id\",\"targetField\":\"AccountId\"}]}",
      "description": "Optional at schema level. Validation rule Credits__VR_PoC_Requires_Opp enforces required when Credit_Type='PoC'. Other future types (e.g., CS credits for existing customers) may not have an Opportunity. Filtered lookup via runtime_scoped_lookup validator: the picker is scoped to Opportunities whose AccountId matches the current Credits record's Account__c — mirroring Cloud_Org__c's parent-derived mapping pattern. Substrate mechanism: filterMapping recordField=Account__c (read Credits.Account__c, references Account) → sourceField=Id (navigate to Account, take its Id) → targetField=AccountId (filter Opportunity rows where Opportunity.AccountId equals the resolved Account.Id). The trivial identity (recordField == sourceField) is rejected by upstream `runtime_scoped_lookup currently requires parent-derived mappings`; the Account→Account-Id traversal satisfies that constraint. Validation rule Credits__VR_Opp_Matches_Account enforces server-side as defense in depth (catches the API-direct-write path where the UI picker is bypassed). Without this filter, AE could pair Credits.Account__c=customer-A with Credits.Opportunity__c=customer-B's-Opp — Credit_Type='PoC' would even pass the existing Credits__VR_PoC_Requires_Opp because Opp is not blank. childRelationshipName registers Credits as a child relationship in Opportunity.describe so SOQL parent.child subqueries (`SELECT ..., (SELECT Id FROM Credits_FromOpp) FROM Opportunity`) and referential integrity work; pair it with the `opportunity-credits` rule in `standard_related_list_rules.json` (bucket=recommended) so the Opportunity detail page's Related List Quick Links surface a 'Credits' entry — the UI is driven by the rules file, not by childRelationshipName alone."
    },
    {
      "apiName": "Opportunity_Name__c",
      "label": "Opportunity Name",
      "type": "string",
      "createable": false,
      "updateable": false,
      "description": "Snapshotted from Opportunity.Name at create when Opp linked. Stamped by Credits_Prefill_Snapshot_Fields before-save Flow (writes survive pruneNonCreateableFields because the Flow runs at RecordBeforeCreate, after the user-data prune pass). createable:false hides this field from the Create form so the Snapshot section auto-hides on AE input — Opp Name is also visible via the Opportunity__c lookup picker, so no info loss."
    },
    {
      "apiName": "Cloud_Org__c",
      "label": "Cloud Org",
      "type": "reference",
      "referenceTo": [
        "Org__c"
      ],
      "relationshipName": "Credits_OnOrg",
      "childRelationshipName": "Credits_OnOrg",
      "deleteConstraint": "Restrict",
      "required": true,
      "validator": "runtime_scoped_lookup",
      "validatorConfig": "{\"filterMappings\":[{\"recordField\":\"Account__c\",\"sourceField\":\"Id\",\"targetField\":\"Account__c\"}]}",
      "description": "The PingCAP Cloud org where the credits land. Schema-level required across all Credit_Types — every credit grant lands on a Cloud Org (Trial / Marketing / CS / Partner all share the Cloud-billing target model). Filtered lookup via runtime_scoped_lookup validator: the picker is scoped to Orgs whose Account__c matches the current Credits record's Account__c. Substrate mechanism: filterMapping recordField=Account__c (read Credits.Account__c, which references Account) → sourceField=Id (navigate to Account, take its Id) → targetField=Account__c (filter Org rows where Org.Account__c equals the resolved Account.Id). The trivial identity (recordField == sourceField) is rejected by upstream `runtime_scoped_lookup currently requires parent-derived mappings`; routing through Account.Id satisfies that constraint while preserving the desired filter semantics. Validation rule Credits__VR_CloudOrg_Matches_Account still enforces server-side as defense in depth (and catches the API-direct-write path where the UI picker is bypassed). Replaces legacy Cloud_Account_ID__c text field — gives a real relationship for the Phase 2/3 Cloud-billing CallWebhook to update Org.remaining_points__c."
    },
    {
      "apiName": "Account_Owner__c",
      "label": "Account Owner",
      "type": "reference",
      "referenceTo": [
        "User"
      ],
      "relationshipName": "Credits_AccountOwner",
      "deleteConstraint": "SetNull",
      "required": false,
      "createable": false,
      "updateable": false,
      "description": "Account's owner at create time, snapshotted from Account.OwnerId by Credits_Prefill_Snapshot_Fields before-save Flow. createable:false hides this field from the Create form (Flow stamps it at RecordBeforeCreate, after the user-data prune pass). The genuine schema invariant is Account__c (the anchor); snapshot fields derive from it."
    },
    {
      "apiName": "Region__c",
      "label": "Region",
      "type": "picklist",
      "required": false,
      "createable": false,
      "updateable": false,
      "options": [
        {
          "value": "APAC",
          "label": "APAC"
        },
        {
          "value": "EMEA",
          "label": "EMEA"
        },
        {
          "value": "Japan",
          "label": "Japan"
        },
        {
          "value": "NA",
          "label": "Americas"
        },
        {
          "value": "LATAM",
          "label": "LATAM"
        }
      ],
      "description": "Snapshotted from Account.Region at create. Drives approval routing — the stored value must match Region_All__c.Region__c key verbatim (APAC / EMEA / Japan / NA / LATAM are the 5 active PoC regions, per PM confirmation). Labels can be human-friendly; values are exact lookup keys."
    },
    {
      "apiName": "Industry__c",
      "label": "Industry",
      "type": "string",
      "createable": false,
      "updateable": false,
      "description": "Snapshotted from Account.Industry (a picklist upstream) at create by Credits_Prefill_Snapshot_Fields before-save Flow. createable:false hides this field from the Create form. Stored as string (source picklist already constrains value space)."
    },
    {
      "apiName": "Vertical__c",
      "label": "Vertical",
      "type": "string",
      "createable": false,
      "updateable": false,
      "description": "Snapshotted from Account.Vertical__c at create by Credits_Prefill_Snapshot_Fields before-save Flow. createable:false hides this field from the Create form."
    },
    {
      "apiName": "Initial_Credits__c",
      "label": "Initial Credits (USD)",
      "type": "currency",
      "required": true,
      "description": "Initial credit amount granted in USD. PoC credits are USD-only at Phase 1; if a non-USD program adopts the object under a future Credit_Type, currency handling re-enters then. See §3 + §7 of the proposal."
    },
    {
      "apiName": "Approved_Topups__c",
      "label": "Approved Top-ups (USD)",
      "type": "rollupsummary",
      "rollupConfig": {
        "summaryObject": "Credits_Topup__c",
        "summaryField": "Topup_Amount__c",
        "relationshipField": "Credits__c",
        "calcType": "SUM",
        "filter": "Approval_Status__c = 'Approved'"
      },
      "createable": false,
      "updateable": false,
      "description": "SUM of Topup_Amount__c across approved Credits_Topup__c children. Filter excludes in-flight (Pending) and rejected top-ups — only Approval_Status='Approved' contributes — so simultaneous top-up submissions don't race-condition the parent's running total. Drives Total_Disbursed__c (active formula on this object) and feeds Cumulative_Disbursed__c on each child (active formula reading Credits__r.Total_Disbursed__c). Requires NX#947 substrate (closed via #1125 — runtime-acceptance test deferred there, this object's end-to-end behavior is the acceptance proof). Build script must include this in `wave6_root_rollupsummary_fields`. Phase 1 stripped via the unsupported-types filter; restored in Phase 2 when Credits_Topup__c child object lands."
    },
    {
      "apiName": "Total_Disbursed__c",
      "label": "Total Disbursed (USD)",
      "type": "formula",
      "formulaMode": "active",
      "returnType": "currency",
      "formula": "Initial_Credits__c + NULLVALUE(Approved_Topups__c, 0)",
      "createable": false,
      "updateable": false,
      "description": "Active formula: parent's running grand total = Initial_Credits__c + Approved_Topups__c (rollupsummary of approved children). NULLVALUE wrapper handles the no-top-ups case where the rollup is NULL. Child Credits_Topup__c records reference this field via Credits__r.Total_Disbursed__c in their own Cumulative_Disbursed__c active formula. No relationship traversal here (Approved_Topups__c is same-object), but the dependency on a rollupsummary field means the build script must include this in `wave6_active_formula_fields` (otherwise the script would strip the formula because its dependency Approved_Topups__c is in unsupported_field_types). Phase 1 deferred — restored in Phase 2 together with Approved_Topups__c."
    },
    {
      "apiName": "Credit_Lifecycle__c",
      "label": "Credit Lifecycle",
      "type": "picklist",
      "required": true,
      "default": "Draft",
      "options": [
        {
          "value": "Draft",
          "label": "Draft"
        },
        {
          "value": "Active",
          "label": "Active"
        },
        {
          "value": "Provisioned",
          "label": "Provisioned"
        },
        {
          "value": "Completed",
          "label": "Completed"
        },
        {
          "value": "Cancelled",
          "label": "Cancelled"
        }
      ],
      "description": "The credit's business-lifecycle state — what the AE / list view / report cares about (vs Approval_Status__c which tracks the approval engine's workflow). Renamed from Engagement_Status__c (the 'engagement' framing did not communicate intent on the form). Values: Draft (Create default, schema-set) → Active (set by Credits_Lifecycle_On_Approval_Decision Flow when Approval_Status='Approved' — approved but credits not yet loaded onto the Cloud Org) → Provisioned (manual flip by Sales Ops in Phase 1, will be Cloud-billing-webhook-stamped in Phase 2 — credits live on Cloud and customer can spend) → Completed (manual flip by Sales Ops when credits used up or Expiration_Date reached; Phase 2 may automate via a scheduled Flow) / Cancelled (Flow-stamped on Approval_Status='Rejected', also Sales Ops manual on AE recall). FLS-locked to Sales Ops Edit (see deploy-baseline.seed.yaml FieldPermissions). The earlier 'InApproval' value was removed: Approval_Status='Pending' already expresses the same state."
    },
    {
      "apiName": "Has_Open_Org_Ledger__c",
      "label": "Has Open Org Ledger",
      "type": "boolean",
      "default": "0",
      "createable": false,
      "updateable": false,
      "description": "Stamped by the Credits_Stamp_Open_Org_Ledger before-save Flow on create AND on update: true when the Cloud_Org__c on this record already carries another Credits__c ledger OF THE SAME Credit_Type__c whose Credit_Lifecycle__c is not 'Cancelled'. Sole consumer is Credits__VR_One_Open_Ledger_Per_Org, which blocks the save so a PoC follow-on grant goes through Credits_Topup__c on the existing ledger instead of opening a second one (Startup grants are one per org and have no top-up path). That is what makes the approval gate see the org's cumulative total: step criteria aggregate within one ledger (Initial_Credits__c + Approved_Topups__c), so two sibling ledgers are each gated on their own smaller amount and the combined figure never reaches an approver (issue #4733). Recomputed on update, not only at create, because the value is a statement about this record's Cloud Org and goes stale the moment Cloud_Org__c changes - that field is updateable and on the detail layout, so a create-only stamp let an ordinary edit repoint a ledger at an org that already had one. The slot is keyed by (Cloud_Org__c, Credit_Type__c): each credit program runs its own approval chain with its own thresholds, so a live ledger only represents a bypass when the follow-on grant belongs to the same program, and one org legitimately carrying both a PoC engagement and a Startup Program grant must not be blocked. Credit_Type__c is updateable:false and locked after create, so it can never move a record between buckets and adds no event to the triad below - that immutability is load-bearing for this control and is pinned by a guard test. The rule that reads it fires on the complete set of three events by which this record can newly occupy, or move between, its (Cloud Org, Credit Type) slot - create, Cloud_Org__c change, and leaving Cancelled (reactivation) - and only while the record itself occupies a slot, so creating or repointing an already-Cancelled record is never blocked and transitions into Cancelled stay free. Ordinary lifecycle progression such as Active -> Provisioned is unaffected because the reactivation gate keys on PRIORVALUE. Boolean rather than the count the design sketched: the Flow engine exposes no collection-size reference, so a real count would need a Loop with numeric Add, whose operator silently no-ops on a type mismatch - a miscount there would fail OPEN and disable the control. Nothing reads a count (validation-rule messages take no merge fields), so existence is the whole requirement. Stamped on both branches rather than leaning on the column default, so the value is never ambiguous."
    },
    {
      "apiName": "Approval_Status__c",
      "label": "Approval Status",
      "type": "picklist",
      "default": "NotSubmitted",
      "options": [
        {
          "value": "NotSubmitted",
          "label": "Not Submitted"
        },
        {
          "value": "Pending",
          "label": "Pending"
        },
        {
          "value": "Approved",
          "label": "Approved"
        },
        {
          "value": "Rejected",
          "label": "Rejected"
        },
        {
          "value": "NeedsTriage",
          "label": "Needs Triage"
        }
      ],
      "description": "Approval workflow state, distinct from Credit_Lifecycle__c. NeedsTriage indicates the pre-submission Flow could not stamp one or more approver fields — sales ops checks a saved list view per ops handbook. See §7 lookup-failure fallback."
    },
    {
      "apiName": "Presales_Manager_User__c",
      "label": "Presales Manager (Stamped)",
      "type": "reference",
      "referenceTo": [
        "User"
      ],
      "relationshipName": "Credits_PresalesManager",
      "deleteConstraint": "SetNull",
      "createable": false,
      "updateable": false,
      "description": "Stamped by Credits_Stamp_Approvers before-save Flow at Create from Region_All__c. createable:false + updateable:false prevent any non-Flow write (closes the AE-redirects-approver integrity hole sydpz called out in PR #2055 review). Flow writes survive both prune passes because RecordBeforeCreate/RecordBeforeUpdate events fire after pruneNonCreateableFields/pruneNonUpdateableFields. Used by ApprovalStep 1."
    },
    {
      "apiName": "Finance_Approver_User__c",
      "label": "Finance Approver (Stamped)",
      "type": "reference",
      "referenceTo": [
        "User"
      ],
      "relationshipName": "Credits_FinanceApprover",
      "deleteConstraint": "SetNull",
      "createable": false,
      "updateable": false,
      "description": "Stamped by Credits_Stamp_Approvers before-save Flow at Create from Region_All__c.Regional_Finance__c. createable:false + updateable:false prevent any non-Flow write — Stamp_Approvers_Retry Flow can still rewrite at NeedsTriage→NotSubmitted because before-save Flow events fire after the prune pass. Used by ApprovalStep 2."
    },
    {
      "apiName": "BG_Leader_User__c",
      "label": "BG Leader (Stamped)",
      "type": "reference",
      "referenceTo": [
        "User"
      ],
      "relationshipName": "Credits_BGLeader",
      "deleteConstraint": "SetNull",
      "createable": false,
      "updateable": false,
      "description": "Stamped by Credits_Stamp_Approvers before-save Flow at Create from Region_All__c. createable:false + updateable:false prevent any non-Flow write. Used by ApprovalStep 3. BG = Region at PingCAP."
    },
    {
      "apiName": "Regional_Ops_User__c",
      "label": "Regional Ops (Stamped)",
      "type": "reference",
      "referenceTo": [
        "User"
      ],
      "relationshipName": "Credits_RegionalOps",
      "deleteConstraint": "SetNull",
      "createable": false,
      "updateable": false,
      "description": "Stamped by Credits_Stamp_Approvers before-save Flow at Create from Region_All__c.Region_Ops__c. Notified on final approval to perform the downstream credit reset in the external system (proposal §0 Phase 3 item 2). NOT an approval-routing field and NOT gated by the NeedsTriage fallback — its absence does not block submission (unlike the three approver fields). createable:false + updateable:false keep it Flow-only."
    },
    {
      "apiName": "Requires_Finance_Approval__c",
      "label": "Requires Finance Approval (Manual)",
      "type": "boolean",
      "default": "0",
      "createable": false,
      "description": "DORMANT at launch — the Finance approval step is not active and nobody sets this field. It is intentionally hidden from all layouts (createable:false already hid it from Create). Background: Finance review is really a per-region annual-budget gate (offline-tracked); when a region's budget exec rate crosses the threshold, ALL subsequent approvals in that region need Finance — a one-time region-level switch, not a per-record decision. At that point the gate is (re)enabled via the region-toggle design (see proposal §7 'Finance budget gate' + the A-blueprint): a Region_All__c toggle, a submit-time Flow that stamps this mirror field from the region toggle, and re-enabling the Finance approval step. Until then this field stays present-but-unused."
    },
    {
      "apiName": "Effective_Date__c",
      "label": "Effective Date",
      "type": "date",
      "required": true,
      "description": "Date the credit grant becomes effective on the Cloud Org. AE fills on Create — required at submit. Replaces the prior Start_Date__c (which was auto-stamped at approval time and conflated 'approved on' with 'effective on'). Sales Ops can correct via FLS Edit if the original date needs adjustment after approval."
    },
    {
      "apiName": "Expiration_Date__c",
      "label": "Expiration Date",
      "type": "date",
      "required": true,
      "description": "Date the credit grant expires — after this date the Cloud Org should reject further consumption of this grant. AE fills on Create — required at submit. Phase 1 has no scheduled job to auto-flip Credit_Lifecycle__c to Completed at expiration; Sales Ops monitors and manually flips. Phase 2 candidate: scheduled Flow that flips records where Expiration_Date < TODAY()."
    },
    {
      "apiName": "Completed_Date__c",
      "label": "Completed Date",
      "type": "date",
      "createable": false,
      "description": "The date the credit grant actually ended — distinct from the AE-set Expiration_Date__c which is an intent/promise. Auto-stamped by the Credits_Stamp_Completed_Date before-save Flow when Credit_Lifecycle__c flips to 'Completed' (operator: Assign $Flow.CurrentDate). Sales Ops can override via Edit if a historical date needs backfill (the Flow only fires on the lifecycle transition, not on later Completed_Date edits). createable:false hides this field from the New Credits form — a brand-new Draft credit is never Completed. Used for reporting: PoC duration = Completed_Date - Effective_Date, cohort conversion timing, finance reconciliation by completion quarter."
    },
    {
      "apiName": "Notes__c",
      "label": "Notes",
      "type": "textarea",
      "description": "AE narrative on what the engagement is for."
    },
    {
      "apiName": "Pre_Sales_Owner__c",
      "label": "Pre-Sales",
      "type": "reference",
      "referenceTo": [
        "User"
      ],
      "relationshipName": "Credits_PreSalesOwner",
      "deleteConstraint": "SetNull",
      "required": false,
      "createable": false,
      "updateable": false,
      "description": "Pre-Sales SE from the parent Opportunity (Opportunity.Pre_Sales_Owner__c) at grant time. Snapshotted at create by Credits_Prefill_Snapshot_Fields (before-save — the only supported write path for this locked field). Display/reporting only and create-time-only: like the other snapshot fields (Account_Owner__c etc.) it is NOT re-stamped when the Opportunity's pre-sales later changes; the current pre-sales always lives on the Opportunity. The pre-sales READ visibility share (_System_RecordShare, Reason='PreSalesAuto') is driven by the LIVE Opportunity.Pre_Sales_Owner__c — read fresh by Credits_Share_With_Presales (create/update) and swapped by Opportunity_Resync_Credits_PreSales_Share on reassignment — NOT by this snapshot, so a stale value here can never grant or re-grant access. createable:false + updateable:false = Flow-only. Restores the pre-sales linkage that legacy PoC__c carried as Pre_sales__c."
    }
  ],
  "keyPrefix": "a0X"
}
