docs(loyalty): record B1-F resolution + 6 follow-ups for next session
All checks were successful
All checks were successful
End-of-day 2026-05-17 update to the go-live readiness doc. Welcome email B1-F is now fully resolved end-to-end (enrollment -> celery dispatch -> email_logs status=sent -> emails landing). The issue was a chain of four nested bugs each masking the next — the doc lists all four with commit hashes (44c42909,3e650ff8,2a216101,5b21908b) plus the three earlier same-session fixes for the SMTP password eye toggle, the JS error on /admin/loyalty/programs, and the 422 on ProgramCreate. Also captured: - Audit finding: prospecting/tasks/__init__.py has the same bug as bug #3 (scan_tasks.py exists but not imported). - Six queued follow-ups for next session: two Test 1 storefront nits (date format on FR, "Continuer mes achats" CTA), Test 2 cross- store re-enrollment, Hetzner doc check, a concrete unit-test list that would have caught each of the four B1-F bugs, the prospecting __init__.py fix, and a wider audit of every module's email path to find any other silently-broken @shared_task registration. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -70,6 +70,67 @@ Session paused 2026-05-16 evening. To resume Test 1 round 2:
|
|||||||
2. Confirm `/admin/loyalty/programs` shows the Fashion Group program (should be seeded by `seed_demo.py`).
|
2. Confirm `/admin/loyalty/programs` shows the Fashion Group program (should be seeded by `seed_demo.py`).
|
||||||
3. Tail `api` and `celery-worker` logs live, then enroll at `https://fashionhub.rewardflow.lu/loyalty/join` with a fresh email. The point of the live tail is to catch where B1-F actually dies — at dispatch, at SMTP, or somewhere else.
|
3. Tail `api` and `celery-worker` logs live, then enroll at `https://fashionhub.rewardflow.lu/loyalty/join` with a fresh email. The point of the live tail is to catch where B1-F actually dies — at dispatch, at SMTP, or somewhere else.
|
||||||
|
|
||||||
|
## 2026-05-17 update — B1-F resolved (chain of 4 nested bugs)
|
||||||
|
|
||||||
|
End-to-end enrollment → Celery dispatch → email_logs `status=sent` → real
|
||||||
|
emails arriving in inbox. Verified with the FR locale: enrollment
|
||||||
|
("Bienvenue chez Fashion Group S.A. Loyalty !") and welcome-bonus
|
||||||
|
("Vous avez gagné 50 points bonus !") both send within ~4s of submit.
|
||||||
|
|
||||||
|
The "no welcome email" symptom hid four layered bugs; each silently
|
||||||
|
masked the next, which is why early diagnostics looked clean:
|
||||||
|
|
||||||
|
| # | Bug | Fix |
|
||||||
|
|---|---|---|
|
||||||
|
| 1 | `@shared_task` defaulted to `amqp://localhost//` because `celery_app.set_default()` was never called AND the api process never imported `celery_config`. `.delay()` raised `kombu.OperationalError: Connection refused`. | `44c42909` — `set_default()` + early import in `main.py` (with `# isort: split` so ruff doesn't reorder it). |
|
||||||
|
| 2 | `on_failure` log handler crashed on reserved `LogRecord` attribute name `args` → `KeyError` masked every real task exception. | `3e650ff8` — rename to `task_args` / `task_kwargs`. |
|
||||||
|
| 3 | `loyalty.send_notification_email` wasn't in worker's task registry — `notifications.py` wasn't imported by `loyalty/tasks/__init__.py`. Worker received the message, couldn't find the task, ACKed silently. | `2a216101` — add the import + `__all__` entry. |
|
||||||
|
| 4 | Celery worker process never imported all models. First DB query failed `InvalidRequestError: expression 'ContentPage' failed to locate a name`. | `5b21908b` — `_preload_all_module_models()` walks the registry and force-imports each module's `models` package at celery_config load. |
|
||||||
|
|
||||||
|
Three earlier same-session commits also shipped: SMTP password eye toggle
|
||||||
|
(`64a178f4`), JS error on `/admin/loyalty/programs` (`8d6830fc`), 422 on
|
||||||
|
ProgramCreate (`120532e6`).
|
||||||
|
|
||||||
|
### Audit finding
|
||||||
|
|
||||||
|
`app/modules/prospecting/tasks/__init__.py` has the same shape as bug #3
|
||||||
|
above — `scan_tasks.py` exists but isn't imported. Not blocking anything
|
||||||
|
today (no prospecting Celery dispatch is wired up yet), but should be
|
||||||
|
fixed alongside the unit-test pass below.
|
||||||
|
|
||||||
|
### Follow-ups (queued for next session)
|
||||||
|
|
||||||
|
1. **Two Test 1 nits** — date format mm/dd/yyyy on FR storefront
|
||||||
|
enrollment form (verify the `<html lang>` deploy actually landed; if it
|
||||||
|
did, the user's browser doesn't honor `lang` for `<input type="date">`
|
||||||
|
and we need a JS date-picker swap); "Continuer mes achats" CTA on
|
||||||
|
`enroll-success.html:118` is wrong for loyalty-only storefronts with no
|
||||||
|
catalog.
|
||||||
|
2. **Test 2** — cross-store re-enrollment at FASHIONOUTLET with the email
|
||||||
|
from Test 1.
|
||||||
|
3. **Hetzner doc check** — verify whether `docs/deployment/hetzner-server-setup.md`
|
||||||
|
needs any new step from tonight's fixes. Most likely no (the fixes are
|
||||||
|
in-code, not deployment), but worth a glance.
|
||||||
|
4. **Unit tests** — none of the four B1-F bugs were caught by the existing
|
||||||
|
suite. Add at minimum:
|
||||||
|
- Assert `celery_app.conf.broker_url` is `redis://...` after importing
|
||||||
|
`main` (catches future `set_default()` ordering regressions).
|
||||||
|
- Assert `loyalty.send_notification_email` is in `celery_app.tasks`
|
||||||
|
after importing `app.modules.loyalty.tasks` (catches future missing
|
||||||
|
imports in task package `__init__.py`).
|
||||||
|
- Assert `configure_mappers()` succeeds after importing
|
||||||
|
`app.core.celery_config` (catches future missing-models regressions
|
||||||
|
in celery).
|
||||||
|
- Either assert `task_base.on_failure` doesn't crash on a synthetic
|
||||||
|
failure, or standardize an `extra=` sanitiser that strips reserved
|
||||||
|
`LogRecord` attribute names.
|
||||||
|
5. **Fix `prospecting/tasks/__init__.py`** — add the missing import.
|
||||||
|
6. **Audit every other module's email path** — are billing's trial-expiration
|
||||||
|
emails really dispatched via Celery? Messaging's password-reset emails?
|
||||||
|
If yes, same silent-failure risk exists until a real send hits prod. Add
|
||||||
|
an integration test that triggers a representative email from every
|
||||||
|
module and asserts an `email_logs` row appears within N seconds.
|
||||||
|
|
||||||
## Status board
|
## Status board
|
||||||
|
|
||||||
| # | Pre-launch step | State | Notes |
|
| # | Pre-launch step | State | Notes |
|
||||||
|
|||||||
Reference in New Issue
Block a user