NEXX_Roadmap_Developer_Budget.md §4.2 · Updated: 2026-07-02 · Contract v1.7.0 (staging + prod) · Backend: prod platform-api.nexx.beyondhorizon.devplatform-api.nexx.beyondhorizon.dev (контракт v1.7.0), фронт v2 в проде на nexx-business-app-v2.vercel.app (demo-персоны, пароль demo-pass-123). Приёмка Stage 3: V2 App · Тестирование ↗./api/mock-backend/{health,auth/token,companies}/ · все три curl-проверены зелёные на staging · envelope с contractVersion: mock-v0, live: false · FR / FT N/A by designnexx-app (Django+gunicorn) + nexx-db (Postgres 15) + nexx-status (nginx) + 2 TG bridges · HTTPS через Cloudflare на 3 домена · реальный SMTP (seohost.pl) · миграции авто-применяются через entrypoint.sh при старте · secrets через server-side .env · scripts/deploy.sh = rsync + docker compose · pytest suite + verify_backend.sh локально перед коммитом. Gap: нет backend GitHub Actions CI, нет разделения prod/staging окружений — Stage 2 carry-over.POST /api/internal/v1/auth/register/ — создаёт user + UserProfile, шлёт verification email через SMTP nexx-platform@acebox.euPOST /api/internal/v1/auth/email/verify/ — потребляет one-shot token, помечает email верифицированнымPOST /api/internal/v1/auth/email/resend/ — повторная отправка (rate-limit 60s)curl -X POST https://nexx-api.srv.acebox.eu/api/internal/v1/auth/register/ \
-H "Content-Type: application/json" \
-d '{"email":"new.user@example.com","password":"newPass123!"}'
# → 201 {userId, email, emailVerified: false}
EMAIL_NOT_VERIFIED)POST /api/internal/v1/auth/token/ — login → access (15min) + refresh (30d)POST /api/internal/v1/auth/token/refresh/ — обмен refresh на новый accessPOST /api/internal/v1/auth/token/logout/ — revoke refresh token (запись в AuthRefreshToken)GET /api/internal/v1/auth/token/me/ — текущий user из access tokencurl -X POST https://nexx-api.srv.acebox.eu/api/internal/v1/auth/token/ \
-H "Content-Type: application/json" \
-d '{"email":"demo.buyer.admin@example.com","password":"demo-pass-123"}'
# → 200 {accessToken, refreshToken, tokenType: "Bearer", expiresIn: 900}
curl https://nexx-api.srv.acebox.eu/api/internal/v1/auth/token/me/ \
-H "Authorization: Bearer <accessToken>"
# → 200 {userId, email, ...}
POST /api/internal/v1/auth/password/reset/request/ — создаёт one-shot token, шлёт письмоPOST /api/internal/v1/auth/password/reset/confirm/ — потребляет token, меняет password, обновляет password_changed_atСтарые JWT access tokens с iat ≤ password_changed_at теперь отвергаются (core/auth_tokens.py). Поймано в human QA — Sasha залогинен в браузере A, сменил пароль в браузере B, после refresh в A старый token больше не работает.
curl -X POST https://nexx-api.srv.acebox.eu/api/internal/v1/auth/password/reset/request/ \
-H "Content-Type: application/json" \
-d '{"email":"demo.buyer.admin@example.com"}'
# → 202 (всегда 202, чтобы не раскрывать наличие аккаунта)
POST /api/internal/v1/companies/onboarding/manual/ — создаёт Company + Membership с ролью COMPANY_ADMINEMAIL_NOT_VERIFIED (403 если email не подтверждён)POST /api/internal/v1/companies/memberships/ — known email → активный member; unknown email → invitation + emailPATCH /api/internal/v1/companies/memberships/<id>/ — смена роли, suspend, reactivateGET /api/internal/v1/auth/invitations/<token>/ — публичный lookup для invite registration screenX-NEXX-Company-ID на каждом authenticated requestdemo.context.switcher@example.com состоит в 3 компанияхX-NEXX-Company-ID через membership lookupRequestLog middleware: каждый HTTP request → запись (timestamp, userId, companyId, method, path, statusCode, durationMs, request/response previews с PII redaction)/api/internal/v1/admin/logs/ — фильтрация по method/level/path/statusЕсли Костя ожидает отдельную AuditLog таблицу для бизнес-событий (`user.created`, `membership.suspended`, ...), а не HTTP-уровень — это Stage 2 задача. Спросить на созвоне: достаточно ли request-log для acceptance, или нужна отдельная business-event таблица.
Открыть live logs panel ниже на этой странице, нажать Reload.
contractVersion: mock-v0, live: falseGET /api/mock-backend/health/POST /api/mock-backend/auth/token/GET /api/mock-backend/companies/curl https://nexx-api.srv.acebox.eu/api/mock-backend/health/
# → 200 {contractVersion: "mock-v0", live: false, data: {status: "ok", service: "nexx-backend-mock"}}
curl -X POST https://nexx-api.srv.acebox.eu/api/mock-backend/auth/token/
# → 200 {data: {accessToken: "mock-access-token", tokenType: "Bearer", expiresIn: 900, ...}}
curl https://nexx-api.srv.acebox.eu/api/mock-backend/companies/
# → 200 {data: {items: [Mock Buyer SIA, Mock Supplier SIA], count: 2}}
Это не настоящий backend. Envelope (contractVersion, isFinalContract: false, live: false) явно показывает контракт-стаб. Семантика payload-полей deferred к NEXX BACKEND / 1C.
backend/core/mock_backend.pybackend/core/engine_contract.py — registry команд + событийdocs/NEXX_PLATFORM_BACKEND_MOCK_CONTRACT_v0.mdУ Кости в roadmap указан домен staging.nexx.global — мы используем *.srv.acebox.eu (наш инфраструктурный домен). Функционал staging работает; домен — отдельный вопрос про DNS/брендинг.
curl https://nexx-api.srv.acebox.eu/api/health/
# → 200 {status: "ok"}
v1.2.0 — покрывает auth, companies, memberships, catalog, cart, checkout, orders, admin logs.
docs/NEXX_PLATFORM_BACKEND_MOCK_CONTRACT_v0.md — описание seam mock-v0backend/core/engine_contract.py — registry COMMAND_FAMILIES + BUSINESS_EVENTSДокумент существует, нужна формальная подпись Ильи (CTO) и Alex'а на созвоне 21:00.
scripts/deploy.sh — manual rsync + docker compose up./scripts/verify_backend.sh запускает pytest вручную перед коммитомBackend не имеет GitHub Actions — отдельный CI runner и pipeline не настроены. Stage 2 carry-over.
Измеренного % покрытия (pytest --cov) пока нет. Можно запустить за 2 минуты до созвона если Костя хочет точные цифры.
demo-pass-123 · Passed 2026-05-18 / 2026-05-19 by Sasha + Andrei. Click any scenario for click-by-click steps and pass criteria.demo.buyer.admin@example.com, Password: demo-pass-123Log inDemo Buyer SIADemo Buyer SIABuyer и Company AdminLogoutdemo.supplier.admin@example.com, Password: demo-pass-123Log inDemo Supplier Food SIASupplierdemo.both.admin@example.com, Password: demo-pass-123Log inDemo Both Trade SIAdemo.context.switcher@example.com, Password: demo-pass-123Log indemo.context.switcher@example.comCreate account, не подтверждать emailVerify emailResend verification emailVerification email requested. Check the inbox for that address.Forgot password?Send reset linkIf the account exists, a password reset email has been sent.Log inEmail or password is invalidLog inCreate your company по правиламAccess token is invalid or expired.Этот тест поймал backend issue. Фикс: password_changed_at + iat-check в JWT validation.
Create accountEMAIL_NOT_VERIFIED)Create accountCreate your company заполнить Registration number, Company name, Legal address, Market roleCreate companydemo.buyer.admin@example.comCompany → panel MembershipsAdd membership: email existing verified non-member, role ManagerAdd membershipMember added: <email> as Manager.Company → Add membershipManagerAdd membership → проверить inboxInvitation sent to <email> as Manager.NEXX Platform <nexx-platform@acebox.eu>demo.buyer.admin@example.com invited youDemo Buyer SIAMANAGER/auth/invite?token=…/auth/invite?token=…Invitation registrationPreview на live invite lookupDemo Buyer SIA, не в Create your companyDemo Buyer SIABuyer + ManagerCompany → MembershipsRole selectorMembership role updated. внутри affected rowSuspendSuspend this member? → Confirm suspendSuspendedReactivate становится видимойMembership suspended. внутри rowSuspended status filterReactivateActiveSuspend снова виднаMembership reactivated. внутри rowActive filterCompany → MembershipsDemo Buyer SIABuyer laneAdd member, role selector, Suspend, Reactivatedemo.buyer.admin@example.comCompany → MembershipsYour current membership is protected.Suspend / Reactivatedemo.buyer.admin@example.comDemo Buyer SIAdemo.buyer.admin@example.comDemo Buyer SIAdemo.context.switcher@example.comActive company selector → выбрать другую companydemo.context.switcher@example.comDemo Both Trade SIAdemo.context.switcher@example.complatform-api.nexx.beyondhorizon.dev) · Пароль demo-юзеров: demo-pass-123 · Lursoft fixtures: LV-FOUND-1 / LV-NOTFOUND-1 / LV-DOWN-1 · Формат: куда зайти → что нажать → что увидеть → границы R1. Кликните сценарий для шагов. R1 scope: все D2.1–D2.14 passed (UI + backend verification evidence). Главная R1/R2 граница: price lists — в R1 один default price list на supplier; per-buyer/multi price lists — Phase 2/R2.Create new companyLursoft lookup / Registry lookup40003170000) → Lookup companyLV-FOUND-1 → Lookup company → market role → Create companySIA "LURSOFT IT"); UI честно отличает live от demo-fixtureРеальный Lursoft тратит quota — для повторов брать fixtures. Доп. backend-настройки (cache/audit/env) ведутся как backend evidence, не часть ручного UI acceptance.
Create new companyLV-NOTFOUND-1 или LV-DOWN-1 → Lookup companyCreate companyCreate company проходит, попадание в workspaceStrict backend verification flags / audit / Platform Admin review — backend/product evidence, не UI-клик.
Create new company → ввести LV-NOTFOUND-1 → Lookup companyLV-DOWN-1R1 lookup + fallback — passed. Доп. backend-настройки cache/audit/env ведутся как backend evidence и не блокируют R1 UI acceptance.
Company → Profiles → Buyer profileSave buyer profileWorking hours — человеческий текст; save-feedback = compact toastR1: buyer/supplier profile CRUD через backend — passed. Расширенный external sync / audit — будущий этап (роадмап), не блокирует R1.
Company → Profiles → Supplier profileCatalog → этот supplier → buyer-facing detailR1 supplier profile CRUD — passed. Strict external sync/audit = R2 / backend-evidence (как D2.3).
Company → StructureQA Warehouse <дата> + code QA-WH-<дата> + address → Create warehousePrimaryStage 2 = structure/list/create. Stock balances / accounting / финансовые потоки складов тут не ожидаются. (FIND-2: seed без default-склада — исправлено.)
Company → My catalog → Supplier catalog categoriesQA Cat <дата> → проверить в спискеQA Empty Cat <дата> и удалить еёOptional category description в текущем R1-contract нет — граница R1, не bug. FIND-3 (молчание UI при blocked-delete) — исправлено.
Company → My catalog → Supplier catalog itemsQA-ITEM-<дата>-01, name, category, net price, VAT, опц. barcode → сохранитьDeactivate → подтвердить → item inactive → Reactivate → снова activeBack to items; feedback = compact toastPhotos не поддержаны текущим contract — Phase 2/R2. Pagination/filter — story-level границы R1.
Company → My catalog → Default price listSave price list → должна появиться visible validationSave price listGET/PUT /price-lists/default/Кнопки "add price row" нет — строки приходят из catalog items. Per-buyer/personal price lists = Phase 2/post-MVP.
Catalog → supplier card (напр. Demo Supplier Food SIA)У части demo-suppliers профильные данные sparse — это fixture/data limitation, не UI-failure.
Catalog → supplier без active relationship (NoRel, если есть в demo)Request relationship виден и enabledЧеклист-ревью во время остальных D2-шагов, не отдельный клик-тест.
При необходимости попросить подтвердить, что frontend-handoff.json + OpenAPI + typed client соответствуют текущему Stage 2 (контракт v1.5.0, /contracts/).
Backend/platform evidence, не клик-сценарий. Открыть: backend issue #6 comment 4633669716 + docs/stage2-d2.13-mock-backend-evidence.md.
mock-v0 поведение + boundary notesНе доказывать кликами в UI. Принято обеими сторонами как backend-evidence accepted.
Company → My catalog: categories/items есть (10+ item demo supplier)Default price list published/active, цены/VAT заполненыCatalog → supplier с видимыми ценами: profile/card, categories/items, prices, Add-to-cartCart (группировка по suppliers, whole-cart submit) → Validate checkoutOrders → новые заказы → order detail: lines, totals, status читаемы (и на mobile)Per-supplier selective checkout в Stage 2/R1 не ожидается (whole-cart submit). Видимы: no photos, no category description, no per-buyer price lists.
platform-api.nexx.beyondhorizon.dev), автотесты + deployed smoke зелёные, контракт опубликован в /contracts/.
cbaac99→8c34f3a).
demo-pass-123 · сценарии в V2 App · Тестирование.
area= filter (BKG-009); 20-supplier HoReCa seed (BKG-016) · BKG-021 price-wording audit covered · фронт-фильтры/сортировка/поиск поставщиков/детальная карточка закрыты в gap-closeout (#6–#8) · в проде на v2 · клиентская приёмкаPOST /orders/ (per-supplier set, atomic; omit = all-or-nothing); immutable order snapshots; supplier-specific split · списки заказов buyer/supplier + фильтры статусов на фронте закрыты в gap-closeout (#9) · в проде на v2 · клиентская приёмкаorder.created IntegrationJob + retry/backoff + idempotency; read-only export block in order responses (null for legacy/seed); frontend shows Preview-marked status (v1.6.6). Mock-граница Stage 3 закрыта — live Engine connector + lifecycle events = Stage 4 (по проекту) · в проде · клиентская приёмкаverify_frontend.sh green (12 component files / 147 tests). Evidence: memory-bank/tasks/2026-06-17-FE-STAGE3-D3.12-001-e2e-acceptance-checkpoint/ · всё в проде (backend + v2) · остаётся клиентский walkthrough-приём| Time | Lvl | Method | Path | Status | ms | User | Co | Request | Response |
|---|---|---|---|---|---|---|---|---|---|
| No data — click Reload. | |||||||||
| Role | Company / id | |
|---|---|---|
| Buyer | demo.buyer.admin@example.com | Demo Buyer SIA — 1 |
| Supplier | demo.supplier.admin@example.com | Demo Supplier Food SIA — 2 |
| Both roles | demo.both.admin@example.com | Demo Both Trade SIA — 4 |
| Switcher (3 cos) | demo.context.switcher@example.com | 1 + 2 + 4 |
demo-pass-123