Vendune
docs/storyfront.mdView on GitHub ↗

Storyfront connector

Choose the integration path

Adding the app from Studio: the Storyfront workspace's Add Storyfront integration button uses the same permissions review as the Apps library. Confirm the reviewed package before installation; canceling grants nothing. Installation and activation refresh the shared app registry immediately. If the app is installed but its workspace is unavailable, the operator still needs to configure the independently deployed service below. Installation alone does not create a managed Experience. Existing Experience connections continue to open their original editor.

Experience-created shops: onboarding mounts the frontend and installs the Storyfront integration automatically. Apps, Storyfronts and Sales channels → Domains & experiences expose the owned connection and its existing editor. Use channel management and the generic Experience contract; do not recreate that shop through the companion setup below. Native versus retained editor/rendering is a deployment choice, not something inferred from the installed app.

Independent companion installations: the remainder of this guide documents the separately configured catalog publication and one-use checkout-transfer path. Its dated acceptance evidence retains its original scope.

Storyfront (Ambient-C) can use Vendune as its catalog and checkout backend. The connector is an independently deployed app, not storefront-specific business logic inside the Rust core. It creates a valid merchant manifest, copies product images, and connects the cinematic bag to the same cart and order operations used by the native storefront and agents.

flowchart LR
  A[Merchant: Generate / refresh] --> B[Storyfront app service]
  B --> C[Rust tenant catalog + variants]
  B --> D[Storyfront manifest + merchant media]
  D --> E[Storyfront experience + existing AI composer]
  E --> F[Product IDs + quantities]
  F --> G[Rust authoritative cart]
  G --> H[Single-use checkout handoff]
  H --> I[Review shipping, tax, address and payment]
  I --> J[Persisted order + stock + existing event outbox]

Responsibilities and contracts

Component Owned behavior
extensions/apps/storyfront/manifest.json Versioned merchant capabilities, iframe slot, generate and status actions
extensions/apps/storyfront/ui.html English, German, French and Spanish merchant interface using the generic guest SDK
Ambient-C packages/manifest/src/rust-commerce.ts Catalog-to-manifest mapping, SKU identity mapping, variant relationships, image references and supplied product facts
Ambient-C scripts/connect-rust-commerce.ts Tenant catalog/variant reads, bounded image copying and manifest compilation
Ambient-C scripts/rust-commerce-app.ts Independent authenticated service, tenant-scoped durable job status and serialized import execution
Ambient-C packages/runtime-api/src/rust-commerce-publication.ts Existing hostname registry admission and catalog publication; respects closed/restricted merchant status
Ambient-C apps/storefront/src/pages/api/v1/commerce.json.ts Same-origin intent admission, own-manifest product check, mapping to authoritative commerce SKU
Ambient-C packages/runtime-api/src/rust-commerce.ts Operator-controlled destinations, bounded HTTP requests and checkout transfer
Rust src/checkout_handoff.rs Generic single-use cart transfer, expiry, tenant scope and token rotation
Rust frontend/src/storefront/shell/Storefront.tsx Consume transfer and open the existing one-page checkout review

The shopper sends only { "items": [{ "id": "mug-terracotta-500", "quantity": 1 }] } to the Storyfront endpoint. The merchant scope and destination come from server configuration. Prices, URLs, tokens, arbitrary tenant IDs and customer identity are not accepted in that intent. Source SKUs incompatible with Storyfront identifiers get stable hashed manifest IDs; commerce-sku preserves the exact purchase identity.

The local bag remains purchase intent until checkout. The connector creates a fresh Rust cart and writes the requested items, using its revision and context token. It refuses silent quantity normalization. Rust calculates the actual totals and returns a checkout transfer ticket. This ticket expires after ten minutes, is stored only as a digest, and can be consumed exactly once. Consumption rotates the cart's context token. A ticket is carried in the URL fragment and is removed on arrival. The browser receives no merchant credential or original cart token from Storyfront.

Run a connected shop

Use the companion Ambient-C branch containing the connector. Configure server-side variables in its own ignored environment file, never in browser build variables:

AMBIENT_DATA_ROOT=/absolute/isolated/storyfront-data
AMBIENT_MERCHANT_STORE=fs:/absolute/isolated/storyfront-store
AMBIENT_POSTGRES_URL=postgres://USER:PASSWORD@HOST:5432/ISOLATED_STORYFRONT_DB
AMBIENT_TENANCY=hostname
AMBIENT_COMPOSER=llm
AMBIENT_RUST_COMMERCE_CONNECTIONS={"my-storyfront":{"origin":"https://commerce.example","tenant":"my-shop","locale":"en-GB"}}
AMBIENT_RUST_STOREFRONT_ORIGINS={"my-storyfront":"https://storyfront.example"}
AMBIENT_RUST_STOREFRONT_NAMES={"my-storyfront":"My Shop"}
AMBIENT_RUST_APP_TOKEN=GENERATE_A_RANDOM_SECRET_AT_LEAST_24_CHARACTERS
AMBIENT_RUST_APP_PACKAGE=/absolute/vendune/extensions/apps/storyfront
AMBIENT_RUST_APP_DB=/absolute/isolated/storyfront-jobs.sqlite
AMBIENT_RUST_APP_PORT=8796

Migrate that Storyfront database using its normal migration script. Start the app service with bun scripts/rust-commerce-app.ts and its storefront with the normal hostname build/runtime. Put an HTTPS reverse proxy in front of each service for remote access. Local development permits loopback HTTP. Configure the existing Storyfront OpenRouter provider separately for AI composition; catalog generation itself makes no model, media-generation, embedding or warm-job requests.

Configure the Rust operator's server environment:

APP_SERVICES={"storyfront":{"url":"https://storyfront-app.example","uiUrl":"https://storyfront-app.example/","token":"THE_SAME_PRIVATE_SERVICE_SECRET"}}

Preserve other configured app services when adding this entry. Install Storyfront from Apps, then choose Generate / refresh shop. Polling shows the actual job status and the generated product/image counts. Open shop leads to the operator-configured storefront. The same capabilities are available through the existing authenticated HTTP/MCP app gateway. A viewer cannot schedule generation. The operator admits each tenant-to-Storyfront pair in configuration; shoppers and model output cannot register destinations. This initial app supports one Storyfront per configured Rust tenant. It is not a public unauthenticated provisioning service.

An operator can also import with:

bun scripts/connect-rust-commerce.ts my-storyfront 'My Shop'

What is real, and what remains a prototype

A connected checkout persists an actual order in the Rust database, updates stock, and uses the existing idempotency and event paths. This is more than the old local Storyfront bag. Payment behavior still depends on the configured Rust payment adapter. The default demo payment is simulated. The native PayPal adapter supports explicitly configured Sandbox/Live; no actual PSP transaction was performed in this verification. This connector does not turn a simulated payment into a real settlement.

Catalog publication generates the catalog-based Storyfront, not a reviewed AI story release. Storyfront's existing LLM composer can answer questions from the imported facts. Paid composition uses its existing provider and approval rules. Refresh is a new catalog snapshot; price, tax, shipping, payment eligibility and stock are checked again at checkout. It does not yet implement continuous inventory/webhook sync, curated story-release preservation or automatic reconciliation of removed items in old browser bags.

The current importer caps the snapshot at 250 SKUs and refuses incomplete catalogs. It maps EUR prices from this prototype's retail context. Sold-out variants are omitted from refreshed recommendation snapshots; checkout checks current stock again. Bundles and app-specific product configuration are refused by this checkout intent contract rather than inventing prices or dropping customization. Guest checkout uses the existing Rust checkout form, embedded in a native Storyfront dialog on registered shop domains. Core sends an exact-origin shopper receipt; Storyfront verifies the completed cart, order ID and purchased SKU quantities against Core before subtracting those quantities from its original bag. Receipt IDs prevent duplicate subtraction; an unverified receipt is retained for retry, and abandoning checkout retains the bag. Cross-origin customer account SSO and provider-specific external redirect/3DS return flows are not yet verified. This is not full Shopware/Storyfront feature equivalence or a production SaaS scaling claim.

Verification

python3 scripts/checkout_handoff.py uses real PostgreSQL and verifies empty/stale cart rejection, tenant separation, revocation on reissue, expiry, one winner under concurrent consume, old-token revocation, actual merchant-visible ordering and idempotent replay. CI runs it with the other database suites.

The companion connector tests compile an imported manifest, retain variant facts and SKU mappings, reject foreign media/duplicates, and exercise destination validation, bounded responses and authoritative cart transfers. Browser verification covers Storyfront bag to the existing Rust checkout. See checkout-handoff-verification.json for the sanitized local database checks.

Local end-to-end evidence

The isolated Atelier import published 10 available SKUs and copied 33 images. The merchant app generated the snapshot through its authenticated action gateway. A browser added mug-terracotta-500, transferred it to the native Rust checkout and placed order RAC-a36105f8 for EUR 29.90. The database contains the exact variant and explicitly records realMoneyCharged: false.

Storyfront order saved by the Rust checkout

Seven live HTTP checks covered authoritative pricing, unavailable variants, injected tenants/prices, foreign origins, unknown hosts and oversized requests. Eleven companion connector tests cover catalog compilation, identity mapping, bounded transfers, service authentication, job coalescing and status persistence across restart.

The single approved AI question made real OpenRouter calls: two composer calls with openai/gpt-oss-120b and one answer-agent call with minimax/minimax-m2.7. Recorded cost was about USD 0.005206. The model produced product chapters and a price, but the leading answer was incomplete, remained English and recommended a sold-out variant. The stock filter is now applied during refresh, without an additional paid question. This verifies provider wiring and catalog access; it does not establish satisfactory answer quality or a verified AI story release. No image generation, warm-up or external indexing was triggered.

Four-language and complete-page refresh

The connector now follows root and variant cursor pages and fails at its explicit 250-SKU prototype cap instead of silently truncating catalogs. A local refresh published ten available SKUs and 33 images with authoritative English, German, French and Spanish title/description maps. Native product translations are imported, not generated. Existing AI scenes are not automatically translated. The companion workspace check passed 135 steps with zero failures/skips; no additional paid inference was used for this refresh.

Native checkout continuity (9 October 2026)

The existing frontend route /checkout?embed=1 renders the same checkout and immutable order receipt. It permits framing only by the exact HTTPS origin registered for the requested tenant and sales channel in hosted_frontends. Ordinary checkout retains its configured frame policy. Receipts carry only the shopper cart capability, cart ID and order ID, never merchant credentials or customer details.

sequenceDiagram
    participant Bag as Original Storyfront bag
    participant Bridge as Private Storyfront API
    participant Core as Vendune checkout
    Bag->>Bridge: Product IDs and quantities
    Bridge->>Core: Existing one-use handoff
    Core-->>Bag: Native checkout in a registered-origin frame
    Core->>Core: Lock, price and save localized order snapshot
    Core-->>Bag: Exact-origin completion receipt
    Bag->>Bridge: Receipt and original purchased quantities
    Bridge->>Core: Verify completed cart and order
    Core-->>Bridge: Authoritative completed snapshot
    Bridge-->>Bag: Verified completion
    Bag->>Bag: Subtract purchased quantities once

checkout_products.rs reuses catalog translation inheritance inside the purchase transaction. The saved cart locale selects the order labels, including variant parent inheritance; later product translations do not rewrite an existing order. checkout_page.rs owns frame admission; embedded-checkout.ts owns receipt transport. The private upstream owns the original bag, its idempotent subtraction, dialog and completion verification. No private Storyfront source is shipped in this repository.

Database regression checks cover foreign origins, tenant/channel mismatch, parent translations on variants and unchanged saved labels after a later translation edit. Browser acceptance and deployment evidence are tracked separately from these checks. Real provider popups, redirects and 3DS are a separate acceptance requirement; a simulated order does not prove real payment capture.