334 Commits

Author SHA1 Message Date
223650a52b docs(ops): record 2026-06-06 Gitea+CI migration execution + runbook lessons
Some checks are pending
CI / pytest (push) Waiting to run
CI / validate (push) Waiting to run
CI / dependency-scanning (push) Waiting to run
CI / docs (push) Blocked by required conditions
CI / deploy (push) Blocked by required conditions
CI / ruff (push) Successful in 15s
Add the "Executed: 2026-06-06" record to the 2c runbook (new box
gitea-ci-fsn1-1, Falkenstein CX22, IPs, outcome) and fold the real-world
lessons into the steps: pin the Gitea image version (not latest),
ON_ERROR_STOP + count check on DB restore, the old-runner-survives-in-
migrated-DB gotcha (delete from action_runner + stop prod service), generate
runner token as the git user, expected volume-already-exists warning, and the
root-vs-sudo note.

Held local (not pushed) — pushing stacks a 2nd ~3h CI run behind the in-flight one.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 21:01:54 +02:00
c93346f8ff docs(ops): add CI-runner offload (2a) + Gitea migration (2c) runbooks
Some checks failed
CI / docs (push) Blocked by required conditions
CI / deploy (push) Blocked by required conditions
CI / ruff (push) Successful in 2m7s
CI / validate (push) Successful in 39s
CI / dependency-scanning (push) Successful in 45s
CI / pytest (push) Failing after 3h3m22s
Document two ways to take CI/Gitea load off the production box, since the
HostHighCpuUsage floods are caused by act_runner running ruff/pytest/validate
on the prod server (not by Gitea hosting, which is light):

- 2a "Offloading CI to a Separate Server" — move just the act_runner to a
  cheap x86 box (no data migration, no DNS, no downtime). Includes the smaller
  build-burst caveat (deploy still builds on prod) + the registry-pull path.
- 2c "Migrating Gitea to a Separate Server" — full separation runbook:
  pg_dump + data-volume tar/restore, DNS cutover, Caddy/SSL, rollback. Notes
  the box becomes stateful/critical (backups + hardening).

mkdocs --strict clean; arch validation 0 new findings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 22:45:23 +02:00
64c8a0ec2c chore(ops): prune build cache in deploy.sh + document rescale & disk maintenance
All checks were successful
CI / ruff (push) Successful in 44s
CI / pytest (push) Successful in 2h39m22s
CI / validate (push) Successful in 32s
CI / dependency-scanning (push) Successful in 34s
CI / docs (push) Successful in 54s
CI / deploy (push) Successful in 3m15s
deploy.sh already pruned old images but never build cache — the larger half
of disk creep from CI rebuilds (root fs hit 83% on prod). Add
`docker builder prune --filter until=168h` alongside the existing image prune
so cleanup happens every deploy, version-controlled, no host cron.

Docs (hetzner-server-setup.md, Maintenance section):
- New "Rescaling / Upgrading the Server" — when/why, same-arch (Arm/CAX) +
  CPU-RAM-only vs irreversible disk-expand constraints, poweroff→rescale→
  power-on→verify steps, and the Arm-capacity-unavailable-in-DC caveat.
- New "Disk Maintenance (Docker Pruning)" — emergency manual prune + the
  automated deploy.sh approach.
- Fixed stale Resource Budget: cadvisor 128→192 MB (matches compose),
  total 672→736 MB, and "live-upgrade" wording (rescale needs a power-off).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 23:36:53 +02:00
ac7850b880 docs(auth): record 2026-05-31 401-redirect review + dev/prod symmetry proposal
Some checks failed
CI / ruff (push) Successful in 19s
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has been cancelled
Reviewed commit 4423f0a5 (generalize 401 redirect to all 4 personas) and
verified production has no coverage gap: the only uncovered storefront page
(/loyalty/join) hits exclusively public, no-auth endpoints that can't 401,
and authenticated storefront pages live under /account/ which is already
covered. Remaining gap is dev/path-based-mode only (/platforms/...) — captured
as a deferred enhancement in docs/proposals/auth-redirect-dev-prod-symmetry.md
with the proposed one-line prefix-strip fix and two open implementation
details. No code shipped this session. mkdocs --strict clean; architecture
validation passed (0 new findings).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 22:34:09 +02:00
947ca43c7b docs(loyalty): record 2026-05-30 afternoon — prod-readiness 1-3 done + alerting back online
All checks were successful
CI / ruff (push) Successful in 18s
CI / pytest (push) Successful in 2h39m33s
CI / validate (push) Successful in 35s
CI / dependency-scanning (push) Successful in 36s
CI / docs (push) Successful in 56s
CI / deploy (push) Successful in 1m13s
Picked up the morning's carry-over and ran the full prod-readiness
chain end-to-end. Resolution: SG credential out of git permanently
via untrack + .example template (e44f5c04); per-host migration on
prod (alertmanager.yml gitignored, real file lives outside git);
deploy-api-only.sh succeeded for the first time; today's 9 queued
loyalty commits live on prod with ?v=e44f5c04 (and verified by
re-running the loyalty redirect flicker repro — clean).

Multi-hour rabbit hole on actual email delivery: provider's port 587
PLAIN backend is OAuth-wired (returns RFC 6749 invalid_grant text
for password auth); switched to provider's documented port 465 SSL/TLS
endpoint. Discovered Hetzner Cloud blocks outbound 25 and 465 by
default as anti-spam policy. Auto-approved unblock ticket landed in
minutes; one-line smarthost change to :465 reactivated email
alerting after 13+ days down. alertmanager handles implicit TLS on
465 natively, no stunnel/relay needed.

Hetzner doc updated with the egress-block warning + mail1 SMTP
callout in 1227567d as 5h-debug payback. Next session resumes at
Test 5.2 (/account/loyalty with 168 pts customer) → 5.3 history.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-30 20:20:00 +02:00
1227567d08 docs(hetzner): record 25/465 egress block + mail1 SMTP setup (5h debug payback)
Some checks failed
CI / ruff (push) Successful in 18s
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has been cancelled
Hetzner Cloud silently blocks outbound TCP 25 and 465 on every Cloud
Server. The block sits upstream of the VM — UFW and iptables look
completely clean — so it presents as a generic "connection times out"
that's easy to misdiagnose as a credential or DNS issue. Spent ~5 hours
on 2026-05-30 working through swaks/tcpdump/auth-backend hypotheses
before finding Hetzner's own docs that mention the policy.

Two doc additions:

- Step 4 (Firewall Configuration) gets a warning admonition right after
  the UFW status check. Explains the upstream nature of the block,
  gives the symptom signature (nc to 587 succeeds, nc to 465 silently
  times out), and includes the auto-approved unblock ticket template
  with sample text.

- Step 19.5 (Alertmanager SMTP) gets a "live prod uses
  mail1.myservices.hosting:465" callout reflecting the reality that
  the SendGrid setup documented in that section is no longer how this
  prod env is wired. The callout captures the actual smarthost config
  (with smtp_auth_password kept gitignored, only .example ships in
  repo), the two prerequisites (Hetzner unblock + implicit-TLS-aware
  smarthost port), and the redacted swaks verification command. The
  rest of §19.5 stays as a reference for greenfield deploys that
  prefer SendGrid.

Saves the next person from repeating the same hours-long detour.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-30 19:54:10 +02:00
cff0b3f911 docs(loyalty): record 2026-05-30 widget i18n + cache-bust + 401 redirect + alertmanager finding
All checks were successful
CI / pytest (push) Successful in 2h49m26s
CI / docs (push) Successful in 55s
CI / ruff (push) Successful in 18s
CI / validate (push) Successful in 35s
CI / dependency-scanning (push) Successful in 35s
CI / deploy (push) Successful in 1m51s
Nine code commits shipped today (5f359283c13e8e29) covering Test 5
widget/customer-module i18n, a 53-template cache-bust sweep with
FE-024 rule tightening, the customer-storefront 401-to-/account/login
redirect, the loyalty redirect-flicker fix, the login JS i18n sweep,
and a new scripts/deploy-api-only.sh script + Hetzner §16.5 split.
None of them are on prod yet — surfaced during the deploy that the
new dirty-tree gate is correctly blocking on monitoring/alertmanager/
alertmanager.yml, which holds a SendGrid API key pasted into a tracked
file. Knock-on finding: alertmanager has been running with stale empty
SMTP config for 13+ days, AND the file still references SendGrid
instead of the post-migration smarthost, so prod's alerting is silently
broken. User opted to fix prod-readiness items first thing tomorrow
before resuming Test 5.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 23:20:07 +02:00
c13e8e29b5 feat(deploy): scripts/deploy-api-only.sh + Hetzner doc for manual code-only redeploys
Some checks failed
CI / pytest (push) Has been cancelled
CI / ruff (push) Successful in 18s
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
Manual deploys had been using a bare `git pull && docker compose up -d
--build api` sequence, which works for the container itself but silently
skipped writing `.build-info`. The stale `.build-info` left
`?v=<commit-sha>` pointing at the previous deploy's SHA on every shared
JS/CSS URL — so browsers happily kept cached pre-fix assets even after
a successful rebuild. Bit us today: ~5 hours of "is this even deployed?"
debugging on the loyalty-dashboard redirect-flicker fix.

deploy.sh wasn't a substitute because it's a CI/CD script: stashes
working tree, runs alembic, restarts every service in the full profile
(db, redis, api, celery-worker, celery-beat, flower), 60s health budget.
Heavy and disruptive for an api-only hotfix.

New scripts/deploy-api-only.sh fills the gap with the narrow path:

  - Refuses if working tree is dirty (no silent stash → no pop conflicts).
  - git pull --ff-only.
  - Writes .build-info (the critical missing step).
  - docker compose -f docker-compose.yml --profile full up -d --build api
    (only the api service — db/redis/celery untouched).
  - Tight 30s health budget since DB doesn't need to come back up.
  - Exit codes 0/1/2/3 for clean automation.

docs/deployment/hetzner-server-setup.md §16.5 split into 16.5a
(code-only — points at the new script as the default) and 16.5b
(full deploy fallback — kept the existing deploy.sh path for migrations
/ Dockerfile / docker-compose / requirements changes). §12 footnote on
.build-info refreshed to mention both scripts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 22:55:05 +02:00
acbe2eff1a docs(loyalty): record 2026-05-29 Test 5.0 i18n sweep + FR/DE email accents
All checks were successful
CI / ruff (push) Successful in 19s
CI / pytest (push) Successful in 2h48m44s
CI / validate (push) Successful in 33s
CI / dependency-scanning (push) Successful in 34s
CI / docs (push) Successful in 55s
CI / deploy (push) Successful in 1m52s
Five-issue triage shipped as four commits today: storefront i18n sweep
(10a99f98), FR password_reset accents + store-name signature
(b463c6bf), DE password_reset umlauts (36fd3781), Alpine x-text quoting
fix (1bade6e6), plus a seed-script sys.path fix (213a6053) hit during
the prod reseed. Test 5.0 (forgot-password end-to-end on FR) verified
end-of-day; Test 5 proper (login + dashboard + history) blocks on
recreating the prod api container tomorrow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 00:17:49 +02:00
f04cbb8ca2 docs(audit): lessons learned from loyalty migration
All checks were successful
CI / ruff (push) Successful in 18s
CI / pytest (push) Successful in 2h51m39s
CI / validate (push) Successful in 40s
CI / dependency-scanning (push) Successful in 36s
CI / docs (push) Successful in 54s
CI / deploy (push) Successful in 1m46s
Adds a post-audit section to the persona-template consolidation audit
capturing what came out of the in-prod card-detail test on
rewardflow.lu vs fashionhub.rewardflow.lu:

- Template alignment != data alignment: shared partial guarantees the
  markup is the same per persona, NOT that the API response is.
  Loyalty's category column rendered empty on merchant + admin
  because only the store route enriched category_names. Future
  migrations should diff API response shapes per persona, not just
  templates. Fixed in d32c1fd5.

- Locale-aware formatters are infrastructure, not per-feature. The
  hardcoded 'en-US' bug spanned 27 callsites across 20+ files. Now
  swept (dd1f9af8 + 06e59f73 + bb4c4004) and locked down by the
  JS-016 architecture rule at error severity (eaf180c6).

- Sweep + rule, not just sweep. Each cleanup should land with a
  matching arch rule so the work doesn't decay. Table of the three
  rules currently guarding this surface (TPL-016, FE-024, JS-016).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 23:59:05 +02:00
a21dbbcddf docs(loyalty): record 2026-05-24 Test 4 + storefront auth body-schema fix
Some checks failed
CI / ruff (push) Successful in 16s
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has been cancelled
End-of-day update.

- Test 4 (cross-store redemption) verified: card #5's transaction
  history now spans store_id=4 (FASHIONHUB, all the earnings) and
  store_id=5 (FASHIONOUTLET, today's -100 redemption). Cross-location
  flow confirmed.

- Bug found + fixed (478c3a9c) on the storefront auth API. Both
  POST /api/v1/storefront/auth/forgot-password and .../reset-password
  declared bare `email: str` / `reset_token: str, new_password: str`
  params, which FastAPI treats as query strings. The frontend sends
  JSON body, so the call 422'd with "missing query parameter email".
  Added PasswordResetRequest + PasswordResetConfirm Pydantic body
  schemas; switched both endpoints to body: <Schema>. Surfaced
  trying to test Test 5's customer login flow.

- /loyalty-wrap skill committed (d03b96da) — mechanises the end-of-day
  routine. First invokable as /loyalty-wrap tomorrow (skills load at
  session start).

Carries Test 5 into next session (now unblocked by the auth fix), plus
a new TODO from the user: transaction categories should be creatable
by merchants and store owners, not admin-only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 23:36:31 +02:00
58a9e3f740 docs(proposals): cross-module persona-template consolidation audit
All checks were successful
CI / ruff (push) Successful in 18s
CI / pytest (push) Successful in 2h49m10s
CI / validate (push) Successful in 34s
CI / dependency-scanning (push) Successful in 35s
CI / docs (push) Successful in 56s
CI / deploy (push) Successful in 1m11s
Walks every multi-persona module's templates/{admin,merchant,store}/
and classifies each feature cluster as YES / PARTIAL / NO (legit
exception) / N/A for consolidation. Produces a prioritized 10-item
backlog across 3 waves (~8-9 days of focused work, ~3,100-3,500 LOC
removable).

Headline findings:
- 141 persona templates across 9 modules; loyalty already migrated
  with 8 shared partials.
- Wave 1 (low risk, ~1,190 LOC): messaging.messages,
  messaging.notifications, billing.billing-history.
- Wave 2 (3-persona my-account is the marquee item, ~1,430 LOC):
  tenancy.my-account, tenancy.profile, messaging.email-templates.
- Wave 3 (higher complexity, ~1,820 LOC): tenancy.team,
  catalog.store-products lists, customers.customers, tenancy.login
  (security-gated).
- Anti-candidates documented inline so contributors don't try to
  force-fit them (catalog product forms, marketplace admin vs store,
  cms content-page-edit, etc.).

Backend services are uniformly scope-agnostic for every top-10
candidate -- no service/route work required.

Added to mkdocs nav under Proposals.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 14:09:39 +02:00
82939c0005 docs(api): document apiClient error.status / errorCode / details surface
All checks were successful
CI / ruff (push) Successful in 16s
CI / pytest (push) Successful in 2h48m39s
CI / validate (push) Successful in 33s
CI / dependency-scanning (push) Successful in 36s
CI / docs (push) Successful in 57s
CI / deploy (push) Successful in 1m53s
Adds a "Frontend Error Handling (apiClient)" section to the error
handling guide. Callers can branch on `error.errorCode` and read
`error.details` to localise toasts instead of rendering the raw
English `message`, as already done in the loyalty terminal cooldown
handler.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 00:32:42 +02:00
78621cb7bb docs(loyalty): record 2026-05-23 Test 3 + cooldown bug + routing investigation
Some checks failed
CI / ruff (push) Successful in 17s
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has been cancelled
End-of-day update. Three things in this session:

- Test 3 (staff stamps/points at terminal) — all 6 sub-steps verified
  on prod, including the new cooldown rejection and its localised
  toast. Tests 1-3 now done; Tests 4-8 ahead.

- Cooldown bug (93ab072f) + localised toast (aa8ca594) — points-based
  programs were silently bypassing program.cooldown_minutes because
  points_service.earn_points wrote last_points_at but never read it.
  Mirror the stamp check + raise new PointsCooldownException with
  error_code POINTS_COOLDOWN. Then localise the terminal toast in
  en/fr/de/lb (new cooldown_wait_minutes key) and propagate
  error.details through the apiClient so the catch site can render
  {minutes}.

- Routing investigation (no fix yet, queued for post-walkthrough) —
  user hit a 404 on .../platforms/loyalty/store/fashionhub/dashboard
  on the subdomain. Diagnosed 4 distinct bugs from path-based→
  subdomain/custom-domain drift (Mount 1 broken, server redirect
  store.py:86, JS login.js:155, sidebar URL builder). Ran the full
  middleware suite (185 tests pass) — depth on inbound resolution,
  zero coverage on outbound URL construction; that's why the bugs
  slip through. Scoped a Redirect Trace tool on /admin/platform-debug
  + matching integration tests as the regression net.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 23:44:23 +02:00
f82dce30ca docs(architecture): persona template consolidation pattern + proposal
Document how admin/merchant/store templates share a single shared/ body
partial while keeping their three separate base templates. Covers:

- The wrapper/partial split and why the three base templates must stay
  separate (nav + permissions isolation).
- The scope contract: pass strings + booleans only, no macro objects,
  no `persona` enum.
- The backend mirror: services scope-agnostic, routes inject scope via
  auth deps, same Pydantic shape across personas.
- Legit exceptions and the heuristic for when to keep a template
  standalone (multi-tenant aggregators, persona-unique features).
- Forward reference to the TPL-016 architecture rule.

Adds both docs to mkdocs nav under Architecture and Proposals
sections.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 23:10:29 +02:00
4b64233b5f docs(loyalty): record 2026-05-19 Test 2 complete + subtitle fix
All checks were successful
CI / ruff (push) Successful in 18s
CI / pytest (push) Successful in 2h48m35s
CI / validate (push) Successful in 34s
CI / dependency-scanning (push) Successful in 38s
CI / docs (push) Successful in 1m3s
CI / deploy (push) Successful in 1m52s
End-of-day update. Test 2 (cross-store re-enrollment at FASHIONOUTLET
with the email from Test 1) walked cleanly with all behavioral checks
green:
  - exactly 1 loyalty_cards row, no duplicate
  - zero new email_logs rows (no duplicate welcome email)
  - cross-location locations block lists both stores
  - title already-enrolled branch renders correctly

One copy bug surfaced and was fixed in dee2eab2: the title at
enroll-success.html:21 was already x-text-conditional on
already_enrolled, but the subtitle below was a static
{{ _('success.message') }} so two contradicting messages stacked
("You're already a member!" + "You're now a member..."). Made the
subtitle conditional the same way and added a new
already_enrolled_message i18n key in en/fr/de/lb.

Test 2 marked done. Carries forward Test 3 plus the existing follow-ups
(Hetzner doc check, B1-F unit tests, prospecting tasks/__init__ fix,
other-module email audit).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 22:27:16 +02:00
f68a30a820 docs(loyalty): record 2026-05-18 Test 1 round 2 cleanup + admin polish
All checks were successful
CI / ruff (push) Successful in 19s
CI / pytest (push) Successful in 2h48m6s
CI / validate (push) Successful in 32s
CI / dependency-scanning (push) Successful in 34s
CI / docs (push) Successful in 53s
CI / deploy (push) Successful in 1m16s
End-of-day update. Adds a new section to the go-live readiness doc
covering today's three shipped commits:

- 236fee01 — enrollment-success CTA rename (Continuer mes achats
  -> Retour à l'accueil)
- ab3e133a — flatpickr birthday picker so Firefox honors dd/mm/yyyy on
  FR. Firefox-specific limitation (Mozilla bug #1344625) — Chrome /
  Safari / Edge already respected <html lang="fr"> from earlier fix.
- 5f288502 — admin program form now warns when terms fields are both
  empty and disables save until at least one is filled, so a
  merchant can't accidentally ship a program with a non-clickable
  Conditions Générales link on the storefront.

Marks Test 1 fully done (all 6 originally-reported bugs B1-A..B1-F
plus today's 2 follow-up nits resolved end-to-end on prod). Carries
forward the remaining items from yesterday's queue: Test 2, Hetzner
doc check, unit tests for the B1-F chain, prospecting tasks/__init__
fix, and the other-module email-path audit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 23:42:16 +02:00
54247ca4f0 feat(static-assets): cache-bust JS/CSS via ?v=<commit-sha>, immutable in prod
All checks were successful
CI / ruff (push) Successful in 18s
CI / pytest (push) Successful in 2h50m43s
CI / validate (push) Successful in 33s
CI / dependency-scanning (push) Successful in 33s
CI / docs (push) Successful in 50s
CI / deploy (push) Successful in 1m15s
Adds a `static_v(request, name, path=...)` Jinja helper that appends
?v=<commit-sha> from app.core.build_info, plus a CachedStaticFiles
subclass that serves Cache-Control: public, max-age=31536000, immutable
in production and no-cache in development. Browsers refetch JS/CSS
automatically on every deploy without the user having to hard-reload.

- New: app/core/static_files.py (CachedStaticFiles)
- Updated: app/templates_config.py (static_v helper)
- Updated: main.py (use CachedStaticFiles for *_static mounts)
- Codemod: 143 url_for('*_static', path='*.js'|'*.css') → static_v(...)
  across 123 templates. Images/fonts/JSON locales intentionally
  unchanged (out of scope).
- Arch rule: FE-024 (warning) flags raw url_for on JS/CSS to prevent
  drift. Note: FE-008 was already taken by the number_stepper rule.
- docs/proposals/static-asset-cache-busting.md marked Done.

Closes plan from docs/proposals/static-asset-cache-busting.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 19:35:59 +02:00
7cf2420bba 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>
2026-05-17 23:08:27 +02:00
e680fda8bd docs(proposals): static asset cache-busting plan
Some checks failed
CI / ruff (push) Successful in 16s
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has started running
Proposes a one-liner Jinja helper that appends ?v=<commit-sha> to
static JS/CSS URLs, leveraging the existing build_info pipeline so
users no longer need to hard-reload after every deploy. Documents the
codemod scope (143 callsites), open decisions, and the server-side
Cache-Control: immutable follow-up that makes the version flip pay off.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 22:35:24 +02:00
1425b48239 docs(loyalty): record session pause + next-session resume sequence
All checks were successful
CI / ruff (push) Successful in 17s
CI / pytest (push) Successful in 2h41m22s
CI / validate (push) Successful in 32s
CI / dependency-scanning (push) Successful in 36s
CI / docs (push) Successful in 58s
CI / deploy (push) Successful in 1m12s
Add a short "Next session" subsection to the 2026-05-16 update
spelling out the three steps to pick up Test 1 round 2 (SMTP
re-apply post-reset, program sanity check, live log tail + fresh
enrollment).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 23:50:23 +02:00
eb9afd3cdd docs: loyalty go-live update + Hetzner reset fix + sweep nav
Some checks failed
CI / ruff (push) Successful in 20s
CI / docs (push) Has been cancelled
CI / pytest (push) Has been cancelled
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / deploy (push) Has been cancelled
- docs/proposals/loyalty-go-live-readiness.md — record the
  2026-05-16 session: 7 bugs found during Test 1 round 1
  (TimestampMixin, CardDetailResponse, storefront i18n triple,
  Makefile, meta_keywords), 6 fixed and deployed with commit
  hashes, B1-F still pending repro on the clean DB.

- docs/deployment/hetzner-server-setup.md — fix section 12
  step 8 to call seed_email_templates_core.py +
  seed_email_templates_loyalty.py instead of the non-existent
  seed_email_templates.py, and add seed_demo.py at the end.
  Append a note about post-reset state (.build-info + admin
  settings).

- mkdocs.yml — sweep 14 previously-unlinked proposals and 2
  module docs (loyalty/production-readiness.md,
  prospecting/batch-scanning.md) into the nav so the strict
  build no longer warns.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 23:23:50 +02:00
29b2170448 docs(onboarding): merchant intake checklist (EN + FR)
All checks were successful
CI / ruff (push) Successful in 17s
CI / pytest (push) Successful in 2h39m52s
CI / validate (push) Successful in 31s
CI / dependency-scanning (push) Successful in 35s
CI / docs (push) Successful in 52s
CI / deploy (push) Successful in 1m43s
Practical field-by-field intake doc for the first merchants — maps
each question to its DB column so the call → admin UI is 1:1.
Includes a section on marketing-material reuse aimed at franchisees,
with written-authorization clauses for the future marketing module.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 23:08:35 +02:00
a3fb7029bd docs(loyalty): add Android terminal E2E tests to user-journeys checklist
All checks were successful
CI / ruff (push) Successful in 14s
CI / pytest (push) Successful in 2h25m39s
CI / validate (push) Successful in 31s
CI / dependency-scanning (push) Successful in 32s
CI / docs (push) Successful in 50s
CI / deploy (push) Successful in 1m11s
The web user-journey checklist (Tests 1–8) only covers human-using-loyalty
flows from a browser. The cashier-facing Android tablet built in Phases
A–F goes through a different surface and has its own failure modes that
won't surface in any web test. Adding 6 dedicated Android tests so a
tablet-in-hand verification has the same level of structure as the web
side.

- Test 9: Tablet pairing — QR scan + manual entry fallback, with the
  audit (paired-device row appears, last_seen_at populated)
- Test 10: PIN screen — wrong/right PIN, offline-capable bcrypt verify,
  locked-PIN rejection
- Test 11: Daily flows — search, scan, enroll, stamp, earn, redeem,
  with the acting_terminal_device_id audit column check at the end
- Test 12: Offline queue + sync — airplane mode → queued → re-online →
  drain; redeem is hard-disabled offline per spec
- Test 13: Auto-lock + manual lock — 2 min idle, immediate lock button,
  the known caveat that AlertDialog pointer events don't bubble
- Test 14: Device revocation — revoke on web → 401 on tablet next call

Updated the go-live readiness snapshot to reference these as Step 6b
(gated on user obtaining a tablet, not on schedule).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 22:05:00 +02:00
d3b1670623 docs(loyalty): go-live readiness snapshot — 2026-05-10
Some checks failed
CI / ruff (push) Successful in 16s
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has been cancelled
Captures where the loyalty pre-launch checklist actually stands after
tonight's prod readiness pass:

  - Step 1 (email templates seeded) 
  - Step 2 (Google Wallet config)  validated via wallet-debug
  - Step 3 (migrations)  all module heads incl. loyalty_011 on prod
  - Step 7 (Wallet real-device test) 
  - Steps 4, 5 (FR/DE/LB analytics keys, store-owner template
    permission) deferred — cosmetic / non-blocking
  - Step 6 (8 user-journey E2E tests) is the remaining human gate
  - Step 9 (Google Wallet production access) post-launch

Also records the SMTP path-change diagnosis (own mail server on port
465 blocked outbound from Hetzner; switched to 587 STARTTLS via
/admin/settings DB overrides) and the cosmetic fix shipped in
f2d1bdcd so the test email reports the *effective* config.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 22:00:43 +02:00
b27d4ba6ff docs: add Android terminal implementation plan
Some checks failed
CI / ruff (push) Successful in 15s
CI / pytest (push) Failing after 2h21m3s
CI / validate (push) Successful in 29s
CI / dependency-scanning (push) Successful in 32s
CI / docs (push) Has been skipped
CI / deploy (push) Has been skipped
Full implementation plan for the RewardFlow Terminal Android app:
4 screens (Setup, PIN, Terminal, Enrollment), 6 phases (~5 days),
ASCII wireframes, data layer design, offline queue strategy,
multi-language support, and API model changes needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 22:52:00 +02:00
cd4f83f2cb docs: add proposal for transaction categories (what was sold)
Some checks failed
CI / deploy (push) Has been skipped
CI / ruff (push) Successful in 20s
CI / pytest (push) Failing after 2h34m11s
CI / validate (push) Successful in 35s
CI / dependency-scanning (push) Successful in 44s
CI / docs (push) Has been skipped
Client requirement: sellers must select a product category (e.g.,
Men, Women, Accessories, Kids) when entering loyalty transactions.
Categories are per-store, configured via admin/merchant CRUD.

Proposal covers: data model (StoreTransactionCategory + FK on
transactions), CRUD API for admin + store, web terminal UI, Android
terminal integration, and analytics extension path.

Priority: urgent for production launch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 00:29:41 +02:00
e759282116 refactor: rename apps/ to clients/ + update architecture docs
Some checks failed
CI / ruff (push) Successful in 20s
CI / pytest (push) Failing after 2h37m33s
CI / validate (push) Successful in 41s
CI / dependency-scanning (push) Successful in 42s
CI / docs (push) Has been skipped
CI / deploy (push) Has been skipped
Rename apps/ → clients/ for clarity:
- app/ (singular) = Python backend (FastAPI, server-rendered web UI)
- clients/ (plural) = standalone client applications (API consumers)

The web storefront/store/admin stays in app/ because it's server-
rendered Jinja2, not a standalone frontend. clients/ is for native
apps that connect to the API externally.

Updated:
- docs/architecture/overview.md — added clients/ to project structure
- clients/terminal-android/SETUP.md — updated path references

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 18:09:24 +02:00
51a2114e02 refactor(cms): migrate store theme UI from tenancy to CMS module
Move store theme admin pages, templates, and JS from tenancy module
to CMS module where the data layer (model, service, API, schemas)
already lives. Eliminates split ownership.

Moved:
- Route handlers: GET /store-themes, GET /stores/{code}/theme
- Templates: store-theme.html, store-themes.html
- JS: store-theme.js, store-themes.js
- Updated static references: tenancy_static → cms_static

Deleted old tenancy files (no remaining references).
Menu item in CMS definition already pointed to correct route.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 10:30:09 +02:00
b5bb9415f6 feat(cms): Phase A — page type selector, translation UI, SEO cleanup
Some checks failed
CI / ruff (push) Successful in 16s
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has been cancelled
Content page editor improvements:
- Page type selector: Content Page / Landing Page dropdown (sets template)
- Title language tabs: translate page titles per language (same pattern as sections)
- Content language tabs: translate page content per language
- Meta description language tabs: translatable SEO descriptions
- Template-driven section palette: template defines which sections are available
  (store landing pages hide Pricing, platform homepages show all)
- Hide content editor when Landing Page selected, hide sections when Content Page

Schema changes (migration cms_003):
- Add meta_description_translations column (JSON) to content_pages
- Drop meta_keywords column (obsolete, ignored by all search engines since 2009)
- Remove meta keywords tag from storefront and platform base templates

API + service updates:
- title_translations, content_translations, meta_description_translations
  added to create/update schemas, route handlers, and service methods

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 22:30:55 +02:00
3044490a3e feat(storefront): section-based homepages, header action partials, fixes
Phase 1 — Section-based store homepages:
- Store defaults use template="full" with per-platform sections JSON
- OMS: shop hero + features + CTA; Loyalty: rewards hero + features + CTA
- Hosting: services hero + features + CTA
- Deep placeholder resolution for {{store_name}} inside sections JSON
- landing-full.html uses resolved page_sections from context

Phase 2 — Module-contributed header actions:
- header_template field on MenuItemDefinition + DiscoveredMenuItem
- Catalog provides header-search.html partial
- Cart provides header-cart.html partial with badge
- Base template iterates storefront_nav.actions with {% include %}
- Generic icon fallback for actions without a template

Fixes:
- Store theme API: get_store_by_code → get_store_by_code_or_subdomain

Docs:
- CMS redesign proposal: menu restructure, page types, translations UI

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 23:33:06 +02:00
adc36246b8 feat(storefront): homepage, module gating, widget protocol, i18n fixes
Some checks failed
CI / ruff (push) Successful in 14s
CI / pytest (push) Failing after 2h32m45s
CI / validate (push) Successful in 30s
CI / dependency-scanning (push) Successful in 31s
CI / docs (push) Has been skipped
CI / deploy (push) Has been skipped
Storefront homepage & module gating:
- CMS owns storefront GET / (slug="home" with 3-tier resolution)
- Catalog loses GET / (keeps /products only)
- Store root redirect (GET / → /store/dashboard or /store/login)
- Route gating: non-core modules return 404 when disabled for platform
- Seed store default homepages per platform

Widget protocol for customer dashboard:
- StorefrontDashboardCard contract in widgets.py
- Widget aggregator get_storefront_dashboard_cards()
- Orders and Loyalty module widget providers
- Dashboard template renders contributed cards (no module names)

Landing template module-agnostic:
- CTAs driven by storefront_nav (not hardcoded module names)
- Header actions check nav item IDs (not enabled_modules)
- Remove hardcoded "Add Product" sidebar button
- Remove all enabled_modules checks from storefront templates

i18n fixes:
- Title placeholder resolution ({{store_name}}) for store default pages
- Storefront nav label_keys prefixed with module code
- Add storefront.account.* keys to 6 modules (en/fr/de/lb)
- Header/footer CMS pages use get_translated_title(current_language)
- Footer labels use i18n keys instead of hardcoded English

Icon cleanup:
- Standardize on map-pin (remove location-marker alias)
- Replace all location-marker references across templates and docs

Docs:
- Storefront builder vision proposal (6 phases)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 22:53:17 +02:00
27ac7f3e28 docs: add nav fix to POC content mapping proposal
Some checks failed
CI / ruff (push) Successful in 15s
CI / pytest (push) Failing after 2h40m46s
CI / validate (push) Successful in 32s
CI / dependency-scanning (push) Successful in 37s
CI / docs (push) Has been skipped
CI / deploy (push) Has been skipped
E-commerce nav (Products, Cart, Account) shows on hosting POC sites.
Preview mode should render only CMS pages (Services, Projects, Contact)
in the nav, not module-defined e-commerce items.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 21:32:15 +02:00
dfd42c1b10 docs: add proposal for POC content mapping (scraped → template)
Some checks failed
CI / ruff (push) Successful in 16s
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / pytest (push) Has been cancelled
Details the gap between scraped content (21 paragraphs, 30 headings,
13 images) and what appears on POC pages (only placeholder fields).

Phase 1 plan: programmatic mapping of scraped headings/paragraphs/
images into template sections (hero subtitle, gallery, about text).
Phase 2: AI-powered content enhancement (deferred, provider TBD).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 21:14:17 +02:00
dd09bcaeec docs: add proposal for HostedSite → Store cascade delete
All checks were successful
CI / ruff (push) Successful in 33s
CI / pytest (push) Successful in 2h46m24s
CI / validate (push) Successful in 30s
CI / dependency-scanning (push) Successful in 31s
CI / docs (push) Successful in 49s
CI / deploy (push) Successful in 2m55s
Deleting a HostedSite leaves the Store orphaned, blocking subdomain
reuse. Proposal: cascade delete the Store when deleting the site.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 23:31:31 +02:00
2bc03ed97c docs: add end-to-end plan from prospecting to live site
Master plan covering 4 workstreams:
1. Fix hosting foundation (merchant/prospect required)
2. Security audit pipeline + report + live demo
3. POC builder with industry templates (restaurant, construction,
   auto-parts, professional-services, generic)
4. AI content enhancement (deferred, provider TBD)

Target: 10-step journey from prospect discovery to live website.
Steps 1-3 work today, steps 4-10 need the work described.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 23:05:21 +02:00
91963f3b87 docs: architecture decision — hosting sites reuse CMS + Store + StoreDomain
Hosted sites leverage existing CMS module (ContentPage, StoreTheme,
MediaFile) instead of building a separate site rendering system. Industry
templates (restaurant, construction, auto-parts, professional-services,
generic) are JSON presets that populate CMS entities for a new Store.

POC phase uses subdomain routing (acme.hostwizard.lu), go-live adds
custom domain via StoreDomain (acme.lu). All routing handled by existing
StoreContextMiddleware + Caddy wildcards.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 22:42:10 +02:00
3ae0b579d3 docs: add security audit + demo + POC builder proposal
4-phase plan for integrating scripts/security-audit/ into the
prospecting module: security audit pipeline, report generation,
live demo server, and POC site builder architecture.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 22:27:59 +02:00
f310363f7c fix(prospecting): fix scan-jobs batch endpoints and add job tracking
- Reorder routes: batch endpoints before /{prospect_id} to fix FastAPI
  route matching (was parsing "batch" as prospect_id → 422)
- Add scan job tracking via stats_service.create_job/complete_job so
  the scan-jobs table gets populated after each batch run
- Add contact scrape batch endpoint (POST /contacts/batch) with
  get_pending_contact_scrape query
- Fix scan-jobs.js: explicit route map instead of naive replace
- Normalize domain_name on create/update (strip protocol, www, slash)
- Add domain_name to ProspectUpdate schema
- Add proposal for contact scraper enum + regex fixes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 23:31:33 +02:00
8a70259445 fix(tenancy): use absolute URL in team invitation email link
Some checks failed
CI / ruff (push) Successful in 15s
CI / pytest (push) Has been cancelled
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
Email clients need absolute URLs to make links clickable. The
acceptance_link was a relative path (/store/invitation/accept?token=...)
which rendered as plain text. Now prepends the platform domain with
the correct protocol.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 17:39:46 +02:00
c6b155520c docs: add security hardening plan from 360 audit
18 prioritized findings (6 HIGH, 6 MEDIUM, 6 LOW) filtered against
what is already deployed on Hetzner. Covers app-layer issues like
login rate limiting, GraphQL injection, SSRF, GDPR/Sentry PII,
dependency pinning, and CSP headers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 21:17:48 +01:00
9bceeaac9c feat(arch): implement soft delete for business-critical models
Adds SoftDeleteMixin (deleted_at + deleted_by_id) with automatic query
filtering via do_orm_execute event. Soft-deleted records are invisible
by default; bypass with execution_options={"include_deleted": True}.

Models: User, Merchant, Store, StoreUser, Customer, Order, Product,
LoyaltyProgram, LoyaltyCard.

Infrastructure:
- SoftDeleteMixin in models/database/base.py
- Auto query filter registered on SessionLocal and test sessions
- soft_delete(), restore(), soft_delete_cascade() in app/core/soft_delete.py
- Alembic migration adding columns to 9 tables
- Partial unique indexes on users.email/username, stores.store_code/subdomain

Service changes:
- admin_service: delete_user, delete_store → soft_delete/soft_delete_cascade
- merchant_service: delete_merchant → soft_delete_cascade (stores→children)
- store_team_service: remove_team_member → soft_delete (fixes is_active bug)
- product_service: delete_product → soft_delete
- program_service: delete_program → soft_delete_cascade

Admin API:
- include_deleted/only_deleted query params on admin list endpoints
- PUT restore endpoints for users, merchants, stores

Tests: 9 unit tests for soft-delete infrastructure.
Docs: docs/backend/soft-delete.md + follow-up proposals.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 21:08:07 +01:00
661547f6cf docs: update deployment docs for CI timeouts, build info, and prod safety
- hetzner-server-setup: runner timeout 3h, shutdown_timeout 300s,
  deploy.sh now writes .build-info and uses explicit -f flag
- gitea: document unit-only CI tests and xdist incompatibility
- docker: add build info section, document volume mount approach

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 14:00:35 +01:00
644bf158cd chore: dev/prod Docker compose separation with safety docs
Some checks failed
CI / ruff (push) Successful in 15s
CI / validate (push) Successful in 29s
CI / dependency-scanning (push) Successful in 32s
CI / pytest (push) Failing after 1h10m52s
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
- Add docker-compose.override.yml exposing db/redis ports for local dev
- Remove override from .gitignore so all devs get port mappings
- Use explicit -f in deploy.sh to skip override in production
- Document production safety rule: always use -f on the server

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 16:16:29 +01:00
40da2d6b11 feat: add build info (commit SHA + deploy timestamp) to health endpoint and admin sidebar
Some checks failed
CI / ruff (push) Successful in 45s
CI / validate (push) Successful in 29s
CI / dependency-scanning (push) Successful in 32s
CI / pytest (push) Failing after 1h11m44s
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
- deploy.sh writes .build-info with commit SHA and timestamp after git pull
- /health endpoint now returns version, commit, and deployed_at fields
- Admin sidebar footer shows version and commit SHA
- Hetzner docs updated: runner --config flag, swap, and runner timeout

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 22:35:01 +01:00
7d652716bb feat(loyalty): production readiness round 2 — 12 security, integrity & correctness fixes
Some checks failed
CI / ruff (push) Successful in 12s
CI / validate (push) Successful in 27s
CI / dependency-scanning (push) Successful in 31s
CI / pytest (push) Failing after 3h14m58s
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
Security:
- Fix TOCTOU race conditions: move balance/limit checks after row lock in redeem_points, add_stamp, redeem_stamps
- Add PIN ownership verification to update/delete/unlock store routes
- Gate adjust_points endpoint to merchant_owner role only

Data integrity:
- Track total_points_voided in void_points
- Add order_reference idempotency guard in earn_points

Correctness:
- Fix LoyaltyProgramAlreadyExistsException to use merchant_id parameter
- Add StorefrontProgramResponse excluding wallet IDs from public API
- Add bounds (±100000) to PointsAdjustRequest.points_delta

Audit & config:
- Add CARD_REACTIVATED transaction type with audit record
- Improve admin audit logging with actor identity and old values
- Use merchant-specific PIN lockout settings with global fallback
- Guard MerchantLoyaltySettings creation with get_or_create pattern

Tests: 27 new tests (265 total) covering all 12 items — unit and integration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 23:37:23 +01:00
540205402f feat(middleware): harden routing with fail-closed policy, custom subdomain management, and perf fixes
Some checks failed
CI / pytest (push) Waiting to run
CI / ruff (push) Successful in 12s
CI / validate (push) Successful in 26s
CI / dependency-scanning (push) Successful in 31s
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
- Fix IPv6 host parsing with _strip_port() utility
- Remove dangerous StorePlatform→Store.subdomain silent fallback
- Close storefront gate bypass when frontend_type is None
- Add custom subdomain management UI and API for stores
- Add domain health diagnostic tool
- Convert db.add() in loops to db.add_all() (24 PERF-006 fixes)
- Add tests for all new functionality (18 subdomain service tests)
- Add .github templates for validator compliance

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 18:13:01 +01:00
adec17cd02 docs(deployment): add future scaling section for 50+ custom domains
Document two strategies for scaling beyond manual Caddyfile management:
- Caddy on-demand TLS (simple, no Cloudflare protection)
- Cloudflare for SaaS / Custom Hostnames (recommended, full protection)
- Infrastructure scaling notes for 1,000+ sites

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 19:41:35 +01:00
183f55c7b3 docs(deployment): add runbooks for store subdomains, custom domains, and new platforms
- Update origin cert config: wildcards for omsflow.lu, rewardflow.lu, hostwizard.lu
- Add wildcard Caddy blocks to production Caddyfile example
- Replace "Future" section with actionable runbooks:
  - Add a Store Subdomain (self-service, no infra changes)
  - Add a Custom Store Domain (Cloudflare + Caddy + DB)
  - Add a New Platform Domain (full setup)
- Document wizard.lu exception (no wildcard due to git.wizard.lu DNS-only)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 12:31:56 +01:00
c2c0e3c740 refactor: rename platform_domain → main_domain to avoid confusion with platform.domain
Some checks failed
CI / ruff (push) Successful in 10s
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has started running
The setting `settings.platform_domain` (the global/main domain like "wizard.lu")
was easily confused with `platform.domain` (per-platform domain like "rewardflow.lu").
Renamed to `settings.main_domain` / `MAIN_DOMAIN` env var across the entire codebase.

Also updated docs to reflect the refactored store detection logic with
`is_platform_domain` / `is_subdomain_of_platform` guards.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 04:45:28 +01:00