⚠ CRITICAL: stage ID names DO NOT match what they represent
The pipeline labels were renamed to fit SkyRun's BD process, but the underlying default-pipeline stage IDs kept the original HubSpot generic names. Result: contractsent is actually the Won stage, and closedwon is actually the Lost stage. Get this wrong and you trigger the wrong HS auto-notification (e.g. the Hadank "Contract Sent" false-positive on 2026-04-30 morning came from deal_sync.py mapping post_walkthrough_pending_agreement → presentationscheduled, which fires the "Contract" notification).
ALWAYS reference this table before patching any code that writes deal stage IDs.
Canonical mapping (live source — pipelineId: default, portal 23273108)
| displayOrder | Stage ID | SkyRun Label | Probability | What this means in our process | |
|---|---|---|---|---|---|
| 0 | appointmentscheduled | First \ | Ground Rules Appointment Scheduled | 0.1 | Initial intro / qualification call booked |
| 1 | qualifiedtobuy | Secondary \ | Discovery Appointment Scheduled | 0.3 | Discovery / walkthrough scheduled or complete |
| 2 | presentationscheduled | Contract \ | Contract Received by Decision Makers | 0.7 | We've SENT the management agreement; awaiting return |
| 3 | decisionmakerboughtin | Final \ | Decision Makers Have Scheduled a Final Appointment | 0.9 | Verbal commit + agreement returned but not yet signed |
| 4 | contractsent | Won \ | Contract Signed by Decision Makers | 1.0 | 🎉 SIGNED — closed-won |
| 5 | closedwon | Lost \ | Decision Makers Declined to Sign the Contract | 0.0 | ❌ Declined / closed-lost |
Code-side stage transitions
deal_sync.py maps internal deal-state strings to HS stage IDs. The current correct mapping (patched 2026-04-30 PM after Hadank incident):
python
INTERNAL_TO_HS_STAGE = {
"discovery_call_scheduled": "appointmentscheduled", # First
"discovery_complete": "qualifiedtobuy", # Secondary
"post_walkthrough_pending_agreement": "qualifiedtobuy", # Secondary ← was wrong, fixed
"agreement_sent": "presentationscheduled", # Contract
"agreement_received": "decisionmakerboughtin", # Final ← was wrong, fixed
"agreement_signed": "contractsent", # Won
"lost": "closedwon", # Lost
}
Stage-related HS auto-notifications (what fires when)
HubSpot auto-notifies the deal owner (and configured watchers) when a deal moves into specific stages:
appointmentscheduled→ no notification by defaultqualifiedtobuy→ no notification by defaultpresentationscheduled→ "Contract Sent" email/in-app notification fires (this is the one that hit Joseph for Hadank)decisionmakerboughtin→ "Final Appointment Scheduled" notification firescontractsent→ "Won" celebration notification firesclosedwon→ "Lost" notification fires
If you transition a deal forward without explicit confirmation from Joseph or Rachel, you risk firing a notification that contradicts the actual sales reality.
How to query live
javascript
// In an authenticated HubSpot tab, via chrome_bridge:
fetch('/api/crm-pipelines/v1/pipelines/deals?portalId=23273108', {
headers:{'X-HubSpot-CSRF-hubspotapi': (document.cookie.match(/csrf\.app=([^;]+)/)||[])[1]}
}).then(r=>r.json())
Returns the full pipeline with pipelineId, label, and a stages array containing stageId, label, displayOrder, and metadata.probability.
Cross-references
~/Library/Application Support/SkyRun/deal_sync.py— the stage-mapping table (lines 65-80)feedback_no_fabrication_personal.md— the system-tenant-values rule (don't fabricate stage IDs from generic LLM training)project_active_deal_hadank.md— incident: false-positive "Contract Sent" notification 2026-04-30 AMproject_audit_manifest_2026-04-30_pm.md— closes flag #61