SecretoCho
menu

case 03 / 05in build

Sultana Express

A B2B sourcing marketplace for Turkish manufacturing, rebuilt as a platform.

role
Co-founder and CTO, leading the rebuild
period
beta November 2026
platforms
web consoles · iOS
public
sultana.express

at a glance

  • A B2B marketplace that connects buyers with Turkish mills, factories and cooperatives.
  • Rebuilt from a no-code prototype into a Go monolith, three Next.js consoles and native iOS apps.
  • Money moves through a double-entry ledger, and escrow releases on the buyer’s quality acceptance.
  • Consoles in English, Turkish and Arabic, right-to-left by construction.
  • A 145-phase plan, built by AI agents under deterministic CI gates.
phases in the plan, 651 sub-phases
145
OpenAPI operations over 467 paths
556
architecture decision records
45
visual-regression baselines
4,430

Context

Sultana Express connects buyers with Turkish manufacturers. Wholesalers, brands and hospitality groups find verified mills, factories and cooperatives, send requests for quotes, compare and negotiate, pay into escrow, and follow samples, production, inspection, shipping and customs.

Suppliers handle verification, catalogs with volume pricing, capacity, sampling, production and payouts; operators run verification reviews, disputes, queues and analytics. It starts with textiles, and a new sector arrives as data.

As co-founder and CTO, I’m rebuilding it from a no-code prototype into a Go, Next.js and SwiftUI platform. The marketing site sultana.express is live, and the beta is announced for November 2026.

3consoles: buyer, supplier and operator

Constraints

  • Money and personal data: every money mutation needs an idempotency key, and personal data must be erasable while legal records stay.
  • Three languages in the consoles, English, Turkish and Arabic, with right-to-left decided on the server.
  • A new sector must need no change to the engine or the schema.
  • The apps in use keep running on the old backend while the rebuild happens.
  • Agents write the code; deterministic gates, not models, decide what merges.

Architecture

One Go codebase builds three binaries, the API, the worker and the realtime gateway, each deployed and scaled on its own. It holds 38 modules in 7 clusters, each a four-layer slice behind interfaces, and lint rules keep the boundaries.

The rebuild inverts the old stack: the Go service is the only trusted decider, and the database stores rows without deciding who may read them.

The API is contract-first: OpenAPI 3.1 generates the server and the web client, and CI fails on drift or a breaking change. Events leave only through an outbox row written in the same transaction as the change.

38modules in 7 clusters, one Go monolith
  1. API serviceGo, cmd/api
    • to Postgres: one tx
  2. Postgres storestate + outbox
    • to Outbox relay: notify
  3. Outbox relay processsingle, locked
    • to JetStream: publish
  4. JetStream queueper-cluster
    • to Consumers: durable
  5. Dead letters queueper consumer
  6. Consumers servicededupe by id
    • to Dead letters: failed
    • to Search: project
  7. Search storeprojection only
  1. A state change and its outbox row commit in one transaction.
  2. One relay holds the lock and publishes.
  3. JetStream drops duplicates by message id.
  4. Consumers dedupe on the event id; each has its own dead-letter queue.
  5. Search is a projection, never the source of truth.
an event leaves only with the change that caused it.

Decision log

Where the rules live

options
  • rules in database policies and functions
  • the Go service decides; the database stores
chose
the Go service decides; the database stores

Escrow release

options
  • on delivery
  • on the buyer’s quality acceptance
chose
on the buyer’s quality acceptance

Search

options
  • query the database
  • a projection fed by the outbox
chose
a projection fed by the outbox
why
Only the worker holds a write credential, and search is never the source of truth.

Sessions in the browser

options
  • tokens in browser storage
  • httpOnly cookies behind a BFF
chose
httpOnly cookies behind a BFF
why
Tokens never reach page JavaScript, and a test in a real browser proves it.

Money moves when goods do

deep dive

An append-only, double-entry ledger is the only authority on balances. Every movement balances to the kuruş at commit, no role can edit or delete a written entry, balances are computed from history, and money is whole minor units, never floating point.

The payment provider sits behind a port, and its webhooks are the only proof that money moved, so a duplicate notification cannot post twice. Escrow releases on the buyer’s quality acceptance, and payouts wait for the supplier’s verification. A merge-blocking wall proves these invariants, down to exactly one reversing entry under a genuine concurrent race.

∑ = 0T+1 releasepayout after KYB

money moves when goods do. not before.

The request gauntlet

deep dive

Every tenant row carries its organization, and the active organization comes from the session on the server. One permission check per request runs in a chain that denies by default, and a refusal never says whether a resource exists.

1permission check per request, in a chain that denies by default
  1. Browser clientno token in JS
    • to Cookie BFF: cookie
  2. Cookie BFF servicecookie → Bearer
    • to Session check: Bearer
  3. Session check processverified token
    • to Active org: claims
  4. Active org processfrom session
    • to Permission: org_id
  5. Postgres storeorg_id per row
  6. Handler serviceorg-scoped
    • to Postgres: org rows
  7. Permission processone authority
    • to Handler: allowed
    • to Refusal: denied
  8. Refusal processreveals nothing
  1. The session lives in an httpOnly cookie; page JavaScript never holds a token.
  2. The BFF swaps the cookie for a Bearer token on the server.
  3. The API verifies the session.
  4. The active organization comes from the session, never from the request.
  5. One permission check decides; anything not allowed is denied.
  6. Only then does the handler run, scoped to the organization.
  7. A refusal never says whether the thing exists.
default deny. a refusal says nothing.

Gates that prove their own teeth

deep dive

Agents write the code, so the gates carry the weight. Each phase gate plants deliberate defects on every run, and each must be rejected by name; a wall is also tested with one of its arms secretly switched off.

A language model’s review is never the only gate for payments or personal data.

~45merge-blocking checks on the CI wall
  1. every editsecrets, card numbers, linters, float money
  2. reviewer agentssecurity, correctness, contract, privacy, UX
  3. pre-commit
  4. the CI wallabout 45 merge-blocking checks
  5. scheduled jobs

Adding an industry is a data entry

deep dive

Each industry is a data pack: product types, attributes, units, and its manufacturing, inspection and certificate rules. Food and agriculture was the maximum-difference test: 14 product families, origin, grade and harvest-year attributes, selling by the kilogram and the tonne, and harvest lots with aflatoxin quarantine. Home textiles was the maximum-reuse test. Neither needed an engine or schema change, and a CI gate registers a throwaway fifth sector end to end on every run.

5tha throwaway sector, registered end to end on every CI run

Stack

frontend

Next.js 16 Multi-Zones
the buyer, supplier and operator consoles, on one domain
React Aria and Tailwind CSS 4
accessible components on design tokens
next-intl
English, Turkish and Arabic, with right-to-left decided on the server

backend

Go 1.26
one modular monolith: the API, worker and realtime binaries
OpenAPI 3.1 and AsyncAPI
the contracts the server and the client are generated from

data

PostgreSQL
the system of record and the ledger
NATS JetStream
durable domain events
Redis
cache, rate limits and the idempotency fast path
Typesense
the search projection
S3-compatible storage
private media behind presigned URLs

ai

Claude Code
one headless session per sub-phase, with reviewer agents

mobile

SwiftUI
buyer and supplier apps on a shared package

infra

k3s and Argo CD
the GitOps production target

other

Playwright and axe
the visual and accessibility walls

Design

This site’s diagram and motion language comes from this project: a design system with 22 animated diagrams under six motion laws, semantic motion from three primitives, and CSS-only SVG. Its tokens come in three layers and flow into CSS variables, a Tailwind preset and a DTCG export. The consoles’ redesign is under way on design branches.

22animated diagrams under six motion laws

wc -l

as of Oct 2026
phases in the plan, 651 sub-phases
145
OpenAPI operations over 467 paths
556
architecture decision records
45
visual-regression baselines
4,430
modules in 7 clusters, one Go monolith
38
AsyncAPI event channels
167
database migrations
261
web zones, sharing 7 packages
5
visual-regression wall, sharded from 68m46s
16m45s

Timeline

  1. The clean-rebuild plan
  2. The backend build starts
  3. The marketing site goes live
  4. The supplier console built and gated
  5. The backend through Stage 5, and the buyer console built and gated
  6. The operator console built and gated
  7. Betaannounced

Status and next

In build and pre-launch. What comes next is drawn faint until it ships.

Nov 2026the beta, announced
  1. backendthrough Stage 5
  2. three consolesbuilt and gated
  3. design overhaulon design branches
  4. production and live payments
  5. betaannounced for November 2026