{
  "id": "merge-contact",
  "title": "HubSpot contacts merge leaves no orphan Lead (survivor kept, merged-away cleaned)",
  "kind": "hubspot_sync",
  "caseTimeoutMs": 720000,
  "linkedIssues": [2744],
  "provenance": {
    "businessScenario": "MERGE-CONTACT (#2744) — merging two HubSpot contacts (A=primary/survivor, B=objectIdToMerge) must keep A's NX Lead, clean/re-point B's Lead, leave no orphan. P-MERGE. #2744 is now fixed (issue CLOSED), so step6 (COUNT by B old id = 0) must PASS — a non-zero orphan count is a #2744 regression. onTimeout fail.",
    "reportedBy": "HubSpot sync spec MERGE-CO/DEAL/CONTACT (hubspot-nexuscrm-sync-CASE-EXEC-SELFCONTAINED)",
    "capturedAt": "2026-06-18",
    "calibrated": "[calibrated] Per #2744-fixed behavior the merge assigns a NEW canonical id to the survivor; assert-survivor now queries the merge RESPONSE id ({{steps.merge.response.body.id}}) instead of the original primaryObjectId, while assert-no-orphan keeps the merged-away create-b id (count 0). 2026-06-26 staging run 28222667769 showed contact merge cleanup can exceed the old 90s post-merge poll window, so merge cleanup uses the established 240s async mutation budget with connector-event diagnostics."
  },
  "steps": [
    {
      "id": "create-a",
      "system": "hubspot",
      "method": "POST",
      "path": "/crm/v3/objects/contacts",
      "body": {
        "properties": {
          "email": "merge-a-{{__runId}}@example.com",
          "firstname": "SurvA",
          "lastname": "MergeCase"
        }
      },
      "expectStatusRange": [200, 299],
      "assertions": [
        { "path": "response.body.id", "exists": true }
      ]
    },
    {
      "id": "create-b",
      "system": "hubspot",
      "method": "POST",
      "path": "/crm/v3/objects/contacts",
      "body": {
        "properties": {
          "email": "merge-b-{{__runId}}@example.com",
          "firstname": "GoneB",
          "lastname": "MergeCase"
        }
      },
      "expectStatusRange": [200, 299],
      "assertions": [
        { "path": "response.body.id", "exists": true }
      ]
    },
    {
      "id": "wait-a-row",
      "system": "nexuscrm",
      "authRole": "admin",
      "method": "GET",
      "path": "/services/data/v63.0/query?q=SELECT Id FROM Lead WHERE HubSpot_Id__c='{{steps.create-a.response.body.id}}' LIMIT 1",
      "expectStatus": 200,
      "poll": { "budgetMs": 180000, "intervalMs": 6000 },
      "onTimeout": "fail",
      "diagnostics": {
        "connectorEvent": {
          "sourceObjectId": "{{steps.create-a.response.body.id}}",
          "blockOnBacklog": true,
          "blockOnForeignClaimedBy": true,
          "foreignClaimedByPattern": "(MacBook|\\.local)"
        }
      },
      "assertions": [
        { "path": "response.body.records", "lengthAtLeast": 1 }
      ]
    },
    {
      "id": "wait-b-row",
      "system": "nexuscrm",
      "authRole": "admin",
      "method": "GET",
      "path": "/services/data/v63.0/query?q=SELECT Id FROM Lead WHERE HubSpot_Id__c='{{steps.create-b.response.body.id}}' LIMIT 1",
      "expectStatus": 200,
      "poll": { "budgetMs": 180000, "intervalMs": 6000 },
      "onTimeout": "fail",
      "diagnostics": {
        "connectorEvent": {
          "sourceObjectId": "{{steps.create-b.response.body.id}}",
          "blockOnBacklog": true,
          "blockOnForeignClaimedBy": true,
          "foreignClaimedByPattern": "(MacBook|\\.local)"
        }
      },
      "assertions": [
        { "path": "response.body.records", "lengthAtLeast": 1 }
      ]
    },
    {
      "id": "merge",
      "system": "hubspot",
      "method": "POST",
      "path": "/crm/v3/objects/contacts/merge",
      "body": {
        "primaryObjectId": "{{steps.create-a.response.body.id}}",
        "objectIdToMerge": "{{steps.create-b.response.body.id}}"
      },
      "expectStatusRange": [200, 299],
      "assertions": [
        { "path": "response.body.id", "exists": true }
      ]
    },
    {
      "id": "assert-no-orphan",
      "system": "nexuscrm",
      "authRole": "admin",
      "method": "GET",
      "path": "/services/data/v63.0/query?q=SELECT COUNT() FROM Lead WHERE HubSpot_Id__c='{{steps.create-b.response.body.id}}'",
      "expectStatus": 200,
      "delayMs": 20000,
      "poll": { "budgetMs": 240000, "intervalMs": 6000 },
      "onTimeout": "fail",
      "diagnostics": {
        "connectorEvent": {
          "sourceObjectId": "{{steps.merge.response.body.id}}",
          "blockOnBacklog": true,
          "blockOnForeignClaimedBy": true,
          "foreignClaimedByPattern": "(MacBook|\\.local)"
        }
      },
      "assertions": [
        { "path": "response.body.totalSize", "numericEquals": 0 }
      ]
    },
    {
      "id": "assert-survivor",
      "system": "nexuscrm",
      "authRole": "admin",
      "method": "GET",
      "path": "/services/data/v63.0/query?q=SELECT COUNT() FROM Lead WHERE HubSpot_Id__c='{{steps.merge.response.body.id}}'",
      "expectStatus": 200,
      "poll": { "budgetMs": 120000, "intervalMs": 6000 },
      "onTimeout": "fail",
      "diagnostics": {
        "connectorEvent": {
          "sourceObjectId": "{{steps.merge.response.body.id}}",
          "blockOnBacklog": true,
          "blockOnForeignClaimedBy": true,
          "foreignClaimedByPattern": "(MacBook|\\.local)"
        }
      },
      "assertions": [
        { "path": "response.body.totalSize", "numericEquals": 1 }
      ]
    }
  ]
}
