docs(loyalty): record B1-F resolution + 6 follow-ups for next session
All checks were successful
CI / ruff (push) Successful in 19s
CI / pytest (push) Successful in 2h52m5s
CI / validate (push) Successful in 34s
CI / dependency-scanning (push) Successful in 36s
CI / docs (push) Successful in 58s
CI / deploy (push) Successful in 1m52s

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:
2026-05-17 23:08:27 +02:00
parent 5b21908ba4
commit 7cf2420bba

View File

@@ -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 |