gendesign/.claude/rules/backend.md
lekss361 8b7696ad15 chore(claude-rules): promote 3 deferred feedback rules from memory
Follow-up to #495 — integrating 3 stable feedback rules into path-scoped
rule files (per MEMORY.md sweep 2026-05-24).

- frontend.md: + package.json + lockfile sync section
  (mismatch -> npm ci fails -> deploy aborts; ref PR #345)
- deploy.md: + Post-deploy verification section
  (spawn qa-tester smoke after every deploy success on main)
- backend.md: + Web probing section
  (playwright MCP for probing scrapers, curl/curl_cffi for actual scraping)

Source feedback files deleted from ~/.claude/projects/.../memory/
and added to Removed table in MEMORY.md.
2026-05-24 11:56:06 +03:00

4.9 KiB
Raw Blame History

paths
backend/**/*.py

Backend conventions — Python 3.12 / FastAPI

psycopg v3 (CRITICAL — recurring bug class)

  • import psycopg2ModuleNotFoundErrorего нет в зависимостях. Только psycopg[binary]>=3.2.0.
  • Bulk INSERT: cur.executemany() или COPY — НЕ execute_values (это psycopg2 API).
  • В SQL text(...): всегда CAST(:name AS type) — НИКОГДА :name::type.
    • SQLAlchemy 2.0 + psycopg3 игнорирует ::type после :namepsycopg.errors.SyntaxError: syntax error at or near ':'
    • Исключение: ARRAY[:rc]::int[] РАБОТАЕТ — :: приклеено к ), не к bind-name
    • Pre-PR check: grep -nE ':[a-z_]+::[a-z]' backend/app → должен быть пуст
    • Reference: vault Pattern_CAST_AS_Type, Bug_SQLAlchemy_DoubleColon_Cast

SAVEPOINT pattern (loop upserts)

В цикле INSERT/UPSERT — обязательно with db.begin_nested(): per-row:

for row in items:
    try:
        with db.begin_nested():
            db.execute(text("INSERT INTO ..."), {...})
    except Exception as e:
        logger.warning("row failed: %s", e)

Bare db.rollback() в loop — откатывает всю outer tx, счётчики inserted/updated продолжают расти → inconsistent state.

Reference: vault Bug_Pzz_Loader_Missing_Savepoint_May14, domrf_kn.py:427/452/472.

SQL injection prevention

Никогда:

  • f-string в SQL: text(f"INSERT ... VALUES ({values})")
  • Home-rolled quote escape: c.replace(chr(39), chr(39)*2)
  • String concat: "... WHERE id = " + str(id)

Canonical: parametrized batch через SQLAlchemy text + list of dicts:

db.execute(
    text("INSERT INTO targets (job_id, cad_num) VALUES (:job_id, :cad_num) "
         "ON CONFLICT (job_id, cad_num) DO NOTHING"),
    [{"job_id": j, "cad_num": c} for c in cad_nums],
)

Reference: vault Bug_Nspd_Geo_Sql_Injection_May14.

Ruff / code style

  • Line length 100 (backend/pyproject.toml)
  • Типичные ловушки E501:
    • SQL-литералы в text("""...""") — переноси после FROM / WHERE / AND
    • logger.info("...", a, b, c, d) — разбивай на 2 строки
    • Длинная сигнатура — аргументы по одному
  • Ruff rules: E F I B UP N RUF ASYNC (RUF001/002/003 игнор — кириллица OK)
  • mypy strict: app.services.generative.* + app.services.site_finder.scorer
  • target-version py312dict | None, list[int], walrus OK

Async / Celery

  • FastAPI handlers: async def
  • Celery tasks: def (Celery sync) — НЕ async def внутри task
  • Sync↔async bridge через asyncio.run() внутри Celery task
  • Tests: pytest + asyncio_mode = auto

Запреты

  • print() — только logger.info/warning/error
  • import requests — только httpx
  • except Exception: pass без re-raise или logger.exception
  • Hardcode credentials — os.environ.get("KEY") или settings.KEY
  • Any без комментария почему

Web probing (scrapers — use playwright MCP, not curl)

При исследовании веба (как страница реагирует на параметры/фильтры, что меняется в DOM, какие селекторы возвращают данные, anti-bot behavior) — сначала mcp__playwright__*, а не curl/curl_cffi.

Why: Реальный браузер видит финальный URL после canonicalization + JS state без anti-bot ловушек. Curl попадает в captcha на 2-3-й итерации. User rule (2026-05-23): «если что-то не понятно лучше через плэйврайт смотри чем через консоль».

How to apply:

  • Discover URL params / filter behavior → browser_navigate + browser_snapshot
  • Verify selectors / count cards → browser_evaluate с JS возвращающим counts
  • Inspect network → browser_network_requests
  • Captcha detection → browser_snapshot + check для challenge elements

Curl/curl_cffi оставляем для actual scraping в production-скриптах после того как param set уже известен (playwright медленнее + heavier для bulk fetch).

Reference incident: 2026-05-23 reverse-engineering Yandex SERP filter params через curl — captcha на 2-й итерации, recovery через cookie-warm session. С playwright сразу видели бы JS state.

Worker crash checklist

Worker падает на старте → скорее всего import error: pyproject.toml имеет dep но uv.lock не обновлён → cd backend && uv lock → commit lock → push.