Proves DELETE /api/groups/<gid> removes the product, its personas (stored on the
group), its own sessions, and uploads, while sessions of an unrelated product/
org survive. Guards the cascade against future regressions.
Records written before the visibility field existed carry none; the new
fail-closed authorization treated missing visibility as invalid, making every
legacy group unlistable and unreadable. Add resolved_visibility(group) that
derives effective visibility for legacy records only (owner present => private,
absent => public), leaves explicit-malformed visibility fail-closed (None), and
never derives demo/hidden. Apply it at every list, authorization, chat, and
analytics boundary while keeping demo and hidden-preview paths raw and
owner_user_id-based private isolation intact. No persisted data is rewritten.
Backend full suite passes 517; frontend 26/26; production build passes.
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).
- listPersonas: compute per-user my_outcome for ALL roles; always set the
key (default 'not_tried') so the all-roles UI never sees undefined
(fixes global super_admin without org showing every persona as trained)
- start_session/mode lookups (send/finish/resume/authorize): no preview
mode; every role creates one-shot 'trainee' sessions. The one-shot lock
(1 persona chat per user; many users per persona) now applies to all roles
- Chat.vue: always start in trainee mode, remove preview label
- Personas.vue: unified branches - show 'แชท' when untrained, 'สรุปผล'+variant
when trained, for all roles
- Rewrite test_admin_preview.py to assert the new no-preview one-shot behavior
- Backend: strip pain/painProgress/revealed_persona.pains from debrief
serializer and remove pain/initialPainFit from admin report so the
coaching formula never leaks to any user-facing role (persona keeps
pains internally to drive the judge/training)
- Fix [object Object] array-of-objects rendering in Chat/SessionDetail
- Personas: trained persona shows 'สรุปผล/Summary' button -> chat page
with past result (chat already loads finished session)
- Top bar: username dropdown containing Settings + Logout (was separate
logout button); variant-from-base already preserves tier/difficulty
- Updated debrief allowlist tests to reflect new redaction
Re-verified staged increment from a clean requirements.lock.txt venv:
- 330 backend tests pass (17/17 in new error_handlers + json_import tests)
- compileall + frontend npm build clean
- git diff --check clean; no secrets in diff
- importer CLI dry-run bootstrap works
Includes JSON HTTPException handler under /api/* and parse-safe static 404
via abort. JSON stores remain runtime-authoritative; production operation
still gated behind operator approval.