# HubSpot Legacy Source Identity Reconciliation

This runbook is an approval-gated successor to the code fix tracked by
`pingcap-inc/nexuscrm-pingcap-pack#4144`. It does not authorize production
mutation, durable-link adoption, event replay, cleanup, repair, retirement, or
deployment.

The only permitted order is **rollout readiness -> adoption -> replay**. Each
phase requires its own immutable approval and receipt. A later phase cannot be
combined with, or implicitly approved by, an earlier phase.

## Scope and identity authority

The initial read-only evidence window is `2026-07-18T09:17:00Z` through
`2026-07-18T10:28:14Z`: 24 dead `contact.propertychange` events covering 12
HubSpot remote IDs. A later run must use a fresh evidence timestamp and must not
assume that this set or any pairing remains unchanged.

Email is consistency evidence only. A pair is eligible solely when the live
HubSpot record supplies exactly one usable `salesforceleadid` or
`salesforcecontactid`, and that explicit ID identifies the same eligible local
record after every check below. If both properties are populated, reject the
pair unconditionally, even when they appear to describe the same person.

Every candidate and receipt is bound to the complete durable tuple, never a
remote ID alone:

- environment and runtime release identity
- installed L0/L1/L2 refs, final-pack digest, and installed-pack fingerprint
- the durable LinkKey six-tuple: `ConnectorOwnerKey`, local source object,
  local source ID, `ActionSlotID`, `ActionType`, and `DestinationSlotID`
- inbound evidence `ConnectorNamespace` and `RemoteIDField`, plus HubSpot
  `RemoteID`
- local source object and local source ID
- exact identity-only reconciliation Flow definition/version and its protected
  action definition ID/version

Changing any tuple segment invalidates the candidate and requires a new
manifest and approval. Recompute the platform-canonical LinkKey hash from its
six-tuple and the remote-locator hash from `ConnectorOwnerKey`,
`DestinationSlotID`, and `RemoteID`; both hashes must match physical durable
readback. Never trust a stored hash without independently deriving it from the
manifest tuple.

## Phase 0: Rollout Readiness Receipt

Before generating a write approval, produce a read-only hard-gate receipt that
proves all of the following for the exact target environment:

- released L0, L1, and L2 refs, runtime release tag, final-pack digest,
  installed-pack fingerprint, identity-only reconciliation Flow
  definition/version, and protected action definition ID/version match the
  reviewed release manifest
- `_System_ObjectFieldMapping.ProjectionOnly` schema/readiness checks pass
- every execution and authorization reader is on the new L0 runtime
- old readers and workers are stopped and all old tasks are drained
- only after those facts were recorded, the reviewed L1/L2 projection metadata
  was installed and its projection snapshot was read back

The receipt records timestamps and readback evidence for each step and proves
their order. Any mixed-version reader, undrained task, schema mismatch, or
metadata installed before reader rollout is a hard stop. Phase 0 authorizes no
adoption or replay.

## Phase 1: Read-Only Canonical Manifest

Generate the manifest without changing HubSpot, NexusCRM records, connector
mirrors/evidence/links, events, or queues. Canonical JSON is the authority. Use
a named deterministic serializer and schema version, sort arrays by their
declared keys, hash the exact canonical bytes with SHA-256, and publish them at
a content-addressed immutable URI. A CSV is only a review derivative; each CSV
row carries and verifies the SHA-256 hash of its canonical JSON row.

Each canonical row must contain:

- the complete durable tuple defined above and the Phase 0 receipt hash
- recomputed LinkKey and remote-locator hashes plus their physical readback
- HubSpot remote ID, live record version, and evidence timestamp
- legacy property name and value (`salesforceleadid` or
  `salesforcecontactid`), plus both raw property-presence verdicts
- local object and exact local ID
- active/deleted lifecycle and, for Lead, conversion state
- normalized live HubSpot Email and local Email comparison result
- local `HubSpot_Id__c` value
- finalized durable link counts by remote locator and by local source
- conflicting remote ID, source owner, alias, or connector evidence, if any
- exact affected event IDs, event payload schema/version, payload SHA-256,
  current status/version, last error, and last-error timestamp
- verdict and machine-readable rejection reasons

The generator must fail closed instead of emitting an eligible row when:

- both legacy properties are populated, regardless of apparent equivalence
- the ID has the wrong object type or no unique exact local match
- the local record is deleted, or a Lead is converted
- Email is missing or differs after the same normalization used by the Flow
- the mirror is nonblank and differs from the HubSpot remote ID
- a finalized locator/source link, alias, or durable evidence conflicts
- the event, payload, durable tuple, or live HubSpot record changes during
  collection
- a recomputed LinkKey or remote-locator hash differs from physical readback

Sort rows by the full durable tuple and event ID. Record query/tool versions,
the evidence window, row counts by verdict, canonical schema/serializer
versions, the canonical JSON URI/hash, and the CSV URI/hash. A reviewer must
reproduce the counts, verify every CSV row hash against canonical JSON, and
read the immutable canonical object before any write approval is requested.

## Approval Gate A: Adoption

Approval must name the canonical manifest URI/hash, Phase 0 receipt hash, and
enumerate each approved complete durable tuple. Approval by remote IDs, counts,
or query predicates is invalid.

The hard execution budget is at most 10 pairs per batch, 15 minutes wall time
per batch, and zero tolerated errors; approval may set stricter limits. Before
each batch and pair, repeat every lifecycle, identity, tuple, link, evidence,
mirror, payload, and derived-hash check. Any drift, ambiguity, timeout, or error
stops the batch and prevents later batches.

Before attempting a pair, append a write-ahead checkpoint containing the
manifest hash, full tuple, event IDs/payload hashes, attempt ID, and preflight
facts to an append-only receipt stream.

Gate A requires a separately reviewed and released **identity-only atomic
reconciliation entrypoint** bound to the exact Flow definition/version and
protected action definition/version in the tuple. That entrypoint may write
only the selected record's compatibility mirror and the inbound
evidence/finalized durable link through
`finalizeConnectorInboundRemoteIdentity`. It must not run the ordinary HubSpot
inbound Flow or workflow-action Flow because those paths can update business
fields, owner, associations, CampaignMember records, and event state. It must
not call `AdoptFinalizedLink`, transfer identity, or select a record by Email.

No such released identity-only entrypoint is supplied by the current #4144
code scope. **Approval Gate A is BLOCKED until that prerequisite has its own
reviewed implementation, exact release lineage, atomicity tests, and write-set
proof.** A manifest or approval cannot waive this block.

All direct SQL or storage writes are forbidden, including writes to mirrors,
connector evidence, durable links, CRM records, and event or queue state. The
released identity-only entrypoint is the only permitted adoption path, and its
write-set is limited to mirror plus protected evidence/link finalization. It
cannot write business fields, owner, associations, CampaignMember records, or
event/queue state.

After each protected Flow transaction commits, append its result and read back
exactly one finalized remote locator, exactly one matching source owner, a
blank-or-matching compatibility mirror, and unchanged bound event payloads.
Any post-commit mismatch isolates that pair and stops the run before replay.

A crash or partial run is not resumed by blind retry. Reconcile every
write-ahead entry against durable readback, append a crash/partial-run receipt,
and request approval for any remaining attempt. Durable adoption is not an
automatic rollback boundary. Retirement, reassignment, or repair of a committed
identity requires a separate reviewed plan and approval; this runbook never
reverses it.

The canonical adoption receipt includes the manifest and Phase 0 hashes,
approved complete tuples, exact event IDs and payload hashes/versions/statuses,
per-pair write-ahead and commit entries, before/after durable facts, isolation
state, batch/time/error-budget usage, and its own content-addressed immutable
URI/hash.

## Approval Gate B: Replay

Replay requires a separate approval bound to the canonical adoption receipt
URI/hash. The receipt, not a live query, is the event allowlist. Replay only the
exact event IDs whose payload hash, schema/version, status/version, and complete
durable tuple are recorded in that receipt.

Immediately before each replay batch, perform a compare-and-set/drift preflight
for event status/version/last-error timestamp and payload hash, and re-read the
durable owner, mirror, action definition, installed pack, and runtime identity.
Any mismatch rejects and isolates that event without broadening the batch.

Use only the normal protected replay/queue API. Direct SQL or storage mutation
of event or queue state is forbidden. The hard replay budget is at most 5
events per batch, no more than 1 event per second, 15 minutes wall time per
batch, and zero tolerated errors; approval may be stricter. Append a
write-ahead checkpoint before each event, then append the protected API result
and event/durable/duplicate readback. Stop on any identity collision, fan-in,
duplicate creation, timeout, partial result, or authority drift.

Events rejected by the manifest or a batch preflight remain dead and isolated
for manual investigation. Never broaden replay to all failed HubSpot events,
and never weaken fail-closed logic to make a rejected row pass.

## Completion Evidence

Completion requires immutable, content-addressed canonical JSON receipts for
Phase 0, manifest, adoption, crash/partial-run state if any, and replay. Read
back finalized locator/source ownership, mirror projection, exact event
outcomes, payload hashes/statuses, and the duplicate-record check. CSV exports
remain hash-checked derivatives, never authority.

The code release, rollout readiness, production adoption, and event replay are
separate approvals and remain separate in the durable source of truth. This
document still authorizes none of them.
