# Platform mechanics that change the design

Vendor behaviours that break a workflow written from stage names alone. Each group gives the date the facts were checked and the page in `sources.yaml` that holds them. Re-check before quoting a limit or menu path to a user, and say so when the live page differs.

## HubSpot lifecycle stage

Checked 2026-11-06. Sources: use-lifecycle-stages, change-record-lifecycle-stages-in-bulk, forms-faq, manage-how-lifecycle-stages-sync-between-objects, create-and-customize-lifecycle-stages, hubspots-default-contact-properties.

- Imports, forms, the API, workflows and the Salesforce integration only move lifecycle stage forward. Setting an earlier value through them leaves the value unchanged, so check the property history, not the tool's run log.
- A manual edit in a record can move the stage backward.
- An earlier value through automation needs clear, then set: two Edit record actions in a workflow (clear the property, then set the stage). That workflow action needs Professional or Enterprise. Treat it as the correction path only, never as part of normal recycling.
- First value for a record created by a form: the stage chosen in the form's settings, then the default for newly created records if it is on, then the first stage in the display order. A copied form keeps its stage setting.
- Lifecycle automation settings (page updated 2026-09-09): a default stage for new contacts and companies; a stage when the first associated deal is created; a stage when an associated deal is won (with it off, Close date and Days to close are not updated); a stage when a lead is associated, which does not fire for leads created by automation; a primary company's stage copied to its contacts (never the other way); and deal pipeline stages that set company and contact stages. None of them moves a record backward.
- Backward moves and dates: a manual reversal clears the legacy "Became a [later stage] date" and fills the earlier stage's legacy date. Clearing alone leaves the latest legacy date. The newer calculated stage properties are not cleared by moving backward, so a mistaken Customer entry stays in history after the correction.
- Latest time and cumulative time in a stage cover completed visits only. While a record sits in a stage, they can be blank or show an earlier visit. They need Professional or Enterprise.
- Custom lifecycle stages exist on all products and plans; creating or editing one needs Super Admin.
- The automatic contact Close date from won deals needs the default Customer stage. A custom stage that replaces Customer breaks it.
- A separate custom property named "Lifecycle" does not get stage timestamps or the standard funnel path. Only options on the built-in property do.

## HubSpot leads object and lead pipeline

Checked 2026-11-06. Sources: create-leads, set-up-lead-pipeline-automation, hubspots-default-lead-properties, set-up-and-customize-pipelines, stage-calculated-properties.

- Needs Sales Hub Professional or Enterprise. A Super Admin activates it.
- A lead needs a primary association with a contact or a company. One contact can have several leads, which is what makes leads usable as request episodes.
- Workflows can create leads with the Create record action.
- Default lead pipeline stages: New, Attempting, Connected, Qualified, Disqualified.
- Lead pipeline automation (Settings > Data Management > Objects > Leads > Pipelines > the pipeline > Automate; page updated 2026-08-10) moves stages only in the default lead pipeline. Sending or logging an email or call, or logging an SMS, LinkedIn or WhatsApp message, moves a lead to Attempting. An email reply, a meeting with the outcome Completed or a call logged with the Connected outcome moves it to Connected.
- The same page has a "Create a lead" automation that creates a lead when a contact reaches chosen lifecycle stages. It does not fire when the change came from company lifecycle sync. Because lifecycle only moves forward, a person who re-qualifies or asks again does not reach the stage a second time, so re-qualifications and repeat requests need a workflow Create record action.
- The page documents no check for an open or duplicate lead. A demo request that creates a lead in an intake workflow and also moves the contact to the trigger stage gets a second lead, so use the automation or a workflow for each trigger, never both (FM26 in [failure-modes.md](failure-modes.md)).
- A lead moved back by hand is not moved forward again by later activity. Leads then sit in New while reps work them.
- Default lead properties include Time to First Touch (lead creation to first outreach activity), First Outreach Date, Owner Assigned Date (the most recent assignment only) and Disqualification Reason.
- The default lead stages have no accept step. Record the SDR's accept or reject in its own property (sdr_decision in the Acme spec) and stamp disposition_at when it is first set, or the MQL disposition SLA has no stop event.
- Lead stage calculated properties: the stage calculated properties article (updated 2026-07-15) says service and lead stage calculated properties need a Service Hub Professional or Enterprise subscription. Verify in the account before using Date entered for lead stages; without them, a workflow stamps a lead_stage_changed_at property.

## HubSpot owner rotation

Checked 2026-11-06 (page updated 2026-09-23). Source: assign-and-rotate-record-owners-using-workflows.

- Needs Sales Hub or Service Hub Professional or Enterprise. Only users with paid Sales or Service seats can receive records.
- The action is Rotate record to owner. For most objects it has "Overwrite if [object] has an existing owner"; with it on, any re-enrollment reassigns.
- Lead and ticket assignment offer Load Balanced, Round Robin and Random. Lead assignment is in beta, needs Sales Hub Professional or Enterprise and needs "Sync lead owner with contact or company owner" turned off. Its options are "Overwrite any existing lead owner" and "Assign to available users only", and it can assign to specific users and teams, the contact owner or no one. The Contact owner option needs a contact owner and a contact tracked with a cookie; otherwise the fallback option runs, so a contact the tracking code never identified falls back.
- "Assign to available users only" skips users set to Away. On leads, if every user is Away, the record gets no owner. If no user is eligible, the record gets no owner.
- Adding or removing users in the rotation resets the distribution counts.
- With the Salesforce integration, Salesforce can write the previous owner back after a rotation.

## HubSpot workflows and delays

Delays and enrollment checked 2026-11-06; the rest 2026-11-01 (Create record 2026-11-02, page updated 2026-09-23). Sources: use-delays, create-workflows, add-re-enrollment-triggers-to-a-workflow, create-records-with-workflows.

- Records enroll once unless re-enrollment triggers are set. A record still inside a workflow does not start a second pass, so a second request during an open episode is missed. Read the current re-enrollment rules before relying on them.
- "For a set amount of time" accepts days, hours and minutes. "Business days only (Monday - Friday)" accepts whole days only. The time zone is captured when the record enters the delay.
- "Until a date property" delays accept offsets in days, hours or minutes. A minute-level SLA timer is a deadline datetime property plus this delay, with a branch that re-checks state.
- Check whether each workflow unenrolls records that stop meeting its criteria; read the setting rather than assuming its default.
- Create record can assign the new record to the enrolled record's existing owner and copy compatible properties onto it. When the enrolled record has no owner, the new record stays unassigned. The page does not describe the owner option for leads, so confirm it in the editor before relying on it.

## HubSpot reports, stage properties and required properties

Lead response time, stage calculated properties and required properties checked 2026-11-06; funnel builders 2026-11-01. Sources: create-sales-reports-in-the-sales-analytics-suite, stage-calculated-properties, set-up-and-customize-pipelines, create-custom-funnel-reports, create-new-custom-funnel-reports.

- The Lead response time report measures from assignment to the user's first interaction (an email, call, chat, marking a task in progress or complete, or a meeting start) and resets when the contact is reassigned. It is an owner clock.
- Stage calculated properties (Date entered, Date exited, Latest time and Cumulative time per stage, plus Date entered current stage and Time in current stage) are off by default for new pipelines and new stages.
- Turning them on per stage needs Edit property settings or Super Admin. Values are filled in retroactively from existing data.
- Subscriptions: Professional or Enterprise in general; service and lead stage calculated properties need Service Hub Professional or Enterprise, and custom object ones need Enterprise.
- Workflows cannot enroll on Latest time or Cumulative time while the record is in that stage.
- Internal names of the per-stage family follow a pattern with the stage ID, but the exact names were not confirmed in public docs: read them with get_properties before writing a report or query.
- Required stage properties are documented for manual creates and moves in the UI (pipelines page updated 2026-09-29). The page does not address imports, integrations, workflows or API writes, so an exception view is the real control.
- The legacy funnel builder distinguishes All and Any stage membership. The newer builder is in beta, allows optional stages, and its date range filters activity dates, not record creation.

## HubSpot history and read access

Checked 2026-11-06. Sources: export-property-history, view-record-property-history, the remote MCP server reference, the Agent CLI skills, the Contacts API guide.

- Export property history: Settings > Properties > choose the object > hover the property > More > Export property history. Tick "Include Source Information" to add the user or tool behind each change.
- It needs the CRM Export and [Object] Edit All permissions and is on all products and plans. Dates are UTC.
- Caps: up to 45 revisions per contact property and 20 per company, deal, ticket or custom object property, counting UI and API changes. The cap for the leads object is not documented.
- Values run from newest to oldest, left to right. The file holds only records that have or had a value, and has a Record ID column. Other column names are not documented: inspect the header.
- Agent CLI: `hubspot objects history --type <object> --properties <property>` flattens version history into one row per change. The agent-cli-skills bulk-operations skill names it and the CLI guide page does not, so confirm it with `hubspot objects --help`. `--id <recordId>` reads one record; without it, the command scans a page of records. CRM_UI changes are excluded unless you add `--include-ui`. Sources include WORKFLOW, INTEGRATION, IMPORT and CRM_UI. The output format and key names are not documented: inspect the first row.
- Agent CLI: `hubspot workflows list` reads v4 flows only (JSON lines with id, name, isEnabled, type, objectTypeId and revisionId). Classic contact workflows are not returned, so an empty list proves nothing. Writes in these skills go through a dry-run, digest and confirm flow.
- CRM API: a GET on a record with `propertiesWithHistory=lifecyclestage` returns the history; run it under the team's own read access.
- Remote MCP server (https://mcp.hubspot.com) acts as the signed-in user. Read tools include get_user_details, get_organization_details, discover_hubspot_schema, search_crm_objects, get_crm_objects, query_crm_data (call tool_guidance first), search_properties, get_properties and search_owners. Allow these and deny every other tool, so a new write tool under another name stays blocked.
- Limits: 100 IDs per get_crm_objects call; search_crm_objects takes up to 5 filter groups of 6 filters and returns at most 200 results per page; search_owners returns at most 100.
- No MCP tool reads property history or workflows. With Sensitive Data on, activities and conversations are blocked through MCP (not through the standard APIs).
- Tools named manage_* write (records, properties, pipelines, segments and content). This skill never calls them.

## HubSpot and Salesforce integration

Checked 2026-11-06. Sources: map-hubspot-properties-to-salesforce-fields, manage-your-salesforce-integration-settings, sync-salesforce-opportunities-to-hubspot.

- Field rules: Prefer Salesforce unless blank, Always use Salesforce, Two-way (latest value wins, clears propagate) and Don't sync.
- Owner mapping is two-way even when a preference label shows. On a first sync without prior history, Salesforce's current value can set the baseline and overwrite a newer HubSpot value.
- Automatic lifecycle transitions from Salesforce opportunities need the Salesforce Contact on the opportunity as a contact role. An account link alone is not enough. They never move lifecycle backward.
- Setting HubSpot lifecycle to Customer does not convert a Salesforce Lead into a Contact.

## Salesforce assignment and intake

Checked in a browser on 2026-11-06. developer.salesforce.com refuses automated fetches. help.salesforce.com answers, but some pages return an error shell with no article, so read those in a browser. Sources: creating_assignment_rules, customize_leadrules, leads_create_guidelines, customize_leadpreparation, the REST Assignment Rule header, the SOAP AssignmentRuleHeader, the Bulk API create job page, the Web-to-Lead limits article, the ID prefix article and the Lead field reference.

- One lead assignment rule is active at a time. Its entries run in order and the first match wins. Targets are users or queues. When no entry matches, a web lead goes to the default owner in Lead Settings and an imported lead goes to the admin running the import. With no active rule, web leads go to that default owner.
- OwnerId values that start 005 are users. Queues usually start 00G, but Salesforce's prefix list does not show it, so confirm it on one known queue. A Lead in a queue has no accountable person until someone takes it.
- Standard Lead records have no AccountId before conversion: ConvertedAccountId fills at conversion and PartnerAccountId serves partner-owned leads. Rule entries cannot keep an open opportunity or account owner unless a lead-to-account match writes the account and its owner onto the Lead first.
- Creation paths invoke rules differently, so test every path the team uses.
- REST: the `Sforce-Auto-Assign` header applies when creating or updating Accounts, Cases or Leads. TRUE applies the active assignment rule, FALSE does not, and an AssignmentRule ID applies that rule. Omitting the header means TRUE, so an enrichment or sync update can reassign a Lead.
- SOAP needs AssignmentRuleHeader on create or update for a rule to apply, the reverse of the REST default. Bulk API 2.0 takes `assignmentRuleId` when the job is created. Data Loader runs a rule when its ID is in the Assignment rule setting. Web-to-Lead and the Data Import Wizard can run rules. The UI checkbox is Salesforce Classic only.
- Entry limits per lead assignment rule: verify at run time.
- Web-to-Lead: 500 Leads per day by default. Extra requests wait in a pending queue shared with Web-to-Case, up to 50,000; the allowance resets at midnight UTC. A queued Lead's CreatedDate is the creation time, later than the submission. Only reCAPTCHA v2 is supported.
- A hidden browser timestamp on a form is editable. Use a trusted server receipt for received_at when precision matters.

## Salesforce conversion and history

Checked in a browser on 2026-11-06. Sources: leads_convert, leads_notes, lead_conversion_mapping, the Lead field reference, convertLead, tracking_field_history, the hosted MCP pages.

- Conversion creates or selects an Account and a Contact and optionally an Opportunity. It fills empty fields on existing records and keeps populated ones.
- Email Opt Out and Do Not Call are not updated on an existing Contact. The most recently associated campaign becomes Primary Campaign Source.
- ConvertedDate is a date, not a timestamp. The converted status comes from the LeadStatus values. Conversion cannot be undone. SOAP convertLead accepts at most 100 Leads per call.
- History exists only for fields with field history tracking turned on (Status, OwnerId and StageName must be on for this audit), and it starts when tracking is turned on. Tracking covers up to 20 fields per object. History is kept up to 18 months, or 24 through the API, unless Field Audit Trail is on.
- History rows start at the first tracked change. The value at creation is the old value of the oldest row; `normalize_history.py` writes it when you map `old=`.
- An owner change can be recorded twice, once with IDs and once with names. Keep the ID form (`--keep-values "Owner=^(005|00G)"`); verify on one owner change in the record's History related list.
- Hosted MCP sobject-reads is read-only: getObjectSchema, soqlQuery, find, getUserInfo, listRecentSobjectRecords, getRelatedRecords. Production URL: https://api.salesforce.com/platform/mcp/v1/platform/sobject-reads. Hosted MCP servers are off by default; an admin turns one on under Setup > API Catalog > MCP Servers, and activation can take up to 2 minutes. Orgs that block logins from login.salesforce.com or test.salesforce.com use a My Domain URL.
- How business hours apply in Flow timers: verify at run time.

## Stripe billing state

Checked 2026-11-06. Sources: billing subscriptions overview, subscription webhooks, webhooks, customer emails.

- Subscription statuses: trialing, active, incomplete, incomplete_expired, past_due, canceled, unpaid, paused.
- A send_invoice subscription without a trial starts active even when the first invoice is unpaid. Active does not mean every invoice is paid, and a paid invoice does not always make the subscription active.
- Payment methods with delayed confirmation, such as ACH Direct Debit, can move a subscription straight to active while the payment is still processing. If the payment later fails, Stripe voids the invoice and the subscription stays active; voided invoices do not affect subscription status.
- So the paid billing state a spec uses for Customer is the invoice status paid (the invoice.paid event, confirmed by reading the invoice), never the subscription status alone.
- Webhooks can arrive more than once and out of order. Treat an event as a prompt to read the current state.
- `invoice.upcoming` is not sent for send_invoice subscriptions. Built-in renewal reminder emails cover charge_automatically subscriptions only.

## Attio

Checked 2026-11-06. Sources: Attio llms.txt, the MCP overview, the attribute value endpoints, status attribute type.

- Historic values come from the list record attribute values and list attribute values for a list entry endpoints with `show_historic`, sorted oldest to newest by active_from. Not available for enriched or communication intelligence attributes.
- The write attribute values endpoints replace an attribute's whole value history. Never use them for a correction.
- A list can hold several entries for the same record, so list entries work as request episodes.
- The Attio MCP server acts as the signed-in user; reads are auto-approved and writes ask for confirmation.

## Warehouse copies

Checked 2026-11-06. Sources: dbt_hubspot model files.

- The Fivetran HubSpot connector table contact_property_history has contact_id, name, value, timestamp, source and source_id.
- The dbt_hubspot model hubspot__contact_history exposes contact_id, field_name, new_value, valid_from, valid_to, change_source and change_source_id. Deal history is in hubspot__deal_history.
