# Field ownership for workflow fields

The workflow-critical rows of the ownership register and how to find the real writers. The register template with fictional Acme rows is [../assets/field-ownership-register.csv](../assets/field-ownership-register.csv).

## Register columns

- field, object: internal name and object (lifecyclestage, contacts).
- meaning: one sentence a new hire can apply.
- allowed_values: internal values, separated by semicolons.
- writer: the one automated writer, as a source label (WORKFLOW:mql-scoring). Value-level writers go here too ("customer only AUTOMATION:deal-won").
- proposers: who may suggest a change and how (a task, a separate proposal field, a request to the owner).
- conflict_rule: precedence when two writers disagree, or "single writer".
- blank_input: what an empty incoming value means (preserve or clear).
- explicit_clear: how a real clear is sent, and who may send it.
- freshness_field: the timestamp that says when the value was last confirmed at its source.
- correction_route: the governed path for fixing a wrong value.
- downstream_uses: workflows, routing, reports and syncs that read it.
- observed_writers: sources seen in history with counts (from T06), and the date read.
- status: agreed, proposed or in conflict.

## Fields to register first

These route work, start clocks, set customer state or feed revenue reports:

- lifecycle stage; lead status or lead stage
- owner fields: contact, lead, company or account, deal, customer success owner
- deal stage per pipeline
- the SDR's accept or reject decision (sdr_decision on HubSpot lead records) and disposition_at
- rejection reason, disqualification reason, closed-lost reason, recycle reason and recycle date
- MQL date, or the episode count on request records
- fit and engagement scores, with the model version
- received_at, assigned_at, last_assigned_at, first_response_at, disposition_at
- routing rule ID and routing reason
- subscription status, latest invoice status, collection method
- renewal decision date, notice deadline, service period boundary
- product summary fields that act as evidence (activation date, seats used)

## Finding the observed writers

Configuration lists what should write. History shows what did. When they differ, history wins and the difference is a finding.

- HubSpot: the source columns of Export property history (tick Include Source Information) or the Agent CLI history rows with `--include-ui`.
- Salesforce: CreatedById on LeadHistory, OpportunityFieldHistory and OpportunityHistory rows. Give each integration its own integration user, or every sync looks like the same writer. Process Builder and record-triggered flows run with the person whose edit launched them as running user, so expect history to show that person, and a missing row does not prove nothing changed: Salesforce skips the row when that user cannot edit the object.
- Warehouse: change_source and change_source_id in hubspot__contact_history and hubspot__deal_history.
- Attio: the actor on historic values read with show_historic.
- Normalize every raw source to a label of the form TYPE:detail (FORM:demo-request, WORKFLOW:mql-scoring, CRM_UI:rep_a, AUTOMATION:deal-won, INTEGRATION:billing-sync, IMPORT:events-list-07) with `normalize_history.py --source-map`. Real tools record their own source strings and user emails; the labels are the team's own, and user emails become role labels before any file is shared.
- The HubSpot CLI workflow list shows v4 flows only, and integrations and imports never appear in a workflow list. Build the inventory from history sources first and configuration second.

## Conflict rules

- One automated writer per field. Other systems propose through a separate field or a task.
- Two automated writers with no written precedence is a FAIL in the register review, even before history shows a conflict.
- When a person must be able to override a calculation, use three fields: calculated, override (with reason and expiry) and effective. The effective value takes an active override, else the calculation. Nobody fights the calculated writer.
- Reject stale updates. Compare the source's version or effective time with the stored one; a delayed export must not restore an old value.
- Last write wins only when arrival order equals authority. Retries, batches and manual edits break that assumption.

## Value-level permissions

A field-level writer list is too coarse for state fields. Record who may set each value that matters:

- Subscriber: forms and the correction workflow. Lead: forms, imports, users and the correction workflow.
- MQL: only the scoring workflow with the fit gate, and the correction workflow.
- SQL and Evangelist: users (sales, customer success) and the correction workflow.
- Opportunity: the deal-created automation and the correction workflow. A deal must exist first, so a manual lifecycle move to Opportunity is a T06 FAIL.
- Customer: only the deal-won automation (or the paid-invoice rule the spec names) and the correction workflow.
- Clear (empty) and any value without its own list (other, unknown options): only the correction workflow.
- The audit config mirrors this in `writers.<field>.<value>`, with `"*"` holding only the correction workflow, so T06 catches a copied form that sets Customer or Sales Qualified Lead even though forms may set Lead.

## Integration traps that hit workflow fields

- HubSpot data sync: the chosen default app wins conflicts, subject to direction and blank handling, and sync filters control initial inclusion only; records already syncing keep syncing.
- HubSpot to Salesforce: Two-way rules let the latest value win and propagate clears. Owner mapping is two-way. A first sync can set the baseline from Salesforce's current value.
- Salesforce REST: an update without `Sforce-Auto-Assign: FALSE` re-runs the active assignment rule, so an enrichment update can move a Lead to a new owner. Detect owner changes by the integration user seconds after its own updates.
- HubSpot rotation re-enrolled by a field update (company size, region) reassigns accepted requests when overwrite is on.
- Enrichment tools that map their own owner field onto the contact owner.
- Reverse ETL writing deal stage or lifecycle from product data. Product data belongs in summary properties that the spec reads as evidence.
- A marketing automation platform (Marketo, for example) whose sync writes Salesforce Lead Status, flipping a rep's disposition back. The Salesforce fixture in [worked-example.md](worked-example.md) shows the pattern.
- Billing integrations writing lifecycle stage from subscription status. Billing writes customer state fields; lifecycle Customer comes from closed won or the paid-invoice rule.
- Company lifecycle sync copying a company's stage to every associated contact, including contacts that never engaged.

## Who owns a two-writer finding

- Evidence: crm-audit FLD-02 rates every critical field across the whole CRM over 30 days. T05 here fails any A to B to A inside 24 hours against an agreed spec, a rep's reversal included, so the same history can read FAIL here and WARN there. Keep each status in its own report and cite the other by rule ID.
- Who may write: this skill says who may set lifecycle, stage or owner fields, owns the workflow-critical rows above and decides allow, narrow or stop for each unexpected writer.
- The repair: martech-integration-engineering, one flow at a time. Once crm-sync-and-field-ownership ships it takes connector troubleshooting, the full integration contract and every other field.
- crm-data-hygiene-audit rates no writers: it names the creation path and hands the finding on.
- Until then, the rules on this page apply to any field that routes work or appears in a revenue report.
