Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ Configuration is validated with Zod at startup (`src/config/env.ts`); the server
| Variable | Required | Description |
| --- | --- | --- |
| `PORT` | no (3000) | HTTP port |
| `NODE_ENV` | no | `development` \| `production` \| `test`. Production additionally requires `SOROBAN_BILLING_CONTRACT_ID`, `UPSTREAM_HOST_ALLOWLIST` |
| `NODE_ENV` | no | `development` \| `production` \| `test`. Production requires `UPSTREAM_HOST_ALLOWLIST` and, outside demo mode, `SOROBAN_BILLING_CONTRACT_ID` |
| `DATABASE_URL` | yes | PostgreSQL connection string (metering, billing, audit) |
| `JWT_SECRET` | yes | Secret for access/refresh tokens |
| `ADMIN_API_KEY` | yes | Key for `/api/admin/*` (`x-admin-api-key` header) |
Expand Down Expand Up @@ -131,12 +131,33 @@ See [ARCHITECTURE.md](ARCHITECTURE.md) for the route map and design notes.

## Demo mode

Unsupported capabilities return HTTP **503** with the canonical error envelope,
`error.code: "DEMO_MODE_UNSUPPORTED"`, `error.details: { feature }`, and the
request ID. There is no `Retry-After`: these features require standard mode.
The guard runs before authentication, validation, and database-backed handlers.

| Route prefix (including subpaths) | Methods unavailable in demo |
| --- | --- |
| `/api/billing`, `/api/invoices` | All |
| `/api/usage`, `/api/exports` | All |
| `/api/admin/usage`, `/api/admin/db/explain` | All |
| `/api/vault/deposit/prepare` | POST |
| `/api/vault/onchain`, `/api/vault/balance` | GET, HEAD |
| `/api/audit` | POST, PUT, PATCH, DELETE (reads remain available) |
| `/api/credits`, `/api/refunds` | All |
| `/api/health/health`, `/api/health/dependencies` | GET, HEAD |

`GET /api/health` remains available and reports `data.mode: "demo"` or
`"standard"`. `GET /api/apis` continues serving the seeded SQLite catalog.
Demo mode never constructs the billing Soroban client or opens the shared
PostgreSQL pool; dependency probes cannot contact external services.

`DEMO_MODE=true` is what the public deployment uses. On startup it classifies migrations (via `-- dialect: sqlite|postgres|both` header comments or the built-in manifest) and applies every SQLite-compatible migration to an ephemeral SQLite database, logging applied/skipped/failed counts. Any genuine failure in a SQLite-applicable migration fails fast naming the broken file.

It then executes an idempotent seed that creates a demo developer and 3 sample APIs, referencing the developer's dynamic ID (`lastInsertRowid`) rather than assuming ID 1. Consequences:

- Catalog/marketplace/health/docs endpoints return real data; state is **not persistent** (serverless instances are ephemeral).
- Features needing PostgreSQL (metering, billing, invoices, audit persistence, usage exports) respond with standard 503 error envelopes (`SERVICE_UNAVAILABLE`) in demo mode, avoiding module-resolution crashes on missing Prisma clients.
- Features needing PostgreSQL (metering, billing, invoices, audit persistence, usage exports) respond with typed 503 error envelopes (`DEMO_MODE_UNSUPPORTED`) in demo mode, avoiding module-resolution crashes on missing Prisma clients.
- OpenAPI *response* validation is switched off in demo mode (some responses omit optional contract fields); request validation stays on.
- Stellar/Soroban calls are not made.

Expand Down
1 change: 1 addition & 0 deletions docs/error-codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ This section is generated from `docs/error-codes.yaml`. Run `npm run error-codes

| Code | Catalog section |
|---|---|
| `DEMO_MODE_UNSUPPORTED` | HTTP status derived / base app codes |
| `BAD_REQUEST` | HTTP status derived / base app codes |
| `UNAUTHORIZED` | HTTP status derived / base app codes |
| `FORBIDDEN` | HTTP status derived / base app codes |
Expand Down
4 changes: 4 additions & 0 deletions docs/error-codes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
# The 'code' field becomes both the TypeScript enum key and value.

error_codes:
- code: DEMO_MODE_UNSUPPORTED
section: HTTP status derived / base app codes
description: The feature requires PostgreSQL or Stellar integrations unavailable in demo mode (HTTP 503)

# HTTP status derived / base app codes
- code: BAD_REQUEST
section: HTTP status derived / base app codes
Expand Down
12 changes: 11 additions & 1 deletion docs/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -691,7 +691,8 @@
"success": true,
"data": {
"status": "ok",
"service": "callora-backend"
"service": "callora-backend",
"mode": "standard"
},
"requestId": "req-mock-uuid",
"timestamp": "2026-07-25T10:00:00.000Z"
Expand Down Expand Up @@ -8278,6 +8279,7 @@
"ErrorCode": {
"type": "string",
"enum": [
"DEMO_MODE_UNSUPPORTED",
"BAD_REQUEST",
"UNAUTHORIZED",
"FORBIDDEN",
Expand Down Expand Up @@ -8514,6 +8516,14 @@
},
"checks": {
"$ref": "#/components/schemas/HealthChecks"
},
"mode": {
"type": "string",
"enum": [
"demo",
"standard"
],
"description": "Runtime capabilities: demo uses seeded SQLite without PostgreSQL or Stellar integrations."
}
}
},
Expand Down
Loading
Loading