# Cleanup plan and change lists

The audit ends with drafts: a merge plan, change lists and a suppression list. A person with edit rights applies them after approval. This file sets the order, the operations and the rules that make a change list safe to hand to a colleague. Merges have their own rules: [merge-playbook.md](merge-playbook.md). Template: [../assets/change-list-template.csv](../assets/change-list-template.csv).

## Order, and why

- 1. Stop harm to people: bounced or opted-out records still marketable (E06, E07), Salesforce conversion opt-out drift (L10), test records in send lists (F07). In HubSpot the platform already blocks marketing sends to bounced and unsubscribed addresses, so the risk is the marketing contact bill and any tool that syncs marketing status. In Salesforce, or a CRM that feeds another sender, they can be emailed today.
- 2. Stop the writer: the integration, form or import that creates the defect, as one review_only item with an owner. Otherwise the next sync undoes the cleanup.
- 3. Owners of open deals and customers (O01, O02 and O04 FAIL): work with nobody accountable.
- 4. Export or key integrity first (X01, X02, X03, X05), then certain duplicates (D01, D02) through the merge plan.
- 5. Revenue reporting: won without amount (L03), closed in the future (L04), orphaned stages (L07). Finance confirms every value.
- 6. Probable duplicates, after review and calibration.
- 7. Lifecycle, associations, required fields and format variants.
- 8. Dormancy and sunset proposals; deletion candidates for the retention owner.

## Operations

- set: write a value into an empty or wrong field.
- clear_then_set: a HubSpot lifecycle stage moving to an earlier stage. Imports, forms, the API, workflows and the Salesforce integration only move it forward, so the row clears the value first. Moving backwards changes the legacy "Became a" date properties; list that under downstream_effects.
- associate and dissociate: add or remove an association, naming the association label where one applies.
- reassign_owner: the new owner comes from the routing policy, never from rotation. current_value and proposed_value hold owner IDs; the names go in evidence. In HubSpot an owner ID is not a user ID: confirm each proposed owner ID with `search_owners` before the import.
- set_non_marketing (HubSpot): not importable. HubSpot's import can set contacts as marketing; its documented non-marketing routes are the contacts index bulk action Set as non-marketing contacts, Clean up contacts (Account & Billing > Marketing Contacts; verify the path on the live page), a workflow's Set marketing contact status action and the API. Use Clean up contacts filtered to a static segment of the record IDs, or the bulk action. Read back Marketing contact until next update = Yes, then Marketing contact status after the next update date. It takes effect at the next update date and does not lower the contact tier mid-term (checked 2026-11-06).
- suppress: add to a suppression list or set do-not-email. Keeps history and the reason the record must not be re-imported as new. Name the apply route (suppression list, subscription tool or workflow) and confirm on the live page whether the CRM takes it by import.
- review_only: a question for an owner, or a source fix such as an integration mapping or a form setting. No record write.
- delete_candidate: listed for the retention owner. Never executed from this plan.

## Change list rules

- One file per object and operation, ready to import: record ID plus the internal field name (HubSpot internal property name, Salesforce API name, Attio attribute slug), never the export label. Confirm names with `get_properties`, Object Manager or `list-attribute-definitions`.
- set_non_marketing and suppress rows name their apply route in downstream_effects and are not imported.
- IDs in current_value and proposed_value (owner IDs, for example), names in evidence. A value such as "102 Sam Ortiz" is not an owner ID, so the row cannot be imported as written.
- Columns: change_id, object, record_id, field, current_value, proposed_value, operation, check_id, evidence, reversible, downstream_effects, approver, approved_at, applied_by, applied_at, verified_at.
- current_value comes from the export, as of its export time. Re-read the record before applying; if it changed since, the row is stale and goes back to review.
- evidence names the check and the fact: "users file: inactive since September", "no closed-won deal on the contact or company 9002".
- Save a before-export of every record and field the change touches, next to the change list.
- First batch: 25 records or 5% of the file, whichever is smaller. Read them back, then apply the rest. Stop at the first mismatch.
- reversible: yes for field values that can be set back. HubSpot's Restore CRM changes rolls back imports, integrations and workflows for 14 days (Super Admin, Starter or above). No for merges, Attio deletions, opt-out and subscription changes, and messages already sent or prevented.
- downstream_effects: the lists, workflows, sequences, reports and syncs that read the field, and the numbers that move (customer count, pipeline, marketing contacts).
- Tell report owners which numbers will move before anything is applied.

## Applying changes by CRM

- HubSpot imports: key update files on Record ID; HubSpot gives it precedence when it is the identifier. A blank Record ID in a create-and-update file creates a record. Leave email out of an update file unless the email is the change: importing a secondary email next to Record ID can replace the primary email.
- Salesforce: update by Id (Data Loader Update), not Upsert, unless an external ID is the intended key. Field-level security must allow the edit. Email Opt Out and Do Not Call changes need the privacy owner.
- Attio: update by record ID. An upsert matches on the attribute you name, which need not be unique, refuses a value that matches several records, and creates a record when nothing matches, so a change list keyed on email can create people.
- Every CRM: keep IDs as text and never round-trip a change list through a spreadsheet (X02, X03).

## Suppression before deletion

- Suppress first; deletion is a retention decision. Details and the reasons: [decay-and-bounces.md](decay-and-bounces.md).
- Run suppression rows after the merges they depend on, so the merged record carries them.

## Approvals and close-out

- Approver per operation: CRM owner (merges, bulk changes), privacy owner (consent, subscriptions, suppression), retention owner (deletion), routing owner (owners), finance (amounts and close dates).
- After applying, rerun the profiler with the same rules file. The repaired record IDs leave the findings, and trend.md shows no new defect from the fixed creation path. If it does, the source fix comes before the next cleanup.
