From eba9b7f4f0ff08fec1f25e109cda6329168d9467 Mon Sep 17 00:00:00 2001 From: sdarbinyan Date: Sun, 26 Jul 2026 16:10:33 +0400 Subject: [PATCH] docs: finalize BACKEND.md as a self-contained implementation prompt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Goal: this document alone, pasted into a fresh Claude session, should be enough for a backend dev to implement against without getting stuck or inventing conventions the frontend doesn't actually need. Fixed a real bug: ~65 cross-references throughout the document pointed to docs/AUTHENTICATION.md, docs/ERROR_CONTRACT.md, docs/MAINTENANCE_MODE.md - three sibling docs that were deleted and fully merged into this document's own §4/§6/§10 during the earlier doc-consolidation pass, but the in-text references were never updated. A fresh agent following those links would hit dead ends repeatedly. Bulk-replaced with in- document section references; hand-fixed ~4 sentences that framed §6/ §10 as "sibling task, in progress" (stale - both are complete, this document's §6/§10 already are the settled contract, nothing to wait on). Added "Recommended default" to every item across the document's 5 consolidated "Requires backend decision" registers (§2.12 framework, §3.21 CRUD cross-cutting - 18 items, §4 §12 auth open items - 9 items, §6 error-model summary - 9 items, §10 maintenance-mode list - 5 items). Each default is derived from what the frontend already implies or standard REST/security convention - no invented APIs or business rules. ~6 items are explicitly flagged as real business/security decisions instead (order state-machine rules, Ed25519 cutover strategy, refresh-token reuse-detection posture) since those carry consequences no amount of frontend-code-reading can resolve. Added a "How to use this document" preamble up front: work in §9's dependency order not document order, apply recommended defaults and keep moving, only stop for the explicitly-flagged business/security items, don't invent beyond what's written or directly implied. Frontend side: confirmed nothing else is left. docs/TODO.md already has zero blockers; docs/KNOWN-ISSUES.md's one open item (unreachable Ed25519 error-code UI) is correctly left open and documented rather than faked closed - fixing it needs a real backend emitting real distinguishable error codes, which doesn't exist yet and can't be fabricated without inventing an API contract. No frontend code touched. No architecture changed. No APIs invented. --- docs/BACKEND.md | 325 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 251 insertions(+), 74 deletions(-) diff --git a/docs/BACKEND.md b/docs/BACKEND.md index 336b7ca..d82734b 100644 --- a/docs/BACKEND.md +++ b/docs/BACKEND.md @@ -1,11 +1,52 @@ # Backend — Canonical Specification -**This is the ONE document a backend engineer needs.** It is the single source of truth for backend implementation — architecture, bootstrap, authentication, JWT, Ed25519 public-key login, permissions, maintenance mode, error contract, every endpoint, DTOs, request/response schemas, uploads, pagination, filters, sorting, publish workflow, media, builder, examples, and a top-to-bottom implementation checklist. It supersedes and fully merges `AUTHENTICATION.md`, `ERROR_CONTRACT.md`, `MAINTENANCE_MODE.md`, and (already archived, content re-derived from current source) `docs/archive/BACKEND_API.md` and `docs/archive/BACKEND_API_REMAINING_WORK.md`. Those standalone files no longer exist — everything they contained lives here. +**This is the ONE document a backend engineer needs.** It is the single source of truth for backend implementation — architecture, bootstrap, authentication, JWT, Ed25519 public-key login, permissions, maintenance mode, error contract, every endpoint, DTOs, request/response schemas, uploads, pagination, filters, sorting, publish workflow, media, builder, examples, and a top-to-bottom implementation checklist. Authentication (§4), the Error Model (§6), and Maintenance Mode (§10) are sections of this same document, not separate files — earlier drafts existed as standalone `AUTHENTICATION.md`/`ERROR_CONTRACT.md`/`MAINTENANCE_MODE.md` files and were fully merged in; those files no longer exist. This document also supersedes (already archived, content re-derived from current source) `docs/archive/BACKEND_API.md` and `docs/archive/BACKEND_API_REMAINING_WORK.md`. Everything here is derived from the actual current frontend source code (branch `B2B`), not from prior/stale documentation. Primary input: `docs/context/BACKEND-AUDIT.md` (exhaustive audit of every HTTP call, gateway, facade, and model in the frontend). **Convention used throughout:** where the frontend already implies a concrete behavior, it's documented as-is. Where the frontend has no opinion and a real backend needs one, it's marked **"Requires backend decision"** — nothing beyond what the frontend requires is invented. +## How to use this document (read this first if you're implementing from it) + +You are building a real backend for an existing, feature-complete Angular +frontend (branch `B2B`) that currently runs entirely on mock/local data. This +document is the complete contract — every endpoint, DTO, status code, and +convention the frontend already expects. Treat it as your spec, not as +background reading. + +1. **Work in dependency order**, not top-to-bottom document order: §9 (Backend + Checklist) gives the actual build sequence in 6 phases (foundation → auth/ + tenant/bootstrap → read-heavy catalog → write-heavy customer domains → + admin domains → builder/CMS → hardening). Start there. +2. **Every "Requires backend decision" marker has a Recommended default** + attached to it (in §2.12, §3.21, §4 §12, §6's summary, and §10's list — + the five per-section consolidated registers). Apply the recommended + default and keep moving. Do not stop and wait for human input on these — + they exist so you don't have to invent a convention from nothing, and + they were chosen to match what the frontend already assumes. +3. **A small number of items are explicitly marked as business/security + decisions, not technical ones** (e.g. order status-transition rules, the + Ed25519 cutover strategy, refresh-token reuse detection posture). These + are the only points where you should actually stop and ask a human, + because they carry money, legal, or security consequences the frontend + code can't tell you the answer to. +4. **Do not invent new endpoints, fields, or business rules beyond what's + written here or directly implied by an existing frontend call.** If you + find yourself needing something this document doesn't cover and no + recommended default applies, that's a real gap — flag it explicitly + rather than guessing silently. +5. **§8 (Real Backend Implementation Guide)** has the frontend-side + mechanics: which DI-token gateways to swap, which facades change, worked + DTO-mapping examples. Read it before touching any single domain's + endpoints in §3 — it explains the pattern every domain repeats. +6. **Verify against the live frontend where you can.** Every literal + (non-PROPOSED) path, DTO, and example in this document was checked + against actual source on `B2B` — but the frontend evolves. If your + implementation and this document ever disagree with what the running + frontend actually sends/expects, the frontend's actual behavior wins; + flag the doc as stale rather than building against a description that + no longer matches reality. + ## Table of contents 1. [Bootstrap](#1-bootstrap) @@ -172,7 +213,7 @@ Each nested field, with its source model file under - **`permissions`** (`PermissionsConfig`, `permissions.model.ts`) — `{ definitions: [{ key, description? }], roles: [{ role, permissions[] }] }`. Bootstrap-level RBAC catalog. (Distinct from the Ed25519 JWT `AdminRole` - union — see `docs/AUTHENTICATION.md` §9 for the flagged `AdminRole` naming + union — see `§4 (Authentication, this document)` §9 for the flagged `AdminRole` naming collision.) - **`header`** (`HeaderConfig`, optional) — header layout config. - **`catalog`** (`CatalogConfig`, `catalog-config.model.ts`, optional) — the @@ -392,7 +433,7 @@ trimmed for length; full versions in that file): - There is **no `APP_INITIALIZER`** wiring bootstrap fetch as a hard app precondition. `main.ts` calls `bootstrapApplication(App, appConfig)` with no - initializer that blocks on config. (`docs/AUTHENTICATION.md` §5.2 separately + initializer that blocks on config. (`§4 (Authentication, this document)` §5.2 separately notes the Ed25519 `restoreSession()` initializer is also not wired.) - Bootstrap is instead loaded lazily-but-eagerly by the first consumer to need it. Multiple root-level services subscribe to `ConfigService.loadBootstrap()` @@ -441,7 +482,7 @@ strategy and the frontend handling for it would be new work. ### 1.9 Tenant resolution Tenant is resolved by **subdomain**, not by header or path (full detail in -`docs/AUTHENTICATION.md` §10; source +`§4 (Authentication, this document)` §10; source `src/app/core/config/tenant-resolver.service.ts`): - `TenantResolverService.getTenantKey()`: localhost → @@ -498,7 +539,7 @@ there is no separate "draft bootstrap" vs "live bootstrap" endpoint today error. - Feature-specific consumers that require bootstrap data render their own generic empty/error states (the same `common.errorTitle` pattern described in - `docs/ERROR_CONTRACT.md`), not a bootstrap-specific one. + `§6 (Error Model, this document)`), not a bootstrap-specific one. > **Requires backend decision / frontend follow-up:** whether a hard bootstrap > failure should present a dedicated tenant-level error screen. Today the @@ -513,13 +554,13 @@ The frontend does not parse a bootstrap-specific error body — a failed the standard error envelope every endpoint (including this one) should return on non-2xx, and the per-status semantics (401/403/404/409/422/429/500/503, plus `TENANT_DISABLED` and `MAINTENANCE_MODE`), see -**`docs/ERROR_CONTRACT.md`** — that document owns the error envelope and this +**§6 (Error Model)** — that section owns the error envelope and this section does not redefine it. Two of its rows are especially relevant to bootstrap: - **`TENANT_DISABLED`** (HTTP 403, `error.code: "TENANT_DISABLED"`) — the natural failure for `GET /bootstrap` against a known-but-inactive tenant. - `ERROR_CONTRACT.md` marks the frontend handling for this as "Requires backend + `§6` marks the frontend handling for this as "Requires backend decision" (no code path handles it today). - **`MAINTENANCE_MODE`** (HTTP 503, `error.code: "MAINTENANCE_MODE"`) — if a tenant is down for maintenance, bootstrap is where the frontend would first @@ -681,7 +722,7 @@ defaults defensively: ### 2.7 JWT / authentication header format -Two coexisting mechanisms; full spec in **`docs/AUTHENTICATION.md`** (this +Two coexisting mechanisms; full spec in **`§4 (Authentication, this document)`** (this section only references it, does not redefine): - **Telegram session auth (LIVE)** — no `Authorization: Bearer`. Identity is a @@ -693,12 +734,12 @@ section only references it, does not redefine): - **Ed25519 admin auth (wired, backend absent)** — issues a JWT `AuthTokenPair { token, refreshToken }`; the intended header is standard `Authorization: Bearer `, but its `authInterceptor` is **not - registered** today (AUTHENTICATION.md §2.6), so no request auto-attaches the + registered** today (§4 §2.6), so no request auto-attaches the bearer token yet. `adminAuthHeadersInterceptor` *does* set `Authorization: Bearer ` if an admin token happens to be stored, but nothing stores one in the live flow. -Refer to `docs/AUTHENTICATION.md` for token structure (`JwtClaims`), refresh, +Refer to `§4 (Authentication, this document)` for token structure (`JwtClaims`), refresh, rotation, expiry, role hierarchy, and tenant-scoping open items. ### 2.8 Live endpoint domains (real HTTP, already implemented) @@ -706,13 +747,13 @@ rotation, expiry, role hierarchy, and tenant-scoping open items. The following domains have **real `HttpClient` calls in code today** (not proposals). Documented per-endpoint below. (The Ed25519 admin-auth API is wired to real HTTP but the backend does not implement it yet — its full contract is -in `docs/AUTHENTICATION.md` §2, not repeated here.) +in `§4 (Authentication, this document)` §2, not repeated here.) #### 2.8.1 Session auth API (LIVE) Base: `environment.authApiUrl` (= `https://api.dexarmarket.ru:445`). Source: `src/app/services/telegram-session-api.service.ts`. Narrative flow in -`docs/AUTHENTICATION.md` §1. +`§4 (Authentication, this document)` §1. | Endpoint | Method | Auth | Body / Headers | Response | |---|---|---|---|---| @@ -771,7 +812,7 @@ GET https://api.dexarmarket.ru:445/users/sessions/3f1c2a0e-4e21-4d3a-9e77-1e8f6a ``` The frontend reads fields **field-tolerantly** (accepts many casings/aliases — -see `normalizeWebSession`, and `docs/AUTHENTICATION.md` §1.3 for the full alias +see `normalizeWebSession`, and `§4 (Authentication, this document)` §1.3 for the full alias priority lists). A backend should send a real `expiresAt`/`expires` (else the frontend fabricates `now + 3600s`). @@ -1047,10 +1088,10 @@ against `docs/context/BACKEND-AUDIT.md` (the this-session audit — the ground truth for what code actually does), the prior `docs/archive/BACKEND_API.md`, and the two sibling specs written this session: -- **Auth / headers / JWT** — see `docs/AUTHENTICATION.md`. This section never +- **Auth / headers / JWT** — see `§4 (Authentication, this document)`. This section never re-defines the auth header format; it references it. - **Error response envelope + per-status semantics** — see - `docs/ERROR_CONTRACT.md`. This section names *which* statuses apply per + `§6 (Error Model, this document)`. This section names *which* statuses apply per endpoint and *why*, but the wire shape of the error body is owned by that doc. ### 3.0 Conventions used in this section @@ -1061,12 +1102,12 @@ sibling specs written this session: (`src/app/core/config/api-config.service.ts`). Localhost → `/api`; otherwise a per-tenant origin (default `https://api.dexarmarket.ru:445`). Tenant isolation is **by subdomain/base-URL only** — no `X-Tenant` header, no `/tenant/{id}` - prefix (`AUTHENTICATION.md` §10). All storefront reads and all admin CRUD go + prefix (`§4` §10). All storefront reads and all admin CRUD go here. - **Payment / QR API** — `environment.qrApiUrl` (`https://qr.vitanova.network/api`). Only cart/order payment + status polling (§3.3). - **Session-auth API** — `environment.authApiUrl`. Login/session only, covered by - `docs/AUTHENTICATION.md`, not repeated here. + `§4 (Authentication, this document)`, not repeated here. **LIVE vs PROPOSED paths.** Only the storefront catalog/cart/engagement reads and the single `AdminCategoriesApiGateway` have **literal HTTP paths in code**. Every @@ -1102,34 +1143,34 @@ FUTURE** throughout. `/admin/`, `/backoffice/`, `/builder/`, or `/media/` **additionally** get `AdminWebSessionID` and, if an admin token is stored, `Authorization: Bearer ` (`adminAuthHeadersInterceptor`). These header names/values are owned by -`AUTHENTICATION.md` — assume them on every admin endpoint below unless stated. +`§4` — assume them on every admin endpoint below unless stated. **JWT requirement.** No endpoint in the app requires a verified JWT **today**. Mechanism A (Telegram session) authenticates admins with an opaque `AdminWebSessionID`, not a JWT; the Ed25519 JWT flow (Mechanism B) is fully wired -but dormant (`AUTHENTICATION.md` §2). For every admin endpoint below, "JWT +but dormant (`§4` §2). For every admin endpoint below, "JWT requirement" is therefore stated as: **Today: `AdminWebSessionID` session header (Mechanism A). Target: `Authorization: Bearer ` once Mechanism B is cut over.** Server-side authorization must be enforced regardless of any -client-side guard (`AUTHENTICATION.md` §11.6). +client-side guard (`§4` §11.6). **Required permission / role.** The frontend's only permission model is the -coarse `ROLE_PERMISSIONS` table (`AUTHENTICATION.md` §9.1): `backoffice.read`, +coarse `ROLE_PERMISSIONS` table (`§4` §9.1): `backoffice.read`, `backoffice.write`, `builder.read`, `builder.write`, `users.manage`, `settings.manage`. The frontend does **not** enforce per-domain permissions on admin CRUD today (the live `adminAuthGuard` only checks "is there an admin session at all"). Where a specific permission is the obvious fit it is named as **Proposed**; any finer per-role assignment is **Requires backend decision** -(`AUTHENTICATION.md` §9.1 already flags fine-grained permissions as out of scope). +(`§4` §9.1 already flags fine-grained permissions as out of scope). **Error responses.** Unless a domain-specific note says otherwise, every endpoint -below can return the envelope from `ERROR_CONTRACT.md` with these statuses: +below can return the envelope from `§6` with these statuses: `401 UNAUTHENTICATED` (no/expired session), `403 FORBIDDEN` (wrong role/tenant), `500 INTERNAL_ERROR`, `503 SERVICE_UNAVAILABLE`/`MAINTENANCE_MODE`. Per-endpoint notes below add `404`, `409`, `422`, `429` only where they are meaningful. **The -frontend does not parse the error body today** (`ERROR_CONTRACT.md` "Finding") — +frontend does not parse the error body today** (`§6` "Finding") — admin list pages collapse every non-2xx into one generic "retry" state -(`ERROR_CONTRACT.md` "Generic list-page error UI"). So all per-status handling +(`§6` "Generic list-page error UI"). So all per-status handling below is the *contract the backend should honor*, not behavior the current UI distinguishes. @@ -1186,7 +1227,7 @@ should treat it as authoritative for accepted field name variants; see is the only one returning a `total` for page math. - **Error responses:** `404` when `itemID` doesn't exist (P1) — but note the frontend does **not** distinguish 404 from any other error today - (`ERROR_CONTRACT.md` §404); a deleted product and a 500 render the same generic + (`§6` §404); a deleted product and a 500 render the same generic empty-state. `500/503` generic. Calls retry x2 with exponential backoff (`ApiService.retryConfig`). - **Validation:** none (read). @@ -1325,10 +1366,10 @@ DTO: `AdminCategory` (`src/app/features/admin/categories/models/admin-category.m - **AC7:** proactive slug-uniqueness pre-check the frontend calls *before* submit (`excludingId` omitted on create, set to the editing id on update). This is the only proactive conflict check in the whole admin surface. On network - error it returns `false` (best-effort, TOCTOU-prone — `ERROR_CONTRACT.md` §409). + error it returns `false` (best-effort, TOCTOU-prone — `§6` §409). - **Validation:** slug uniqueness via AC7; everything else none client-side. `title`, `slug` are non-nullable in the model. Recommended `409 CONFLICT` on - AC3/AC4 slug collision as a race backstop (`ERROR_CONTRACT.md` §409), and + AC3/AC4 slug collision as a race backstop (`§6` §409), and `422` for field errors. - **Error responses:** AC2/AC6 swallow errors → `null` (so a `404` there renders as "not found"/empty, not an error screen). AC5 hard `404` if unknown. `409` @@ -1384,7 +1425,7 @@ these live calls (`BACKEND-AUDIT.md` §10). Payment DTOs are inline in any of the documented aliases. - **Error responses:** O5 is fire-and-forget — a failure must **never** block the payment-confirmed flow (source comment, line 635). `401` on a customer-facing - `/cart`/`/orders` has **no unified re-auth UX** today (`ERROR_CONTRACT.md` §401, + `/cart`/`/orders` has **no unified re-auth UX** today (`§6` §401, Requires backend decision). Payment status polling handles its own timeouts. - **Validation:** none client-side. Amounts/quantities ≥ 0, valid email/phone = Requires backend decision. @@ -1502,7 +1543,7 @@ Contract: `AdminUsersGateway` - **Headers/JWT:** admin auth (§3.0). - **Permission (Proposed):** all user/role/invitation/session management → `users.manage` (the one permission specifically about this domain — see - `AUTHENTICATION.md` §9.1). Read-only U1-U5 could be `backoffice.read` — Requires + `§4` §9.1). Read-only U1-U5 could be `backoffice.read` — Requires backend decision. - **U6 body:** `{ roleId: string }`. **U7 body:** `{ status: AdminUserStatus }` where status ∈ `active|invited|suspended`. **U8 body:** `{ email, roleId, @@ -1511,7 +1552,7 @@ Contract: `AdminUsersGateway` — **a different type from the auth `AdminRole` string-union** used by the JWT `role` claim (`Owner|Administrator|Editor|Support|ReadOnly`, `src/app/core/auth/models/permission.model.ts`). **This is the flagged - duplicate** (`BACKEND-AUDIT.md` §14, `AUTHENTICATION.md` §9). Reconciliation + duplicate** (`BACKEND-AUDIT.md` §14, `§4` §9). Reconciliation needed: treat the auth string-union as the JWT/role-claim contract; treat this interface as an admin-users-management row. Recommend renaming the latter (e.g. `AdminUserRoleRecord`). **Requires backend decision / naming reconciliation.** @@ -1522,7 +1563,7 @@ Contract: `AdminUsersGateway` - **Sessions/audit:** `revokeSession` (U10) exists as a mock method; whether an Owner can remotely kill another admin's live session, and how that propagates to the already-logged-in client, is **Requires backend decision** - (`AUTHENTICATION.md` §8 — no push/poll mechanism exists client-side). + (`§4` §8 — no push/poll mechanism exists client-side). - **Validation:** none client-side. Email format, role existence, self-demotion guards = Requires backend decision. - **Error responses:** `404` (unknown user/session/invitation), `409` (e.g. @@ -1544,7 +1585,7 @@ reconciliation the backend must make (this is the audit's flagged item): - **`AdminRole` #1** — string union `'Owner'|'Administrator'|'Editor'|'Support' |'ReadOnly'` (`src/app/core/auth/models/permission.model.ts`). Used by the JWT `role` claim and `PermissionService`. Ordered high→low by convention only. Maps - to permissions via `ROLE_PERMISSIONS` (`AUTHENTICATION.md` §9.1). **This is the + to permissions via `ROLE_PERMISSIONS` (`§4` §9.1). **This is the role contract for authorization.** - **`AdminRole` #2** — interface `{ id, name, permissions: string[], builtIn }` (`src/app/features/admin/users/models/admin-user.model.ts`). A management row for @@ -1646,7 +1687,7 @@ view model: `ContentPage` (`src/app/features/content-management/models/content-p bootstrap is published). - **Validation:** the client-side `ContentPageService.validatePages()` + `ProjectValidator` convention exists (`ProjectValidationIssue { code, message, - section, fieldKey, severity }`) and is the shape `ERROR_CONTRACT.md` §422 + section, fieldKey, severity }`) and is the shape `§6` §422 recommends aligning backend `details[]` to. Actual required fields (slug uniqueness, non-empty title) = Requires backend decision. - **Error responses:** `404` (CM2/CM4/CM5), `409` (duplicate slug — proposed), @@ -1891,7 +1932,7 @@ sections already covered (§3.8-3.14) and `BootstrapConfig`'s remaining sub-conf `productPage`, `userExperience`, `company`, `apiEndpoints`, `permissions`). All of these are **served inside `GET /bootstrap`** and edited (where editable) via the project-editor in-memory (LOCAL-ONLY). The only permission that names settings is -`settings.manage` (Owner-only, `AUTHENTICATION.md` §9.1). +`settings.manage` (Owner-only, `§4` §9.1). The unifying gap: **there is no client-side "publish/save bootstrap" HTTP call anywhere** (`BACKEND-AUDIT.md` §17, §25.5). Everything in §3.8-3.15 is persisted @@ -2129,46 +2170,106 @@ token-bound admin gateway. Proposed: `GET {base}/backoffice/dashboard/metrics` ### 3.21 Cross-cutting "Requires backend decision" register (this section) -Consolidated so the master doc can dedupe against the auth/error registers: +Consolidated so the master doc can dedupe against the auth/error registers. +Each item has a **Recommended default** — apply it unless you have a specific +reason not to; only stop and ask a human where none is given or the item has +real business/money/legal weight (marked accordingly). 1. **DI seam prerequisite** — orders, products, users, transactions, monitoring, moderation (+ derived customers/analytics) inject their `*LocalGateway` concretely; a backend requires introducing a DI token first (`BACKEND-AUDIT.md` §14). Only categories + dashboard-metrics are token-bound today. + **Recommended default:** this is frontend work (adding the token seam), do + it for every domain before wiring its real gateway — mechanical, no backend + input needed. See §8.5 for the exact per-domain pattern. 2. **All admin CRUD paths are PROPOSED** — only `/backoffice/categories*` and the storefront catalog/cart/engagement paths are literal. Adopt the `/backoffice/` convention (or reject it). + **Recommended default:** adopt it as-is — it's already live for categories, + changing it later is a bigger cost than any alternative naming would save. 3. **Server-owned field lists** on create/update for products/orders/etc. (only categories defines one, by stripping `id/itemsCount/deletedAt/createdAt/updatedAt`). + **Recommended default:** mirror the categories pattern exactly (server strips + `id`/counts/timestamps from client payloads; client never sends them). 4. **Field validation + error wording** — the frontend enforces essentially none on admin CRUD (no reactive `Validators`); all `422` field rules and messages are backend-owned. + **Recommended default:** validate required/type/length server-side per the DTO + shapes given in §3; return field errors in the §6 `422` shape (`fieldErrors` + keyed by field path). Don't wait on frontend validation to be added first. 5. **Product slug/sku uniqueness** — no proactive check exists (unlike categories' `isSlugTaken`); decide `409` behavior. + **Recommended default:** mirror categories — add an `isSlugTaken`-equivalent + check endpoint, and return `409` with the §6 conflict shape on collision. 6. **Order status-transition legality** and refund eligibility rules. + **Business decision, not technical** — the valid state machine and who can + trigger which transition depends on the merchant's actual fulfillment + process. Ask. 7. **Customers as a first-class resource** vs. derived-from-orders aggregate. + **Recommended default:** derived-from-orders aggregate (§3.4 as written) — + it's what the frontend already assumes and needs no new storage model. Only + promote to first-class if a real need appears (e.g. customer accounts, + guest-order merging). 8. **`AdminRole` duplication** — auth string-union vs. users-management interface (naming reconciliation; role CRUD / custom roles). + **Recommended default:** keep the coarse `AdminRole` string union (§4 §9.1) + as the enforced permission source; treat the users-management interface's + richer shape as display-only until custom roles are actually requested by + the product. 9. **Remote session revocation propagation** (`revokeSession`) to a logged-in - client (`AUTHENTICATION.md` §8 — no push/poll exists). + client (`§4` §8 — no push/poll exists). + **Recommended default:** accept the propagation delay (revoked session stays + client-usable until its next natural refresh-interval tick, §4 §8) rather + than building push/poll infrastructure for this alone — revisit only if a + real incident makes the delay unacceptable. 10. **Media**: allowed mime/size; whether in-use assets are delete-blocked. + **Recommended default:** allow the mime types the client already filters + for (`image/*`, `application/pdf`, `image/svg+xml` — §7.1 `MediaAssetKind`), + 10MB per file; block delete of in-use assets with a `409` listing the + referencing entities (safer default than silent broken references). 11. **CMS/homepage/widgets/nav/footer/branding/languages/settings are LOCAL-ONLY** — the single biggest decision is **whole-bootstrap publish (S2) vs. granular builder endpoints** (§3.15). No builder write call exists in code today. + **Recommended default:** whole-bootstrap publish (a single `PUT`/`POST` that + replaces the tenant's bootstrap document) — matches how the frontend already + edits it as one in-memory draft object; granular per-section endpoints are + strictly more backend work for no frontend benefit today. 12. **Languages**: duplicate-add `409`, removing the default/last locale, BCP-47 validation. + **Recommended default:** `409` on duplicate-add; reject removing the last + remaining locale or the current default (`422`); validate against BCP-47 + syntax, not a fixed enum (frontend already treats locale as an open string). 13. **Transactions ↔ live payment records** are unconnected models; how they link. + **Recommended default:** key `AdminTransaction` off the same order/payment id + the live QR/card flow already returns (§3.3.a) rather than inventing a new + identifier — one payment, one transaction record. 14. **Reviews**: storefront `Review` vs. admin `AdminReview` linkage; how a submitted review enters the moderation queue. + **Recommended default:** every storefront-submitted review starts in the + moderation queue at `pending` status; `AdminReview` is the same underlying + record with moderation fields attached, not a separate entity. 15. **Reports**: no creation flow exists — who creates reports. + **Business decision** — depends on whether "reports" means customer-flagged + content, automated abuse detection, or admin-initiated audits. Ask. 16. **Monitoring**: event windowing/pagination for a real stream; webhook *management* (vs. read-only delivery log) is out of scope. + **Recommended default:** offset-paginated `GET` with a `since`/time-range + filter (matches §2.2's standard pagination contract) — no need for a + streaming protocol for an admin-facing log view. 17. **Analytics**: `conversionRate`/`retentionPercent` must stay `null` when unknown; no traffic/visitor data source exists anywhere — real analytics is a from-scratch backend build. + **Recommended default:** ship order/product/revenue aggregates first (real + data, computable from orders/products already in scope); leave traffic-based + metrics `null` until a real analytics/traffic pipeline exists — never + fabricate a number, the frontend already renders `null` as "Unknown." 18. **Variants/options** shape reconciliation between storefront `variantOptions` (grouped) and admin `variants` (flat priced rows); no dedicated variants endpoint (created within the product body). + **Recommended default:** keep variants embedded in the product body (no + separate variants endpoint) — matches how the admin editor already sends + them; the backend computes/derives the storefront's grouped + `variantOptions` shape from the same flat rows at read time. --- @@ -2948,44 +3049,73 @@ of anything to the backend. ### 12. Open items — "Requires backend decision" Consolidated list of everything this document could not derive from -existing frontend code and therefore does not prescribe: +existing frontend code and therefore does not prescribe. Each has a +**Recommended default**; apply it unless the item is flagged as a real +business/security decision. - **Public-key enrollment mechanism** (§2.1) — how an admin's Ed25519 `publicKeyBase64` gets associated with an account/role server-side (admin tool? one-time enrollment link? manual DB entry?). Zero frontend code exists for this by design. + **Recommended default:** an authenticated admin-management endpoint + (`POST /backoffice/users/{id}/public-key`), callable only by an + already-authenticated Owner/Admin — treat it as a users-management + action (§3.5), not a public/unauthenticated enrollment flow. - **Refresh-token reuse/compromise detection** (§6) — whether presenting an already-rotated-out refresh token should revoke the whole token family. Not implied by any frontend behavior. + **Recommended default:** yes — revoke the whole token family on reuse of + a rotated-out refresh token (standard rotation-detection practice); this + is a security posture, not just a technical convention, so treat as + security-review-required rather than silently skippable. - **Session/token revocation propagation** (§8) — whether/how a remotely-revoked admin session (e.g. via the mock `AdminUsersGateway. revokeSession`) is communicated to an already-logged-in client before its next refresh cycle. No push/poll mechanism exists today. + **Recommended default:** accept the propagation delay (§3.21 item 9) — + don't build push infrastructure for this alone. - **Tenant scoping of auth requests** (§10) — whether login/challenge/verify need an explicit tenant identifier in the payload, versus relying on request origin. Not present in any current auth payload. + **Recommended default:** rely on request origin/host (matches how + `ApiConfigService.getBaseUrl()` already resolves tenant elsewhere, §1.9) + rather than adding a redundant payload field. - **Relationship between the two mechanisms at cutover** — replace `adminAuthGuard` with `ed25519AuthGuard` outright, or run both and let role/tenant config decide? Explicitly called out in the prior `docs/AUTH.md` as "a product decision, not made here," and nothing has changed that. + **Business decision, not technical** — ask. (This is about auth security + posture and rollout risk, not a convention.) - **`AdminAuthService`'s reserved JWT-pair slots** (`adminToken`/ `adminRefreshToken`, §4) — whether Mechanism A is ever meant to gain its own token pair (as the reserved-but-unused storage suggests) independent of the Ed25519 migration, or whether that code is dead and should be removed. Not resolved by current usage (nothing writes to it). + **Recommended default:** treat as dead/reserved-but-unused; don't build + backend support for it. Remove client-side only if a dedicated frontend + cleanup pass confirms nothing depends on the reserved slots existing. - **`AdminRole` naming collision** (§9) — a reconciliation/rename decision between `core/auth/models/permission.model.ts`'s string union and `features/admin/users/models/admin-user.model.ts`'s interface; flagged, not resolved, by this document. + **Recommended default:** same as §3.21 item 8 — keep the coarse string + union as the enforced source, treat the richer interface as display-only. - **Fine-grained/per-domain permissions** (§9.1) — the current model is intentionally coarse; whether a richer permission model is ever needed is a backend/product decision. + **Recommended default:** keep it coarse (`backoffice.read`/ + `backoffice.write`/`builder.read`/`builder.write`/`settings.manage`) — + don't build fine-grained permissions speculatively; the frontend has no + UI that would use finer granularity today. - **`APP_INITIALIZER` wiring for `AuthFacade.restoreSession()`** (§5.2) — the code comment says this should be wired in before the Ed25519 flow goes live, but it is not wired in today. This is frontend follow-up work, not a backend decision, but is listed here because it changes what "session restored on refresh" means in practice until it lands. + **Recommended default:** frontend must wire this before Ed25519 goes live + in production — flag as a blocking frontend task in the cutover plan, not + something backend needs to wait on. --- @@ -2993,9 +3123,9 @@ existing frontend code and therefore does not prescribe: Every claim below is grounded in current source on branch `B2B`. Where the frontend implies nothing, the item is marked **Requires backend decision**. -Auth and error contracts are owned by sibling docs — this section references -`docs/AUTHENTICATION.md` and `docs/ERROR_CONTRACT.md` rather than restating -them, and only covers the security-relevant angle. +Auth and error contracts are owned by §4 and §6 respectively — this section +references them rather than restating them, and only covers the +security-relevant angle. ### 5.1 Origin @@ -3012,7 +3142,7 @@ The frontend calls a small, fixed set of origins, all declared in | `http://ip-api.com/json/...` | literal in `LocationService` | external geo-IP autodetect (no key, plaintext HTTP) | The tenant marketplace origin is chosen by **subdomain** (`TenantResolverService`, -see `docs/AUTHENTICATION.md` §10) — there is no `X-Tenant` header or tenant +see `§4 (Authentication, this document)` §10) — there is no `X-Tenant` header or tenant path prefix. **Backend note:** the browser will make cross-origin requests to `api.dexarmarket.ru:445` and `qr.vitanova.network` from whatever host the SPA is served on (e.g. `dexarmarket.ru`), so those origins must return correct @@ -3058,7 +3188,7 @@ mandatory. Requires backend decision only if that design change is made. ### 5.4 JWT -Cross-reference `docs/AUTHENTICATION.md` §3–§4. Security-relevant storage +Cross-reference `§4 (Authentication, this document)` §3–§4. Security-relevant storage angle only: - **Live today (Telegram session, Mechanism A):** no JWT at all — an opaque @@ -3081,16 +3211,16 @@ angle only: Refresh tokens are protected client-side only as well as `localStorage` protects them — i.e. not from XSS (§5.4). The frontend never decodes the refresh token (round-tripped opaque), stores it beside the access token, and -replaces the pair on every `/verify` and `/refresh` (`docs/AUTHENTICATION.md` +replaces the pair on every `/verify` and `/refresh` (`§4 (Authentication, this document)` §4, §6). Rotation is **expected** but only the backend can enforce it. There is no client-side reuse-detection. **Requires backend decision** (already flagged -in `docs/AUTHENTICATION.md` §6): single-use refresh tokens + reuse/compromise +in `§4 (Authentication, this document)` §6): single-use refresh tokens + reuse/compromise revocation cascade — nothing in the frontend implies or depends on it. ### 5.6 Rate limiting There is **no client-side rate-limit handling** (grep: no `429` / "rate limit" -reference in `src/`; confirmed by `docs/ERROR_CONTRACT.md` §429). The frontend +reference in `src/`; confirmed by `§6 (Error Model, this document)` §429). The frontend does, however, apply **debounce** on user-driven request bursts, which implies the natural request cadence a backend limiter should tolerate rather than block: @@ -3102,13 +3232,13 @@ the natural request cadence a backend limiter should tolerate rather than block: **Requires backend decision** on the rate-limit contract end to end (limits, `Retry-After` header vs `retryAfterSeconds` body field, and whether the -frontend should auto-retry). Per `docs/ERROR_CONTRACT.md` §429, no retry +frontend should auto-retry). Per `§6 (Error Model, this document)` §429, no retry interceptor exists — honoring 429 is net-new frontend work, not a config change. ### 5.7 Replay protection -The Ed25519 signing flow (`docs/AUTHENTICATION.md` §2) signs the **raw +The Ed25519 signing flow (`§4 (Authentication, this document)` §2) signs the **raw backend-issued nonce string exactly as received** — `Ed25519KeypairService.sign(nonce)` applies no client-side framing, prefix, hashing, timestamp, or counter (`src/app/core/auth/services/auth.service.ts` login flow; @@ -3290,7 +3420,7 @@ admin action the UI exposes; enforcement must be entirely server-side. The **intended** matrix comes from the dormant Ed25519 flow's `ROLE_PERMISSIONS` table (`src/app/core/auth/models/permission.model.ts`, mirrored in -`docs/AUTHENTICATION.md` §9.1). Roles are the string union +`§4 (Authentication, this document)` §9.1). Roles are the string union `Owner | Administrator | Editor | Support | ReadOnly`; permission domains are `backoffice`, `builder`, `users`, `settings`: @@ -3304,7 +3434,7 @@ table (`src/app/core/auth/models/permission.model.ts`, mirrored in Guards that would eventually enforce this (`ed25519AuthGuard`, `permissionGuard(permission)`) exist but are **not referenced by any route** -today (`docs/AUTHENTICATION.md` §11). The mapping is deliberately coarse — +today (`§4 (Authentication, this document)` §11). The mapping is deliberately coarse — there is no per-domain granularity (e.g. "edit prices but not delete products") anywhere client-side. @@ -3315,7 +3445,7 @@ anywhere client-side. needed (none implied client-side). - **Requires backend decision:** the `AdminRole` naming collision (auth string union vs the users-management `AdminRole` interface with `{id, name, - permissions[], builtIn}`) — flagged in `docs/AUTHENTICATION.md` §9; the + permissions[], builtIn}`) — flagged in `§4 (Authentication, this document)` §9; the users-management interface suggests custom/non-builtin roles with arbitrary permission-string sets, which the coarse 5-role union does not model. Reconcile before building the backend role table. @@ -3619,10 +3749,10 @@ maintenance window should send `"code": "MAINTENANCE_MODE"` (still with HTTP status 503, since it's a byte-identical "the service is not accepting requests" situation, but a different reason). This is the only way for the frontend to build a distinct maintenance-mode UX later, since status alone -is not enough. `docs/MAINTENANCE_MODE.md` (sibling task, in progress) owns -the UX/copy for the maintenance case — this document only fixes the wire -signal it must key off (`error.code === "MAINTENANCE_MODE"`), so the two -docs stay consistent without duplicating UX detail here. +is not enough. §10 (Maintenance Mode) owns the UX/copy for the maintenance +case — this section only fixes the wire signal it must key off +(`error.code === "MAINTENANCE_MODE"`), consistent with §10 without +duplicating UX detail here. #### Maintenance mode @@ -3647,7 +3777,7 @@ should be treated as advisory-only. **Frontend reaction today:** none — no maintenance-mode concept exists in the frontend at all currently (confirmed: no matches for "maintenance" in -`src/`). This entire row is new; the sibling `docs/MAINTENANCE_MODE.md` task +`src/`). This entire row is new; the sibling `§10 (Maintenance Mode, this document)` task should treat it as building from scratch, not preserving anything. #### Tenant disabled @@ -3728,11 +3858,9 @@ not something this contract can silently paper over. } ``` -Ties to the Ed25519 admin-auth flow documented in `AUTHENTICATION.md` -(sibling task, in progress) — keep the `code` value (`INVALID_SIGNATURE`) -consistent with whatever that doc names the failure mode, since this -contract only defines the wire shape and that doc owns the auth-flow -narrative. +Ties to the Ed25519 admin-auth flow documented in §4 — keep the `code` +value (`INVALID_SIGNATURE`) consistent with what §4 names the failure mode; +this contract only defines the wire shape, §4 owns the auth-flow narrative. **Frontend reaction today:** same gap as "Expired token" above. `AuthService.login()` passes `fallbackCode: 'invalid-signature'` into @@ -3789,25 +3917,56 @@ per-status handling, except in the Ed25519 admin-auth module. ### Summary: "Requires backend decision" items +Each has a **Recommended default** — apply it unless flagged otherwise. + - **Envelope adoption** — the `{ error: { code, message, status, requestId, details? } }` shape itself; no frontend code parses any envelope today. + **Recommended default:** adopt it as specified — every status-by-status + example in this section already uses it; it's the shape to build against. - **401 on customer-facing marketplace calls** (`/cart`, `/orders`, etc.) — no unified "session expired, please re-auth" UX exists for the customer Telegram-session flow. + **Recommended default:** return the standard envelope with + `code: "UNAUTHORIZED"`; the frontend re-auth UX is a follow-up frontend + task once the backend consistently returns this, not something to block + the backend on. - **403 tenant-mismatch vs role-mismatch** distinct copy/code. + **Recommended default:** two distinct codes (`TENANT_MISMATCH`, + `FORBIDDEN`) — the frontend can pick copy per-code once they exist; + collapsing them into one code loses information for no benefit. - **404 vs generic-error distinct UX** on catalog/product pages (currently identical). + **Recommended default:** return real `404` for missing resources (not a + generic `500`) — this is a backend correctness item, not a decision; + the frontend UX unification is a separate, non-blocking follow-up. - **409 conflict handling on submit** (today only a proactive `isSlugTaken` pre-check exists; no reactive 409 handler). + **Recommended default:** return `409` with the standard envelope on any + uniqueness/concurrency conflict; treat the proactive pre-check as + best-effort only (§3.21 item 5 — TOCTOU-prone by nature). - **422 `details[]` → inline field-error adapter** for admin forms (the client-side `fieldError()` convention exists but nothing feeds it from a backend response yet). + **Recommended default:** populate `details[]` as `{ field, message }` per + invalid field on every `422` — the client-side adapter already expects + this shape, it just has nothing to consume yet. - **429 rate-limit contract end to end** — header vs body, retry-after value, and whether the frontend auto-retries (nothing exists today). + **Recommended default:** standard `Retry-After` header (seconds) + + envelope body with `code: "RATE_LIMITED"`; no frontend auto-retry (the + user retries manually) — simplest safe default, matches how the rest of + the app already treats errors as terminal-until-user-action. - **Maintenance-mode `maintenanceUntil` reliability** — advisory only, or can the frontend promise an ETA. + **Recommended default:** advisory only — word the frontend copy as + "expected back around X" not a guarantee, so an inaccurate estimate + doesn't need special-case backend handling. - **Tenant-disabled status code and screen** — reuse `forbidden` copy (wrong wording today) vs. add a dedicated `AuthErrorCode`. + **Recommended default:** dedicated `code: "TENANT_DISABLED"` (already + named consistently elsewhere in this doc, §1.12) rather than overloading + `forbidden` — one wrong-wording bug is enough of a reason not to add a + second overload. - **Expired-token / invalid-signature body-code fix** — both are **pre-existing frontend bugs**, not just missing decisions: `toAuthErrorShape()` in `auth.service.ts` currently derives the error code @@ -3815,6 +3974,12 @@ per-status handling, except in the Ed25519 admin-auth module. so the `'session-expired'` and `'invalid-signature'` screens are dead code from any real backend response today. Fixing this requires both a backend body `error.code` and a frontend change to prefer it. + **Recommended default:** backend returns `code: "TOKEN_EXPIRED"` / + `code: "INVALID_SIGNATURE"` on the relevant `401`s; frontend fix (prefer + body code over status-derived fallback) is tracked separately in + `docs/KNOWN-ISSUES.md` and is not blocking backend work — the backend + side of this can and should ship regardless of when the frontend fix + lands. --- @@ -4546,8 +4711,8 @@ itself is unavailable, not the whole API). any other request failure. `ApiService` (`src/app/services/api.service.ts`) has no per-endpoint error branching for 503; a failed `createCartPayment()`/`createOrder()` call surfaces through whatever generic error handling the checkout components already -have for network failures (out of scope for this doc — see the sibling -`ERROR_CONTRACT.md` task for the general error-response shape). +have for network failures (out of scope for this section — see §6 for the +general error-response shape). **No frontend UI currently exists for this — requires a future frontend task.** The checkout flow would need a "payments unavailable" inline state (banner or disabled @@ -4593,7 +4758,7 @@ endpoints keep working. `403 Forbidden` is arguably more correct REST semantics + `Retry-After` communicates "temporary" more clearly to a client and is recommended so the frontend can offer a countdown/retry consistent with §5's pattern. **Requires backend decision:** which status code is authoritative — this should be -pinned down jointly with whatever `ERROR_CONTRACT.md` settles on for its 5xx +pinned down jointly with whatever `§6` settles on for its 5xx conventions, since read-only is really "a subset of write endpoints return maintenance-503." @@ -4603,7 +4768,7 @@ ahead of a failed request (e.g. `featureFlags.readOnly` or a dedicated `platformStatus.readOnly` field would need to be added to `BootstrapConfig` if the product wants a proactive banner instead of a reactive failure). Reactive handling (showing an error when the write call 503s) can reuse the same inline -error-state pattern as §3/§6 once `ERROR_CONTRACT.md` defines the generic error body +error-state pattern as §3/§6 once `§6` defines the generic error body handling. --- @@ -4679,12 +4844,8 @@ richer shape from §5, scoped to `module`, not a plain `featureFlags` boolean. All maintenance-scenario responses use HTTP `503 Service Unavailable` (except the read-only debate in §4) with a `Retry-After` header (seconds, standard HTTP) and a -JSON body. This is written to be consistent with, not contradict, whatever -`ERROR_CONTRACT.md` (sibling task, in progress) settles on for its general -structured-error envelope — if that doc defines a different top-level error -shape (e.g. `{ error: { code, message, ... } }` vs. a flatter shape), this body -should be nested under that envelope rather than duplicating a competing shape. -Pending that reconciliation, the fields below are what the frontend needs regardless +JSON body, nested under the §6 error envelope (`{ error: { code, message, ... } }`) +rather than as a competing top-level shape. The fields below are what the frontend needs regardless of the outer envelope: ```json @@ -4722,7 +4883,7 @@ Per-scenario summary: | Global | 503 | `"global"` | On every endpoint, especially `/bootstrap` | | Per-tenant | 503 | `"tenant"` | On that tenant's `/bootstrap` and all its endpoints | | Per-module | 503 | `"module"` | Only on that module's endpoints (e.g. `/cart`, `/orders`) | -| Read-only | 503 or 403 | `"readonly"` | Only on write endpoints; GETs unaffected — pin down with `ERROR_CONTRACT.md` | +| Read-only | 503 or 403 | `"readonly"` | Only on write endpoints; GETs unaffected — pin down with `§6` | | Scheduled (advance notice) | 200, via `bootstrap.maintenanceNotice` | n/a | Not an error response — a proactive field on the normal `/bootstrap` payload, see §5 | | Temporary feature disable | 200, via `bootstrap.featureFlags. = false` | n/a | Not an error response — existing bootstrap mechanism, see §6 | @@ -4771,19 +4932,35 @@ these. ### Requires backend decision (full list) +Each has a **Recommended default** — apply it unless flagged otherwise. + - §1: whether a dedicated `GET /status`/`GET /maintenance` probe should exist for auto-recovery polling, beyond a plain 503 on `/bootstrap`. + **Recommended default:** no dedicated probe — reuse `GET /bootstrap`'s + existing 503/200 as the recovery signal; a second endpoint duplicates it. - §2: the `scope` discriminator (`"global"` vs `"tenant"`) so the frontend can tell the two apart from a single tenant's bootstrap response; and whether a disabled tenant's branding can still resolve for a branded takeover page. + **Recommended default:** include `scope` in the 503 body (§7 shape); + serve branding for a disabled-but-known tenant so the takeover page can + still be branded (only truly unknown tenants get the unbranded default). - §3: none beyond adopting the §7 response shape per-endpoint — this one is mostly frontend-gap, not backend-undecided. - §4: which status code is authoritative for read-only (`503` vs `403`) — to be - pinned down jointly with `ERROR_CONTRACT.md`. + pinned down jointly with §6. + **Recommended default:** `503` with `code: "READ_ONLY"` — it's a temporary + service-state condition, not a permissions failure, so it belongs in the + 5xx family alongside the rest of maintenance mode, not 4xx. - §5: whether scheduled-maintenance notice ships via a `bootstrap.maintenanceNotice` field (proposed) or a separate polling endpoint. + **Recommended default:** the `bootstrap.maintenanceNotice` field — it + rides the existing bootstrap fetch/cache lifecycle for free; a separate + polling endpoint adds a new recurring request for no added value. - §7: how this document's 503 body nests inside whatever outer envelope - `ERROR_CONTRACT.md` defines. + §6 defines. + **Recommended default:** already resolved — nest under the §6 envelope + (`{ error: { code, message, status, requestId, ... } }`) as shown in this + section's JSON examples; no separate top-level shape. ### No frontend UI currently exists for this — requires a future frontend task (full list)