# HubSpot Set Campaign Performance Acceptance

## Contract

The campaign cases in `pingcap-inc/nexuscrm-hubspot-acceptance` are the
machine-readable source of truth for the Set Campaign performance suite. The
registered `set-campaign-performance` group runs these cases serially in order:

| Case | Contacts | Performance tier | Required acceptance |
| --- | ---: | --- | --- |
| [`set-campaign-canary-20-contacts`](https://github.com/pingcap-inc/nexuscrm-hubspot-acceptance/blob/main/config/cases/hubspot/campaign/set-campaign-canary-20-contacts.json) | 20 | `must_pass` | Yes |
| [`set-campaign-canary-100-contacts`](https://github.com/pingcap-inc/nexuscrm-hubspot-acceptance/blob/main/config/cases/hubspot/campaign/set-campaign-canary-100-contacts.json) | 100 | `must_pass` | Yes |
| [`k-camp-10`](https://github.com/pingcap-inc/nexuscrm-hubspot-acceptance/blob/main/config/cases/hubspot/campaign/k-camp-10.json) | 2,000 | `should_pass` | No |

All three are performance cases and are excluded from ordinary `full`
functionality runs. Their `performanceContract` uses schema
`hubspot-set-campaign-performance/v1`; the 20- and 100-contact cases declare
`releaseBlocking=true`, while the 2,000-contact case declares
`releaseBlocking=false`.

The 2,000-contact contract declares:

- profile `set_campaign_2000_contacts_43m`;
- acceptance scope `set_campaign_performance_claim`;
- tier `should_pass`, with `releaseBlocking=false`;
- 2,000 HubSpot Contacts in 20 batches of 100;
- 2,000 real signed Set Campaign workflow actions at admission concurrency 4;
- a 15-minute action-admission deadline measured from case start, a 20-minute
  Lead terminalization budget, a five-minute deep-consistency readback budget,
  and a one-minute stability delay, totaling 41 minutes; the final snapshot's
  SQL and provider requests each have a 60-second timeout, and the complete
  snapshot must finish before both the 43-minute case deadline and the shared
  suite deadline;
- correctness required: zero admission failures, exactly 2,000 attributed
  Leads, exactly 2,000 CampaignMembers, and no lost or duplicate projection.

`kind=hubspot_sync` remains correct because `kind` selects the executor. The
separate `performanceContract` supplies the governance class, tier, blocking
scope, workload, and budgets. Standard JSON and Markdown reports surface those
fields for pass, fail, skip, timeout, and unexpected-error outcomes.

## Execution boundary

The standard `HubSpot Integration Test` workflow exposes
`suite_mode=set_campaign_performance`, which selects the registered
`set-campaign-performance` case group. The mode is main-only, serial, forces an
idle connector-queue preflight, and verifies the configured HubSpot credential
is the expected SANDBOX portal before a case creates its first NexusCRM
Campaign or makes any provider mutation.

Before Staging full functionality and Set Campaign performance, the workflow
automatically purges prior HubSpot queue work. Under the Staging deployment lock,
the host operator stops the exact active service containers, deletes the scoped
queue data once, and restarts those same containers. The scope is namespace
`hubspot-crm-connector` and outbound owner `pingcap-hubspot-companion`, including
prior runs. Business records and other connectors' queues are preserved.

The purge runs unconditionally. One check afterward covers Integration work,
receipt-linked Runtime commands, Runtime outbound admission, and Integration
outbound delivery. If matching work remains, the test fails. There is no second
purge or passive wait. Other environments receive only the read-only pipeline
gate. An empty queue cannot rule out a later provider webhook.

The purge step is capped at four minutes within the existing inbound-restore
job. The later preflight step is capped at four minutes, including route
resolution and database inspection, inside the existing integration job limit.
Both retain their JSON evidence as artifacts even if acceptance never starts;
a preflight failure is not a functionality or performance result.

The performance mode has three sequential job caps: five minutes for runtime
credential admission, five minutes for staging inbound restore, and 50 minutes
for integration execution, setup, and reporting. Their maximum active-job
budget is 60 minutes. GitHub queue, scheduling, and cancellation overhead are
outside these job limits; this is not a one-hour dispatch-to-completion SLA.

Within the integration job, the execution step has a 45-minute hard limit and
passes `--suite-timeout-ms 2580000`: one shared 43-minute deadline for the
entire performance suite. It ignores the dispatch's per-case timeout input.
The existing case limits remain 13 minutes for 20 contacts, 40 minutes for
100 contacts, and 43 minutes for 2,000 contacts; each is capped by the time
remaining in the shared suite budget. They are upper bounds, not three
successive reservations. Time consumed by the required cases reduces the time
available to the optional case. The suite deadline leaves two minutes for the
runner to finish its report; setup and artifact upload share the integration
job's remaining time. These are
nested limits: slow setup consumes the 50-minute job budget and may cause a
hard cancellation before a complete report can be written. The existing
always-run summary and artifact steps retain available failure evidence.
Timeout is a non-PASS; deadlines must not be expanded to make a slow cohort
pass. Other functionality and pressure suite budgets remain unchanged.

In the dedicated suite, a non-PASS for either the 20- or 100-contact case fails
required performance acceptance. A failure of only the optional 2,000-contact
case remains a failed case in the report but does not fail the required suite
gate. Explicitly selecting the 2,000-contact case alone still returns a failing
workflow status if it fails. Optional does not turn a non-PASS into a
performance PASS or weaken any correctness assertion. This suite is independent
of the inbound
`8/16/32 workers x 2k/4k/6k events` capacity matrix.

## Nearby cases that are not this acceptance

- `k-camp-05` is the ordinary-full N=8 bulk-integrity representative. Its
  documented N=500 shape is a manual scaling instruction, not its checked-in
  workload.
- `k-camp-13` is the ordinary-full serial N=5 same-Campaign parent-lock
  regression, not a throughput or latency benchmark.
- `acq-bulk-440` is an optional N=3 historical representative and does not
  execute the Set Campaign workflow action in its current JSON; it cannot
  certify Set Campaign performance.

## Evidence boundary

Local unit tests and workflow dry-runs prove contract consistency, selection,
reporting, and fail-closed admission wiring only. A required performance PASS
needs passing 20- and 100-contact results from the exact-main dedicated run.
The optional 2,000-contact result must be reported separately; a green required
suite does not establish that cohort's performance. Live evidence requires the
standard report and artifacts from the completed run.
