# 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; contactInfo: Record; } ``` 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. ## 3a. The server re-validates everything the editor validates Added 2026-08-21 (FH-2.10). Our storefront editor has the stronger validation engine of the two implementations reviewed — blockers, warnings and informational notices, evaluated before publish. It all runs in the browser, which means it constrains the editor UI and nothing else. Anyone with a session and `curl` bypasses the entire thing. The backend re-runs the same rules on write. It is the only copy that binds. Ergonomics matter here, because a validator that hard-fails on cosmetic input makes the editor unusable. Follow clamp-and-fallback: - Numbers outside their allowed range are **clamped** to the range, not rejected. - A colour that is not a valid hex value falls back to the documented default. - A URL is accepted only if it is a same-origin path (`/…`, not `//…`, no backslashes) or `https://`. Anything else is stored as empty, not stored as given. - Free text is trimmed and truncated at its documented maximum. - Structural violations — an unknown block type, a malformed id, more blocks than the page allows, more referenced entity ids than the list allows — are a `400`. These cannot be silently coerced into something meaningful. Hard limits belong in this contract rather than in the client: maximum blocks per page, maximum referenced ids per block, maximum length per text field. Publish it as one schema and let both sides read it, so the editor and the server cannot drift. Referential checks run at publish, not on every keystroke: a block pointing at a deleted category or an unpublished offer is a publish **blocker** unless the block declares a fallback. This is the same rule as [Phase 3 §5](PHASE-3-CATALOG-OFFER-FULFILLMENT-CONTRACT.md) publish-time executability, applied to content instead of offers. **Acceptance:** a hand-crafted API call cannot store a configuration the editor would have refused. ## 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.