# Architecture Conventions

## Deployment Conventions

### Multi-brand testing requirement

Any code change to shared services (audit pipeline, site-tag-analytics,
synthesizers, categorizers, conversion type registry) must be verified
against at least:

- One medical brand (cardiology or imaging) for dense events, multi-language
  data shapes, and HIPAA-relevant surfaces
- One service brand (lawn care portfolio) for high-volume events and
  multi-domain reconciliation
- One B2B brand (TPAction / AiGrowth360) for low-event-volume edge cases
  and lead gen flows
- One ecommerce brand if available for product schema and checkout flows

Verification recipes must specify at least two brands of different verticals.
"Verify on cardiology" alone is insufficient.

Single-brand verification was the root cause of the Phase 19A.16 missed
form_filled crash on Fairway Lawns (May 27, 2026).

### Conversion type registration requirement

Before merging any change to shared event or conversion categorization code,
run the comprehensive type discovery SQL:

```sql
SELECT "brandId", "conversionType", COUNT(*)
FROM "SiteConversion"
WHERE timestamp >= NOW() - INTERVAL '30 days'
  AND "conversionType" NOT IN (
    'appointment_booked','form_submitted','phone_call','email_contact',
    'sms_contact','purchase','appointment_attempted','form_started',
    'appointment_intent','chat_initiated','newsletter_signup','add_to_cart',
    'checkout_started','file_download','waitlist_signup','form_filled',
    'form_submit','form_submission','phone_number_click','click_to_call',
    'calendly','booking_confirmed','booking_attempt','email_click',
    'sms_click','phone_copy','phone_input','form_iframe_present',
    'error_count_update'
  )
GROUP BY "brandId", "conversionType"
ORDER BY count DESC;
```

Confirm zero rows returned, or explicitly handle every type returned before
shipping.

New types must be registered in:
1. `SITE_CONVERSION_TYPES` in `src/lib/services/site-tag-analytics.ts`
   (query allowlist -- unregistered types are silently dropped)
2. `CONVERSION_TIERS` in `src/lib/conversions/tiers.ts`
   (tier mapping -- unregistered types crash route.ts via undefined key lookup)

The defensive guard at `src/app/api/site-tag/analytics/route.ts` around
the `tierTotals[cfg.tier]` access will log a warning and skip rather than
crash for any future type that slips through, but the correct fix is always
to register the type proactively.

### Tier assignment guidelines

| Tier | Description | isCounted | Examples |
|------|-------------|-----------|---------|
| completed | Definitive conversion action | true | form_submit, phone_call, appointment_booked, purchase |
| intent | Started a conversion flow | false | form_filled, appointment_attempted, checkout_started |
| signal | Engagement only (no conversion flow) | false | phone_copy, phone_input, form_iframe_present |

The `DEFAULT_COUNTED_TIERS` constant controls which tiers roll up into the
headline conversion count and conversion rate. Only "completed" is counted
by default. Brand-level overrides via `BrandConversionConfig.isCounted` can
promote intent signals to counted status for specific brands.
