Files
marketplaces/docs/backend/BACKEND-HANDOFF.md
sdarbinyan f9e09b1757 docs(backend): harvest platform mechanisms into the contracts (Wave 2, FH-E.1-E.4)
Writes the 14 harvested mechanisms from FORK-ANALYSIS-2026-08-21.md into
the backend contracts. Each section is dated 2026-08-21 and tagged FH-*
so any wording traces back to why it is worded that way.

The through-line: several contracts stated correctness as behaviour
("the webhook must be idempotent"). Behaviour written as an if-statement
gets deleted by a refactor and the failure mode is a double charge. These
sections restate it as schema and mechanism.

PHASE-3  3.1 conditional-write reservation, 409 on zero rows, cart-wide
             rollback, 15 min TTL
         3.2 InventoryMovement append-only journal with resultingAvailable
         6   bulk import idempotent by SKU, rollback while unsold
         6a  digital code pools, revealed only when paid
PHASE-7  5   unique constraints for payment idempotency and webhook
             replay, insert-first handling, signature over raw body,
             24h poll as reconciliation not primary
TRACK-S  2.1 session model - 32 bytes stored as SHA-256 only, HttpOnly,
             one cookie per contour, Argon2id params, mandatory TOTP
         2.2 origin allowlist ahead of routing on every cookie mutation
         4.2 AES-256-GCM envelope for stored secrets, HMAC fingerprints
         8a  order manager as a separate contour, scoped by membership
             rows rather than by configuration
PHASE-9  5.1 revision immutability, version = max+1, pointer flipped
             in-transaction, operational state does not travel
         5.2 clone carry / no-carry list, inventory to zero
         5.3 signed read-only preview, non-GET 404s while previewing
         6   host normalization, verifiedAt required, cache invalidation
PHASE-10 3a  server re-runs the editor's validation, clamp-and-fallback
PHASE-2  3.1 order publicToken, snapshot completeness, never updated

FH-2.12 rejected on the merits: our marketplace lifecycle state machine
is richer than theirs, adopting it would be a downgrade. Recorded in the
TODO so it is not raised again.

Also adds BACKEND-HANDOFF.md sections 0 and 0a - nine falsifiable
invariants as a release gate, each cross-referenced to the contract that
specifies it, plus PR and release discipline. And ADR-0006 recording what
we take, what we reject, what we keep because ours is better, and the
organizational question it deliberately does not settle.

No implementation changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 11:12:05 +04:00

136 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Backend handoff — start here
Single entry point for a backend developer picking this up cold. Written 2026-08-18.
## 0. Invariants — the acceptance gate
Added 2026-08-21 (FH-E.1). Nine statements. Each is falsifiable, each has a test, and a release that violates any of them does not ship regardless of what else is finished. They come before the contracts because a contract can be read selectively and these cannot.
1. The public tenant is determined by verified `Host` alone. No public endpoint accepts a `marketplaceId` from the browser.
2. The price of an order is computed by the backend. A price arriving in a request is ignored, never validated-and-used.
3. Stock and reservation change atomically. Two buyers racing for the last unit produce one payable order.
4. Payment creation and webhook receipt are idempotent, enforced by unique constraints rather than by handler logic.
5. Provider credentials never leave the backend — not in a response, not in a bundle, not in a log.
6. A published revision is immutable. Rollback creates a new revision; history is never rewritten.
7. Rolling back design does not roll back live inventory, orders, or payments.
8. No user reads a marketplace they are not assigned to — through the UI or through a direct API call.
9. Every administrative mutation leaves an audit record naming actor, action, before and after.
Where each is specified: 1 and 67 in [Phase 9](PHASE-9-TENANT-REGISTRY-DOMAINS-CONTRACT.md) §56; 2 in [Phase 1](PHASE-1-MONEY-FX-PAYMENTS-CONTRACT.md) §5; 3 in [Phase 3](PHASE-3-CATALOG-OFFER-FULFILLMENT-CONTRACT.md) §3.1; 4 in [Phase 7](PHASE-7-PAYMENTS-RECONCILIATION-CONTRACT.md) §5; 5, 8 and 9 in [Track S](TRACK-S-SECURITY-RBAC-CONTRACT.md) §2, §3, §4.2.
## 0a. How work lands
Added 2026-08-21 (FH-E.2, FH-E.3).
**One functional area per pull request.** A PR carries: what it does, screenshots where there is UI, API changes, migrations, test evidence, security impact, and a rollback plan. Never change a payment, inventory, or order state machine in the same PR as a redesign — those two things fail differently and must be revertable separately.
**Migrations are expand/contract.** A migration and the frontend change that depends on it belong to one release train, but they may be separate PRs, and the expand step must be deployable on its own.
**A release is not "the build passed."** Each one records: version, migrations applied, healthcheck result, post-deploy smoke result, production dependency audit, and the rollback path actually available. A local build succeeding and a UI existing are not evidence of production readiness — the deploy and the smoke check are.
## 1. What this is
`marketplaces` is a multi-tenant marketplace platform frontend (Angular 22). The frontend is built and waiting; **there is no backend yet**. Every wire contract the backend needs to implement is already written and sitting in this directory — see [README.md](README.md) for the full index and build order.
Contract sections dated 2026-08-21 and tagged `FH-*` come from [FORK-ANALYSIS-2026-08-21.md](../FORK-ANALYSIS-2026-08-21.md) — a review of a parallel platform implementation that has a working backend. They are mechanisms that implementation already proved, harvested deliberately; the tag is there so you can trace any one of them back to why it is worded the way it is.
## 1a. Multi-tenancy — the thing that shapes every endpoint
The final executable infrastructure/backend contract is
[TENANT-API-DOMAIN-HANDOFF.md](TENANT-API-DOMAIN-HANDOFF.md). Follow it for
hostname normalization, CORS, nginx, TLS, CI secrets, and acceptance checks.
One deployed bundle serves **every customer domain**. There is no per-tenant build. The chain is:
1. [`TenantResolverService`](../../src/app/core/config/tenant-resolver.service.ts) reads the complete current browser hostname and protocol (localhost still uses the development proxy).
2. [`ApiConfigService`](../../src/app/core/config/api-config.service.ts) uses one API host per base domain: both `example.com` and `store1.example.com` use `api.example.com`.
3. `ApiBootstrapProvider`, auth, legacy API calls, and versioned `/api/...` calls all use that same base.
4. Each base domain owns one API DNS/TLS/reverse-proxy entry. nginx validates the browser origin and forwards the complete storefront hostname as `X-Storefront-Host`.
5. Backend tenant lookup uses that trusted storefront hostname, not the shared API `Host`; frontend nginx remains `default_server` / `server_name _`, so any attached storefront domain receives the same bundle.
**What this means for you:** the bootstrap endpoint is the single most important thing to build after auth. Every request must be tenant-scoped server-side, and a tenant must never be able to read another tenant's data — return `403`, not an empty result (see [TRACK-S §2](TRACK-S-SECURITY-RBAC-CONTRACT.md)). The frontend supplies the tenant identity from the hostname; the backend must treat that as an untrusted hint and derive real scope from the authenticated session.
Constraints already fixed by the frontend design (see the platform-vision facts in `docs/context/`): no marketplace-specific code or hardcoded marketplace data in the frontend; bootstrap carries only what is needed before app start (branding, languages, homepage layout, navigation, enabled widgets, footer pages) and **never** products, orders, cart, or users.
[PHASE-9](PHASE-9-TENANT-REGISTRY-DOMAINS-CONTRACT.md) covers the marketplace registry, domain attachment, and publish/revision model.
## 2. Read in this order
1. [README.md](README.md) — index of all contracts, build order, and what's deliberately excluded.
2. [PHASE-1-MONEY-FX-PAYMENTS-CONTRACT.md](PHASE-1-MONEY-FX-PAYMENTS-CONTRACT.md) — start here. Everything after depends on the money model.
3. Phases 2→4 — the rest of the launch gate (orders, catalog, connectors).
4. [TRACK-S-SECURITY-RBAC-CONTRACT.md](TRACK-S-SECURITY-RBAC-CONTRACT.md) — **gates the launch.** Today the admin role model is decorative: nothing server-side enforces any permission. §8 covers per-marketplace bootstrap admin accounts and self-service sub-admin management.
5. [TRACK-A-ANALYTICS-CONTRACT.md](TRACK-A-ANALYTICS-CONTRACT.md) — longest lead time, start it in parallel with Phase 1.
6. Phases 5→10 — post-launch-gate.
7. [PARTNER-PROVISIONING-API-CONTRACT.md](PARTNER-PROVISIONING-API-CONTRACT.md) — the inbound partner API. Read it **before implementing Phase 1**, not after: it adds `RoutingContext` to `CheckoutSession`/`PaymentIntent`/`Payment` ([Phase 1 §6.5](PHASE-1-MONEY-FX-PAYMENTS-CONTRACT.md)) and two levels above `Marketplace` ([Phase 9 §1](PHASE-9-TENANT-REGISTRY-DOMAINS-CONTRACT.md)). Building the partner API itself can wait; carrying its routing dimension in the payments tables cannot.
[../../BACKEND-API-REFERENCE.md](../../BACKEND-API-REFERENCE.md) documents the *current* live API surface (legacy endpoints, error envelope, mock-only areas). New endpoints use `/api/v2/...` namespaces; legacy endpoints are not being migrated.
## 3. Auth — read before writing any endpoint
Auth is no longer part of this repo. It lives in `@marketplaces/auth`, published from [vitanovaPackages](https://sources.vitanova.network/sdarbinyan/vitanovaPackages.git). See [../PACKAGES-USAGE.md](../PACKAGES-USAGE.md) for the full client surface. What matters on the backend side:
**Two mechanisms exist client-side.**
- **Telegram QR/session (live).** Endpoints under `{authApiUrl}/users/sessions``POST` to create, `GET /{id}` to poll, `DELETE /{id}` to log out. Both customer and admin login call the *same* endpoints; only client-side storage differs. The response shape is normalized permissively client-side (many key spellings accepted), but a clean implementation should return `{ webSessionID, user: { userId, username, firstName, lastName }, status, expiresAt }`.
- **Ed25519 challenge/response (not built).** `GET /api/admin/auth/challenge`, `POST /api/admin/auth/verify`, `POST /api/admin/auth/refresh`, `POST /api/admin/auth/logout`. Contracts in [TRACK-S](TRACK-S-SECURITY-RBAC-CONTRACT.md) and the package's `ed25519/models/auth-api.model.ts`. Until these ship, the client shows a `backend-unavailable` screen — nothing is mocked.
**The critical gap:** the session API has no concept of "admin." The frontend cannot distinguish an admin session from a customer one — it only chooses where to *store* the result. **Every admin endpoint must independently verify authorization server-side.** Client-side guards are UI convenience, never security. This is the single most serious open issue in the system.
Admin requests carry `AdminWebSessionID: <sessionId>` (and `Authorization: Bearer <token>` once admin JWTs exist) on paths containing `/admin/`, `/backoffice/`, `/builder/`, `/media/`.
## 4. Environment / infrastructure state
Dev server `213.21.246.138` (user `seto`, sudo, SSH key provided separately).
| Thing | State |
|---|---|
| nginx 1.24 | **Installed, running.** Config at `/etc/nginx/sites-enabled/marketplaces-dev.conf`. Serves frontend from `/srv/marketplaces/current/frontend`, backoffice from `/srv/marketplaces/current/backoffice`, proxies `/api/``127.0.0.1:8080`. `/health` returns `ok`. |
| Go toolchain | Installed (`/usr/local/bin/go`). |
| Backend service on :8080 | **Not running.** Nothing is listening. `/srv/marketplaces/current/api` is an empty shell. nginx's `/api/` proxy currently 502s. |
| PostgreSQL | **Installed but inactive.** Needs starting, a database, a user, and schema before anything works. |
| Shared packages | `@marketplaces/auth` installs over plain git from a release branch — no registry, token, or tunnel needed. `npm install` works out of the box. |
| Verdaccio (npm registry) | Running in Docker on port 4873, but **superseded and unused** — nothing depends on it. See [../PACKAGE-EXTRACTION.md](../PACKAGE-EXTRACTION.md) §5. |
| Firewall (ufw) | Active. 80/tcp, 443/tcp, OpenSSH. |
| TLS / certbot | **Not installed.** No certificates. Everything is plain HTTP today. For multi-tenant this is real work: every customer domain needs a certificate (per-domain issuance, or a wildcard if all tenants sit under one apex). |
| DNS / dynamic subdomains | **Not set up.** No domain currently points at the server (reverse DNS is the provider default `silky-bronze.ptr.network`). No wildcard record, no per-tenant subdomain automation, no Hostinger DNS integration. The *application* is fully multi-tenant (§1a) — this is the missing infrastructure underneath it. [PHASE-9](PHASE-9-TENANT-REGISTRY-DOMAINS-CONTRACT.md) specifies the target. |
| Frontend deploy (CD) | **None.** Pushing to `main` deploys nothing. `architecture-governance.yml` builds and checks boundaries but has no deploy step, and nothing writes to `/srv/marketplaces/current/frontend`. Deploys are manual today. |
| CI runner | None on this server; `sources.vitanova.network` CI runs elsewhere. |
## 5. To get a working dev environment
Nothing here is done yet — this is the setup a backend dev does on day one.
1. Start and configure PostgreSQL; create the database and application user.
2. Design the schema from the Phase 14 contracts (schema design is explicitly the backend's own call — the contracts specify entities, endpoints, and invariants, never tables). Tenant scoping belongs in the schema from day one; retrofitting it is painful.
3. Build the API service, listen on `127.0.0.1:8080`. nginx already proxies `/api/` to it.
4. Implement the **bootstrap config endpoint** (§1a) — without it the frontend cannot render for any tenant.
5. Implement the Telegram session endpoints — the login flow is fully built client-side and blocked only on these.
6. Implement `GET /api/identity/v1/session/permissions` ([TRACK-S §2](TRACK-S-SECURITY-RBAC-CONTRACT.md)) — frontend route guards derive from it.
7. Seed per-marketplace bootstrap admins ([TRACK-S §8](TRACK-S-SECURITY-RBAC-CONTRACT.md)): login = marketplace slug, password = `{slug}2026$`, `mustChangePassword: true`.
Steps 46 unblock the entire frontend. Everything after is feature work.
## 6. Frontend deploy
```bash
git clone <marketplaces repo>
npm install # pulls @marketplaces/auth over git, no credentials needed
npm run build # -> dist/dexarmarket
```
Angular 22, Node 24+. nginx serves `/srv/marketplaces/current/frontend`.
[`deploy.yml`](../../.github/workflows/deploy.yml) builds and atomically deploys
pushes to `main`; one deployment updates every domain at once. Before activation,
the workflow reconciles TLS, exact CORS, and reverse proxying for every host in
`STOREFRONT_DOMAINS`. Production deployment requires the documented CI secrets
and the one-time [`server-setup.sh`](../../scripts/deploy/server-setup.sh) run.
## 7. Known open decisions
- Registry reachability for CI (reverse proxy + TLS, or a different registry entirely).
- ~~Backend ownership.~~ Answered 2026-08-18: implemented by a separate backend developer against this contract set.
- Additional payment providers (wallets, BNPL) — [Phase 7 §4](PHASE-7-PAYMENTS-RECONCILIATION-CONTRACT.md).
- Per-connector marketplace adapters — written per partner at onboarding, [Phase 4 §8](PHASE-4-CONNECTOR-FRAMEWORK-CONTRACT.md).
- Backfill of `Company`/`Project`/`PaymentPoint` for existing marketplaces — sequence specified in [Phase 9 §1.2](PHASE-9-TENANT-REGISTRY-DOMAINS-CONTRACT.md), not yet scheduled.