---
title: Sultana Express
description: A B2B sourcing marketplace for Turkish manufacturing, rebuilt as a platform.
url: https://secretocho.design/work/sultana-express/
type: CreativeWork
status: in build
published: 2026-10-04
---

# Sultana Express

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

case 03 / 05 · in build

- **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.

**3** · consoles: 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.

**38** · modules in 7 clusters, one Go monolith

1. **API** service · Go, cmd/api
   - to Postgres: one tx
2. **Postgres** store · state + outbox
   - to Outbox relay: notify
3. **Outbox relay** process · single, locked
   - to JetStream: publish
4. **JetStream** queue · per-cluster
   - to Consumers: durable
5. **Dead letters** queue · per consumer
6. **Consumers** service · dedupe by id
   - to Dead letters: failed
   - to Search: project
7. **Search** store · projection 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 functionsthe Go service decides; the database stores
- **chose**: the Go service decides; the database stores

### Escrow release

- **options**: on deliveryon the buyer’s quality acceptance
- **chose**: on the buyer’s quality acceptance

### Search

- **options**: query the databasea 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 storagehttpOnly 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.

*Diagram: Escrow, money moves when goods do: the buyer's deposit passes an idempotency gate, where a duplicate webhook is a no-op, into an escrow vault; the double-entry, append-only ledger always sums to zero. On the buyer's QC acceptance a T plus 1 release splits the money to supplier, commission and tax, and the supplier payout waits for a KYB seal. The live-payments leg is in test mode, drawn faint.*

∑ = **0** · T+1 release · payout 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.

**1** · permission check per request, in a chain that denies by default

1. **Browser** client · no token in JS
   - to Cookie BFF: cookie
2. **Cookie BFF** service · cookie → Bearer
   - to Session check: Bearer
3. **Session check** process · verified token
   - to Active org: claims
4. **Active org** process · from session
   - to Permission: org\_id
5. **Postgres** store · org\_id per row
6. **Handler** service · org-scoped
   - to Postgres: org rows
7. **Permission** process · one authority
   - to Handler: allowed
   - to Refusal: denied
8. **Refusal** process · reveals 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.

**~45** · merge-blocking checks on the CI wall

1. every edit · secrets, card numbers, linters, float money
2. reviewer agents · security, correctness, contract, privacy, UX
3. pre-commit
4. the CI wall · about 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.

**5th** · a 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.

**22** · animated 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. 17 Jun 2026 · The clean-rebuild plan
2. 8 Jul 2026 · The backend build starts
3. 1 Aug 2026 · The marketing site goes live
4. 4 Aug 2026 · The supplier console built and gated
5. 19 Aug 2026 · The backend through Stage 5, and the buyer console built and gated
6. 24 Aug 2026 · The operator console built and gated
7. Nov 2026 · Beta · announced

## Status and next

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

**Nov 2026** · the beta, announced

1. backend · through Stage 5
2. three consoles · built and gated
3. design overhaul · on design branches
4. production and live payments
5. beta · announced for November 2026

## Proves

- [Backend and distributed systems](https://secretocho.design/about/#capabilities)
- [Payments and money correctness](https://secretocho.design/about/#capabilities)
- [Technical leadership](https://secretocho.design/about/#capabilities)
- [Motion and diagram language](https://secretocho.design/about/#capabilities)
- [Infrastructure, CI and release](https://secretocho.design/about/#capabilities)
