docs: backend contracts for Phases 8-10 (identity/messaging, tenant registry, content modules)
- Phase 8: Customer/ExternalIdentity/ContactChannel, VK ID OAuth 2.1/PKCE
built first per Sprint 0.1 ("do all after vk"), then OTP, then MAX/
Telegram bot linking, then the Notification Orchestrator + Delivery
Conversation State Machine. Hard rule carried through: bots never touch
financial statuses, only delivery fields via a dedicated Delivery Service.
- Phase 9: Marketplace/MarketplaceDomain/MarketplaceFeatureSet/
MarketplaceRevision, full Hostinger DNS automation sequence (snapshot
before change, never touch MX/SPF/DKIM/DMARC/CAA), lifecycle state
machine that must expose its own blocker on every transition, publish
model with orders/payments/inventory explicitly excluded from revisions.
- Phase 10: Gorbushka-class content entities (Shop/Service/Floor/
SchemePin/RentListing/News/Lead/MallSettings), explicitly lowest
priority and gated on Commerce Core being real first.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
118
docs/backend/PHASE-10-CONTENT-MODULES-CONTRACT.md
Normal file
118
docs/backend/PHASE-10-CONTENT-MODULES-CONTRACT.md
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
# Phase 10 Backend Contract — Tenant Content Modules (Gorbushka-class tenants)
|
||||||
|
|
||||||
|
Companion to [PRODUCT-PLAN-v3.1-DELIVERY-PLAN.md](../PRODUCT-PLAN-v3.1-DELIVERY-PLAN.md) Phase 10 (Sprints 10.1–10.2). Covers plan §11.
|
||||||
|
|
||||||
|
**Status: ready to build, lowest priority.** Only after Commerce Core (Phases 1–7) is real — the plan is explicit that this tenant type does not define the platform architecture; it is one configuration of the shared runtime, not a separate build.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Entities
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface Shop {
|
||||||
|
id: string;
|
||||||
|
marketplaceId: string;
|
||||||
|
shopCategoryId: string;
|
||||||
|
name: string;
|
||||||
|
floorId?: string;
|
||||||
|
status: 'draft' | 'published';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ShopCategory {
|
||||||
|
id: string;
|
||||||
|
marketplaceId: string;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Service {
|
||||||
|
id: string;
|
||||||
|
marketplaceId: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
status: 'draft' | 'published';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Floor {
|
||||||
|
id: string;
|
||||||
|
marketplaceId: string;
|
||||||
|
order: number;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SchemePin {
|
||||||
|
id: string;
|
||||||
|
marketplaceId: string;
|
||||||
|
floorId: string;
|
||||||
|
shopId?: string;
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RentListing {
|
||||||
|
id: string;
|
||||||
|
marketplaceId: string;
|
||||||
|
title: string;
|
||||||
|
areaSqm: number;
|
||||||
|
floorId?: string;
|
||||||
|
status: 'available' | 'leased';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Lead {
|
||||||
|
id: string;
|
||||||
|
marketplaceId: string;
|
||||||
|
rentListingId?: string;
|
||||||
|
contactName: string;
|
||||||
|
contactPhone: string;
|
||||||
|
message?: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NewsPromo {
|
||||||
|
id: string;
|
||||||
|
marketplaceId: string;
|
||||||
|
title: string;
|
||||||
|
body: string;
|
||||||
|
publishedAt?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MallSettings {
|
||||||
|
marketplaceId: string;
|
||||||
|
openingHours: Record<string, string>;
|
||||||
|
contactInfo: Record<string, string>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Every entity above carries `marketplaceId`, an audit trail, and the same draft/preview/publish flow as [Phase 9's revision model](PHASE-9-TENANT-REGISTRY-DOMAINS-CONTRACT.md) §5 — not a separate content pipeline.
|
||||||
|
|
||||||
|
## 2. Endpoints
|
||||||
|
|
||||||
|
```
|
||||||
|
GET/POST/PATCH/DELETE /api/admin/v2/content/shops
|
||||||
|
GET/POST/PATCH/DELETE /api/admin/v2/content/shop-categories
|
||||||
|
GET/POST/PATCH/DELETE /api/admin/v2/content/services
|
||||||
|
GET/POST/PATCH/DELETE /api/admin/v2/content/floors
|
||||||
|
GET/POST/PATCH/DELETE /api/admin/v2/content/scheme-pins
|
||||||
|
GET/POST/PATCH/DELETE /api/admin/v2/content/rent-listings
|
||||||
|
POST /api/admin/v2/content/rent-listings/{id}/leads
|
||||||
|
GET/POST/PATCH/DELETE /api/admin/v2/content/news
|
||||||
|
PATCH /api/admin/v2/content/mall-settings
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Tenant feature configuration (Gorbushka's v1 default, per plan §11.1)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"cms": true, "shops": true, "services": true, "mallScheme": true,
|
||||||
|
"rentListings": true, "news": true, "seoMedia": true,
|
||||||
|
"catalog": false, "sellerPortal": false,
|
||||||
|
"cart": false, "checkout": false, "payments": false, "orders": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Commerce modules are **platform-ready but off** — the point of Phase 10 is proving this tenant can flip `catalog`/`cart`/`checkout`/etc. to `true` later via [Phase 9's `MarketplaceFeatureSet`](PHASE-9-TENANT-REGISTRY-DOMAINS-CONTRACT.md) with zero backend or storefront code changes, since the commerce core is already generic by the time Phase 10 starts.
|
||||||
|
|
||||||
|
## 4. What the frontend will start doing once this ships
|
||||||
|
|
||||||
|
- Mall scheme / floor / pin editor UI.
|
||||||
|
- Rent listing + lead capture forms.
|
||||||
|
- Confirm the existing Gorbushka frontend/archive is used as UX reference only — production data and auth route through the shared platform per ADR-0001.
|
||||||
151
docs/backend/PHASE-8-IDENTITY-MESSAGING-CONTRACT.md
Normal file
151
docs/backend/PHASE-8-IDENTITY-MESSAGING-CONTRACT.md
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
# Phase 8 Backend Contract — Customer Identity, VK ID, MAX/Telegram Messaging
|
||||||
|
|
||||||
|
Companion to [PRODUCT-PLAN-v3.1-DELIVERY-PLAN.md](../PRODUCT-PLAN-v3.1-DELIVERY-PLAN.md) Phase 8 (Sprints 8.1–8.5). Covers plan §2.9, §3.4, and all of §14 (the v3.1-only addition).
|
||||||
|
|
||||||
|
**Status: ready to build. Sprint order fixed by Sprint 0.1 decision: VK ID first, then everything else** ("do all after vk"). Sequence below follows that: identity core → VK ID → email/phone OTP → MAX/Telegram → Notification Orchestrator.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Entities
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface Customer {
|
||||||
|
id: string;
|
||||||
|
marketplaceId: string; // or global identity strategy, tenant-configurable
|
||||||
|
name?: string;
|
||||||
|
email?: string;
|
||||||
|
phone?: string;
|
||||||
|
status: 'active' | 'suspended';
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExternalIdentity {
|
||||||
|
customerId: string;
|
||||||
|
provider: 'vk_id' | 'telegram' | 'max';
|
||||||
|
providerUserId: string;
|
||||||
|
verifiedAt: string;
|
||||||
|
metadata: Record<string, unknown>;
|
||||||
|
lastUsedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ContactMethod {
|
||||||
|
customerId: string;
|
||||||
|
type: 'email' | 'phone';
|
||||||
|
value: string;
|
||||||
|
verifiedAt?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ContactChannel {
|
||||||
|
customerId: string;
|
||||||
|
provider: 'telegram' | 'vk' | 'max';
|
||||||
|
chatId: string;
|
||||||
|
verified: boolean;
|
||||||
|
notificationsEnabled: boolean;
|
||||||
|
deliveryEnabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MessagingConsent {
|
||||||
|
customerId: string;
|
||||||
|
channel: string;
|
||||||
|
purpose: 'marketing' | 'order_service_messages';
|
||||||
|
grantedAt?: string;
|
||||||
|
revokedAt?: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Telegram is demoted from sole identity to one `ExternalIdentity` provider among several — it must remain fully functional, just no longer the only path.
|
||||||
|
|
||||||
|
## 2. Sprint 8.2 — VK ID (build first)
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/identity/v1/vk/authorize -> redirects into VK's OAuth 2.1/PKCE flow
|
||||||
|
POST /api/identity/v1/vk/callback { code, codeVerifier } -> completes OAuth **backend-side**,
|
||||||
|
links ExternalIdentity, returns session
|
||||||
|
```
|
||||||
|
|
||||||
|
Invariants:
|
||||||
|
- OAuth completion happens entirely backend-side; the VK client secret never reaches the frontend.
|
||||||
|
- A repeat login for the same `providerUserId` must resolve to the same `Customer`, never create a duplicate.
|
||||||
|
- If `providerUserId` is already linked to a *different* `Customer` than the one currently authenticated (or none), this is an identity conflict — route to controlled resolution, never silently overwrite the existing binding (plan §14.3).
|
||||||
|
|
||||||
|
## 3. Sprint 8.3 — Email/phone OTP (after VK ID)
|
||||||
|
|
||||||
|
Implements the already-approved [email/phone login spec](../superpowers/specs/2026-08-15-email-phone-login-design.md). Per v3.1 §14, position this as **recovery/fallback** when a messenger channel is unavailable — not the primary login path. No new contract beyond that spec; this section exists only to fix its place in the build order relative to VK ID.
|
||||||
|
|
||||||
|
## 4. Sprint 8.4 — MAX + Telegram bot channels
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface BotConversationBinding {
|
||||||
|
customerId: string;
|
||||||
|
marketplaceId: string;
|
||||||
|
provider: 'telegram' | 'max';
|
||||||
|
chatId: string;
|
||||||
|
state: string; // see §5 state machine
|
||||||
|
orderId?: string;
|
||||||
|
lastMessageAt: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
MAX linking flow (bot-assisted, one-time code):
|
||||||
|
```
|
||||||
|
POST /api/identity/v1/max/link-code -> { code, expiresAt } (TTL, single-use, bound to marketplace + browser session)
|
||||||
|
```
|
||||||
|
User opens the MAX bot, sends the code; a confirmed bot update on the backend calls:
|
||||||
|
```
|
||||||
|
POST /api/providers/v1/max/bot-webhook -- idempotent; a repeated update must not create a duplicate binding
|
||||||
|
```
|
||||||
|
which links the pending `Customer` session to the MAX `chatId`.
|
||||||
|
|
||||||
|
All three providers' incoming bot updates (VK, MAX, Telegram) normalize into one shape:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface MessagingEvent {
|
||||||
|
provider: 'telegram' | 'vk' | 'max';
|
||||||
|
chatId: string;
|
||||||
|
orderId?: string;
|
||||||
|
text?: string;
|
||||||
|
receivedAt: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Provider bot tokens/secrets never reach the frontend, ever — only the backend calls each provider's Bot API.
|
||||||
|
|
||||||
|
## 5. Sprint 8.5 — Notification Orchestrator + Delivery Conversation State Machine
|
||||||
|
|
||||||
|
On `order.paid` (Phase 2 event bus), the orchestrator picks the customer's chosen channel (captured at checkout, see [Phase 6](PHASE-6-CART-CHECKOUT-CONTRACT.md) and `OrderContactSnapshot` in [Phase 2](PHASE-2-ORDERS-NOTIFICATIONS-CONTRACT.md)) and drives:
|
||||||
|
|
||||||
|
```
|
||||||
|
not_started -> awaiting_customer -> details_received -> manager_assigned/auto_confirmed -> shipment_planned -> completed
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface DeliveryDetailsSnapshot {
|
||||||
|
orderId: string;
|
||||||
|
city?: string;
|
||||||
|
address?: string;
|
||||||
|
recipientName?: string;
|
||||||
|
phone?: string;
|
||||||
|
timeWindow?: string;
|
||||||
|
comment?: string;
|
||||||
|
receivedAt: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Hard rules:
|
||||||
|
- **The bot never changes financial statuses.** It can only write `DeliveryDetailsSnapshot` fields via a dedicated Delivery Service — no bot code path touches `Order.paymentStatus`/`orderStatus`.
|
||||||
|
- The backoffice `Notification` (Phase 2 §6) fires unconditionally on `order.paid`, independent of whether the customer's messenger channel is reachable.
|
||||||
|
- If the chosen channel is unavailable, log a `DeliveryAttempt` error (Phase 2 §6) and fall back per tenant-configured policy (e.g. email/SMS) — never block the order itself.
|
||||||
|
- Follow-up messages are rate-limited per tenant policy; after the configured attempt limit, hand off to a human manager instead of continuing to message.
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/providers/v1/{provider}/bot-webhook -- generic entrypoint for all three providers
|
||||||
|
GET /api/admin/v2/orders/{orderId}/conversation -- message history + current state, for manager handoff
|
||||||
|
POST /api/admin/v2/orders/{orderId}/conversation/handoff
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. What the frontend will start doing once this ships
|
||||||
|
|
||||||
|
- VK ID login button + OAuth redirect flow on storefront (primary social login).
|
||||||
|
- MAX/Telegram linking UI (one-time code flow).
|
||||||
|
- Checkout channel-choice step ("where should we send confirmation?") — VK / MAX / Telegram / email/SMS fallback.
|
||||||
|
- Manager-facing conversation view (message history, current delivery state, accept handoff).
|
||||||
130
docs/backend/PHASE-9-TENANT-REGISTRY-DOMAINS-CONTRACT.md
Normal file
130
docs/backend/PHASE-9-TENANT-REGISTRY-DOMAINS-CONTRACT.md
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
# Phase 9 Backend Contract — Tenant Registry, Domain Automation, Publish Model
|
||||||
|
|
||||||
|
Companion to [PRODUCT-PLAN-v3.1-DELIVERY-PLAN.md](../PRODUCT-PLAN-v3.1-DELIVERY-PLAN.md) Phase 9 (Sprints 9.1–9.3). Covers plan §4.3, §8.
|
||||||
|
|
||||||
|
**Status: ready to build.** Zero `hostinger` references exist in the codebase today.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Entities
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface Marketplace {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
code: string;
|
||||||
|
type: 'commerce' | 'mall_directory' | 'hybrid' | 'single_brand';
|
||||||
|
ownerId: string;
|
||||||
|
countries: string[];
|
||||||
|
locales: string[];
|
||||||
|
currencies: string[];
|
||||||
|
timezone: string;
|
||||||
|
lifecycleState: MarketplaceLifecycleState;
|
||||||
|
}
|
||||||
|
|
||||||
|
type MarketplaceLifecycleState =
|
||||||
|
| 'draft' | 'configured' | 'content_ready' | 'domains_planned'
|
||||||
|
| 'staging_live' | 'qa_passed' | 'production_ready' | 'live' | 'paused' | 'archived';
|
||||||
|
|
||||||
|
interface MarketplaceDomain {
|
||||||
|
marketplaceId: string;
|
||||||
|
domain: string;
|
||||||
|
type: 'production' | 'www' | 'staging' | 'preview' | 'api' | 'seller';
|
||||||
|
status: 'planned' | 'dns_pending' | 'ssl_pending' | 'active' | 'failed';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MarketplaceFeatureSet {
|
||||||
|
marketplaceId: string;
|
||||||
|
features: Record<string, boolean>; // e.g. { catalog: true, sellers: true, cart: true, checkout: true, payments: true, orders: true, refunds: true, directory: false, ... }
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MarketplaceRevision {
|
||||||
|
id: string;
|
||||||
|
marketplaceId: string;
|
||||||
|
status: 'draft' | 'validated' | 'preview' | 'published';
|
||||||
|
publishedAt?: string;
|
||||||
|
supersedesRevisionId?: string; // rollback creates a NEW revision, never mutates the old one
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Hard invariant:** `Order`, `Payment`, `InventoryRecord`, and every financial ledger row are **not part of a `MarketplaceRevision`**. Rolling back a storefront design revision must never touch commerce data.
|
||||||
|
|
||||||
|
## 2. Lifecycle state machine
|
||||||
|
|
||||||
|
```
|
||||||
|
draft -> configured -> content_ready -> domains_planned -> staging_live -> qa_passed -> production_ready -> live -> paused/archived
|
||||||
|
```
|
||||||
|
|
||||||
|
Every state transition endpoint must return the specific blocker preventing the next transition — not just "not ready."
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/admin/v2/marketplaces/{id}/lifecycle -> { currentState, nextState, blockers: string[] }
|
||||||
|
POST /api/admin/v2/marketplaces/{id}/lifecycle/advance
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Onboarding wizard (8 steps, plan §4.3)
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/admin/v2/marketplaces -- step 1: name/code/type/owner/countries/locales/currencies/timezone
|
||||||
|
PATCH /api/admin/v2/marketplaces/{id}/feature-set -- step 2
|
||||||
|
POST /api/admin/v2/marketplaces/{id}/domains -- step 3
|
||||||
|
PATCH /api/admin/v2/marketplaces/{id}/design -- step 4
|
||||||
|
POST /api/admin/v2/marketplaces/{id}/roles -- step 5
|
||||||
|
PATCH /api/admin/v2/marketplaces/{id}/integrations -- step 6
|
||||||
|
POST /api/admin/v2/marketplaces/{id}/staging-launch -- step 7, runs smoke tests
|
||||||
|
POST /api/admin/v2/marketplaces/{id}/production-launch -- step 8, requires all P0 blockers closed + explicit approval
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Domain automation (Hostinger API, per plan §8.2)
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/dns/v1/zones/{domain}
|
||||||
|
POST /api/dns/v1/zones/{domain}/validate
|
||||||
|
PUT /api/dns/v1/zones/{domain}
|
||||||
|
DELETE /api/dns/v1/zones/{domain}
|
||||||
|
GET /api/dns/v1/snapshots/{domain}
|
||||||
|
GET /api/dns/v1/snapshots/{domain}/{snapshotId}
|
||||||
|
POST /api/dns/v1/snapshots/{domain}/{snapshotId}/restore
|
||||||
|
```
|
||||||
|
|
||||||
|
Process, strictly in this order:
|
||||||
|
```
|
||||||
|
1. Read current DNS zone.
|
||||||
|
2. Save a snapshot (rollback payload) BEFORE any change.
|
||||||
|
3. Build and validate a DNS plan.
|
||||||
|
4. NEVER touch MX/SPF/DKIM/DMARC/CAA records without a separate, explicitly scoped task.
|
||||||
|
5. Apply records only after production approval.
|
||||||
|
6. Verify propagation, SSL issuance, and health checks.
|
||||||
|
7. Mark the domain 'active' only after all checks in step 6 pass.
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Publish model
|
||||||
|
|
||||||
|
```
|
||||||
|
draft -> validation -> preview -> publish
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/admin/v2/marketplaces/{id}/revisions -- create draft
|
||||||
|
POST /api/admin/v2/marketplaces/{id}/revisions/{revId}/validate
|
||||||
|
POST /api/admin/v2/marketplaces/{id}/revisions/{revId}/publish -- becomes immutable
|
||||||
|
POST /api/admin/v2/marketplaces/{id}/revisions/{revId}/rollback -- creates a NEW revision pointing at the prior published content
|
||||||
|
```
|
||||||
|
|
||||||
|
Replaces the current builder's `localStorage`-only draft persistence and the empty `apiEndpoints.builder: {}` placeholder in bootstrap. CMS/static-page content (currently in-memory bootstrap only) gets a real write path through this same revision model.
|
||||||
|
|
||||||
|
## 6. Tenant resolution hardening
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/v2/storefront/bootstrap -- resolved server-side from verified Host header
|
||||||
|
```
|
||||||
|
|
||||||
|
- Host is normalized and matched against `MarketplaceDomain` server-side — the marketplace ID from the browser is never a trust boundary.
|
||||||
|
- Unknown Host → `404`, with **no fallback to any other tenant**.
|
||||||
|
|
||||||
|
## 7. What the frontend will start doing once this ships
|
||||||
|
|
||||||
|
- Build the backoffice **Marketplaces** section (missing from admin nav today): registry, type, status, domains, currencies, feature set, responsible manager.
|
||||||
|
- Build the **Domains & Releases** section: DNS/SSL status, staging/production, health checks, rollback.
|
||||||
|
- Wire the project editor/builder to real revision persistence instead of `localStorage`.
|
||||||
|
- Marketplace dashboard: GMV, paid orders, conversion, payment failure rate, moderation queue, low stock, unmatched events, integration health, domain/SSL/release status (plan §4.2).
|
||||||
Reference in New Issue
Block a user