Skip to main content
Version: Latest

18. Architecture Validation

Purpose: an independent, fresh re-verification of the consolidation guarantees the Phase 1 Runtime Consolidation established, first performed in Phase 2.2 (documented, not fixed) and then re-verified and resolved in Phase 2.3 (the Runtime Audit & Final Stabilization phase that formally closed the WhatsApp Integration initiative). This chapter reflects the final, resolved state.

18.1 Check 1 — Duplicate HTTP Client for Meta Graph API

Verdict: CONSOLIDATED — no duplication found.

WhatsAppCloudApiClient remains the only class in either repository that builds a Graph API endpoint URL from BaseUrl/GraphApiVersion and performs the full send/receive/media protocol. graph.facebook.com appears only in the library's own default option value and its own test fixtures — zero occurrences anywhere in the ERP repository. TenantWhatsAppSender.TestConnectionAsync remains the sole other direct-HTTP call (a business-account lookup with no IWhatsAppService equivalent), unchanged since Phase 1 and still documented as an accepted, narrow exception.

18.2 Check 2 — Duplicate Phone Normalization

Verdict: CONSOLIDATED — no duplicate WhatsApp normalizer found, and the one real bypass is now fixed.

WhatsAppPhoneHelper.TryNormalizePhone is the only WhatsApp-specific phone normalizer in the ERP. One adjacent, unrelated formatter exists — SendSMSService.setMobileFormat — but it is SMS-provider formatting that never touches IWhatsAppService or WhatsAppCloudApiClient; noted for completeness, not a duplicate.

Every send path that originates a Saudi-local phone number now calls this helper: WhatsAppOtpMessageService, WhatsAppActivationMessageService, TenantWhatsAppSender, and — as of Phase 2.3 — MetaWhatsAppProvider (the Notification Framework's live WhatsApp channel; see §18.4 below for what was fixed and why).

One deliberate, documented exception remains: WhatsAppInboxService.SendReplyAsync/ SendTemplateReplyAsync sends to conversation.CustomerWaId without calling the helper. This was re-examined explicitly in Phase 2.3 and confirmed correct as-is, not a bugCustomerWaId is Meta's own canonical wa_id for an existing conversation, which can originate from any country, not only Saudi Arabia. WhatsAppPhoneHelper only recognizes Saudi-market local formats (966-prefixed 12-digit, local 0-prefixed 10-digit, bare 9-digit starting with 5); applying it here would cause replies to any international customer's conversation to fail outright. This exception is intentional and should not be "fixed" by a future phase without first confirming the platform will only ever serve Saudi WhatsApp customers.

18.3 Check 3 — Duplicate Graph API Version Configuration

Verdict: CONSOLIDATED — no drift found.

v25.0 appears in exactly three places, all consistent with each other: the library's own default (WhatsAppCloudApiOptions.GraphApiVersion), and two test fixture constants that assert against that same value. Both appsettings.json and appsettings.Development.json in the ERP configure GraphApiVersion to v25.0 through the same options class. The only occurrence of the pre-Phase-1 v19 string anywhere in either repository is inside a code comment in TenantWhatsAppSender.cs, documenting the historical pre-consolidation state — not a live value.

18.4 Check 4 — Duplicated Runtime Path (Resolved in Phase 2.3)

Phase 2.2 identified two runtime realities outside Phase 1's original scope. Both are now resolved:

ConsumerInjects IWhatsAppService via DI?Calls WhatsAppPhoneHelper.TryNormalizePhone?
WhatsAppOtpMessageService.SendTenantOtpMessageAsyncYesYes
WhatsAppActivationMessageService.SendTenantActivationMessageAsyncYesYes
TenantWhatsAppSender.SendTemplateAsyncConstructs its own WhatsAppCloudApiClient per call, tenant-derived credentials — the accepted Phase 1 exceptionYes
WhatsAppInboxService.SendReplyAsync / SendTemplateReplyAsyncYesNo — intentional, see §18.2
MetaWhatsAppProvider.SendTemplateAsyncYesYes, as of Phase 2.3 (previously no)

Resolved — MetaWhatsAppProvider now normalizes the recipient phone

Phase 2.2 found that MetaWhatsAppProvider — the class actually wired into the live Notification Framework dispatch pipeline via WhatsAppChannelProviderAdapter (Priority = 1, IsPrimary = true) — passed recipientPhone straight through to Meta's Graph API with no normalization, meaning any WhatsApp send through the generic Notification Framework dispatch pipeline could reach Meta with an unnormalized local phone format and be rejected. Phase 2.3 added a WhatsAppPhoneHelper.TryNormalizePhone call at the top of SendTemplateAsync, returning an INVALID_PHONE provider failure if normalization fails, matching the validation-failure pattern already used for empty phone/template name — no other behavior changed.

Resolved — WhatsAppNotificationSender (dead code) removed

Phase 2.2 also found a second class, WhatsAppNotificationSender/IWhatsAppNotificationSender, which did correctly call the phone helper but appeared to have no callers anywhere in the ERP. Phase 2.3 performed an exhaustive reachability audit — direct calls, DI auto-registration vs. actual resolution, reflection/dynamic activation, the Notification Framework's own collection-injection provider-discovery mechanism, generic/factory resolution, and test coverage — and confirmed it was genuinely dead: registered in DI (it implemented the ITransientService marker) but never resolved by anything, superseded by MetaWhatsAppProvider/WhatsAppChannelProviderAdapter the day after it was introduced. It was removed entirely (both the interface and implementation file), together with the stale documentation across this guide and the Notification Framework guide that described it as the live implementation. See docs/ARCHITECTURE/WHATSAPP_DEAD_CODE_ANALYSIS.md in Shumoul.Saas.Api for the full audit trail.

18.5 Additional Confirmations (No Findings)

  • No Meta resumable-upload/POST /media call exists anywhere in either repository. Outbound media remains URL-reference-only (see §9 Templates).
  • No HTTP resilience policy (retry, custom timeout, circuit breaker) is attached to the WhatsAppCloudApi named HttpClient — no Polly package reference or usage exists in either repository. See §19.5 for what this means in practice.
  • No WhatsApp-specific background job exists. The only related scheduled work is the Notification Framework's generic, channel-agnostic retry pipeline (notification-retry-processor-pipeline, every 2 minutes), which retries WhatsApp sends only incidentally, alongside every other channel.
  • No forbidden dependency reference, no circular dependency, no ERP business logic inside the standalone library, and no Meta-specific protocol logic outside the library — independently re-verified in Phase 2.3 (dependency graph, DI registrations, allocation patterns) with the same conclusions as Phase 1/2.2.

18.6 Final Status

As of Phase 2.3, every success criterion for closing the WhatsApp Integration initiative is met: one Meta client, one phone-normalization path (with one documented, justified exception), one webhook parser, one signature validator, one Graph API version, zero dead code, zero duplicate runtime paths, and zero unresolved architectural findings. See docs/ARCHITECTURE/WHATSAPP_RUNTIME_CERTIFICATION.md in Shumoul.Saas.Api for the formal closure record.