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 bug — CustomerWaId 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:
| Consumer | Injects IWhatsAppService via DI? | Calls WhatsAppPhoneHelper.TryNormalizePhone? |
|---|---|---|
WhatsAppOtpMessageService.SendTenantOtpMessageAsync | Yes | Yes |
WhatsAppActivationMessageService.SendTenantActivationMessageAsync | Yes | Yes |
TenantWhatsAppSender.SendTemplateAsync | Constructs its own WhatsAppCloudApiClient per call, tenant-derived credentials — the accepted Phase 1 exception | Yes |
WhatsAppInboxService.SendReplyAsync / SendTemplateReplyAsync | Yes | No — intentional, see §18.2 |
MetaWhatsAppProvider.SendTemplateAsync | Yes | Yes, 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 /mediacall 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
WhatsAppCloudApinamedHttpClient— 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.
