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:
41
CLAUDE.md
41
CLAUDE.md
@@ -22,6 +22,47 @@ at checkout, `select_for_update` guards that SQLite silently ignores).
|
||||
race-free / the right architecture"; write "guards X; not tested under Y" instead.
|
||||
Record uncertainty plainly so the next reader re-checks rather than trusts.
|
||||
|
||||
## No silent fallbacks, no speculative backward compatibility
|
||||
|
||||
Agents keep adding "helpful" fallbacks (`x or default`, `except: pass`, accepting an old
|
||||
field name "just in case") and they cause the hardest bugs here, because the
|
||||
failure never shows up anywhere. An audit on 2026-10-03 found and reproduced these:
|
||||
|
||||
- With RAG keys missing from `.env` and `DEBUG=0`, `/api/ai/ask` answered **200** with
|
||||
«این یک پاسخ نمونه (stub)… OPENAI_API_KEY را تنظیم کنید» and still used up the
|
||||
student's daily quota. It now returns 503 (`StubNeverReachesProductionTests`).
|
||||
- A paid order whose plan had been deleted quietly became 30 days (a yearly buyer got
|
||||
a month). It still activates, but now logs ERROR.
|
||||
- `?grade=` on `/api/home` was an alias "for clients already out there". No such client
|
||||
existed. Removed, and sending it is a 400, not a silent fallback to the user's own grade.
|
||||
- `resolve_grade_title` had `except Exception: pass` (now logged); `curriculum_io` read
|
||||
`intro_text` that no file ever contains (removed).
|
||||
|
||||
Rules:
|
||||
|
||||
1. **Fail loudly over guessing.** A missing setting, row or field in production should
|
||||
be an error the user and `/api/health` can see, not a default that looks like
|
||||
success. A default is fine only when it is a real product choice. When you
|
||||
add one, say in the comment why that value is right, not "just in case".
|
||||
2. **Demo/stub paths only behind `DEBUG` or `RAG_FORCE_MOCK`.** Never reachable on the server.
|
||||
3. **No backward compatibility without a named client.** Before keeping an old field, param
|
||||
or code path, check who sends it (`~/Desktop/MathApp_frontend/src/api/`, the server
|
||||
DB). The only API client is that frontend; there is no Android app yet. If nobody
|
||||
sends it, delete it. If you must keep one, write who uses it and when it can go.
|
||||
4. **Never `except: pass`.** At least `logger.exception`. Swallowing is acceptable only
|
||||
where the comment names the bigger thing it protects (e.g. `affiliate/services.py`:
|
||||
commission failure must not undo the user's payment) and it still logs.
|
||||
5. **Removing a fallback means making the old input fail visibly** (400/503), not
|
||||
silently routing it somewhere else.
|
||||
|
||||
Fallbacks that remain on purpose (re-check before relying on them): settings boot with
|
||||
default hosts/CORS/cache/secret key (`/api/health` warns); `LocMemCache` without
|
||||
`CACHE_URL`; `_annotation` defaults for answer-cache entries older than a field; Bazaar's
|
||||
OAuth path beside the Pishkhan token (server still uses OAuth); `birthYear` alone in
|
||||
`PATCH /api/user/profile` (frontend type still allows it); `totalQuestions` accepted but
|
||||
ignored (frontend sends it); orders with no `plan_id` from before 2026-09-25 (the
|
||||
server DB was not checked for any).
|
||||
|
||||
## Before any real user: read `docs/security-before-production.md`
|
||||
|
||||
The server is deliberately left in demo mode for the internship presentation: the token
|
||||
|
||||
Reference in New Issue
Block a user