- scripts/claude-hooks/check-secret-read.py — PreToolUse(Bash) guard: блокирует чтение/передачу .env*/.pgpass/ssh-key (cat/Get-Content/curl @file/<redirect), закрывает Bash(*) обход Read(.env) deny. Allow: .env.example/templates, ls/stat. Tested 12/12. - scripts/claude-hooks/check-sql-pitfalls.py — PostToolUse(Edit|Write|MultiEdit): блокирует import psycopg2 в backend + tradein-mvp/backend .py (psycopg v3). Cast-trap НЕ чекаем (FP на comment/test-mentions). Tested 7/7. - scripts/claude-hooks/session-preflight.py — SessionStart: статус FORGEJO/OBSIDIAN token + db-tunnel. - .claude/rules/tradein.md — НОВОЕ: tradein-mvp gotchas (2 БД, :8000=gendesign, scheduler в tradein-scraper, strict SQL auto-apply + NN-collisions, rapid-merge trap). - backend.md/sql.md/frontend.md: paths globs расширены на tradein-mvp/**. Wiring (settings.json) — local/gitignored, отдельно. Audit 2026-06-27 P0+P1.
4.9 KiB
| paths | ||
|---|---|---|
|
Backend conventions — Python 3.12 / FastAPI
psycopg v3 (CRITICAL — recurring bug class)
import psycopg2→ ModuleNotFoundError — его нет в зависимостях. Только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после:name—psycopg.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
- SQLAlchemy 2.0 + psycopg3 игнорирует
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 строки- Длинная сигнатура — аргументы по одному
- SQL-литералы в
- 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
py312—dict | 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.