Repository navigation
Description
Summary
The public deployment runs with DEMO_MODE=true. The README promises that features needing PostgreSQL (metering, billing, invoices, audit persistence, usage exports) "respond with errors in demo mode", and that Stellar/Soroban calls are not made. Nothing enforces either. In demo mode those routes still try to use the Postgres pool (2-second connect timeout, then a 500), the Prisma client, or the Soroban clients, and the response is an opaque INTERNAL_ERROR. This issue adds an explicit demo capability guard: a declared list of routes that are unavailable in demo mode, a stable 503 DEMO_MODE_UNSUPPORTED envelope, and a health field that tells clients which mode they are talking to.
Background and evidence
- What the README promises.
README.md, "Demo mode": "Features needing PostgreSQL (metering, billing, invoices, audit persistence, usage exports) respond with errors in demo mode", and "Stellar/Soroban calls are not made." - The only demo branches.
src/db/index.ts,initializeDb(lines 31-34), only switches the SQLite bootstrap:if (process.env.DEMO_MODE === 'true') { bootstrapDemoDb(sqlite, migrationDir); return; }.src/app.ts(line 350) only disables OpenAPI response validation:validateResponses: process.env.DEMO_MODE !== "true".- No other code reads
DEMO_MODE(grep -rn DEMO_MODE src).
- Postgres still used.
src/app.ts(line 180) always setsapp.locals.dbPool = pool(src/db.ts, a realpg.PoolwithconnectionTimeoutMillis). Billing (src/routes/billing/deduct.ts), idempotency, invoices (src/routes/invoices.ts, through Prisma) and forecast all use it in demo mode. - Soroban still called.
createSorobanBillingServicebuilds a client wheneverSOROBAN_BILLING_CONTRACT_IDis set, and the README's Vercel instructions set it, so demo traffic can reach a real RPC. - No mode signal.
/api/healthreturns{ status: "ok", service: "callora-backend" }with no indication of demo mode.
Scope
In scope
- Config. Promote
DEMO_MODEintosrc/config/env.ts(zod boolean) andconfig.demoMode, replacing the rawprocess.envreads. - Capability guard.
- Add
src/middleware/demoGuard.tswith a declared table of route prefixes and methods that require Postgres or Soroban:/api/billing/*,/api/invoices,/api/usage*,/api/exports*,/api/admin/usage/*,/api/admin/db/explain,/api/vault/deposit/prepare,/api/auditwrites,/api/credits,/api/refunds. - In demo mode, matching requests get
503withcode: "DEMO_MODE_UNSUPPORTED",details: { feature }andRetry-Afteromitted. Mount it before those routers. - Add the code to
docs/error-codes.yamland runnpm run error-codes:generate.
- Add
- No external calls. In demo mode,
createSorobanBillingServicereturnsundefinedregardless of env, and the Postgres pool is created lazily so it never connects. - Health.
/api/healthincludesmode: "demo" | "standard". Update the OpenAPI schema for/api/health. - Docs. Update
README.md(Demo mode section) with the exact list of unavailable routes.
Out of scope
- Making Postgres features work on SQLite.
- The Vercel handler and bundle (a separate issue).
Design constraints
- One table. The guarded route list is a single exported constant, and a test asserts every entry corresponds to a mounted route, so it cannot rot.
- No effect outside demo. With
DEMO_MODEunset, behaviour is byte-for-byte unchanged (snapshot test on/api/healthaside from the new field). - Envelope. The 503 uses
errorEnvelopewithrequestId.
Test plan
Add src/middleware/demoGuard.test.ts and extend tests/integration/health.test.ts, using createApp with DEMO_MODE=true.
| # | Scenario | Expected |
|---|---|---|
| 1 | POST /api/billing/deduct in demo |
503 DEMO_MODE_UNSUPPORTED, quickly (no 2s pool timeout) |
| 2 | GET /api/invoices in demo |
503 |
| 3 | GET /api/apis in demo |
200 with the seeded catalog |
| 4 | GET /api/health in demo |
mode: "demo" |
| 5 | Same billing call with demo off | reaches the handler as before |
| 6 | Demo with SOROBAN_BILLING_CONTRACT_ID set |
no Soroban client constructed (spy) |
| 7 | Guard table entries | every prefix resolves to a mounted router |
| 8 | Pool usage in demo | pool.connect never called during the suite |
Regression proof. Row 1 returns a 500 after the pool timeout on current main.
Acceptance criteria
- No raw
process.env.DEMO_MODEreads remain outsidesrc/config. - Every test-plan row passes.
-
npm run lint,npm run typecheck,npm run build(includingerror-codes:checkandvalidate:openapi) andnpm testpass, and CI is green. - At least 90% line coverage on
demoGuard.ts.
Getting started
git checkout -b feat/demo-mode-capability-guard
npm ci && npx prisma generate
DEMO_MODE=true CORS_ALLOW_MISSING_ORIGIN=true npm run dev
curl -i -X POST localhost:3000/api/billing/deductRead first: src/db/index.ts, src/db/demo.ts, src/app.ts (lines 160-360), src/services/createSorobanBillingService.ts, and the README "Demo mode" section.
PR requirements
- Link this issue (
Closes #<n>). - Paste the guarded route table and test output with coverage.
- Example commit:
feat(demo): add capability guard with typed 503 for unsupported routes.
Priority rationale
Medium. The work is contained, but it makes the public demo behave as documented, stops it from contacting real infrastructure, and gives clients a machine-readable signal instead of opaque 500s.
Activity
Gabriel-123-diamond commentedon Oct 18, 2026
@Gabriel-123-diamond has applied to work on this issue as part of the Stellar Wave Program's 10th wave.
ℹ️ Repo Maintainers: To accept this application, review their application or assign @Gabriel-123-diamond to this issue.
drips-wave commentedon Oct 19, 2026
Congratulations, @Gabriel-123-diamond! 🎉 Your application was accepted by the repo's maintainers, and the issue is due on October 22, 2026.
🧑💻 @Gabriel-123-diamond: Please resolve the issue such that the repo's maintainers have enough time to review your contribution before the due date. You'll earn Points for completing the issue on-time, which will make you eligible for a share of the Stellar Wave Program's reward pool.
Warning
When opening a PR, please link it to this issue to ensure it gets tracked accurately. Points are awarded when this issue is marked as completed by the maintainer.
🤠 Repo maintainers: Please keep an eye on the contributor's progress and review their work before the due date. You can manage this issue, including adjusting its complexity and points, here.
🌊 Happy Wave 🌊
Gabriel-123-diamond commentedon Oct 19, 2026
Implementation complete and ready for review: #1526
Updated on October 24 in response to the maintainer's rebase request: head
002a023is based on current main5150bee, retains #1550's restored security/configuration checks and the newer export/vault changes, and has no merge conflicts.Validation: 56 focused tests pass; demo guard coverage is 100%; production and Vercel builds and the secret scan pass. Full run: 2,776 tests pass and 17 fail because four PostgreSQL Testcontainers suites lack a working container runtime, reproduced on unchanged main. Existing lint startup failure and the same 254 typecheck diagnostic locations/codes are documented in the PR.
Remaining CI blocker: OpenAPI compatibility treats the required new DEMO_MODE_UNSUPPORTED response enum member as breaking. This is disclosed in the PR and maintainer reply; CI is not claimed green. Awaiting maintainer review.
feat(demo): guard unsupported PostgreSQL and Soroban capabilities
feat(demo): guard unsupported PostgreSQL and Soroban capabilities (#1526
drips-wave commentedon Oct 24, 2026
This issue has been completed by @Gabriel-123-diamond as part of the Stellar Wave Program's 10th Wave 🥳
😎 @Gabriel-123-diamond: You earned 100 Points for completing this issue! Once the Wave's rewards are finalized, you'll be eligible for a percentage of the Wave's reward pool based on the percentage of total points you've earned. Learn more here. You can also Leave a review to share your experience working on this issue.
🧑💻 Repo maintainers: How'd the contributor do? Leave a review to share your experience working with them.