Product center
Categories, SPU/SKU and attribute validation. A SKU is filed only when its code is unique and its price and currency are valid.
Product center, content, listing, orders and after-sales as one minimal loop. A pluggable ChannelAdapter replays an offline sandbox channel; live channels stay fail-closed until credentials exist. Every amount and external move — publishing, refunds — is decided by the zeus kernel over HTTP and waits on human approval. Orders are idempotent, every key action is audited, and nothing unbuilt ever fakes success.
D3 publish HELD 423 → APPROVED → channel.upsertD2 refund HELD 423 → APPROVED → channel.refundkernel down 503 DECISION_UNAVAILABLE · fail-closedsandbox-douyin ONLINE · offline replaydouyin live FAIL-CLOSED · 422 CREDENTIAL_MISSINGlisting.published decision=D3 sku=SKU-DEMO-001order.imported ext=DY-SB-20261022-0001 1299 CNY idempotent=hitrefund.succeeded decision=D2 ref=DY-SB-REFUND-5001Five stations from product on file to refund settled. Content and channels sit behind ports, and the two money / external moves — listing and refund — pause at a zeus decision gate until a human approves. Nothing jumps the gate.
The category and SPU/SKU center validates title, price, currency and skuCode uniqueness before anything moves downstream.
M3 uses a deterministic offline content stub — banned-word block, a single review, never auto-confirm. M4 swaps in the loom content base behind the same ContentClient port; callers do not change.
Content goes to zeus for a D3 external-action decision and returns 423 DECISION_HELD. It reaches channel upsert/publish only after human approval.
The channel returns orders and the order center stores them, keyed by (channel, external_order_id) for idempotency, with SKU codes backfilled and order.imported written to the ledger.
Refund amounts are computed from order lines only. A D2 decision holds the request; the state machine runs requested → awaiting_approval → succeeded/rejected, and the channel refund fires only on approval.
Each module owns one part of the commerce loop and talks to the rest through explicit ports. The channel, decision and content seams are contracts, so a live channel, the loom content base or a different store all plug in without rewriting the domain.
Categories, SPU/SKU and attribute validation. A SKU is filed only when its code is unique and its price and currency are valid.
The ContentClient port ships a deterministic offline stub in M3 — banned-word block, a single review, never auto-confirm. M4 swaps in the loom content base without changing callers.
A five-method ChannelAdapter contract plus a registry — a new platform is just another adapter. The offline sandbox replays recordings; live channels fail-closed until credentials exist.
Pulls orders from the channel and stores them locally. (channel, external_order_id) is idempotent, so replays never double-create orders.
A DecisionPort hands every amount and external action to zeus over HTTP via zeus-client. There is no local grading; an unreachable zeus returns 503.
Content → D3 decision → human approval → channel upsert/publish. Every step of the external move happens in explicit order.
requested → awaiting_approval → succeeded/rejected. Rejections are recorded and the channel refund fires only on approval.
Inventory is maintained by hand; shipping and payment explicitly return 501 NOT_IMPLEMENTED_PLACEHOLDER in M3 instead of faking success.
commerce_events records every key action — decision requests, listings, order imports and refund transitions — replayable and traceable.
Commerce moves money and triggers real external actions. Commerce OS would rather refuse and return a precise error than perform something it cannot authorize. These four guarantees are enforced by the code and its tests.
Unregistered channels are refused; a live channel without credentialRef returns 422 CREDENTIAL_MISSING; an unreachable zeus returns 503 DECISION_UNAVAILABLE. No amount or external move ever happens without authorization.
The repo never grades locally. Publish (D3), refund (D2) and every other money or external move go to the decision kernel over HTTP; D3/D2 return 423 DECISION_HELD and wait for human approval.
Payment and fulfillment are explicit M3 placeholders returning 501 NOT_IMPLEMENTED_PLACEHOLDER; content is an offline stub, not a model. Placeholders are a stated product stance, not silent mocks.
Orders are deduplicated by (channel, external_order_id) for safe replay. Decision requests, listings, order imports and refund transitions all land in commerce_events, matching zeus decision records one to one.
HTTP v1 routes, a composition root, domain modules and three ports. Storage speaks both SQLite and PostgreSQL, with dialect confined to the storage layer and paired, numbered migrations.
Node ≥22, TypeScript strict (noUncheckedIndexedAccess, exactOptionalPropertyTypes), 48 Vitest cases including a real zeus-client path through a fake HTTP zeus and the GATE-M3 end-to-end loop.
Bundled node:sqlite locally with zero dependencies, PostgreSQL in production; sqlite/pg migrations are paired by number and enforced by check:migrations.
ChannelAdapter (five methods), DecisionPort (zeus-client), ContentClient (M3 stub → M4 loom).
storage · channel · product · order · decision · content · publishing · aftersale · fulfillment.
Injects the DecisionPort, ContentClient and channel registry at assembly; domain modules depend only on ports.
12 routes with one unified error envelope { error: { code, message, retryable, details? } }; the code-to-HTTP mapping is centralized.
No live credentials required: the sandbox channel replays recorded fixtures, a fake-zeus decision path and the GATE-M3 end-to-end test prove the loop, and SQLite is the default with PostgreSQL ready for production.
# clone + install
$ git clone https://github.com/bayernjf/commerce-os.git commerce-os
$ cd commerce-os && npm install
# migrate the local SQLite store
$ npm run migrate:up
# start — http://127.0.0.1:8787
$ npm startM3 milestone artifact. Live channels need qualifications and credentials; M4 content comes from the loom base.