Critical:
- data/sql/100_tradein_fdw_role.sql — remove hardcoded password literal;
role created LOGIN only; explicit REVOKE perimeter on all
tables/sequences/functions in public; only v_tradein_cad_buildings grant
remains. Password is now set by .forgejo/workflows/deploy.yml ALTER ROLE
bootstrap step reading GENDESIGN_FDW_PASSWORD from backend/.env.runtime.
- tradein-mvp/backend/app/core/fdw.py — strict regex whitelist on password
([A-Za-z0-9_-]{32,256}) before inlining into DDL; ValueError on mismatch
(no password in error message); pg_user_mappings query corrected for
PUBLIC mapping (usename IS NULL); commit wrapped in try/rollback.
High:
- 060_postgres_fdw_extension.sql — connect_timeout='3' added to FOREIGN
SERVER; ALTER SERVER ADD/SET fallback for re-apply path.
- geocoder.py — _cadastral_forward_sync / _cadastral_reverse_sync wrapped in
asyncio.to_thread to avoid blocking event loop on FDW outage.
Tests:
- 3 new test_cadastral_reverse.py cases: SQL injection password rejected,
short password rejected, valid hex password accepted with mapping creation.
Old password 40473b7c... remains in git history at 698ef4f (force-push
forbidden) — but the role was never created with it on prod (SQL not
deployed), so abandoned. New password 72df... lives only in VPS env files
and vault meta/00_credentials.md.
502 fix: Next.js standalone server.js биндился на HOSTNAME=<container-id>
вместо 0.0.0.0 → Caddy не мог достучаться до tradein-frontend:3000.
Теперь HOSTNAME=0.0.0.0 и в Dockerfile (runner ENV), и в compose.
API routing fix: фронт собирался без NEXT_PUBLIC_API_BASE_URL, fetch шёл
на same-origin /api/* (главный gendesign-backend) вместо /trade-in/api/*.
Добавлен build-arg в Dockerfile + workflow.
Раньше deploy.yml делал `docker compose exec caddy caddy reload` — это
только reload Caddyfile, но НЕ пересоздаёт container. Когда в
docker-compose.prod.yml добавлялся новый volume mount (например
./preview:/srv/preview:ro в PR #268), он не появлялся в running container
до manual `docker compose up -d --force-recreate caddy` через SSH.
Теперь deploy всегда вызывает force-recreate caddy после image pull —
идемпотентно, +~5 сек к deploy time, гарантирует что новые mounts +
Caddyfile блоки активны.
Problem
- GlitchTip Issues = 0 за всё время, хотя backend + frontend SDK интегрированы (PR #207, #208).
- Старый Sentry.io продолжает получать события — user видит уведомления оттуда.
Root cause
- frontend/Dockerfile не имеет ARG NEXT_PUBLIC_GLITCHTIP_DSN → `npm run build` бьёт
с пустым env var → Next.js инлайнит undefined → SDK init guard `if (dsn)` skips.
Chrome-devtools check на prod bundle подтвердил: ни в одном из 9 chunks DSN-строка
не запечена; `i.env.NEXT_PUBLIC_GLITCHTIP_DSN` evaluates to undefined.
- .forgejo/workflows/deploy.yml build-frontend не передавал build-args.
- На VPS backend/.env.runtime содержит legacy SENTRY_DSN=...@sentry.io/...
config.py:_promote_legacy_sentry_dsn слепо промоутит его в glitchtip_dsn → SDK
шлёт в чужой Sentry. GLITCHTIP_DSN там не задан.
- deploy.yml SSH-скрипт никогда не редактировал SENTRY_DSN/GLITCHTIP_DSN в .env.runtime.
Solution
1. frontend/Dockerfile: ARG NEXT_PUBLIC_GLITCHTIP_DSN + NEXT_PUBLIC_ENVIRONMENT
с пустыми defaults, ENV-mirror перед `npm run build`. Локальный build без
build-args работает по-прежнему (no-op DSN).
2. .forgejo/workflows/deploy.yml:
- build-frontend: `build-args` передаёт `secrets.GLITCHTIP_FRONTEND_DSN`
+ NEXT_PUBLIC_ENVIRONMENT=production. Инвалидирует cache → frontend
image пересоберётся (expected).
- deploy step: GLITCHTIP_BACKEND_DSN через secret, в SSH-скрипте:
a) `sed -i '/^SENTRY_DSN=/d' backend/.env.runtime` — снести legacy
b) upsert GLITCHTIP_DSN (sed/printf) тем же паттерном что SENTRY_RELEASE
c) `compose up -d --force-recreate --no-deps backend worker beat` —
обычный `up -d` не перечитывает env_file без image change.
3. backend/app/core/config.py: _promote_legacy_sentry_dsn ужесточён —
принимает SENTRY_DSN только если host == errors.gendsgn.ru. Для других
URLs (sentry.io) выдаёт UserWarning и НЕ промоутит. Anti-regression на
случай если SENTRY_DSN снова окажется в .env.runtime после ручного
вмешательства.
Required Forgejo secrets (Settings → Actions → Secrets)
- GLITCHTIP_BACKEND_DSN = https://3d6e291003e142458957490c83559867@errors.gendsgn.ru/1
- GLITCHTIP_FRONTEND_DSN = https://5d7bc85e300c4e80a8554ccc818ff56d@errors.gendsgn.ru/2
DSNs публичны (видны в browser bundle) — secrets ради build-time injection,
не для конфиденциальности. Если secrets не заданы → deploy succeeds, SDK
no-op, без регрессии.
Test plan
- Verify Forgejo deploy.yml зелёный после merge
- chrome-devtools: открыть gendsgn.ru → search bundle на DSN string → должна
быть запечена строка errors.gendsgn.ru/2
- Trigger frontend error → POST к errors.gendsgn.ru/api/2/envelope/
- Backend: curl на endpoint вызывающий 500 → событие в GlitchTip backend project
- GlitchTip dashboard https://errors.gendsgn.ru/gendesign/issues — Issues > 0
References
- vault: meta/00_credentials.md (DSNs + incident notes 2026-05-16)
- vault: decisions/Dec_GlitchTip_Frontend_Sentry_SDK.md (env contract)
- vault: fixes/fixes-MOC.md (#204 backend SDK init)