Remove silent fallbacks found in the 2026-10-03 audit

- /api/ai/ask: the stub answer is reachable only under DEBUG or
  RAG_FORCE_MOCK. Without RAG keys on the server it used to return 200 with
  "this is a stub, set OPENAI_API_KEY" and spend the student's quota; now 503.
- /api/home: the `grade` alias is gone and sending it is a 400 instead of a
  silent fallback to the user's own grade. No client sent it.
- activate_subscription logs ERROR when the paid order's plan no longer exists
  (it silently became 30 days).
- resolve_grade_title logs instead of `except: pass`; curriculum_io no longer
  reads an `intro_text` key no file contains.
- CLAUDE.md: "No silent fallbacks" rules for future agents.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 10:38:42 +03:30
parent 7e4b8b54a6
commit 7307bfcadb
10 changed files with 98 additions and 24 deletions

View File

@@ -230,10 +230,14 @@ def generate_tutor_answer(
تولید می‌کنه. مالکیتِ تاریخچه‌ی مکالمه همیشه دستِ دیتابیسِ خودمونه، نه
provider.
اگه RAG پیکربندی نشده باشه (نبودِ OPENAI_API_KEY/QDRANT_API_KEY یا
RAG_FORCE_MOCK)، همون پاسخِ stubِ قطعیِ قبلی برمی‌گرده تا اجرای بدونِ
کلید و تست‌ها دست‌نخورده کار کنن.
اگه RAG پیکربندی نشده باشه، پاسخِ stub فقط در DEBUG یا با RAG_FORCE_MOCK
(تست‌ها) برمی‌گرده. بیرون از آن خطا می‌دهد و AskAIView آن را ۵۰۳ می‌کند.
تا ۲۰۲۶-۱۰-۰۳ stub در پروداکشن هم برمی‌گشت: اگر کلیدی از .env سرور می‌افتاد،
دانش‌آموز با ۲۰۰ متنِ «این یک پاسخ نمونه (stub)… OPENAI_API_KEY را تنظیم
کنید» می‌گرفت و سهمیه‌ی روزانه‌اش هم کم می‌شد (با تستِ دورریختنی بازتولید شد).
"""
from django.conf import settings
from rag.services.config import rag_is_configured
if rag_is_configured():
@@ -247,6 +251,9 @@ def generate_tutor_answer(
answer, follow_ups, _meta = generate_answer(user_question, context_chunks)
return answer, follow_ups
if not (settings.DEBUG or settings.RAG_FORCE_MOCK):
raise RuntimeError('RAG پیکربندی نشده (OPENAI_API_KEY/QDRANT_API_KEY)؛ stub فقط در DEBUG.')
context_note = f' (با context: {context})' if context else ''
answer = (
f'این یک پاسخ نمونه (stub) برای سوال «{user_question}»{context_note} است. '