# HubSpot Set Campaign Compatibility Restore

Status: implementation contract, 2026-07-17

Operator decision: HubSpot is a live production surface. Formal prod releases
always deploy both ingress paths as `proxy/proxy`; block or ack-drop incident
containment is not a prod release input. This slice changes only
workflow-action execution code and its durable replay configuration.

## Target Invariant

The exact PingCAP Set Campaign workflow action may be accepted only after its
signed payload is durably written to a release-stable compatibility inbox, and
that inbox replays to NexusCRM under a bounded production rate.

## Scope Guard

In scope:

- restore the pre-#3937 queue/replay behavior behind an explicit
  `legacy_replay` delivery policy;
- require an exact action definition ID rather than a wildcard policy;
- keep the queue outside versioned release roots and cap prod replay rate;
- fail closed when one callback ID is reused with a different payload;
- retain `best_effort` `FAIL_CONTINUE` as a separate policy.

Out of scope:

- HubSpot `BLOCK` plus callback completion;
- new database schema, migration, or backfill;
- HubSpot subscriptions or CRM field mapping;
- claiming the single-host SQLite inbox is replicated or exactly once.

## Research Evidence

- #3202 added signed `202` admission, callback-ID dedupe, SQLite persistence,
  and bounded replay to NexusCRM.
- #3421 proved a staging Campaign reached 400/400 CampaignMembers and 400
  processed workflow events through that path.
- #3937 removed enqueue/replay and retained only `best_effort`
  `FAIL_CONTINUE`; #3939 made prod ingress default to `block/block`.
- The 2026-07-16 prod incident returned the adapter's exact block response and
  produced zero connector events, receipts, and target CampaignMember writes.
- HubSpot's current contract treats a `2xx` response as completed unless the
  response explicitly selects `BLOCK`. The full asynchronous successor must
  use `BLOCK` and `/automation/v4/actions/callbacks/{callbackId}/complete`.

## Design Options

1. Implement the full `BLOCK` callback state machine now. Rejected for this
   restore because callback-token authority and early/duplicate completion
   behavior do not yet have the required live proof.
2. Revert #3937 blindly. Rejected because it would restore wildcard admission,
   a release-local queue path, and the old unbounded prod replay default.
3. Restore the known queue/replay path behind a named compatibility policy.
   Selected because it restores the demonstrated business behavior without
   claiming the pending callback protocol is complete.

## Selected Slice

- Decision: `CARVE_DOWN` to a compatibility restore.
- Primary protocol axis: durable admission and replay of the existing action.
- Helper axis: duplicate callback conflict detection.
- Forbidden surfaces: callback completion API, new schema, L0/L1 behavior,
  ordinary ingress, metadata regeneration, and provider-side action mutation.
- Protocol exception: none. This slice reuses the existing inbox and replay
  protocol rather than introducing a new durable identity.

## Invariant Inventory

1. Only an exact action ID configured as `legacy_replay` can enter the inbox.
2. Admission is persisted before `202`; identical duplicates reuse the row and
   conflicting duplicate bodies fail closed.
3. The replayer starts only in proxy mode with a `legacy_replay` policy, uses a
   release-stable queue path, and is capped by the configured replay rate.

## Durable State Protocol

- Identity: HubSpot `callbackId`.
- Payload fingerprint: SHA-256 of the exact signed request body.
- Admission: one SQLite transaction before the response is written.
- Duplicate: same callback and same fingerprint returns the persisted status;
  same callback and different fingerprint returns a terminal conflict.
- Retry: the existing bounded attempt count and exponential backoff remain.
- Cutover: formal staging and multi-prod point every slot at one environment-
  shared queue file outside the versioned release directory.
- Terminal state: a NexusCRM `2xx` marks the inbox row delivered; exhausted
  retries mark it dead. This compatibility path does not call HubSpot complete.

## Acceptance

- focused adapter tests prove both policies, exact allowlisting, enqueue-before-
  response, identical/conflicting duplicates, replayer startup, and retry;
- staging/prod runtime rendering tests prove exact action policy, shared queue
  path, replay cap, and independent ordinary/workflow modes;
- formal staging deploy succeeds from exact main and a signed canary produces a
  processed workflow event plus its expected CampaignMember;
- live readback proves the pre-existing workflow route remains enabled without
  an nginx change, then proves accepted workflow traffic, connector
  receipt/event, and CampaignMember mutation.

## Successor

Replace `legacy_replay` with the full `must_complete` `BLOCK`/completion
protocol after live callback-order and duplicate-completion probes establish
the provider contract and token authority.
