feat: 운영 안정성과 세션 음성 경험 개선

This commit is contained in:
Yun Chan 2026-07-31 00:13:08 +09:00
parent facc4ad2d9
commit c788343467
95 changed files with 8431 additions and 1785 deletions

View file

@ -346,7 +346,12 @@ async def _runtime_tables_ready(conn) -> bool:
to_regclass('app.persona_voice_map') IS NOT NULL AS has_persona_voice_map,
to_regclass('app.auth_session') IS NOT NULL AS has_auth_session,
to_regclass('app.user_preferences') IS NOT NULL AS has_preferences,
to_regclass('app.admin_engine_config') IS NOT NULL AS has_engine_config,
EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = 'app'
AND table_name = 'admin_engine_config'
AND column_name = 'reasoning_effort'
) AS has_engine_config,
EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = 'app'
@ -650,11 +655,18 @@ async def ensure_runtime_tables() -> None:
engine_mode TEXT NOT NULL,
engine_url TEXT NOT NULL,
model TEXT NOT NULL,
reasoning_effort TEXT,
updated_by TEXT,
updated_at TIMESTAMPTZ
)
"""
)
await conn.execute(
"""
ALTER TABLE app.admin_engine_config
ADD COLUMN IF NOT EXISTS reasoning_effort TEXT
"""
)
await conn.execute(
"""
CREATE TABLE IF NOT EXISTS app.admin_health_event (
@ -1226,6 +1238,15 @@ async def upsert_managed_user(data: ManagedUserUpsertInput) -> ManagedUser:
normalized_email = _normalize_email(data.email)
normalized_external_id = _normalize_external_id(data.external_id, normalized_email)
manual_external_id = f"email:{normalized_email}"
desired_admin_access = (
data.admin_access
if data.admin_access is not None
else (
True
if has_admin_access(normalized_email, data.role, False)
else None
)
)
desired_account_status = data.account_status or _initial_account_status(
email=normalized_email,
external_id=normalized_external_id,
@ -1242,6 +1263,10 @@ async def upsert_managed_user(data: ManagedUserUpsertInput) -> ManagedUser:
display_name = COALESCE(NULLIF(display_name, ''), $3),
cohort = COALESCE(cohort, $4),
affiliation = COALESCE(NULLIF(affiliation, ''), $5),
admin_access = CASE
WHEN $7::boolean IS NULL THEN admin_access
ELSE $7::boolean
END,
last_seen_at = now(),
updated_at = now()
WHERE user_id = (
@ -1293,6 +1318,7 @@ async def upsert_managed_user(data: ManagedUserUpsertInput) -> ManagedUser:
_cohort_value(data.cohort_ids),
data.affiliation or DEFAULT_AFFILIATION,
manual_external_id,
desired_admin_access,
)
if row is not None:
user = _managed_user_from_row(row)
@ -1314,13 +1340,13 @@ async def upsert_managed_user(data: ManagedUserUpsertInput) -> ManagedUser:
last_seen_at,
updated_at
)
VALUES ($1, $2, $3, $4, COALESCE($9, false), $5, $6, $8, now(), now())
VALUES ($1, $2, $3, $4, COALESCE($9::boolean, false), $5, $6, $8, now(), now())
ON CONFLICT (external_id) DO UPDATE SET
email = EXCLUDED.email,
display_name = COALESCE(NULLIF(EXCLUDED.display_name, ''), app.app_user.display_name),
role = EXCLUDED.role,
admin_access = CASE
WHEN $9 IS NULL THEN app.app_user.admin_access
WHEN $9::boolean IS NULL THEN app.app_user.admin_access
ELSE EXCLUDED.admin_access
END,
cohort = COALESCE(EXCLUDED.cohort, app.app_user.cohort),
@ -1369,7 +1395,7 @@ async def upsert_managed_user(data: ManagedUserUpsertInput) -> ManagedUser:
data.affiliation or DEFAULT_AFFILIATION,
data.reactivate,
desired_account_status,
data.admin_access,
desired_admin_access,
)
if row is None:
_inactive_emails.add(normalized_email)
@ -1382,6 +1408,8 @@ async def upsert_managed_user(data: ManagedUserUpsertInput) -> ManagedUser:
except InactiveUserError:
raise
except Exception:
if not runtime_fallback_allowed():
logger.exception("managed user persistence failed")
require_runtime_fallback_allowed("managed user")
current = _users.get(data.user_id or "") or _users.get(
_email_index.get(normalized_email, "")
@ -1405,7 +1433,7 @@ async def upsert_managed_user(data: ManagedUserUpsertInput) -> ManagedUser:
email=normalized_email,
display_name=data.display_name,
role=data.role,
admin_access=data.admin_access,
admin_access=desired_admin_access,
account_status=fallback_account_status,
cohort_ids=data.cohort_ids,
user_id=fallback_uid,
@ -2008,24 +2036,42 @@ async def get_session(raw_sid: str | None) -> SessionUser | None:
key,
)
if row is not None:
app_role = _app_role(row["role"])
stored_admin_access = bool(
_row_value(row, "admin_access", False)
)
effective_admin_access = has_admin_access(
row["email"],
app_role,
stored_admin_access,
)
await conn.execute(
"UPDATE app.auth_session SET last_seen_at = now() WHERE sid_hash = $1",
key,
)
await conn.execute(
"UPDATE app.app_user SET last_seen_at = now() WHERE user_id = $1",
"""
UPDATE app.app_user SET
last_seen_at = now(),
admin_access = CASE
WHEN $2 THEN TRUE
ELSE admin_access
END,
updated_at = CASE
WHEN $2 AND NOT admin_access THEN now()
ELSE updated_at
END
WHERE user_id = $1
""",
row["user_id"],
effective_admin_access,
)
return SessionUser(
user_id=str(row["user_id"]),
email=row["email"],
display_name=row["display_name"],
role=_app_role(row["role"]),
admin_access=has_admin_access(
row["email"],
_app_role(row["role"]),
bool(_row_value(row, "admin_access", False)),
),
role=app_role,
admin_access=effective_admin_access,
super_admin=is_super_admin_email(row["email"]),
account_status=_account_status(row["account_status"]),
cohort_ids=_cohort_ids(row["cohort"]),