docs(backend): consolidate all backend contracts into one file
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Collapses the entire docs/backend/ set - Phase 1-10, Track A/S, the partner API, the two handoffs, the frontend surface inventory, and the harvest requirements - into a single source of truth, docs/backend/BACKEND-INTEGRATION.md. Every contract's entities, endpoints, and invariants are preserved, reorganised by domain rather than by sprint. The nine release invariants, the FH-* harvest mechanisms, the RBAC/audit/secrets cross-cutting rules, the tenant-routing infra contract, the 15 acceptance tests, build order, dev setup, and open decisions are all in the one file, with a change log (§14) at the bottom. The file opens with the maintenance rule: any new backend need, contract change, or shipped item updates this file in the same change - the affected section and the change log. No new backend .md files. Inbound links from BACKEND-API-REFERENCE, the ADRs, the fork docs, DEPLOYMENT, PACKAGES-USAGE, the delivery plan, and e2e/README are repointed at the single doc (section anchors collapse to the file; the prose section refs remain as context). Also recorded the rule in the repo CLAUDE.md. 17 backend docs removed, 1 added. No implementation changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -49,29 +49,29 @@ Improvements only. Nothing here regresses our Angular version, test count, or ar
|
||||
|
||||
## Wave 2 — Contract hardening (Lane B, parallel with Wave 1)
|
||||
|
||||
Each item is normative text plus an acceptance scenario in `docs/backend/BACKEND-HANDOFF.md`, so it becomes a delivery gate rather than a wish.
|
||||
Each item is normative text plus an acceptance scenario in `backend/BACKEND-INTEGRATION.md`, so it becomes a delivery gate rather than a wish.
|
||||
|
||||
- [x] **FH-2.1 — Conditional-UPDATE stock reservation** · S · `PHASE-6-CART-CHECKOUT-CONTRACT.md`
|
||||
- [x] **FH-2.1 — Conditional-UPDATE stock reservation** · S · `backend/BACKEND-INTEGRATION.md`
|
||||
**Written 2026-08-21:** PHASE-3 §3.1 — the conditional `UPDATE … WHERE (available - reserved) >= qty RETURNING id`, 409 on zero rows, whole-cart rollback, 15 min TTL.
|
||||
`UPDATE … SET reserved = reserved + $qty WHERE (onHand - reserved) >= $qty RETURNING id`; empty result → `409`. Reservation TTL 15 min. Price read only from the server-side snapshot, never from the request.
|
||||
**Acceptance:** two concurrent purchases of the last unit produce exactly one payable order.
|
||||
|
||||
- [x] **FH-2.2 — Idempotency as unique constraints** · S · `PHASE-7-PAYMENTS-RECONCILIATION-CONTRACT.md`
|
||||
- [x] **FH-2.2 — Idempotency as unique constraints** · S · `backend/BACKEND-INTEGRATION.md`
|
||||
**Written 2026-08-21:** PHASE-7 §5 — unique constraints on `payment.idempotency_key` and `(provider, event_key)`, insert-first webhook handling, `sha256(rawBody)` fallback key, signature over the raw body, 24 h poll as reconciliation.
|
||||
`Payment.idempotencyKey UNIQUE`; a key reused against a different order/marketplace → `409`. `PaymentWebhookEvent @@unique([provider, eventKey])`; duplicate insert → `{accepted: true, duplicate: true}`. `eventKey` falls back to `sha256(rawBody)`. Signature verified against the **raw** body. Status poll as a 24-hour reconciliation fallback.
|
||||
**Acceptance:** a replayed webhook neither completes the order twice nor moves stock twice.
|
||||
|
||||
- [x] **FH-2.3 — Session and credential model** · M · `TRACK-S-SECURITY-RBAC-CONTRACT.md`
|
||||
- [x] **FH-2.3 — Session and credential model** · M · `backend/BACKEND-INTEGRATION.md`
|
||||
**Written 2026-08-21:** TRACK-S §2.1 — 32 random bytes stored as SHA-256 only, HttpOnly/Secure/SameSite, one cookie per contour, Argon2id params, mandatory TOTP with a single-use enrolment token, password change revokes all sessions in-transaction.
|
||||
Server-stored sessions; random 32 bytes; **stored as SHA-256 hash only**; HttpOnly + Secure + SameSite; revocable; a distinct cookie per contour (`bo_session` / `manager_session` / `marketplace_session`). Argon2id `memoryCost 65536, timeCost 3, parallelism 1`. TOTP mandatory, gated by a signed 10-minute setup token. Password change ≥16 chars and revokes every live session in the same transaction. Role weights `ORDER_MANAGER 0 < VIEWER 1 < CONTENT_MANAGER 2 < ADMIN 3 < OWNER 4`, checked together with marketplace scope.
|
||||
**Acceptance:** a CONTENT_MANAGER cannot read an unassigned marketplace through a direct API call.
|
||||
|
||||
- [x] **FH-2.4 — Origin allowlist for admin mutations** · S · `TRACK-S-SECURITY-RBAC-CONTRACT.md`
|
||||
- [x] **FH-2.4 — Origin allowlist for admin mutations** · S · `backend/BACKEND-INTEGRATION.md`
|
||||
**Written 2026-08-21:** TRACK-S §2.2 — origin allowlist ahead of routing on every admin/platform/manager mutation, same list for CORS.
|
||||
Global hook: any non-GET on an admin/manager path whose `Origin` is not in the configured allowlist → `403`. CORS uses the same allowlist with `credentials: true`.
|
||||
**Acceptance:** a cross-origin POST with a valid session cookie is refused.
|
||||
|
||||
- [x] **FH-2.5 — Tenant by verified Host only** · S · `PHASE-9-TENANT-REGISTRY-DOMAINS-CONTRACT.md`
|
||||
- [x] **FH-2.5 — Tenant by verified Host only** · S · `backend/BACKEND-INTEGRATION.md`
|
||||
**Written 2026-08-21:** PHASE-9 §6 — normalization specified, `verifiedAt` required, cache with explicit invalidation, proxy header trust, no public endpoint accepts `marketplaceId`.
|
||||
Normalize host (lowercase, strip trailing dot, strip port) → unique `hostname` row → require `verifiedAt` and `ACTIVE`. Short cache with explicit invalidation. Unknown host → `404`, never a fallback tenant. The public API never accepts a `marketplaceId` from the browser.
|
||||
**Acceptance:** an unknown Host returns 404 and leaks no other tenant's data.
|
||||
@@ -86,7 +86,7 @@ Each item is normative text plus an acceptance scenario in `docs/backend/BACKEND
|
||||
`version = max(version) + 1`, immutable snapshot row, `publishedRevision` pointer flipped in the same transaction. Rollback creates a new revision; history is never rewritten. Clone copies design + catalog assignments, **forces inventory to 0**, never copies domains/customers/orders/secrets, and walks the category tree topologically with explicit cycle detection.
|
||||
**Acceptance:** rollback restores the chosen revision and leaves live inventory untouched.
|
||||
|
||||
- [x] **FH-2.8 — Append-only inventory journal** · S · `PHASE-3-CATALOG-OFFER-FULFILLMENT-CONTRACT.md`
|
||||
- [x] **FH-2.8 — Append-only inventory journal** · S · `backend/BACKEND-INTEGRATION.md`
|
||||
**Written 2026-08-21:** PHASE-3 §3.2 — `InventoryMovement` append-only with reason, reference, actor, and `resultingAvailable` written at the time.
|
||||
Every stock change writes `reason`, `referenceType`, `referenceId`, `actorId`, resulting balance. Direct answer to the v3.1 "we cannot explain your numbers" complaint.
|
||||
**Acceptance:** any current quantity is reconstructible from the journal alone.
|
||||
@@ -96,7 +96,7 @@ Each item is normative text plus an acceptance scenario in `docs/backend/BACKEND
|
||||
AES-256-GCM, versioned envelope `v1.iv.tag.ciphertext` (base64url), 32-byte key from the environment. Decrypted only inside the service; never serialized into any response. Redirect/callback URLs built backend-side and allowlisted.
|
||||
**Acceptance:** no credential appears in any API response, JS bundle, or browser storage.
|
||||
|
||||
- [x] **FH-2.10 — Server-side storefront config validation** · M · `PHASE-10-CONTENT-MODULES-CONTRACT.md`
|
||||
- [x] **FH-2.10 — Server-side storefront config validation** · M · `backend/BACKEND-INTEGRATION.md`
|
||||
**Written 2026-08-21:** PHASE-10 §3a — server re-runs the editor rules, clamp-and-fallback ergonomics, structural violations 400, limits published as one schema, referential checks as publish blockers.
|
||||
The server re-runs our editor's validation. Clamp-and-fallback ergonomics: clamp out-of-range numbers rather than rejecting; blank a URL that is not local `/path` or `https://` rather than erroring; fall back an invalid colour. Cap sections per page and IDs per list.
|
||||
**Acceptance:** a hand-crafted API call cannot store a config the editor would have refused.
|
||||
@@ -109,7 +109,7 @@ Each item is normative text plus an acceptance scenario in `docs/backend/BACKEND
|
||||
- [~] **FH-2.12 — Marketplace status machine** · **rejected 2026-08-21 — ours is better**
|
||||
Theirs is `DRAFT → DOMAIN_PENDING → READY → ACTIVE → SUSPENDED`. PHASE-9 §2 already carries `draft → configured → content_ready → domains_planned → staging_live → qa_passed → production_ready → live → paused/archived`, plus a lifecycle endpoint that must name the specific blocker preventing the next transition. Adopting theirs would be a downgrade. Recorded so it does not get raised again.
|
||||
|
||||
- [x] **FH-2.13 — Order public token, not sequential IDs** · S · `PHASE-2-ORDERS-NOTIFICATIONS-CONTRACT.md`
|
||||
- [x] **FH-2.13 — Order public token, not sequential IDs** · S · `backend/BACKEND-INTEGRATION.md`
|
||||
**Written 2026-08-21:** PHASE-2 §3.1 — `publicToken` ≥24 random bytes for every customer-facing route, tenant-scoped lookup, snapshot completeness, snapshots never updated in place.
|
||||
Orders are addressed publicly by a random `base64url` token. Order line items carry an immutable snapshot of name, SKU, price, currency, delivery, and contact data at purchase time.
|
||||
|
||||
@@ -184,7 +184,7 @@ The **client half and the contract are done** (2026-08-21). What remains is back
|
||||
- [x] **FH-4.2 — Move PKCE ownership to the backend** · S · **done 2026-08-21**
|
||||
`completeCallback()` is gone from the frontend entirely. PHASE-8 §2.1–2.2 rewritten: `/authorize` mints and stores `{state, codeVerifier, marketplaceId, returnTo, expiresAt}` single-use for 10 minutes, `/callback` is a backend GET that exchanges, links, issues the session cookie and redirects. `returnTo` validated against the tenant's own origin.
|
||||
Today `completeCallback(code, codeVerifier)` forces the browser to generate and hold the verifier. We are a confidential client. Backend generates `state` + `code_verifier`, stores them single-use for 10 minutes, handles the callback, and redirects. `completeCallback()` leaves the frontend entirely.
|
||||
Contract endpoints: `GET /api/identity/v1/{provider}/authorize`, `GET /api/identity/v1/{provider}/callback`, `POST /{provider}/unlink`, `GET /me/identities`. Update `PHASE-8-IDENTITY-MESSAGING-CONTRACT.md` §2.
|
||||
Contract endpoints: `GET /api/identity/v1/{provider}/authorize`, `GET /api/identity/v1/{provider}/callback`, `POST /{provider}/unlink`, `GET /me/identities`. Update `backend/BACKEND-INTEGRATION.md` §2.
|
||||
|
||||
- [~] **FH-4.3 — `ExternalIdentity` model** · S · Lane B · **contract written 2026-08-21, awaiting backend**
|
||||
PHASE-8 §1 and §2.3: `UNIQUE (provider, providerUserId)`, conflict routes to controlled resolution rather than rebinding, optional email/phone/displayName, per-tenant OAuth app config under the Track S §4.2 envelope.
|
||||
@@ -236,13 +236,13 @@ The **client half and the contract are done** (2026-08-21). What remains is back
|
||||
## Continuous — Process (Lane E)
|
||||
|
||||
- [x] **FH-E.1 — Adopt the nine invariants as an acceptance gate** · S · **done 2026-08-21**
|
||||
Now `BACKEND-HANDOFF.md` §0, ahead of everything else, each one cross-referenced to the contract section that specifies it. Framed as a release gate: violate one and it does not ship, regardless of what else is finished.
|
||||
Now `backend/BACKEND-INTEGRATION.md` §0, ahead of everything else, each one cross-referenced to the contract section that specifies it. Framed as a release gate: violate one and it does not ship, regardless of what else is finished.
|
||||
|
||||
- [x] **FH-E.2 — PR policy** · S · **done 2026-08-21**
|
||||
`BACKEND-HANDOFF.md` §0a, with the expand/contract migration rule alongside it.
|
||||
`backend/BACKEND-INTEGRATION.md` §0a, with the expand/contract migration rule alongside it.
|
||||
|
||||
- [x] **FH-E.3 — Release discipline** · S · **done 2026-08-21**
|
||||
`BACKEND-HANDOFF.md` §0a. A release records version, migrations, healthcheck, smoke, dependency audit, and the rollback path actually available.
|
||||
`backend/BACKEND-INTEGRATION.md` §0a. A release records version, migrations, healthcheck, smoke, dependency audit, and the rollback path actually available.
|
||||
|
||||
- [x] **FH-E.4 — ADR for the harvest** · S · **done 2026-08-21**
|
||||
[ADR-0006](context/adrs/ADR-0006-harvest-mechanisms-from-the-parallel-platform.md). Records what we take, what we reject, what we keep because ours is better, and the one organizational question it deliberately does not settle.
|
||||
|
||||
Reference in New Issue
Block a user