Public social signup into OAUTH_DEFAULT_ORG (role user, seat-checked); email-match links existing active user instead of duplicating. Server-side provider token validation via stdlib urllib only (no new dep): Google tokeninfo (aud + email_verified) and Facebook app/debug-token/me (is_valid, app_id, me.id==user_id). Fail-closed when creds unconfigured, rate-limited per-IP + per-email, /oauth/config leaks no secrets. Frontend: login buttons (only enabled providers), GSI + FB SDK on-demand, monochrome glyphs, TH/EN. Login page shows social buttons only when backend reports provider enabled. 348 backend tests pass (337 + 11 new OAuth), frontend build + 4/4 unit clean, manual security review PASS. Not pushed (push auto-deploys).
20 KiB
HANDOFF — Sales Trainer
Another AI should be able to resume without chat history.
Branch / repo / deploy
- Repo:
~/Gitea/Sales Trainer/— git repo, remote = Gitea. - Remote:
https://git.moreminimore.com/kunthawat/sales-trainer.git(GITEA_TOKEN via credential helper; never committed). - Live deploy:
https://moreminimoreapps-saletrainer.ahkhwd.easypanel.host— EasyPanel, auto-redeploys from Gitea on push tomainvia webhook (≈3 min). Dockerfile builds the frontend in a Node builder stage and serves it from the Python runtime image. LLM vars set in EasyPanel env.
What this is
Corporate multi-user sales-training simulator: admins create persona groups from a product/service/idea (โฟกัส "สินค้า/บริการ/ไอเดีย"), app auto-generates 15 customer personas (5/tier A/B/C) via LLM; trainees pick a scenario (social / พบหน้า), chat 1:1 one-shot to close a sale; a per-turn + final judge LLM evaluates feelings and scores/coaches. Vue SPA + Flask API + filesystem JSON storage (no SQL). i18n TH/EN. No self-registration (admin provisions).
Roles
- super_admin (bootstrap
admin) — full recipe (secret persona fields) + tenant admin. - admin — manages groups/users, sees personas with secret fields stripped (IP protection).
- user (trainee) — trains against personas, own board.
Current state — local code/security gate passed; production-operation gate pending
2026-08-20: OAuth login/register (Google + Facebook) added. Public social signup into a single default org (
OAUTH_DEFAULT_ORG, role user, seat-checked); email-match links existing users. Server-side token validation via stdlib urllib (no new dep; Google tokeninfo + Facebook app/debug-token/me), fail-closed when creds absent (login page shows no social buttons), rate-limited per-IP + per-email. 348 backend tests pass (11 new), frontend build clean, manual security review PASS. Committed locally (seedocs/engineering-log/2026-08-20-oauth-google-facebook.md). Also: thewebsite/marketing site was moved out of this repo into its own project~/Gitea/Sales Trainer Website/(own git repo,7e2b74d, not pushed) — this repo is now app-only (commit74de0d4, not yet pushed). 2026-08-19: app UX/UI redesign (8 files, 100% presentational: global design-token system rebuild instyle.css+ polish of App/Login/Chat/MyBoard/Personas/Setup/Training) AND a new self-contained marketing landing site (website/index.html+main.css, TH-primary with EN toggle, CSS chat mockup) were built in parallel subagents. Frontendvite buildclean, vitest 4/4, independent reviewer PASS (5/5 checks), marketing site verified responsive at 320–768 (no horizontal scroll). PUSHED + LIVE (verified 2026-08-19) — the redesign is deployed to EasyPanel production (backend booted, new bundle + new Login design confirmed live). Thewebsite/landing site is in the repo but NOT yet deployed as a public site (serve fromwebsite/; set real pricing before public launch). Seedocs/engineering-log/2026-08-19-ux-redesign-and-marketing-site.md. 2026-08-18: live-QA UX fixes landed (committed locally, not yet pushed): persona replies can no longer leak raw JSON into the chat bubble; all persona openers are natural greetings (wrong_text cools off only after the seller's first reply); and the chat now auto-closes + auto-summarizes the moment the customer decides (buy / walk / try-to-trial-first), with the manual "สรุปผล"/Finish button removed from the UI. Full backend suite 336 passed (baseline 330 → +6 new tests), frontend build + unit tests clean, independent reviewer passed. Seedocs/engineering-log/2026-08-18-live-qa-ux-fixes.md. Blocker unchanged: production operation (runtime cutover, real-provider QA, Redis, audit apply) still requires operator approval. 2026-08-16: the S4.4 JSON-importer + error-handler hardening increment (previously staged on top ofdbfce9a) was re-verified from a cleanrequirements.lock.txtvenv (330 backend tests, compileall + frontend build clean) and committed + pushed. Seedocs/engineering-log/2026-08-16-s4-4-importer-errorhandler-commit.md. Blocker is unchanged: production operation (runtime cutover, real-provider QA, Redis, audit apply) still requires operator approval.
The current uncommitted remediation is verified on isolated temporary data: 319 backend tests passed from a clean requirements.lock.txt environment, including 166 focused auth/isolation/export/upload regressions; 4 frontend unit tests and 12 Playwright fixture journeys passed across desktop, 320×568, and 500×768; the production frontend build completed with 1,781 modules and npm audit found 0 vulnerabilities. Compile, AST, diff, dependency, and added-line security checks passed. The checked-in lock is reproducible. The existing local backend/.venv has version drift and pip check reports the pre-existing alibabacloud-tea-openapi 0.4.4 versus cryptography 50.0.0 conflict; uv pip sync --dry-run was inspected but not applied. The final fresh exact-current scoped review returned clean five-key verdicts for auth/storage/rate-limit, tenant/group/session isolation, and analytics/export/parser/upload boundaries. JSON stores remain runtime-authoritative; no production operation has been performed.
Newly verified local PostgreSQL evidence — 2026-08-15
- A temporary local PostgreSQL database completed
alembic upgrade head, the 7-table tenant/uniqueness runtime probe, ORM-vs-migration parity, offline PostgreSQL DDL checks, the JSON importer dry-run/apply/idempotency/conflict-rollback probe, andalembic downgrade base; zero application tables remained and all temporary databases/fixtures/backups were cleaned up. - This closes the local PostgreSQL schema/runtime and fixture-importer probes only. Real JSON snapshot parity/rollback, runtime repository cutover, Redis persistence, Docker image/runtime smoke, real-provider QA, authenticated production smoke, and production approval remain open.
Do not restore public/untrusted access. An async reviewer packet created before the latest auth-lock state reported a password auth_version race; the current tree now serializes change_password() and update_user_fields(password=...) through the same per-user record lock, and the deterministic cross-path regression passes. A surgical independent review of this auth path returned valid passed=true with empty blocking arrays. Legacy-security reviewers deleg_ffba9adf, deleg_03d06f1f, and strict retry deleg_0cc095f8 all timed out after 600 seconds without JSON and are no verdicts. Final one-call retry deleg_e61f99ff returned complete five-key JSON with passed=false because the extraction output exceeded the capture window; it is a limitation/no-approval verdict. The latest full-scope reviewer deleg_93ef64c5 timed out after 600 seconds without a complete five-key JSON verdict; it is no approval. The replacement batch deleg_df17f04e was stopped because it began before the final service-boundary hardening and is not approval. Fresh exact-current review batches deleg_e835e807 and deleg_4ec7eb5a completed for auth/storage/rate-limit, tenant/group/ownership/session isolation, and analytics/export/parser/upload boundaries. All three scoped verdicts returned complete five-key JSON with passed=true, security_concerns=[], and logic_errors=[]. S4.3 reviewer deleg_c2e728d8 returned valid passed=false after finding the offline Alembic dialect bypass; the shared validator and regression test fixed it, and fresh post-remediation reviewer deleg_40e8edf9 returned valid passed=true with empty blocking arrays. Combined with the earlier dependency/schema review deleg_e672880a, the S4.2/S4.3 code-schema review scope is closed. Only complete five-key verdicts with passed=true, security_concerns=[], and logic_errors=[] close review gates. Docker is unavailable locally. Local fixture-based browser/mobile E2E passes, but real-provider QA, production-authenticated browser QA, and production operations are not run. The restricted deployment checklist (bootstrap credential change, JWT secret rotation, audit inspection, fresh authenticated smoke) remains pending and requires explicit operator approval.
cd backend
uv run python scripts/test_m0.py # auth/roles/no-self-reg
uv run python scripts/test_m1.py # group create + role visibility
uv run python scripts/test_setup.py # first-time admin setup (email+password+ToS)
uv run python scripts/test_security.py # path traversal / IDOR / XSS
uv run python scripts/test_scenario.py # 2 scenarios (social/f2f_call), recontact default
uv run python scripts/test_e2e.py # full flow -> won via judge
uv run python scripts/test_ip_protection.py # secret fields hidden from admin
uv run python scripts/test_saas_tenant.py # tenant isolation + rate-limit + audit
uv run python scripts/test_user_journey.py # idea-flow end-to-end
uv run python scripts/test_variant.py # clone-persona-from-persona
uv run python scripts/test_resume_decision.py # resume + per-turn LLM decision
# run backend (serves frontend/dist only when a local build exists; Docker builds it reproducibly)
cd backend && uv run python run.py # Flask :5001
Key behaviors (implemented)
- One-shot: 1 persona = 1 chat per user; result final (won/lost).
SessionStoreenforces. - Resume: unfinished session resumes on re-entry — no scenario re-pick (same session+scenario).
- Win/loss = per-turn LLM judge (
Simulator.evaluate_turn): every customer reply is evaluated →{mood, decision(buy|walk|try|pending), score_delta, reason}; session auto-ends (close + summary) when decision = buy/walk/try — no manual "สรุปผล" button (ontrya system note says the customer will trial first and come back). Not fixed keywords. - 2 scenarios only:
social,f2f_call. Unknown → social. - Recontact = persona trait (not a scenario): chats normally, then at turn ≥ 2 a time-lapse system note ("⏳ ผ่านไป 2-3 สัปดาห์…"), then re-engages warmer.
- Persona variant:
POST /api/groups/<gid>/personas/<pid>/variant— new persona (new id) that locks pains/objections/levers/tolerance/special/recontact/goal/budget/difficulty/tier/product but varies identity (name/profession/age/location/background/personality/opener). Lets a trainee re-practice the same challenge (one-shot is per-persona). Trainee-created variants go to the trainee's owner-private group; admin-created variants extend the shared admin pool. UI button on finished personas routes trainees to the private group. - Auto 15 personas on create; no "เพิ่มเติม" button (TARGET=15, retry up to 3× + accept ≥ 8 so real LLM under-count doesn't 500).
- IP protection:
adminpersona responses use an explicit allowlist and omit secret/process fields (pains, objections, negotiation_levers, opener, tolerance, rootCause, resolutionConditions) plus any future unapproved fields; full canonical data is only forsuper_admin. - SaaS Phase 1–3 done: tenant isolation (
g.org_id+assert_tenant), login/chat rate-limit, audit log (data/audit/audit.jsonl), org plan/seats/active model +PATCH /api/admin/orgs/<id>, ToS consent on setup, org-scoped signed expiring CSV export (5-min HMAC). - Reduce raw JSON in UI:
list_groupsreturns lightweight summary (persona_count, no full array); persona detail rendered as readable form/cards (pain = line-by-line, not[object Object]).
Credentials / data (testing)
- Production bootstrap super-admin is
admin; its initial password must come fromBOOTSTRAP_ADMIN_PASSWORDand is never printed or hard-coded. First login forces email + new password + ToS consent. - Do not record or repeat live credentials in this handoff. Existing live test accounts/data require operator review after restricted deployment and secret rotation.
- LLM key is a placeholder on local
.env(replace_me). Real analyze/chat needs a realLLM_API_KEY(+LLM_PROVIDER/LLM_MODEL/LLM_BASE_URL) in EasyPanel env then redeploy.
Environment / gotchas
- Python 3.11 only (system default 3.14 incompatible). Use
backend/.venvorcd backend && uv run python …. - Tooling guard crash: commands whose first token is
./.venv/bin/pythontrip a lifecycle guard → always useuv run python. PrefixPYTHONPATH=when needed. - Frontend build:
cd frontend && npm run build(works even with allowScripts restrictions).frontend/dist/is generated and ignored; Docker builds it in the image, and CI builds to a temporary output directory so clean clones never depend on stale hashed bundles. - Deploy pattern: commit + push → webhook auto-deploys in ~3 min. Cannot run Docker locally
(no Docker on this Mac) → test with nginx/
python http.serveroruv run python run.py. - No remote push without asking unless it's the established auto-deploy cadence.
search_files/read_filesometimes misdetect files as binary (e.g.users.py, some.vue); useterminal+sed -n 'N,Mp' <file>orawkfor those.
Wire-authorized / known-limits
- Chrome/Vivaldi drivable via computer-use; current model (DeepSeek V4 Flash) reads screenshots
imperfectly, and window-edge resize to a 320px viewport didn't land — mobile visual QA at
320/500px still needs the user's eyes (open live URL on phone, or DevTools device toolbar).
Responsive CSS (640px, single-column,
flex-wrap,.btn-back) is present + deployed.
Next actions / backlog (also docs/FUTURE_WORK.md)
- 2026-08-20 pending pushes (operator-approved): two local commits on
mainnot yet pushed: (1)74de0d4—website/removed from repo (app-only now; marketing site lives in its own repo~/Gitea/Sales Trainer Website, not pushed). (2) OAuth Google+FB feature + docs. Each push tomainauto-deploys to EasyPanel. To ENABLE OAuth in production, set in EasyPanel env:OAUTH_GOOGLE_CLIENT_ID+OAUTH_GOOGLE_CLIENT_SECRET(orOAUTH_FACEBOOK_APP_ID+OAUTH_FACEBOOK_APP_SECRET) andOAUTH_DEFAULT_ORG(a tenant id). Until creds are set, OAuth is disabled (fail-closed; no social buttons shown). - 2026-08-19 UX + marketing-site push (operator-approved): the app UX/UI redesign (8 files)
and new
website/landing site are uncommitted onmain. Commit + push ships the redesign to the live EasyPanel app AND adds the marketing site to the repo. Confirm productionJWT_SECRETis set (≥32 chars) in EasyPanel env before/with the push (backend fails closed without it). Then re-verify the live bundle. Optionally deploy thewebsite/landing page as a standalone static site (e.g. nginx / EasyPanel static or another host) — serve fromwebsite/index.html; set real pricing before public launch. - Sprint 1 live-operation gate: deploy behind restricted access, set/verify
BOOTSTRAP_ADMIN_PASSWORD, rotateJWT_SECRETafter the patch, inspect audit data, and run a fresh authenticated smoke. Do not open public access before this checklist. - Sprint 2: implementation and local regression gate complete; retain current tests as the contract.
- P1: run real-provider QA and browser/mobile E2E; local contract tests use deterministic fakes.
- P1: verify final-judge coaching quality with representative provider outputs; correctness and idempotency are locally covered.
- P1: remove any stale admin chat entry points in live UI after restricted authenticated smoke.
- P1/P2 ops: add PostgreSQL repository adapters, browser E2E, and real-provider QA before production rollout. Upload limits, cleanup, fail-closed JWT/bootstrap configuration, Gunicorn, Docker healthcheck, and
.dockerignoreare implemented and tested locally. - S4.2: schema reviewer findings (nullable audit actor link, CWD-relative Alembic paths, ORM/migration defaults drift, unsupported partial-index dialect, and offline batch rendering) plus dependency reproducibility were remediated locally; combined exact-current evidence from
deleg_e672880aand post-remediationdeleg_40e8edf9closes the code/schema/dependency review scope. Temporary-local PostgreSQL execution, ORM/migration parity, offline DDL, and schema rollback now pass; Docker build, importer data parity/rollback, repository cutover, and production-safe rate-limit/audit storage remain blocked. - S4.3: org/users, groups/personas, and sessions/messages repository contracts and SQLAlchemy adapters are local-only; cross-tenant user lookup and offline-dialect remediations are locally verified and independently approved by
deleg_40e8edf9. PostgreSQL schema parity passes on temporary local databases, while runtime repository cutover remains blocked. - S4.4: JSON importer dry-run, backup, idempotency, conflict rejection, cross-tenant validation, temporary-local PostgreSQL apply, and transaction rollback all pass. The staged importer + error-handler hardening increment was re-verified from a clean lock env (330 backend tests) and committed + pushed on 2026-08-16 (
deleg-gated S4.4 importer already independently reviewed in the prior packet). Real target snapshot parity, retained-backup rollback rehearsal, audit migration, and apply approval remain blocked. - 2026-08-15 legacy-security remediation: the latest valid reviewer finding about legacy
ratelimit.jsonmigration was remediated with fail-closed marker/digest validation, structured keys, duplicate-preserving import, and idempotent import metadata. Current local evidence is 319 backend tests, 166 focused regressions, and 4 frontend unit tests. Three fresh exact-current scoped reviewers returned clean five-key verdicts; seedocs/engineering-log/2026-08-15-final-security-gate.md. - Real
/legalpage (setup links to it), billing/payments, per-tenant storage volume, compressed persona recipe, export-token polish. - Mobile visual polish per user feedback on real device.
- Deeper: letting admins bulk-import personas, analytics drill-down per persona variant.
Related docs
docs/PLAN.md,docs/SAAS_PLAN.md,docs/FUTURE_WORK.md.docs/engineering-log/2026-08-13-idea-implementation-audit.md— latest evidence-based audit, runtime probes, and prioritized fixes.docs/engineering-log.md— status index.docs/engineering-log/2026-08-15-final-security-gate.md— final local verification and clean exact-current scoped review gate; production-operation limits remain.docs/engineering-log/2026-08-15-postgresql-runtime-gate.md— temporary-local PostgreSQL schema/runtime, parity, offline DDL, and migration rollback evidence.docs/engineering-log/2026-08-15-postgresql-import-gate.md— temporary-local PostgreSQL importer dry-run/apply/idempotency/conflict-rollback evidence.docs/test-evidence/2026-08-15-postgresql-runtime.md— PostgreSQL automated gate evidence and remaining operational boundaries.docs/test-evidence/2026-08-15-postgresql-import.md— importer evidence separated from real target apply and audit migration.docs/engineering-log/2026-08-09-idea-flow-qa-deploy.md(this session: idea-flow UX, 2-scenario + recontact trait, live QA + auto-deploy, per-turn LLM judge, persona variant, 15-persona auto-gen).docs/engineering-log/2026-08-14-final-review.md— prior five-finding remediation and review history; current exact-tree approval remains pending.docs/engineering-log/2026-08-15-auth-version-review-reconciliation.md— current auth-version lock-path verification and pending fresh reviewer gate.docs/engineering-log/2026-08-15-s4-2-schema-foundation.md— test-first SQLAlchemy/Alembic schema foundation, tenant constraints, and generated-dist cleanup.docs/engineering-log/2026-08-15-s4-3-org-users-repositories.md— tenant-scoped repository contracts and adapters, with no runtime cutover.docs/engineering-log/2026-08-15-s4-3-offline-dialect-remediation.md— offline Alembic dialect finding, test-first fix, verification, and pending review.docs/engineering-log/2026-08-16-s4-4-importer-errorhandler-commit.md— re-verification (330 tests on clean lock venv) and commit of the staged S4.4 importer + error-handler hardening increment.