280 Commits

Author SHA1 Message Date
43e02adf37 216 sshd: LoginGraceTime back to 2m (30s silently cut off people typing a long password)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 00:59:59 +03:30
e25ffb5575 CLAUDE.md: 216 password is the AdminUser-Password line
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 00:48:23 +03:30
eb03b467d4 216: shared admin account mathapp-admin, temp removed, hostname mathapp
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 20:50:17 +03:30
f2c57c1ad1 docs: step-by-step cut-over to 216 with rollback, what waits on others, after-cut-over list
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 20:18:40 +03:30
f6d779255d docs: what was tested on 216 (end-to-end user flow, restore from the 247 encrypted copy)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 20:08:38 +03:30
4cd859b5fa 216 backups encrypted on 216: pgBackRest (AES-256) + age-encrypted payment CSVs, pushed write-only to 247 with rrsync; health watches pg_stat_archiver and OFFSITE_SYNC_STATUS_FILE; 216 scripts/configs in ops/server216; plaintext pg216 pipeline removed
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:43:00 +03:30
dd98f16d12 Live backup of 216 on 247 (zareian-pg216-* units, Postgres 16 tools in our dir, SCRAM hash only on 216), backup scripts take PGB_ROOT/PGB_BIN/PGB_PORT with 249 defaults, anti-bot layers documented
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:22:56 +03:30
bd329f83ae Dedicated server 78.38.35.216: state and cut-over order in docs, CLAUDE.md entry, password snippet for two Password lines, archive-app-log.sh finds its project dir
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:06:04 +03:30
670513b462 tools/figures: server_fixups.json (corrections of already-published questions and lessons), doc line
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-17 03:36:00 +03:30
133233144e Seed: grade 12 questions (every lesson 4+ per lesson, answers computed in code), minus-sign and doubled-x fixes in grade 10 and 11
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-17 03:35:34 +03:30
a5a93ef6ba Seed: grade 11 questions (every lesson 4+ per lesson, answers computed in code)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-17 03:27:10 +03:30
5f963e2898 Seed: grade 10 questions (every lesson 4+ per lesson, answers computed in code)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-17 03:16:51 +03:30
7bd7c637b4 Seed: grade 9 questions (every lesson 4+ per lesson, answers computed in code)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-17 03:00:27 +03:30
ac96f93abc Seed: grade 8 questions (every lesson 4+ per lesson, answers computed in code)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-17 02:48:33 +03:30
9903e0ec91 Seed: grade 5 rebuilt (every lesson 4+ questions computed in code), wording fixes
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-17 02:30:33 +03:30
759bb0d575 Seed: grade 4 rebuilt (every lesson 4+ questions computed in code), slash decimals fixed in chapter 5
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-17 02:06:21 +03:30
f8a93a37c9 Seed: grade 7 rebuilt (every lesson 5+ questions computed in code), lost-exponent questions fixed
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-17 01:47:44 +03:30
9eae99340d Seed: grade 6 rebuilt (every lesson 5+ questions, answers computed in code), supplementary/complementary text fixed in grades 4/6/7, '/' decimals to '٫', wrong keys fixed
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-17 01:31:28 +03:30
f57deb9c1d Seed: grade 3 rebuilt (every lesson 5+ questions, wrong figures/lesson text fixed), Persian digits and plain fractions across all grades, apply_fixups gains lesson/question_set, ledger save chunked for SQLite
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-16 21:27:22 +03:30
5fbbd371fe Seed: questions for the 499 lessons that had none; every lesson now has questions (5,406/5,406)
998 four-choice questions (2 per lesson), written from each lesson's own text,
worked example and the book's extracted problem. Mostly grades 3, 6, 7 (books
imported before us), plus 4, 5 and a few in 8-12. Hands-on lessons (drawing,
measuring, folding) got text questions that test the same idea.
Checked: structure (4 distinct choices, no LaTeX), a blind re-solve of all 998
against the key (no mismatch), and content.tests_seed_content seeding the full
file. Not checked: an independent second solver.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:15:13 +03:30
82fec6bf75 Admin: PaymentEvent read-only ledger view (was documented but not registered)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-16 02:18:07 +03:30
8a2bf26b16 Lesson.order from the book (seed sets it, also re-syncs unit/node order); matrix lesson rows as one multi-line step
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-15 13:53:30 +03:30
3f68abe741 Explicit ordering for Lesson, Question, Choice (+id tie-break on Unit/Node): lessons no longer reshuffle after row updates
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-15 13:39:42 +03:30
7973bf4100 Raw LaTeX guard: vdots/ddots/sqrt( converted, leftover() sees bare words, matrix lesson and 4 intro texts fixed in seed
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-15 13:24:11 +03:30
0c6e155232 More figured questions for grade 12 (geometry conics/vectors, calculus graph reading)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-15 02:26:16 +03:30
e6e983f970 Figures must not give the answer away: blank titles on new figured questions, 7 trivial questions removed, 7 reworded; apply_fixups tool
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-15 02:21:02 +03:30
733796c25a kind=space (fixed oblique 3D: points, vectors, planes, box) + ~190 figured questions for grade 12 (geometry, discrete, calculus)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-15 01:04:08 +03:30
998b68e4bc Figured questions for grade-12 geometry (conics, systems, vectors: 50 new); docs: figured questions win in the island quiz
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 22:24:53 +03:30
cd4f2ce0b2 Figure questions for grade-10 geometry (115 new), quiz prefers a figured question per lesson, tools/figures/batch.py
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 20:24:55 +03:30
a4e0c576bb Docs brought to the current state: CLAUDE.md rewritten, README formal, handbook and diagrams redrawn
- CLAUDE.md: 1,225 → ~430 lines, current state only (the dated status log stays in git history).
  Server rules moved to the top: what is ours on 249/247 and what is not, non-negotiable rules,
  announce-before-acting, password handling, and the full "deploy it" procedure with verification.
- README: formal entry page with current numbers (10 grades, 18,522 questions, 61 API operations,
  973 tests, PostgreSQL/Redis).
- Removed docs/frontend/ (Figma-era guide with guest login and its own api.ts; the frontend repo has
  the real client). Swagger's external-docs link now points at the frontend repo.
- architecture/operations/security/content-authoring/loadtest/textbooks: SQLite, guest login,
  demo accounts, question packs and the port-80 note replaced by what is true now.
- Technical handbook text updated throughout; all seven diagrams redrawn (PostgreSQL, phone-only
  login, subscriptions, current data model); PDF rebuilt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:45:51 +03:30
eb95209ea8 deploy.sh: chmod only our own backups; a root-owned nginx backup aborted the release
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 16:59:08 +03:30
1c7c4b8eca API output without Persian diacritics or written ezafe ی (company decision); tutor answers too
plain_persian() strips kasra/fatha/damma/shadda/sukun/hamza-above and the written «ه‌ی» ezafe, keeping tanwin. Applied
at output, not in data: PlainPersianJSONRenderer runs one byte-level pass over every JSON response (~5 ms per MB
locally), and to_plain() cleans tutor answers including the stream. Rewriting titles in the DB/seed would have broken
question binding by title path and SeedRecord keys. Tests updated to expect the cleaned text; config/tests_plain_persian.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 16:57:17 +03:30
7b241c1aac CLAUDE.md: restore onto 249 was already done (owner)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:16:58 +03:30
e3c8962b7b Load test of 1000 players on the server: CPU is the ceiling (~135 req/s); player-simulation tools
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 19:48:00 +03:30
ba4ecff29e Server hardening: nginx origin locked to ArvanCloud + CSP, demo logins removed, docs
- ops/nginx/: the two nginx sites as they now run on 249. Only ArvanCloud
  edges reach the app (direct hits on the IP get 403, so Arvan's DDoS/WAF
  can't be skipped); the frontend gets CSP (own sources only, the one inline
  script by hash), X-Frame-Options, nosniff, Referrer-Policy,
  Permissions-Policy, HSTS; API body limit 100M -> 20M plus HSTS. Checked from
  outside and in Chrome (all tabs and an island quiz, logged in): no CSP errors.
- Demo accounts (users 11-15, 25) and TEST_LOGIN_PHONES removed from the server;
  the health warning for fixed-code logins now has a test and says how to
  test instead (an OTPCode made in the admin).
- SMS daily cap on the server is 2000 (launch of ~600 users).
- CLAUDE.md: never run tests with --parallel on macOS, always --noinput
  (both hang forever without an error).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 19:17:11 +03:30
2a03dc81b5 Docs: X-Forwarded-For spoofing closed; Arvan bypass (no WAF) still open
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 18:50:47 +03:30
e3bddb3757 Trust X-Forwarded-For only from ArvanCloud edges
nginx on the server also answers on its own IP. A direct request with a fresh
X-Forwarded-For each time got a new throttle identity every time and skipped
every per-IP limit (OTP request/login, admin login): reproduced on the server
2026-10-13, 65 of 65 health requests with rotating XFF got 200.

TrustedProxyMiddleware reads X-Real-IP (set by nginx from the TCP peer) and,
when the peer is not in TRUSTED_PROXY_NETWORKS (default: ArvanCloud's
published list), replaces XFF with the peer address. Logs a WARNING once an
hour per untrusted peer that sent a proxied XFF, so a new Arvan range shows up.

Also ignore the parallel test DB clones (.test-db_N.sqlite3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 18:45:27 +03:30
78c3669119 Docs: app logs archived nightly and kept forever on the backup server
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 17:56:12 +03:30
3559c43902 App logs kept forever: nightly gzip on 249 (LOG_FILE_ROTATION=external), hourly pull to 247 via a forced-command key, health warns if the archive stops
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 17:54:05 +03:30
f4831e93a7 Docs: payment event ledger and its CSV export on the backup server
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:47:24 +03:30
44ad2eabf3 Payment events: one central payment_result row for every path (callback, reconcile, Bazaar, renewal, 100% code), discount_applied, no duplicate order_created; Bazaar token never stored
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:44:50 +03:30
8d8665edf3 Payment event ledger (PaymentEvent) replicated to the backup server, plus CSV export script on 247
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-13 14:18:47 +03:30
947f946909 Question packs removed at the root; all subscriptions 4 assistant questions a day; Bazaar is subscription-only
- subscription.0017/0018, payment.0013: pack plans deleted, QuestionCredit/HeartsPass models and Plan.kind /
  question_credits / hearts_days / Order.question_credits / hearts_days dropped. The only paid pack order was the
  company owner's test account (user 26).
- Assistant: no credit spending; quota is the daily cap only (free 2, every plan 4, gift subscription 4).
- Bazaar: verify_purchase is subscription-only (no inapp guess), start/purchase responses lose productType,
  verify refuses an order without a subscription. A paid order with no subscription now logs ERROR.
- Removed POST /api/payment/orders (internal; no caller). Its permission tests stay.
- Pack-based tests rewritten on subscriptions (refund, security, Bazaar token); pack-only test files deleted.
- docs: profit table for 4/day on both payment routes plus free users; handbook, operations, CLAUDE.md current.
949 tests green.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 20:33:38 +03:30
9c3adc3e60 Subscriptions only: Bazaar panel prices (598/898/1398k), assistant quota 5/3/3 from a cost calculation, packs off sale
- subscription.0016: prices equal to the Bazaar panel (senior), ai_daily_quota 5/3/3, card features; packs on_sale=False
  (one paid pack order, user 26, keeps its credits; pack paths stay for that order and for admin).
- Quota math in docs/ai-cost-and-guardrails.md: ~77% of a Bazaar sale reaches us; worst case (full quota every day)
  the assistant costs 23/27/35% of it.
- Assistant quota message points to the subscription, not packs.
982 tests green.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 19:57:16 +03:30
c0fa46be44 Payment providers as classes (Strategy + factory); 10342 = monthly; health logs a failed Bazaar check
- payment/providers.py: ZarinpalProvider / BazaarProvider with one interface (check_plan, sku_for, payment_url,
  purchase_extras, discount_blocker, reconcile_user); provider_for / provider_for_method pick one; unknown fails.
  create_order, purchase view, discount redeem and reconcile_user_orders use it instead of scattered ifs.
- subscription.0015: Bazaar SKU 10342 on the one-month plan (owner: "probably the monthly one").
- config/health: the Bazaar check no longer swallows its exception without a log.
982 tests green.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 18:21:45 +03:30
76b0ef976e Bazaar: plans cache cleared by migration 0014, enum name override, CLAUDE.md status
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:59:43 +03:30
8077ab4350 Bazaar: subscriptions (10343/10344), dynamic price (discount codes on Bazaar), auto-renewal sync
- Purchase with paymentMethod=bazaar now works for subscription plans with a bazaar_sku (not only packs);
  response carries bazaarProductType. Migration subscription.0014 puts 10343/10344 on the 90/180-day plans.
- Verify asks Bazaar for the right product type (from the order) and stores validUntil (payment.0012).
- POST /api/payment/bazaar/orders/{id}/start: SKU, type, and an HS256 dynamic-price JWT when a discount
  code lowered the price (BAZAAR_DYNAMIC_PRICE_KEY); discount on Bazaar orders allowed only with that key
  and only before start.
- Auto-renewal: sync_bazaar_renewals re-asks Bazaar when the paid period is near its end (status page and
  the paywall via has_subscription_access); each new period becomes a paid order.
- Default BAZAAR_PACKAGE_NAME = com.tadrisino.math.app; health shows bazaarDynamicPrice.
979 tests green. No real Bazaar purchase or renewal tested.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:53:11 +03:30
2e0753587c Like/dislike + report under every question and lesson, lesson reports and vote columns in admin; practice islands refresh from file; audit fixes
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 12:56:47 +03:30
b075b93f2a Tutor/answers: bare subscripts (S_n, a_1) become Unicode; migration 0022 re-cleans DB text
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 12:18:46 +03:30
e662914efc No LaTeX/markdown anywhere students read (seed, import, DB migration 0021, tutor history, frontend guard); quiz of an island is fixed per user again
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 12:15:28 +03:30