18. Phase History / Implementation Status
18.1 Status summary
| Area | Status |
|---|---|
| Survey/question/profile/rule model | ✅ Complete, seeded (23 questions, 27 rules) |
| Recommendation engine | ✅ Complete |
| Settings patch engine (11 supported groups) | ✅ Complete — see Chapter 20 |
| Internal apply bridge | ✅ Complete, secured |
| JWT authentication on the MultiTenancy host | ✅ Complete |
| Tenant-Admin-only restriction on mutating actions | ✅ Complete |
Broader [AllowAnonymous] security review of the host | ✅ Complete |
| New Subscriber E2E flow (Register → Verify → Provision → Login → Onboarding → Dashboard → Repeat login) | ✅ Verified live through the real Register/Verify/Tokens/Create endpoints — see docs/testing/NEW_SUBSCRIBER_E2E_TEST_REPORT.md |
| Angular client | ❌ Not started (see Chapter 13) |
| Real tenant registration/verification API flow | ✅ Now exercised through the real endpoints (previously used a directly-seeded tenant — see updated §17.3) |
| Real OTP/email/SMS/WhatsApp delivery | ⚠️ Still not functional in this dev environment — dispatch failures are now safely swallowed (best-effort, logged) instead of crashing registration, but no real message is ever delivered |
| Platform-admin permission model (non-onboarding) | ✅ Working when hosted inside Shumoul.Api (real DB-backed permission service wins) — deny-by-default only for a standalone host, see §17.4 |
| Onboarding survey/summary text localization | ✅ Complete — sourced from embedded ar.json/en.json via OnboardingLocalization, not hardcoded C# — see Chapter 20, §20.6 |
| Onboarding technical validation message localization | ✅ Complete — SettingsPatchService/HttpOnboardingTenantSettingsApplier warnings/errors and InternalOnboardingSettingsPatchService/OnboardingInternalController warnings/errors no longer hardcoded |
| Database-owned onboarding survey content preserved on reseed | ✅ Complete — seeder fills only blank fields; existing admin/DB content is never overwritten — see Chapter 20, §20.6.1 |
OnboardingRule stable RuleKey + unique index | ✅ Complete — RuleKey is now the seeder's match key for recommendation rules, backed by a real unique index (Add_OnboardingRule_RuleKey migration) — see Chapter 20, §20.6.2 |
18.2 Phase-by-phase
Phase 1.1 — Internal Apply Bridge. Built the internal, service-to-service endpoint on Shumoul.Api
(OnboardingInternalController) and the calling code on Shumoul.MultiTenancyApi
(HttpOnboardingTenantSettingsApplier), replacing an earlier stub that always returned "not configured."
Established the shared-secret (X-Shumoul-Internal-Key) and tenant-header (TenantId) contract between the
two hosts.
Phase 1.2 — CostCenterSettings Support & Final Acceptance. Added CostCenterSettings as an 8th supported
settings group (previously only 7 were wired), verified runtime configuration actually resolves from the real
host's configuration (not just library defaults), and hardened the internal endpoint's tenant-resolution
safety checks.
Phase 1.3 — Live E2E Acceptance. First live, end-to-end run against a real dev database and a real
seeded test tenant. Found and fixed a real bug: the internal apply bridge never forwarded the TenantId
header, so apply always failed with "Tenant could not be resolved." before ever reaching
IAppSettingService. Registration/OTP/email/WhatsApp verification were skipped in this phase because
ISharedLocationService/IPushNotificationService had no implementation in the MultiTenancy host — a tenant
was seeded directly instead. Also discovered (but only worked around temporarily, not fixed) that the host
had no working JWT authentication or permission enforcement at all.
Phase 1.4 — Authentication & Permissions Readiness. Wired real, permanent AddJwtAuthentication(),
AddIdentityPermissions(), and app.UseCurrentUser() into the MultiTenancy host (none had ever been called).
Built OnboardingCurrentUserPermissionsService, a claims-based permission service, since no
user/role/permission table exists in the host's central database. Found and fixed two real bugs surfaced by
turning authentication on for the first time: OnboardingController's class-level [AllowAnonymous] was
silently bypassing every [MustHavePermission] check, and the exception-handling middleware was registered
after the authentication middleware, so invalid/expired tokens surfaced as raw 500s instead of 401s.
Phase 1.5 — Onboarding Authorization & Security Hardening. Restricted Answer/Apply/Skip to the
tenant Admin role (previously any authenticated tenant user could reach them), using the existing, already-
issued Admin role JWT claim — no Shumoul.Api TokenService change was needed. While auditing the host for
the same [AllowAnonymous] pattern, found and fixed the most severe issue in this project's history:
SharedDbConnectionStringsController.GetActiveListAsync() had no permission check at all and leaked raw
shared-database credentials to any unauthenticated caller. Also fixed the same class of bug across 17 other
controllers, including action-level (not just class-level) conflicts on TenantsController's tenant
create/activate/deactivate actions.
Phase 1.6 — Security Cleanup Before Angular. Closed the two items Phase 1.5 had documented but not yet
fixed: gated TestNotificationsController (previously fully anonymous, capable of sending real notifications
to arbitrary users/tenants) behind a new, deny-by-default Permissions.Notifications.TestSend permission,
and removed the remaining [AllowAnonymous]+[MustHavePermission] conflicts on TaxController's and
TaxGroupController's full-listing actions — while explicitly leaving their minimal, permission-less
dropdown/lookup actions public, by design. Re-verified every prior fix still holds, including a full Swagger
exposure check confirming the internal bridge endpoint remains hidden.
New Subscriber E2E Verification (2026-07-11). First full live run of the entire subscriber journey
through the real API surface: Register → Verify → Tenant DB provisioning → Login → First-login onboarding survey → Activity/settings selection → Apply settings → ApplyLog → Dashboard access → Repeat login. Found
and fixed 5 real bugs blocking this path (registration/verification 500s caused by unguarded notification
dispatch; the onboarding apply bridge's config section name mismatch; a missing TenantId header on the
bridge's outbound call; a duplicate JWT authentication scheme registration crash) — full detail in
docs/testing/NEW_SUBSCRIBER_E2E_TEST_REPORT.md. Confirmed repeat login does not re-force onboarding. Also
corrected §17.3 and §17.4,
which had described limitations later found to be already fixed or only partially accurate (see
Chapter 17).
Latest Update — Phase 8. Completed onboarding recommendation + apply support for the three settings
groups still missing after the New Subscriber E2E run: FinancialSettings, LoyaltySettings,
NotificationSettings. All three had the same two gaps (missing from SettingsPatchService's whitelist and
missing a case in InternalOnboardingSettingsPatchService's apply switch); recommendation rules were updated
to produce all three using only existing survey questions/answers; a seeder idempotency bug and an apply
bridge timeout (found during this phase's own E2E verification) were also fixed. Settings groups supported
by onboarding: 8 → 11. Full technical detail, exact fixes, and E2E verification steps are in
docs/testing/NEW_SUBSCRIBER_E2E_TEST_REPORT.md's "Phase 8" section; the survey-facing summary is in
Chapter 20, §20.7.
Onboarding Text Localization (2026-07-12). Moved every hardcoded Arabic/English string out of
OnboardingSurveySeeder.cs, OnboardingService.cs's step titles, and
OnboardingRecommendationService.cs's summary/warning builders into two new embedded resource files,
Onboarding/Localization/ar.json/en.json, read through a new internal reader,
OnboardingLocalization. No question key, answer-option key, recommendation rule, settings-patch behavior,
or apply behavior changed — the database contract (TitleAr/TitleEn, LabelAr/LabelEn, NameAr/NameEn,
OnboardingSummaryLineDto.Ar/En) and the API response shape are unchanged, only the source of those values
changed. This phase's seeder also began refreshing an existing question/profile row's text from the
resources on every reseed — a decision corrected the next day, see the entry below.
Full detail in Chapter 20, §20.6.
Onboarding Technical Validation Message Localization (2026-07-12, continuation). Moved the remaining
hardcoded strings — this time in the settings-patch validation path rather than survey/summary content
— into localization resources: SettingsPatchService.Validate's whitelist/property warnings,
HttpOnboardingTenantSettingsApplier's bridge-disabled/apply-failed messages (both via
OnboardingLocalization, MultiTenancyApi), and InternalOnboardingSettingsPatchService.ApplyAsync's errors/
warnings plus OnboardingInternalController's validation message (both via the app-wide
IStringLocalizer<T> already used by OnboardingService, added to Shumoul.Api/Localization/ar.json/
en.json). All messages use {0}/{1} placeholders substituted via string.Format, never manual string
concatenation. No settings-group whitelist entry, apply behavior, or API contract (errors/warnings
remain plain string[]) changed. Full detail in
Chapter 20, §20.6.
Preserve Database-Owned Onboarding Survey Content on Reseed (2026-07-13). Corrected the prior day's
"always refresh from resources" seeder behavior, which would have silently discarded a future admin's
content edit on the next restart. The seeder now only fills a currently-blank
TitleAr/TitleEn/option-label/profile-name/survey-name field from the localization default — an existing
non-empty value (an admin edit, or a previously seeded default) is left untouched even if the resource value
has since changed. Matching is still exclusively by stable, non-text keys (OnboardingQuestion.Key,
option Value within OptionsJson, OnboardingProfile.Code, survey Code+Version) — verified that the
required unique indexes for all three already exist in the EF model. OnboardingRule has no unique index on
its own (ProfileCode, ConditionJson) match key (ConditionJson is nvarchar(max), un-indexable without a
migration) — flagged as a follow-up, no migration created in this pass. OnboardingRule.SettingsPatchJson/
Priority continue to sync from source on every run, unchanged — they are system logic, not admin-editable
survey content. No question key, option key, recommendation rule, settings-patch behavior, apply behavior,
or API response shape changed. Full detail in
Chapter 20, §20.6.1.
Add Stable RuleKey for Onboarding Recommendation Rules (2026-07-13, same-day follow-up). Closed the
OnboardingRule unique-index gap the entry above flagged. Added OnboardingRule.RuleKey — a stable,
human-readable, non-text identity (Profile.RestaurantCafe, HasInventory.True, etc.) — via migration
Add_OnboardingRule_RuleKey (nullable nvarchar(450) column + a filtered unique index, additive only, no
data touched). SeedRulesAsync now matches existing rules by RuleKey first; a legacy row seeded before
this column existed (RuleKey == null) is matched once by the old (ProfileCode, ConditionJson) identity
and backfilled, purely for migration compatibility — every subsequent run matches it by RuleKey only. All
27 existing rules in the real dev database were backfilled cleanly onto their existing row IDs (verified: 27
rows, 27 distinct RuleKey values, no duplicates, unchanged after two more reseeds). ProfileCode/
ConditionJson/SettingsPatchJson/Priority continue to be rule logic, synced from source on every run
exactly as before — only the match identity changed. No question key, option key, recommendation logic,
settings-patch behavior, apply behavior, or API response shape changed (RuleKey is never returned in any
API response). Full detail in
Chapter 20, §20.6.2.
18.3 What "done" means today
The backend is feature-complete, authorization-hardened, and live-tested end-to-end against a real dev
database — including the real Register → Verify → Login API surface, not a directly-seeded tenant — for
every permission tier described in this guide. All 11 supported settings groups (see
Chapter 11, §11.2 and
Chapter 20) recommend and apply correctly. Angular
integration has not started — see Chapter 13 to begin it.
