This page is the implementation specification for the DomiDo backend. It turns the active requirements and the rest of the architecture into a practical plan for a small team: how the service is shaped, how the runtime processes are arranged, how the internal packages are bounded, how configuration flows in, how the API is implemented against the OpenAPI contract, how authentication and authorization actually decide who can touch what, how data is stored in MongoDB, how transactions and jobs are coordinated, how Stripe and the AI providers are integrated, how security and observability are enforced, how backups and recovery are guaranteed, how the codebase is tested, and what the staged implementation order is. The backend supports a universal React Native plus Expo application that ships to Web/PWA today and to iOS and Android when those targets open, so the API and authentication model never assume the web; the current required execution target is Web desktop for the Web/PWA beta. The canonical API is the OpenAPI file in the requirements section, which this page implements without creating a second contract.
DomiDo ships in staged phases that the backend enforces directly rather than leaving to product copy. Phase A is the public discovery and interest-reservation beta — buyers can browse, designers can publish, and the platform records demand without ever creating a card collection, Stripe SetupIntent, charge, invoice, receipt, order, pre-order, shipment, fulfilment handoff, or payout release for physical-kit demand. Phase A.5 opens reservation-to-pre-order conversion against Stripe SetupIntent, prepares listing-owner terms, designer payout readiness through Stripe Connect, statement previews, and Promo Studio assets, but still produces no charge, invoice, receipt, shipment, fulfilment handoff, or payout release. Phase B is where payment capture, orders, fulfilment, shipment, returns and replacements, disputes, receipts and invoices, and the actual release of payouts become active. Phase C reserves space for remix economics and split royalties as data placeholders that do not yet drive money. Native delivery on iOS and Android is deferred but the API and session compatibility are preserved from the start.
| Phase | Backend state | Priority |
|---|---|---|
| Phase A | Public discovery, user-owned gallery, create-and-publish, interest reservations, dashboard, support and reporting, legal and admin beta, analytics. | Build now. |
| Phase A.5 | Reservation conversion invites, SetupIntent no-capture pre-orders, listing-owner terms and readiness, listing management, Promo Studio where needed, Stripe Connect / KYC preparation, projected entitlements, payout readiness, statement previews. | Build contract and core storage now, activate after Phase A gates. |
| Phase B | PaymentIntent capture, orders, fulfilment, shipment, returns / replacements / disputes, receipts and invoices, actual payout release. | Contract and gated code paths now, activate after readiness. |
| Phase C | Advanced marketplace expansion, remix economics, split royalties. | Data placeholders only where needed. |
| Native deferred | iOS / Android release rails. | Preserve API and session compatibility. |
The backend is written in Go and lives in the existing root module, with a new DomiDo API service binary created when implementation starts. HTTP is handled by the standard library's net/http plus a small router where it helps; JSON is encoded with the standard library against hand-maintained or generated request/response DTOs; persistence runs on MongoDB Atlas through the official Go driver v2, with a single process-wide client, explicit operation timeouts threaded through context.Context, majority write concern for commerce, audit, idempotency, job, and webhook records, and read concerns plus transactions for any multi-collection state transition. Search uses MongoDB Atlas Search and Atlas Vector Search; large objects live in S3-compatible private storage with MongoDB holding metadata and signed references; jobs run on a MongoDB-backed leased job collection and outbox for Phase A. The only payment processor is Stripe, the only OAuth providers are Google, Apple, and Facebook, and the public help projection is rendered by Docusaurus while MongoDB remains the source of truth for admin and runtime metadata. Production and staging use Atlas because Atlas Vector Search is part of the chosen stack, while local development may use a containerized MongoDB for ordinary repository and API tests with a deterministic fake embedding provider; Atlas-backed integration tests run in staging or CI where credentials are available.
The customer-facing app is a separate package embedded in the workspace, and the backend never moves, deletes, rewrites, or absorbs its version-control metadata. Backend code lives in the root module and may reuse the existing internal packages for model and block-kit processing. The recommended future layout keeps each surface in its own directory tree:
cmd/domido-api/
main.go
internal/domido/
app/
config/
httpapi/
auth/
domain/
mongo/
jobs/
integrations/
observability/
testutil/
pkg/
pipeline/
export/
importer/
...
An existing GLTF/GLB processing binary is a development server proof, not the DomiDo business backend, but its processing path can be wrapped by the DomiDo job worker.
Phase A runs both the API server and the worker loop inside one Go binary, controlled by configuration flags so the same image deploys as either or both. Domain services never import provider SDKs directly — they go through the integration adapters in the integrations/* packages, which own timeouts, idempotency, signature verification, and safe error mapping. MongoDB Atlas is the canonical store; object storage holds private uploads and generated assets; every external dependency is reached through a single owned adapter. The two runtimes split cleanly along their responsibilities: the API server handles REST, authentication and session work, request validation, the domain services, and webhook ingestion, while the worker loop owns MongoDB-leased jobs, outbox dispatch, generation and media work, publication, and notification fan-out. The first implementation may run both runtimes in one process under API-enabled and workers-enabled flags; production may later split them into separate processes from the same binary when load or reliability requires it.
The internal package boundaries are intentionally narrow so each surface has one owner. config parses environment variables, exposes public runtime config, references secrets, and carries feature-gate defaults; httpapi owns the router, middleware, DTO binding, the OpenAPI response shape, and the error envelope; auth covers OAuth completion, sessions, access credentials, and resource authorization; domain holds the business services and lifecycle rules; mongo owns collection access, indexes, transactions, migrations, and repository implementations; jobs owns job creation, lease acquisition, retries, dead-lettering, and progress updates; the integrations/* packages cover the Stripe adapter (SetupIntent, PaymentIntent, Connect, webhook mapping, reconciliation), the media adapter (signed upload and download, object lifecycle, private and public asset access), the AI adapter (projection, model, Promo Studio, embedding, and LLM text-translation provider adapters), and the publication adapter (Docusaurus export and build projection); observability owns structured logs, metrics, request ids, and audit correlation. Domain services do not import provider SDKs directly — they call adapters through interfaces.
Three environments cover the lifecycle. Local development runs against a local MongoDB with a fake Stripe, fake embeddings, and seeded data; staging is a full beta rehearsal against an Atlas staging database with Stripe test mode, real object storage, and test analytics; production runs against an Atlas production database with no Stripe payment flow in Phase A and real object storage. Configuration is environment-driven. The required variables cover the environment label, HTTP listen address, public base URL, build version, MongoDB URI and database name, object-storage bucket and region and endpoint, Stripe server key and webhook signing secret, OAuth client identifiers and secrets for each enabled provider, embedding provider and model and dimensions, default and target translation languages, the LLM translation provider with its model and concurrency cap, analytics provider with endpoint and write key, the product-event dictionary version, an optional error-capture DSN, log level and retention, an optional alert webhook URL, support inbox and first-response target, operations export retention, worker concurrency and lease and drain seconds, the transaction retry limit, the maintenance-mode switch, and a comma-separated list of admin emails. Secrets are never returned by configuration endpoints and never logged.
The app-facing API is served under /api, so the OpenAPI path /gallery/listings is served as /api/gallery/listings. Operational endpoints /api/health, /api/livez, and /api/readyz are part of the canonical specification, and internal beta endpoints such as /api/admin/beta are operational rather than replacements for public product resources. Successful responses follow the envelope defined by the canonical schemas, and a mutation returns the updated resource or whatever parent aggregate the screen needs next. Error responses include a stable error code, a human display message when useful, a field path for validation errors, the request id, retryability, a retry-after seconds value or header where applicable, and the phase-gate state when the operation is blocked by phase.
Every externally triggered mutation that could create duplicate state accepts or derives an idempotency key. The canonical external key is the Idempotency-Key request header; a body-level idempotency key remains acceptable for transitional clients only, and the two must match when both are present. Idempotency is required for OAuth completion, session sign-out, upload registration, account-level payment SetupIntent creation, generation job creation, AI text-assistance job creation, user-generated-content translation job creation, draft save and publish, cart item add and update where retries are expected, interest-reservation creation and cancellation, Phase A.5 checkout SetupIntent creation, Phase A.5 checkout confirmation, pre-order cancellation, Stripe webhook handling, designer payout-account connection, designer listing duplication and artifact-pack export creation, payout readiness changes, payout requests, order actions, help comments and replies and likes and reports, article feedback, bookmarks, and admin publication actions. Idempotency records carry the scope, key, actor id, operation, request hash and summary, response status and body snapshot, resource references, state (inProgress, completed, failed, or conflict), lock expiry, conflict reason, and expiry; a repeat with the same key and same hash returns the stored response, a repeat with the same key and a different hash returns an idempotency conflict, and a repeat while the first is in progress returns the current in-progress state or a retryable conflict — never duplicate provider or worker work.
Feature phase gates are evaluated in the API layer and again inside the domain service for state-changing operations. The API may return visible future-phase resources as disabled, read-only, preview-only, waitlisted, or coming-soon — but it never performs inactive behavior. So Phase A /listings/{listingId}/interest, /me/interest-reservations, and /interest-reservations/{reservationId} are active; Phase A /checkout/setup-intent returns blocked until Phase A.5; Phase A /checkout/stripe-session returns blocked until Phase B; Phase A.5 /interest-reservations/{reservationId}/invite-preorder and /checkout/setup-intent become active only when conversion gates pass; Phase A.5 /designer/payout-account/connect becomes active when the designer marketplace opens; Phase A.5 /designer/payouts/request returns preview or blocked because cash payout release requires Phase B; and Phase B /orders/{orderId}/receipt is active only for captured orders.
The API traceability audit is the active page and element audit for the current Web/PWA app, and backend implementation treats every endpoint added by that audit as part of the Phase A implementation contract unless the endpoint is explicitly phase-gated. The major obligations are GET /me/dashboard, where the account / read-model service builds a buyer dashboard aggregate from profile, interest reservations, Phase A.5 pre-orders, future orders, drafts, saved listings, followed designers, notifications, credit summary, addresses and payment references where active, and phase gates so the frontend does not have to join them manually; POST /listings/{listingId}/interest, where the reservations service creates or refreshes a non-binding interest reservation with enforced acknowledgement, rate limits, duplicate policy, and no-card / no-charge semantics; GET /me/interest-reservations and GET /interest-reservations/{reservationId}, which return user or session reservations with listing summary, cancellation state, and conversion-invite state; DELETE /interest-reservations/{reservationId}, which cancels the user's own non-binding reservation idempotently; GET /admin/interest-reservations and POST /interest-reservations/{reservationId}/invite-preorder, the founder demand review and Phase A.5 conversion invitation, gated by buyer, listing, regulatory, moderation, and pricing checks; GET /design-drafts returning the signed-in user's in-progress and saved drafts; GET /designers and GET /listings/{listingId}/related as catalogue read models for rails and browse surfaces with follow state, stats, and UI-ready preview data; GET and POST /listings/{listingId}/reviews with public read active and write requiring eligible buyer context; GET and PATCH /designer/profile for owner editing of banner, avatar, bio, social links, availability, portfolio configuration, and owner analytics summary; POST /designer/listings/{listingId}/duplicate and POST /designer/listings/{listingId}/artifact-pack/download as designer self-service utilities; POST /account/payment-references/setup-intent for adding saved cards from settings without an active checkout; POST /help/articles/{slug}/feedback and /bookmark separate from article source content; and POST /assist/text-jobs for phase-gated AI text assistance covering publish copy, listing copy, review replies, message replies, and support drafts. The implementation also enforces the audit's framing decisions: the app shell navigation hides or disables Journal until a real target exists; search-enabled scopes never expose journal or materials without a working target; Phase A listing and dashboard responses use non-binding interest-reservation, no-card, no-charge language and never return checkout, receipt, invoice, shipment, pre-order, order, or capture affordances; Phase A.5 checkout and dashboard responses use no-capture pre-order language and never return live receipt, invoice, shipment, capture, or payout affordances; and credit top-up controls remain disabled or hidden until a billing or top-up API is intentionally specified.
DomiDo is a multilingual product, and the backend owns all persistent translation of user-generated content; the React Native app may choose a locale and send source-language hints, but it does not call LLM providers directly or persist translated variants locally as authority. Each translatable free-text field is stored as a localized-text object — or inside a localized-field-map — with the original source text, source language, field and resource context, author and audience context, per-target language variants, translation status, provider and model and config metadata, and a source checksum plus a stale marker when the source changes. Translation is asynchronous on save: the backend validates and persists the source mutation first, creates or reuses an idempotent text-translation job for every changed translatable field group, returns the saved source resource immediately with current translation status, lets workers translate missing or stale target variants and update the owning aggregate, and ensures that failed translation jobs never fail the original source mutation (they expose retryable status to owners and admins, and readers see source-text fallback). Read APIs resolve display text by, in order, an explicit API language parameter, the authenticated user's preferred content language, the Accept-Language header, the configured default language, and the source text. Translatable content covers design prompts and briefs, draft publish copy, listing title and description and tags, designer bios and inspirations, reviews, questions, replies, messages, help comments, support conversations, report and dispute notes, and Promo Studio prompts; structured catalogue values, price and currency, legal acceptance text, payment fields, addresses, OAuth identity data, raw provider payloads, audit actor metadata, and system-owned help article source are not translated by this pipeline.
Phase A uses a lean beta reliability model: keep the core product usable, preserve state, and degrade only the affected capability. The target is 99.5% app-facing API availability over a rolling 30 days, excluding announced maintenance. MongoDB business records target a recovery point objective of fifteen minutes or better and a recovery time objective of four hours or better; final and public object-storage assets target a recovery point objective of twenty-four hours or better, while intermediate generated artifacts may be regenerated from source. Critical business mutations fail closed when audit persistence, idempotency persistence, or required domain-state persistence cannot complete — this applies to interest-reservation creation, cancellation, and conversion invite, payment verification, pre-order creation and cancellation, payout readiness and release, provider webhooks, admin content mutation, and privacy, trust, and security-sensitive actions. Non-critical analytics and best-effort notification dispatch fail open through outbox and dead-letter handling. Each dependency has a defined degradation contract:
| Dependency | User / API behavior | Recovery rule |
|---|---|---|
| MongoDB failover or transient transaction failure | Retry boundedly; if exhausted, return a typed retryable service-unavailable error. | Use idempotency and resource refs to reconcile before retrying provider side effects. |
| MongoDB unavailable or read-only | Reads that can be safely served may continue; writes fail closed with 503. |
Do not enqueue jobs or call providers until durable state can be written. |
| Object storage unavailable | Existing public / cached / static media may remain visible; new uploads and download actions return retry or degraded state. | Repair missing-object and object-orphan mismatches before public exposure. |
| Stripe API unavailable | Phase A is unaffected except for gated surfaces; Phase A.5 checkout, card setup, Connect, payment retry, and payout handoff return safe retry or degraded state; cart / pre-order / order / designer state is preserved. | Reconcile against Stripe before committing payment or provider facts. |
| Stripe webhook durable store unavailable | Return retryable failure so Stripe redelivers. | Never acknowledge a valid event before durable storage or duplicate recognition. |
| OAuth provider unavailable | Existing sessions continue; the affected sign-in provider shows safe unavailable state. | Other configured providers may remain available. |
| AI / LLM provider timeout or outage | The affected job fails, retries, or dead-letters safely; source text, drafts, listings, interest reservations, Phase A.5 cart / checkout, and public browsing remain usable where healthy. | Degrade-first; alternate providers are not mandatory before beta. |
| Atlas Search or Vector Search unavailable | Public discovery falls back to text search, curated cards, or a typed degraded empty state. | Search fallback must not leak private or future-phase resources. |
| Docusaurus publication failure | Runtime help and support APIs stay usable; the publication job fails, retries, or dead-letters. | Public static output remains the last successful projection. |
| Email / support / analytics unavailable | Critical records persist; notification and analytics dispatch retry through outbox where useful. | Analytics failure never blocks user workflows. |
| Worker loop unavailable or draining | Job-start APIs may return queued / degraded state or reject by readiness or limit; no inline heavy work. | Leases expire or are released; another worker resumes safely. |
The backend supports anonymous public browsing, a signed-in buyer account, a designer role, an admin role, and a support and trust role when needed, plus resource-owner authorization. OAuth providers are limited to Google, Apple, and Facebook; the server issues its own session or access credential after OAuth completion, and the frontend never receives provider secrets or raw provider profiles beyond safe display fields. For Web/PWA, secure HTTP-only cookies carry browser sessions where feasible; bearer access credentials remain supported for native app compatibility when those phases open. Cookie-authenticated unsafe requests require CSRF state or an equivalent same-site origin control, while bearer-authenticated native-compatible requests do not rely on browser CSRF tokens but pass through the same resource-authorization and rate-limit checks. Session records store the session-id hash, user id, created and last-active and expires and revoked timestamps, device or browser display, platform hint, approximate location when available, the current-session flag for account display, and the CSRF state for cookie-authenticated unsafe methods. Authorization is resource-based: role names alone are not sufficient, and feature gates do not replace authorization.
| Resource | Access rule |
|---|---|
| Draft | Owner, or admin / support with explicit reason. |
| Media | Owner, linked public listing, or an authorized signed action. |
| Cart / checkout | Owner session or user. |
| Pre-order | Buyer owner, or admin / support with reason. |
| Order | Buyer owner, the assigned designer only in a permitted claim context, or admin / support with reason. |
| Designer listing | Owner designer or admin. |
| Promo Studio | Listing owner only until the asset is made public. |
| Payout account | Owner designer and finance / admin only. |
| Help comment | Public read when approved; author / admin mutation according to status. |
| Audit | Admin / security only; audit access itself creates an audit event. |
For the full collection catalogue, document conventions, money and media reference rules, localized-text storage, indexes, and transaction boundaries, see domain and data; the backend implementation follows that map exactly. The backend ships an idempotent index-creation command that produces ordinary indexes, TTL indexes, unique indexes, Atlas Search indexes, and Atlas Vector Search indexes. Production startup verifies that required indexes exist but does not silently apply destructive changes. Each document carries a schema version, and the backend supports read-time tolerance for the previous production schema version during rolling deploys; migrations are idempotent, resumable, logged, safe to run in staging first, and reversible when possible for metadata-only changes.
Vector search supports semantic listing discovery, designer discovery, help and workshop article discovery, listing similarity checks for designer tools, and future recommendation and moderation workflows. Phase A requires listing and help-article semantic search readiness, and Phase A.5 adds designer and listing similarity and Promo Studio search helpers. Embeddings live on the search_documents.embedding field with provider, model, dimensions, version, source-updated-at, indexed-at, and embedding-input-hash metadata; if the model or dimensions change, a new Atlas Vector Search index is created and backfilled before the active config switches over. The Atlas Vector Search index uses cosine similarity over the embedding field with filter fields for scope, visibility, phase status, resource type, category, and tags, with the dimension read from configuration:
{
"fields": [
{ "type": "vector", "path": "embedding", "numDimensions": 1536, "similarity": "cosine" },
{ "type": "filter", "path": "scope" },
{ "type": "filter", "path": "visibility" },
{ "type": "filter", "path": "phaseStatus.status" },
{ "type": "filter", "path": "resourceType" },
{ "type": "filter", "path": "category" },
{ "type": "filter", "path": "tags" }
]
}
The search endpoint loads the search configuration, validates the query and enabled scopes, generates the query embedding when vector search is enabled, runs the vector query with configured filters, optionally combines with text-search results, applies authorization and phase filters, and returns a UI-ready response — internal embedding vectors are never exposed. Phase A.5 designer listing similarity uses the same search-documents collection, filtered to public or owner-authorized listing documents, with returned similarity records carrying a matched listing safe preview, score, reason, gating state, required action, and phase status.
Multi-collection state transitions run inside a shared transaction runner with majority write concern and an appropriate read concern. The runner classifies MongoDB errors before returning to domain code: transient transaction errors, write conflicts, and primary failover cases are retried up to a configured limit within the request deadline; duplicate-key races on idempotency, webhook, payment-reference, pre-order, and job uniqueness are treated as reconciliation cases rather than 500 errors; unknown transaction commit results are resolved by reading the expected resource, idempotency, or webhook record before retrying any external side effect; timeout, pool exhaustion, and retry exhaustion are mapped to typed service-unavailable errors with request id and retryability; and a provider call is never retried merely because the surrounding database transaction was retried. Required transaction boundaries are summarized in domain and data. Provider side effects that cannot participate in MongoDB transactions go through the outbox pattern — email dispatch, analytics dispatch, Docusaurus publication start, fulfilment provider calls, designer notification, and Stripe reconciliation — and outbox events are idempotent and processed by workers.
The domain layer is organized by business capability. Configuration owns jurisdictions, generation costs, create-style presets, Promo Studio config, payout config, app shell and footer and search config, and feature phase gates; records are versioned, active config changes create audit events, and frontend-visible config never includes secrets. Identity owns OAuth start and complete, user bootstrap, session creation and revocation, account profile, preferences, trust restrictions, and notification preferences and items; new users get a cart, default preferences, and first-touch attribution, and admin role bootstrap uses the configured admin-emails list. Media owns upload registration, validation, signed upload and download actions, crop state, asset visibility transitions, and private and public safety, accepting only glb, gltf, obj, and stl for model and source uploads up to 100 MB unless configuration changes; private assets return signed actions rather than raw persistent private URLs, Promo Studio assets remain private until explicitly added to a listing gallery, and object metadata stays recoverable alongside MongoDB metadata.
Creation owns current draft resume, draft create and update, reference media, projection faces and history, model generation jobs, model tryout selection, model feedback, block-kit generation, BOM and assembly read models, publish settings, and listing publication; the front projection face is required while other faces are optional, previous successful draft / model / block-kit state is preserved after job failure, generation cost is config-driven, publish creates or updates listings and search documents, and remix controls stay disabled in Phase A. Catalogue and discovery owns gallery and listing list, listing detail, save and share, listing questions, designer public profile and follow, and global search; public listing APIs return only public media and safe designer display, and search scopes are backend-configured. Commerce owns Phase A.5 and B cart and saved carts, the checkout state and its delivery / payment / review updates, promo-code state, Phase A.5 SetupIntent creation, and Phase A.5 and B checkout confirmation; the server recalculates totals, client-supplied prices are ignored, jurisdiction support and VAT come from configuration, and unsupported jurisdictions block checkout with coming-soon state.
Interest reservation owns the Phase A non-binding record, acknowledgement state, duplicate and reuse handling, cancellation, dashboard and founder summaries, listing-owner attribution, and the Phase A.5 conversion-invite state, with no card, Stripe object, pre-order, order, receipt, invoice, shipment, payout, or promised ship date in Phase A. Pre-order owns the Phase A.5 commitment record, card-verification state, cancellation, dashboard summaries, attribution, and conversion readiness — no receipt, invoice, shipment, capture, payout release, or promised ship date in Phase A.5, and duplicate SetupIntent webhook events never duplicate pre-orders. Order owns Phase B charged orders, payment retry, delivery address and preference updates, tracking, returns, replacements, reports, disputes, receipt and invoice, and build companion; no orders are created from interest reservations or pre-orders until the Phase B capture gate is active. Designer commerce owns the designer dashboard, listing editor, royalty / ad-boost / moderation / similarity / analytics, Promo Studio, designer sales, messages and action queue, payout account and schedule, and payout / reserve / event / statement read models; Phase A.5 creates payout readiness rather than cash payouts, royalty percentages run from 0 to 25 inclusive and include VAT, and actual payout release requires Phase B captured funds, delivery eligibility, reserve and dispute checks, and payout readiness.
Help, support, and publication owns help topics and articles metadata, runtime comments and replies and likes and reports, support conversations, support queue, replies, internal notes, assignment, priority, escalation, first-response tracking, the admin help content workflow, and Docusaurus publication artifacts — the Docusaurus output is a public projection while MongoDB help records remain the source of truth. Localization and translation owns translation runtime configuration, language fallback resolution, localized-text validation and normalization, translation-job creation and deduplication, stale translation detection after source edits, LLM provider adapter calls through workers, and owner and admin retry and diagnostic state. Audit owns the append-only security and business event history, described further in security and operations. Observability, analytics, and beta operations owns the product-event dictionary served at the configuration endpoint, analytics-event ingestion, validation, local mirror, optional provider dispatch, the founder and admin beta dashboard and export, beta feedback ingestion and triage, reliability incidents and alert rules, and operational metric aggregation for support, conversion, reliability, and feedback.
Phase A does not use Stripe for interest reservations. Phase A.5 checkout uses Stripe SetupIntent to collect and verify a payment method for future off-session use, only after a selected interest reservation has a valid conversion invite, and it never captures money. The backend flow opens when the buyer opens an unexpired conversion invite for a selected interest reservation, after which the backend revalidates listing-owner terms, moderation and IP state, the regulatory and product gate, the pricing snapshot, jurisdiction, and the buyer's fresh-consent requirement; it then stores or reuses a local pending SetupIntent request with idempotency key, request hash, checkout id, source reservation id, customer reference state, and an audit event before the provider call where possible; creates or reuses a Stripe Customer using a stable provider idempotency key derived from the local request; creates or reuses a SetupIntent with usage suitable for future off-session payment using a stable provider idempotency key; stores the SetupIntent id, client secret reference, and verification state on the checkout state (and if that write fails after Stripe succeeds, reconciliation recovers by provider idempotency key or Stripe object lookup before another provider call); lets the frontend confirm the SetupIntent through Stripe-controlled UI; updates verification state from the Stripe webhook; and finally lets /checkout/confirm create a pre-order only when verification requirements and buyer fresh consent are met, or when recoverable state is explicit. The backend stores only the Stripe customer id, SetupIntent id, payment-method id, masked card brand and last4 and expiry, verification status, and a safe failure label — raw card data is never stored.
The Stripe webhook route is POST /api/stripe/webhook. The backend uses the raw request body for signature verification, verifies the Stripe-Signature header against the configured endpoint secret, persists the webhook event before mutating business state, uses a unique index on the Stripe event id to prevent duplicate processing, maps each Stripe event to a DomiDo domain event, stores and safely ignores unknown events, and either retries or dead-letters failed events. Invalid, missing, stale, or malformed signatures return a safe 400; duplicate provider event ids return success after duplicate recognition; valid events return success only after durable event storage and either successful processing, safe enqueue for async processing, or safe ignore of an unknown event; if durable storage, duplicate check, or enqueue fails, the route returns a retryable 503 so Stripe redelivers; and after storage succeeds, later business-processing failure is represented by the webhook event's processing status (failed or deadLettered) and handled by retry or reconciliation rather than exposing raw provider payloads. Phase A.5 event handling covers SetupIntent succeeded / requires action / setup failed, payment-method detached or deleted where available, and customer or payment-method changes needed for reconciliation; Phase B event handling, gated, covers PaymentIntent succeeded / failed / requires action, charge and refund events, disputes, invoice and receipt source events where applicable, Connect account updates, and transfer and payout events.
Phase A.5 uses Stripe Connect for designer onboarding and payout readiness. The backend creates or links a Connect account only for an authorized designer, stores the safe account id and readiness labels, never exposes raw Connect payloads, syncs requirements into safe requirement labels on the payout account, and keeps payout release blocked until Phase B eligibility. Admin and internal reconciliation jobs cover SetupIntent state, payment references, Connect account readiness, Phase B PaymentIntent and order reconciliation, and payout-transfer reconciliation; reconciliation writes audit events and never silently changes committed money state without a traceable reason.
The existing internal packages own mesh import, voxelization, blockification, rules, BOM, and assembly export, and the DomiDo backend calls them through a domain adapter from worker jobs — HTTP handlers never run heavy model processing inline. Phase A jobs cover user-generated content text translation, projection face generation, projection autofill, model generation, block-kit generation, AI text assistance, embedding refresh, notification dispatch, and Docusaurus publication; Phase A.5 jobs add listing moderation rerun, listing similarity refresh, Promo Studio photo generation, Promo Studio video generation, and Stripe Connect readiness sync; Phase B jobs add fulfilment sync, shipment sync, payout release and reconciliation, and receipt / invoice export where needed. Generated artifacts are stored as media assets or object-storage artifacts linked from the draft's projection set, model tryouts, block kit, the listing's media references, Promo Studio assets, or the order's build companion, and job results include safe references rather than raw private storage paths.
Model generation stages validate the draft and projection set and selected generation option, prepare the provider request through the AI integrations package, call the provider with a configured timeout and retry budget, validate the returned model artifact and format, persist preview and model media assets, and attach a model tryout to the draft without replacing newer selected tryout state. Block-kit stages validate the selected model tryout and source artifact, import and normalize the mesh through the mesh adapter, repair or reject invalid mesh with safe validation errors, voxelize using configured resolution and profile limits, blockify according to configured rules, calculate the bill of materials and a part / SKU count and cost / build-time estimate, generate assembly and export payloads, and persist artifacts on the draft. Voxelization and blockification run only in workers — HTTP handlers validate request shape and create the job rather than allocating heavy mesh or voxel processing resources. A model or block-kit job result updates the draft only when the source draft version, projection-set id, selected model tryout id, and relevant config versions still match the job input; otherwise the job is marked stale or failed with a safe message and the previous draft state is preserved.
External provider calls for projection generation, model generation, Promo Studio photo and video generation, text assistance, and translation run through provider-neutral adapters. Each call resolves the feature gate, cost config, provider config, and idempotency key; prepares the request using only allowed resource data; executes the provider call with a configured timeout and retry policy; validates the provider response and media or text safety; persists safe artifacts or suggestions; updates the owning aggregate or job result reference; and emits audit, outbox, and observability events. Provider retry and backoff happen through jobs, never as ad hoc loops in HTTP handlers, and safe provider metadata may include provider name, model and config version, duration, token and credit units, and error class — but never provider secrets, raw prompts containing private context beyond the authorized source text, raw provider payloads, or private storage URLs.
The AI integrations package exposes a provider-neutral translation interface. The adapter sends only the field text and minimal context needed for translation; excludes payment, address, OAuth, audit, and raw provider payload data; supports a deterministic fake provider for local tests; preserves product nouns such as DomiDo, BrickLink, Set Companion, and style-preset names unless glossary configuration says otherwise; returns partial success when one language fails but others succeed; exposes provider, model, and config metadata for audit and stale-result detection; and applies retry and backoff only through the job system. A representative interface looks like:
type TranslationRequest struct {
ResourceRef ResourceRef
FieldPath string
SourceLanguage string
SourceText string
TargetLanguages []string
Audience string
ToneHint string
ContextNotes []string
GlossaryTerms []GlossaryTerm
ConfigVersion string
}
type TranslationResult struct {
Variants []TranslationVariant
Usage TranslationUsage
Warnings []string
}
Translation prompts are operational configuration, not frontend code; changes to prompt templates, glossary, model, or target-language list create audit events and a new translation-config version.
The Phase A must-implement list is the open-beta surface area. Operational endpoints /health, /livez, and /readyz come first. Shell and config surfaces include /app-shell, /footer, /config/jurisdictions, /config/generation-costs, /config/create-style-presets, /config/feature-phases, /config/translations, and /config/product-events. Public access covers /search, /newsletter-signups, and /analytics/events. Auth and account cover /auth/oauth, /auth/sign-out, /me, /me/preferences, /me/dashboard, and /me/trust-restrictions. Catalogue covers /gallery/listings, /listings/{listingId}, listing save / share / questions, /designers/{designerId}, and designer follow. Media covers /media/uploads and /media/{mediaId}/crop. Create covers /design-drafts/current, /design-drafts, /design-drafts/{draftId}, draft reference media, projection face read / generate / autofill / restore-history, model jobs and tryout select / feedback, block-kit jobs, BOM and assembly reads, publish settings, save, and publish. Jobs and assistance cover /jobs/{jobId}, /jobs/{jobId}/cancel, /jobs/{jobId}/retry, /tag-suggestions, and /assist/text-jobs. Cart, saved carts, and checkout cover /cart, /cart/items, /cart/items/{itemId}, save-for-later, /saved-carts and resume, and /checkout plus delivery, payment, SetupIntent, promo-code, review, and confirm. Stripe and pre-orders cover /stripe/webhook and /pre-orders plus /pre-orders/{preOrderId} and cancel. Account self-service covers /account/profile and avatar, addresses, payment references and SetupIntent, privacy, data export, lifecycle, security and sessions, and notifications. Help covers /help/articles, /help/topics, /help/articles/{slug} plus feedback, bookmark, comments, replies, like, and report. Beta operations and support cover /beta/feedback, /support/conversations and messages, /admin/beta, /admin/beta/export, /admin/support/conversations and messages, /admin/reliability/incidents, /admin/alert-rules, /admin/help/articles, /admin/help/topics, /admin/help/publication-artifacts, and /legal/cookie-preferences.
Phase A.5 surfaces must be contract-ready — they return valid gated or readiness responses before activation and become active in Phase A.5 — covering /config/promo-studio and /config/payouts; the designer surfaces (/designer/dashboard, listings and the editor, royalty, ad-boost, moderation jobs, similarity, analytics, copy change requests, Promo Studio scene, photo jobs, video jobs, assets and downloads, listing-gallery promotion, sales and dispute, action queue, message and review replies, message thread nodes and reports, claim actions); and payouts (/designer/payout-account and connect / schedule, /designer/payouts, payout reserves, payout events, statements and export, tax statements export). Phase B paths stay blocked or read-only until activation: /checkout/stripe-session, the full /orders family (detail, tracking, cancel, payment retry, delivery address and preferences, returns and return assessments, replacements, reports, disputes, receipt, invoice, build companion and photos), and /designer/payouts/request plus payout detail and retry.
The backend never exposes raw card data, OAuth secrets, payout account secrets, full tax identifiers, raw provider payloads, private media URLs without authorization, unrestricted object-storage paths, or hidden trust-restriction details not needed by the viewer. Structured logs include timestamp, level, request id, actor id when known, route, status, latency, error code, and correlation id; they redact secrets, tokens, raw card and payment data, raw provider payloads, private media URLs, full addresses unless required in a secured support context, and tax identifiers.
Rate-limit and abuse controls cover public reads and search per IP or session with query length and page-size caps; OAuth start and complete per IP, session, and provider with failed-attempt throttling; non-upload JSON body size for all API routes; upload registration per user, session, and upload kind with the 100 MB model cap and separate raster caps; generation, voxelization, block-kit, Promo Studio, AI assistance, and translation job creation per user, resource, and job type; one active model or block-kit job per draft, with bounded active AI and Promo jobs per actor and resource; translation-job coalescing by resource, field group, source checksum, target languages, and config version; checkout SetupIntent and session creation per checkout, user, and session; the webhook route protected by signature verification, timestamp tolerance, duplicate provider event id, and body-size cap; help comments, reports, likes, and support conversation creation per user, session, and IP; and admin publication and export actions per admin and content resource. Rate-limit, quota, and payload-limit rejections return typed errors with request id, retryability, and retry-after guidance where applicable, and security-sensitive rejections are audited without raw secrets or private payloads.
Payload and processing limits are enforced before expensive work for search query length, page number, page size, and result count; prompt, comment, support message, report, dispute, and article-feedback text length; non-upload JSON body size; upload filename length, normalized path safety, declared format, detected signature and content type, and size; model mesh complexity, parser timeout, voxel resolution and profile, blockification timeout, and generated artifact size; Promo Studio batch size, video duration, scene payload size, and custom environment media size; and provider timeout, retry budget, and daily and monthly cost budget where configured. Model parsing, voxelization, blockification, image and video generation, LLM translation, text assistance, and publication and export work run in workers with bounded timeout, memory, CPU, lease, and cancellation behavior; HTTP handlers only validate, authorize, rate-limit, idempotency-check, and enqueue or reuse jobs.
Three operational endpoints carry distinct semantics. GET /api/livez is a process liveness probe and does not fail because MongoDB, object storage, providers, workers, or search are temporarily unavailable. GET /api/readyz is a traffic readiness probe and returns 503 when MongoDB, required config, maintenance mode, or another core dependency prevents safe app traffic. GET /api/health is a sanitized operational health summary that returns build, environment, readiness, and the dependency status of MongoDB, object storage, workers, Stripe configuration, translation provider configuration, search index readiness, publication, email and support, analytics, and audit persistence. Health output never includes secrets, connection strings, private provider details, raw provider payloads, private media URLs, internal hostnames, queue names, or raw user-generated text.
Required metrics cover API request count, latency, and error by route; auth success and failure; upload validation failures; job latency, failure, and dead-letter by type; job queue age, lease expiry, heartbeat gap, retry count, cancellation count, and stale-result rejection by type; voxelization and blockification stage latency and failure by configured profile; translation queued / running / succeeded / failed / stale by resource type and target language; translation provider latency, retry count, partial-success count, and fallback-read count; SetupIntent creation success and failure; checkout confirmation success and failure; pre-order creation and cancellation; webhook duplicate, failure, and dead-letter; search latency and vector-search failure; designer payout-readiness failures; Docusaurus publication failures; product-event ingestion accepted and rejected counts by event name, version, and consent class; founder funnel metrics for public discovery, create, publish, cart, checkout, pre-order, dashboard return, designer activation, and blocked / degraded states; beta feedback count by type, severity, surface, release version, language, and status; support conversation creation, open count, age by priority, first-response overdue count, escalation count, and repeat-contact rate; reliability incident count and duration by severity, dependency, capability, and alert rule; and admin mutations. Phase A production alerts cover API unavailable, MongoDB unavailable, readiness transition to unavailable or maintenance outside an approved window, Stripe webhook verification failure spike, SetupIntent failure spike, checkout confirmation failure spike, job dead-letter count above threshold, job queue age or lease expiry or provider timeout spike above threshold, translation failure or dead-letter spike or provider disabled unexpectedly, object storage upload failure spike, public search failure spike, analytics ingestion rejection spike or provider dispatch backlog above threshold, support first-response SLA breach, beta critical feedback spike, and audit write failure. Every alert rule defines an owner, severity, trigger condition, suppression and deduplication policy, user-impact summary, first action and runbook, and linked-incident creation behavior. Six data classes are kept strictly separate with their own retention, redaction, and access rules: audit events (accountability and compliance traceability), technical logs (request, job, and provider diagnostics with redaction and short retention), product analytics (funnel and behavior events using a controlled taxonomy and consent class), support content (private user and staff conversation records with translation and access control), beta feedback (product-learning records with triage state and optional support linkage), and aggregated metrics (dashboard and alert inputs with minimal personal data); correlation ids may connect records across classes, but raw text, provider payloads, secrets, private media URLs, and payment data are never copied between classes.
See security and operations for the full backup, restore, and rehearsal contract. The backend implementation guarantees that restores preserve consistency between media-asset records and object-storage objects; between pre-orders and Stripe SetupIntent and payment-method references; between orders and Stripe PaymentIntent and charge references when Phase B is active; between payout accounts and Stripe Connect account ids; between help publication artifacts and the public static output; and between localized variants and their owning aggregates. Stripe, Connect, and later fulfilment facts are reconciled against the provider source of truth before changing committed money or payout state after a restore.
Phase A seed data includes enough public user-owned sample listings to exercise discovery, listing detail, reservation, and moderation / reporting flows without implying DomiDo ownership of those designs; at least one public designer profile; Phase A feature gates; UK jurisdiction config and a coming-soon jurisdiction example; generation cost config; create-style presets; shell, footer, and search config; help topics and articles; legal and cookie defaults; and the admin bootstrap user mapping. Seed data is idempotent and safe to run in local and staging; production seed never overwrites live admin edits unless explicitly requested by migration command options.
Unit tests cover feature-gate evaluation, authorization decisions, money calculations and VAT config lookup, upload validation, draft lifecycle, job retry and dead-letter decisions, job public-status mapping, cancellation eligibility, stale-result protection, safe error mapping, checkout state validation, SetupIntent request validation, pre-order lifecycle, idempotency-key conflict and replay, MongoDB error classification and transaction retry and reconciliation decisions, provider degradation and fail-closed and fail-open classification, webhook event mapping, designer payout readiness, and phase-blocked PaymentIntent / order / payout behavior. Integration tests cover repository CRUD, indexes and uniqueness, transactions, transient transaction retry and unknown commit reconciliation, duplicate-key race reconciliation for idempotency / webhooks / jobs / payment references, TTL behavior, job leasing, job heartbeat and lease-expiry recovery, idempotency replay, webhook duplicate processing, and search document indexing; local integration tests may use a containerized MongoDB while Atlas vector tests run only where Atlas credentials are configured.
API contract tests, for every Phase A path, verify that the JSON response matches the OpenAPI shape, validation errors include field path / code / message, unauthorized requests fail correctly, phase-blocked future paths return gated responses rather than accidental 500s, mutations return the updated parent aggregate or resource snapshot, job-start endpoints return 202 with a job resource, job detail returns progress, steps, artifacts, retry / cancel state, safe errors, timestamps, and poll guidance, job retry and cancel endpoints enforce ownership, phase gates, stale source checks, and retry / cancel eligibility, public endpoints are explicitly unauthenticated in OpenAPI, private endpoints reject unauthenticated or unauthorized access, idempotency-key replay and in-progress replay and conflict behavior match the stored contract, 429 responses include retry guidance and never create jobs or provider calls, CSRF-protected unsafe Web/PWA requests reject missing or invalid CSRF state before mutation, payload and query and pagination and upload and job-start limits are enforced before expensive processing, health endpoints match the OpenAPI shape without exposing secrets or raw payloads or private URLs or internal hostnames or connection strings or queue names or raw user-generated text, and dependency outages return typed 503 or degraded states rather than accidental 500s.
Stripe tests cover creating a SetupIntent for a valid checkout, the SetupIntent requires-action path, the SetupIntent failed path, idempotent handling of a duplicate webhook event, rejection of an invalid webhook signature, use of the raw body for signature verification, rejection of stale and replayed webhook signatures according to provider tolerance, success returns without duplicate domain mutation when duplicate provider event ids arrive, retryable failure for provider redelivery when a valid webhook hits a durable-store or enqueue failure, Phase A confirmation creating a pre-order and no order, and a blocked Phase B capture route while the gate is inactive. Reliability and recovery tests cover MongoDB failover or timeout simulation mapping to retry or typed service-unavailable without duplicate provider calls; worker crash or lease expiry resuming a job without publishing partial artifacts; the two object-storage / DB consistency cases (object-storage write succeeds but DB write fails, and DB write succeeds but object is missing) producing safe repair and cleanup state; AI / LLM provider timeout degrading only the affected job capability and preserving source state; Stripe outage preserving checkout and pre-order state and using idempotency and reconciliation before retrying; search and vector outage falling back to configured public discovery behavior without leaking private resources; audit persistence failure failing closed for critical mutations while analytics dispatch failure does not block user workflows; and a staging restore rehearsal verifying users, drafts, listings and media, checkout and pre-orders, audit and webhooks, jobs, localized variants, and configuration. The current mandatory product smoke target is Web desktop: open app shell, browse gallery, open listing, sign in, upload or create draft, process a job, poll model job progress and verify completed tryout or safe retry state, poll block-kit voxelization and blockification progress and verify BOM and assembly output, verify source text persists and the translation job completes or falls back cleanly, publish listing, create a non-binding interest reservation, see the reservation in the dashboard, cancel the reservation where eligible, and verify admin and founder dashboard state. Before native phase gates open, backend tests still verify that APIs do not rely on browser-only storage, that auth can support bearer credentials, that response shapes do not include web-only navigation assumptions as required fields, and that media signed actions can support mobile clients.
The backend Phase A is launch-ready when the required Phase A OpenAPI paths are implemented or explicitly gated with an approved beta fallback; MongoDB indexes are created in staging; seed listings render from the API; design draft upload, processing, and publishing work with real sample GLB / GLTF or supported files; model and block-kit jobs expose polling progress, voxelization and blockification stages, retry and cancel state, safe errors, and artifacts through the jobs endpoint; configured multilingual source fields create async translation jobs and display source fallback until variants are ready; SetupIntent checkout works in Stripe test mode; webhook duplicate delivery is idempotent; Phase A confirmation creates a pre-order and no captured order; the dashboard returns drafts, listings, pre-orders, and support state; the product-event dictionary is served and analytics ingestion accepts and rejects batches with safe receipts and funnel events are visible to the founder and admin dashboard; the support user flow and staff queue flow both work end to end, including reply, assignment and status, first-response due state, and audit records for staff actions; beta feedback submission stores surface and context, translation state, release version, triage status, and optional support link; an alert-rule smoke test creates or updates a reliability incident with owner, severity, trigger, and runbook / action summary; logs, analytics events, support content, beta feedback, and audit events demonstrate separate retention and redaction and access behavior; audit events are written for checkout, pre-order, generation, publish, support, auth, and admin actions; health, livez, and readyz are implemented and safe for public operational use; dependency degradation behavior is tested for MongoDB, object storage, Stripe, AI / LLM provider, search, workers, audit persistence, and analytics dispatch; backup and restore is documented and rehearsed for staging data before open beta; and Web desktop smoke passes.
Foundation comes first: create the API binary; add config loading; add the MongoDB client and health; add /health, /livez, /readyz, dependency status, and readiness classification; add the index and migration runner; add the shared error envelope, request id, logging, and auth-middleware skeleton; and seed Phase A configuration and public user-owned sample listings. Identity, configuration, and public discovery follow: implement OAuth and session minimum; implement the app shell, footer, configuration endpoints, and /me; implement the product-event dictionary endpoint and seed the Phase A dictionary; implement gallery, listing, and designer public reads; implement search-document indexing and fallback search; and implement the translation configuration endpoint, localized-text DTOs, and the deterministic fake translation provider. Media and create come third: implement upload registration and validation, the design-draft aggregate, jobs with worker leases, heartbeat and progress updates, retry and dead-letter handling, cancellation, and stale-result checks, text-translation jobs for draft / listing / designer / support free text, mesh processing wrapped into model and block-kit jobs with voxelization and blockification steps and artifact references, and publish-listing with search-document refresh.
Commerce and pre-orders come fourth: implement cart, saved cart, and checkout state; the Stripe customer and SetupIntent adapter; the webhook route; checkout confirmation to a pre-order; payment references and dashboard pre-order reads; and cancellation. Founder and admin, help, and support come fifth: implement analytics-event ingestion, the local analytics mirror, and provider dispatch / outbox where configured; support conversations, support messages, staff queue, first-response SLA, and support audit events; beta feedback ingestion, translation routing, triage state, and support linkage; reliability incidents, alert rules, the alert smoke path, and dashboard incident rollups; help comments and admin publication metadata; the admin beta dashboard and export; and audit views for admin-only inspection if needed. Phase A.5 readiness comes sixth: implement designer dashboard and readiness storage, the payout account and Stripe Connect onboarding, the Promo Studio project and job skeleton, and designer sales from attributed pre-orders, while keeping payout release blocked. Phase B contract hardening comes seventh: implement blocked or read-only order paths; add PaymentIntent and order conversion behind gates; and add fulfilment, return, receipt / invoice, and payout-release adapters as inactive contracts.
Phase A does not implement active behavior for payment capture for physical kits, receipts and invoices for physical-kit pre-orders, shipment tracking, fulfilment / 3PL handoff, returns and replacements, the build companion for delivered kits, actual designer payout release, remix cash economics, or native app-store release flows; contract stubs or gated read models are allowed. Before backend coding starts, the developer confirms that the canonical API parses successfully, that MongoDB Atlas and local MongoDB are available, that Stripe test keys and a webhook secret are available for staging and local, that an object-storage bucket or local S3-compatible endpoint is available, that the seed data source is agreed, that the customer-facing app remains a separate package with API integration over HTTP contracts, and that Web desktop smoke is the required Phase A acceptance path. During implementation, every change that touches backend behavior answers: which OpenAPI path or worker job does this implement, which phase gate controls it, which MongoDB collections and indexes does it use, is the mutation idempotent, does it write the required audit events, and what test proves the happy path and the highest-risk failure path.