# Rules: pipelines, lifecycle and automation

PIPE-01 to PIPE-06, LIFE-01 to LIFE-06 and AUTO-01 to AUTO-10. Severity, classes and the FAIL downgrade rule are defined at the top of [rules-data-model.md](rules-data-model.md). Checked on 2026-11-06.

LIFE-05, AUTO-03, AUTO-04, AUTO-05, AUTO-06, AUTO-07 and AUTO-10 read automation definitions, so they pass only when every enabled automation was read. One still unread (a Salesforce flow without metadata, a HubSpot summary without actions, a hand-entered automation with nothing recorded) turns a PASS into NOT CHECKED and is named; FAIL and WARN findings stand. What counts as read is in [inventory-format.md](inventory-format.md).

## PIPE-01 Each pipeline represents one sales motion (C, never FAIL)

- WARN: two pipelines with identical stage lists (often one per region or team); a pipeline with no open deals and none created in 180 days; a Salesforce sales process or record type with no records in 180 days.
- Evidence: HubSpot pipelines and stages (the remote MCP server's only pipeline tool, `manage_custom_pipelines`, writes and is denied here; use the Agent CLI `pipelines list` and `pipelines stages`, or a settings capture). Salesforce `BusinessProcess`, `RecordType`, `OpportunityStage`. Attio status attributes or pipeline lists.
- Script: `pipelines[]` stage labels, `open_deals`, `created_180d`, `records_180d`.

## PIPE-02 Stages have entry evidence and a workable count (B, never FAIL)

- WARN: more than 8 open stages in a pipeline; a stage without written entry evidence; stage names that describe seller activity (demo scheduled, contacted, meeting booked) rather than buyer commitment.
- Agent adds: a booked meeting is not qualification evidence. Write each stage's entry evidence as something the buyer did or confirmed.

## PIPE-03 Won and lost meanings are unambiguous (A)

- FAIL: an open stage at 100% probability; a lost stage above 0%; a won stage below 100%; more than one won stage in a pipeline with no documented reason; a Salesforce closed stage mapped to Pipeline, Best Case or Commit, a won stage not mapped to Closed, or an open stage mapped to Closed.
- WARN: open-stage probabilities decrease along the stage order; a pipeline without a lost stage.
- Evidence: HubSpot stage probability (the pipelines API puts deal stage probability between 0.0, closed lost, and 1.0, closed won). Salesforce `SELECT MasterLabel, IsClosed, IsWon, DefaultProbability, ForecastCategoryName, SortOrder FROM OpportunityStage`. Attio status options and how reports treat them.
- Script: for HubSpot the stage type comes from probability (1.0 won, 0.0 lost, anything else open), and a recorded type that disagrees is noted.
- Agent adds: HubSpot works out closed status from probability, so look for more than one 100% stage. A "Contract signed" stage at Won marks deals won at signature, sets their close date then and fires the deal-won lifecycle setting early, while reports filtered on the "Closed won" stage miss them; the close date does not move when the deal later reaches "Closed won". An open stage at 100% or a lost stage above 0% exists only where probability is separate from the closed flags (Salesforce IsClosed and IsWon, Pipedrive). With HubSpot's Automate forecast categories on (Sales Hub or Service Hub Professional or Enterprise), check the stage-to-category mapping as for Salesforce.

## PIPE-04 Stage-required fields are filled whatever the writer (B)

- FAIL: more than 15% of deals in a stage miss a property that stage requires. WARN: more than 5%.
- Script: `pipelines[].stages[].missing_required` count objects per field.
- Agent adds: HubSpot documents conditional stage properties for manual creates and moves in the UI. Imports, integrations, workflows and API writes are not documented as blocked, so find which writer placed the incomplete deals.
- Confirm: keep a saved view per stage of deals missing required fields; that view is the real control.

## PIPE-05 No dead or stuck stages (C, never FAIL)

- WARN: an open stage no deal entered in 180 days; more than 20% of a stage's open deals have sat there longer than the larger of 90 days and twice the stage's median dwell.
- Script: `stages[].entries_180d`, `stages[].stuck_open` (count object built with that threshold).
- Evidence: HubSpot "date entered stage" properties; Salesforce `OpportunityHistory` or field history on `StageName`; Attio status change history.

## PIPE-06 Integrations reference stage IDs, and stage changes run as migrations (B)

- FAIL: a stage was deleted or recreated and an integration, import template or workflow still references the old ID (errors, or records falling into a default stage).
- WARN: mappings document stage labels instead of internal IDs; no record of stage IDs exists.
- Script: `integrations[].stage_refs` against current stage IDs, `integrations[].stage_mapping`.
- Agent adds: recreating or copying a stage changes its internal ID. Read the audit log for stage changes in the window.

## LIFE-01 Lifecycle stages describe the relationship and are defined (B, never FAIL)

- WARN: a stage without written entry evidence and an owner; options that encode non-relationship states (churned, trial, recycled, bad fit, disqualified, nurture).
- Evidence: HubSpot `lifecyclestage` options from `get_properties`; Salesforce Lead Status values and any custom lifecycle field; Attio the status or select attribute used for lifecycle.
- Agent adds: keep subscription, churn and follow-up state in their own fields. In HubSpot, Close date and Days to close on contacts and companies update automatically only while the "Set lifecycle stage when a deal is won" setting is on; record that setting before changing lifecycle options.

## LIFE-02 One field answers one question (B, never FAIL)

- WARN: Lead Status and the leads object both used for follow-up; lifecycle options that describe follow-up (contacted, working, attempting); a custom lifecycle field beside the standard one.
- Script: lifecycle option labels, `lifecycle.leads_object_active`, `lifecycle.lead_status_used_for_follow_up`, custom fields whose normalized name is lifecycle.

## LIFE-03 Lifecycle writers are known and ordered (A, automation gate)

- FAIL: two or more automated writers can set lifecycle (form settings, the default for new records, deal created or won settings, company sync, workflows, Salesforce automatic lifecycle transitions, integrations, scheduled imports) with no written precedence rule; or a billing or enrichment system sets Customer without the documented evidence.
- WARN: precedence is written down but has not been tested.
- Script: writers from `lifecycle.writers`, automations and integrations writing the lifecycle field, and `forms[].sets_lifecycle` (as `form-setting:<form>`); `lifecycle.precedence_rule`, `precedence_tested`, `writers[].customer_evidence_documented`.
- A value-level writer list counts as the written precedence rule: one writer per value (scoring sets MQL, the deal-created setting sets Opportunity, the deal-won setting sets Customer), as revops-workflow-design's register writes it. Cite it in `lifecycle.precedence_rule`.
- Agent adds: HubSpot imports, forms, the API, workflows and the Salesforce integration only move lifecycle forward; going back needs the value cleared first. Salesforce automatic lifecycle transitions need an opportunity contact role and can apply to all contacts of the company; record that scope.
- Confirm: writer sample on the lifecycle field over 30 days.

## LIFE-04 Forms and defaults never set late stages (A)

- FAIL: a public form or the default for new records sets Opportunity, Customer or Evangelist (often a form copied from a customer portal).
- WARN: a form sets MQL or SQL directly instead of a qualification rule.
- Agent adds: in HubSpot a form's own lifecycle setting wins over the account default for new records.
- Confirm: count Customer records created by each public form.

## LIFE-05 Backward moves are governed (A)

- FAIL: an active automation clears or lowers lifecycle without manual-only enrolment and a reason field.
- WARN: no documented correction path. Teams then try a re-import, which reports success and changes nothing for forward-only writers.
- Script: `op` clear, or an empty static value in the v4 JSON. The action reference shows no clear example, so a lifecycle write the definition does not show (`op` set-dynamic) adds a note: open that workflow.
- Agent adds: HubSpot clear-then-set in a workflow needs Professional or Enterprise.

## LIFE-06 Stage evidence matches the facts (B)

- FAIL: more than 5% of Customer records have no closed-won deal or billing evidence (the same line as L01 in crm-data-hygiene-audit, which lists the records).
- WARN: more than 2% of Customer records; more than 10% of Opportunity records without an open deal; more than 5% of contacts created in the last 90 days with no lifecycle stage.
- Script: `counts.customers_without_evidence`, `counts.opportunity_without_open_deal`, `counts.recent_contacts_empty_lifecycle`.

## AUTO-01 Every active automation has an owner and a purpose (C, never FAIL)

- WARN: more than 20% of active automations lack a description or owner; any automation that writes a critical field lacks an owner.
- Evidence: HubSpot workflow list with names, folders and descriptions; Salesforce `FlowDefinitionView` and Flow Trigger Explorer; Attio workflow list.

## AUTO-02 Automations run without errors (B; A when the erroring automation writes a routing, ownership, consent, lifecycle, stage or revenue field)

- FAIL: such an automation had errors in the last 30 days. WARN: any other automation with errors in 30 days.
- Evidence: HubSpot workflow action errors and the workflows issue view (label varies, verify); Salesforce failed and paused flow interviews and flow error emails; Attio workflow run history.

## AUTO-03 No two automations write conflicting values to one field (A, automation gate for critical fields)

- FAIL: two enabled automations write different static values to the same critical field, or one sets while another clears, for overlapping populations without mutually exclusive enrolment criteria.
- WARN: the same on a non-critical field; or overlap cannot be ruled out because enrolment criteria are unreadable.
- Script: `writes` from flow definitions; `mutually_exclusive_with` and `enrollment_readable` from the inventory. Sequential status workflows are fine when their enrolment criteria exclude each other; record that in `mutually_exclusive_with`.
- Confirm: two record timelines showing the alternating values.

## AUTO-04 No broken references (B)

- FAIL: an enabled automation filters, branches or sets on a property or option value that no longer exists, or on an archived list or segment. It stops enrolling or writing without an error.
- WARN: references to fields the change list slates for archive.
- Script: `filter_refs` against field options (compared lowercase with punctuation removed, against values and labels), writes against fields, `archived_list_refs`, `archive_candidates`. An event filter names an event property (`hs_form_id` on a form submission), and an association branch or an Edit record on an associated record names another object's property, so neither is resolved against the enrolled object's fields: `other_object_refs` counts them and the rule notes how many. Stage and pipeline IDs in filters are left to PIPE-06.
- Agent adds: look for zero enrolments since the date the option was removed (audit log).

## AUTO-05 No re-enrolment loops (B)

- FAIL: an automation re-enrols on a field it writes, or two automations write each other's re-enrolment triggers.
- WARN: re-enrolment is on with no suppression or exit for records that already completed.
- Evidence: HubSpot re-enrolment settings in the flow definition; Salesforce recursive record-triggered flows on the same field.
- Script: `reenrollment` (enabled, trigger_fields). A flow that re-enrols on the Property value changed event (`4-655002`) keeps the rule from passing, because the definition does not show which property: read the trigger and record it in `trigger_fields`.

## AUTO-06 Destructive and consent-changing actions have a dated decision (A, automation gate)

- FAIL: an enabled automation deletes records, sets marketing contact status, or changes subscriptions or legal basis, with no `decisions.md` entry naming the owner and policy; or it cites a decision that is not recorded.
- WARN: the decision exists but there is no test record or audit trail.
- Script: `destructive_actions`, `consent_actions`, `decision_ref`, `tested`. The reference counts only when it is the id of a `decisions.items` entry with a date; anything else is FAIL, even when no decisions are recorded at all.

## AUTO-07 Owner assignment protects existing ownership and has a fallback (B, never FAIL)

- WARN: owner-setting actions overwrite existing owners for populations that can include customers or open-deal contacts; rotation limited to available users with no monitored fallback; rotation targets users without paid seats (HubSpot skips them).
- Script: `owner_writes[].overwrite`. A rotate action that names no overwrite setting is recorded as null and noted as unconfirmed; it never counts as protecting existing owners.

## AUTO-08 Dormant automations are reviewed (C, never FAIL)

- WARN: automations off for more than 90 days, or never enrolled 90 days after creation; HubSpot Enterprise account cleanup (beta) set to remove unused workflows automatically with no named owner. Deleted workflows restore for 90 days, as new inactive workflows without their history.

## AUTO-09 Automation layers do not overlap on one object (B)

- FAIL: a field is written by more than one automation layer (Salesforce Workflow Rules or Process Builder plus record-triggered flows or Apex triggers; a HubSpot workflow plus an integration field rule) with no documented order.
- WARN: legacy Workflow Rules or Process Builder still active (Salesforce ended support for both on 31 December 2025; active ones keep running with no support or bug fixes); more than one record-triggered flow per object and trigger with no Trigger Order; asynchronous paths assumed to run in the displayed order, which Salesforce does not guarantee.
- Evidence: Salesforce `FlowDefinitionView` (ProcessType Workflow, CustomEvent or InvocableProcess marks Process Builder; select `TriggerOrder`, or every pair of record-triggered flows reads as unordered), `WorkflowRule` through the Tooling API, Apex trigger list, Flow Trigger Explorer.

## AUTO-10 Essential steps do not depend on credits or send limits without a fallback (B, never FAIL)

- WARN: a routing or alert step uses a credit-consuming action (HubSpot Data Agent, Prospecting Agent and AI actions; Attio credits) with no non-credit fallback; a sequence enrolment on an essential path is skipped when the sender reaches a send limit; sequences and marketing workflows reach the same contacts with no suppression of active conversations.
- Agent adds: HubSpot usage-based features pause by default when included credits run out and no capacity pack was bought.

## Token lists and action types the scripts use

- Non-relationship lifecycle tokens: churned, churn, trial, recycled, bad fit, disqualified, nurture, in sequence. Follow-up tokens: contacted, working, attempting, attempted, in progress, connected.
- HubSpot workflow action type IDs (workflows v4 reference, checked 2026-11-06): `0-5` edit record (set, clear, or increase or decrease a property; an `association` block means it edits an associated record, recorded as an `associated` write), `0-11` rotate owner, `0-14` create record (its `properties` are writes on the new object), `0-15` go to workflow, `0-4` send email, `0-31` set marketing contact status, `0-43347357` manage communication subscriptions, `0-18224765` delete contact, `0-46510720` enroll in sequence, `0-177946906` enrich record.
- Credit-consuming actions the scripts flag for AUTO-10: the Data Agent actions listed as beta (`0-172351286` custom prompt, `0-216647524` research, `0-201091202` fill smart property) and `0-219292676` enroll in prospecting agent. HubSpot says AI workflow actions need credits, so `0-195318603` summarize record, `0-207702619` infer value proposition and `0-218870680` custom LLM do too; they stay unclassified because they can write properties, so pass their IDs with `--credit-action-ids`. `0-177946906` enrich record is not on the list: HubSpot says data enrichment does not consume credits (checked 2026-11-06). Enrich record and the Data Agent actions write properties the v4 JSON may not name; add those writes to the inventory overlay when the property is critical.
- Classified as writing no CRM property, deleting nothing, sending nothing to contacts and changing no consent: delays (`0-1`, `0-29`, `0-35`), internal notifications (`0-8`, `0-9`, `0-23`), tasks (`0-3`), notes (`0-169425243`), static lists (`0-13`, shown in the workflows guide's example, `0-63809083`, `0-63863438`), associations and association labels (`0-63189541`, `0-73444249`, `0-61139484`, `0-61139476`), ads audiences (`0-30`), sequence and prospecting agent unenrolment (`0-4702372`, `0-217709844`) and conversation owner assignment (`0-44475148`).
- Every other ID is listed per workflow as unclassified, never dropped. An action with no `actionTypeId` that is not a branch (custom code and webhook actions are not in the reference) is listed under its `type`. The unclassified list includes app actions (`1-...`), actions that text contacts (`0-40900952` SMS, `0-25085031` WhatsApp, `0-199186210` survey), actions that write to Salesforce (`0-18` set Salesforce campaign, `0-19` create Salesforce task, which matter for the INT cross-reference of a connected pair), intent signal tracking (`0-219160146`, `0-219161394`) and phone formatting (`0-225935194`). The report names each known ID.
- The rules that can miss what an unclassified action does (FLD-02, FLD-03, LIFE-03, AUTO-03, AUTO-06, AUTO-09) carry a NOT CHECKED part naming the workflow, an AUTO-06 PASS reads "among classified actions", and the automation verdict stays "Not assessable". Open each one, add its field writes, deletes and consent actions to the inventory overlay, then set its `unknown_action_types` to `[]`.
- `property_name` and `staticValue` (edit record) come from the public action reference. The rotate-owner keys (`target_property`, `overwrite_current_owner`, `user_ids`) are not documented there (checked 2026-11-02), so confirm them against a real `workflows get` output before relying on AUTO-07 or INT-06.
