# ============================================================================= # 0pt (edge plane) -- environment inventory / example environment # ============================================================================= # Copy to `.env` (gitignored) and fill in real values. Never commit real values. # Vercel env for the edge app; VPS .env (git-ignored) for infra/docker-compose.yml. # See docs/08-infrastructure.md for the canonical description of each integration. # # Two provenance markers are used below and they mean different things: # * "wired in code today" -- read via a `process.env.*` somewhere in # this repo right now (grep-verified). # * "documented target, not yet read" -- named in docs/08-infrastructure.md / # docs/02-architecture.md but with no # `process.env.*` read yet. Filling it in # unblocks the integration; it does not by # itself change behaviour. # # The app is designed to build and boot with all of these UNSET: lib/db.ts and # lib/stripe.ts are lazy + env-guarded, and the storage/payment/doc routes return # HTTP 501 ("not configured") until the relevant keys are provided. # --------------------------------------------------------------------------- # Core / public # --------------------------------------------------------------------------- # Public base URL of this app (used for absolute links / callbacks). NEXT_PUBLIC_APP_URL=http://localhost:3000 # Documented target (docs/08); no call site found reading it in this pass. # NOTE: api.0pt.app is a dead hostname (falls through to wildcard Vercel -> # DEPLOYMENT_NOT_FOUND). The app serves its own /api routes on the site # origin, so the honest base URL is the live origin itself. NEXT_PUBLIC_API_URL=https://0pt.app # --------------------------------------------------------------------------- # Database (Postgres) -- wired in code today: lib/db.ts, app/api/health # --------------------------------------------------------------------------- # Vercel Postgres sets POSTGRES_URL / POSTGRES_PRISMA_URL automatically when the # integration is attached. First one set wins, in this order (lib/db.ts): # DATABASE_URL -> POSTGRES_URL -> POSTGRES_PRISMA_URL. DATABASE_URL=postgres://user:password@localhost:5432/opt POSTGRES_URL= POSTGRES_PRISMA_URL= # --------------------------------------------------------------------------- # Cache / queue (Redis) -- app/api/health only checks presence; no other call # site found reading it in this pass (worker queue usage, if any, lives in # services/api, outside this repo's scope). # --------------------------------------------------------------------------- REDIS_URL=redis://localhost:6379 # --------------------------------------------------------------------------- # Auth -- session / dev-login / service-token secrets (wired in code today) # --------------------------------------------------------------------------- # Session-signing secret (generate a long random string). AUTH_SECRET=change-me-long-random-string # AUTH_URL is documented (docs/08) but not yet read by any route in this pass. AUTH_URL=https://0pt.app # Optional dev-only login shortcut key (leave UNSET in production). DEV_LOGIN_KEY= # Shared token for internal/service-to-service calls; sent as X-Internal-Token # on every engineFetch() call (lib/engine.ts). INTERNAL_TOKEN=change-me-internal-token # --------------------------------------------------------------------------- # Zitadel / OIDC Bearer verification (Wave 2 hardening) -- wired in code today # --------------------------------------------------------------------------- # 0pt verifies inbound Bearer tokens against the fleet Zitadel IdP # (src/auth/zitadel.ts, wired via middleware.ts). JWKS are discovered from # /.well-known/openid-configuration, falling back to /oauth/v2/keys. # # Fleet Zitadel issuer (OIDC). Default when unset: https://id-customers.siax.io ZITADEL_ISSUER=https://id-customers.siax.io # Browser login (Authorization Code + PKCE). Create a Web application in the # Zitadel project with redirect URI https://0pt.app/api/auth/callback. ZITADEL_OIDC_CLIENT_ID= ZITADEL_OIDC_CLIENT_SECRET= # Enforcement is FAIL-CLOSED. Leave this UNSET (or set it to "true") and a # valid, verified Bearer token is REQUIRED on /api/0pt/* -- 401 on # missing/invalid, 403 when X-Tenant-Id disagrees with the token tenant. # ONLY the literal value "false" disables authentication, and that value is # REFUSED when NODE_ENV=production: the app throws at startup instead of # serving openly. Use "false" for local development only. AUTH_ENFORCE=true # Optional override for the claim key that carries the SIAX tenant id. When # unset, tenant derivation uses the documented precedence: # urn:zitadel:iam:org:id -> urn:zitadel:iam:user:resourceowner:id -> tenant_id -> tenant. ZITADEL_TENANT_CLAIM= # --------------------------------------------------------------------------- # Data engine bridge -- wired in code today: lib/engine.ts # --------------------------------------------------------------------------- # When set, edge route handlers defer to the FastAPI engine (services/api) as the # single source of truth (orders/estimates/catalog/ai-parse). When unset, the edge # computes locally / falls back to an honest heuristic -- see lib/engine.ts and # app/api/ai/parse. ENGINE_URL=http://localhost:8000 # --------------------------------------------------------------------------- # SIAX platform clients # --------------------------------------------------------------------------- # WIRED FOR REAL since 2026-08-31: every factory in src/clients/* reads its # *_URL from process.env when no constructor baseUrl is given. Under # NODE_ENV=production an UNSET url is a hard boot failure # (SiaxError DEPENDENCY_UNAVAILABLE) — production never silently falls back # to the in-memory mocks (Master-DoD §3/§10). In dev/test, unset = documented # local mock/no-op behaviour. CL0UD_URL= N0D_URL= AUD0_URL= # Optional service bearer tokens for the platform clients (machine identity). # Values come from Infisical/secret refs — NEVER commit real values here. CL0UD_SERVICE_TOKEN= N0D_SERVICE_TOKEN= AUD0_SERVICE_TOKEN= ST0RE_SERVICE_TOKEN= # INF0 (AI OS) -- documented target, not yet read by any code in this repo. # Route AI-parsning / model calls through the gateway instead of raw provider keys. INF0_URL= # ST0RE_URL is the convention name for src/clients/st0re-client.ts / # st0re-lakehouse-client.ts and is read by the real-infra test suite # (tests/p0/*.realinfra.test.ts). The lakehouse write path was live-verified # against ST0RE on 2026-07-13 with this set; unset, the clients fall back to # their deterministic in-memory mocks. ST0RE_URL= # --------------------------------------------------------------------------- # P0 Data-OS backends (src/search, src/analytics, src/metadata, src/features) # --------------------------------------------------------------------------- # OpenSearch — search projections ONLY, never canonical truth (§42). Unset in # dev/test = search capability unavailable (typed error), never a fake. OPENSEARCH_URL= # ClickHouse — 0pt capability analytics (tenant-isolated metric events). # HAS NO CONNECTION to b0ts pipelines or quarantined market data (legal). CLICKHOUSE_URL= # "user:password" for ClickHouse basic auth; from secret refs only. CLICKHOUSE_AUTH= # OpenMetadata — metadata PROJECTION target (canonical stays in 0pt). OPENMETADATA_URL= OPENMETADATA_TOKEN= # Feast feature server (online features path). FEAST_SERVER_URL= # --------------------------------------------------------------------------- # Payments (Stripe) -- wired in code today: app/api/payments/*, # app/api/webhooks/stripe # --------------------------------------------------------------------------- STRIPE_SECRET_KEY=sk_test_xxx STRIPE_WEBHOOK_SECRET=whsec_xxx # Documented target (docs/08); not yet read by any route in this pass. NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY= # --------------------------------------------------------------------------- # Object storage -- Cloudflare R2 / S3-compatible # --------------------------------------------------------------------------- # R2_ENDPOINT / R2_BUCKET / R2_ACCESS_KEY_ID are read in code today; all three # are required to enable /api/uploads, otherwise it returns 501. # R2_ACCOUNT_ID and R2_SECRET_ACCESS_KEY are documented in docs/08 but were NOT # found via a process.env.* read in this pass -- verify before assuming the R2 # client is fully wired. R2_ENDPOINT=https://.r2.cloudflarestorage.com R2_BUCKET=opt-uploads R2_ACCESS_KEY_ID=change-me R2_ACCOUNT_ID= R2_SECRET_ACCESS_KEY= # Fallback local artifact store used when no object storage is configured # (src/adapters/local-blob-store.ts). Leave unset in production. OPT_LOCAL_BLOB_DIR=./.opt-blobs # --------------------------------------------------------------------------- # Document intelligence / OCR (Azure) -- wired in code today: # src/services/document-extraction-service.ts. Unset => extraction # "not configured". # --------------------------------------------------------------------------- DOC_INTELLIGENCE_ENDPOINT=https://.cognitiveservices.azure.com DOC_INTELLIGENCE_KEY=change-me DOC_INTELLIGENCE_API_VERSION=2024-11-30 # --------------------------------------------------------------------------- # Customer DB-grant encryption -- wired in code today: # /api/connections/db-grant # --------------------------------------------------------------------------- # 32-byte key, AES-GCM. Encrypts customer-supplied read-only connection strings # at rest. GRANT_ENC_KEY=change-me-32-byte-key # --------------------------------------------------------------------------- # External write-back targets # --------------------------------------------------------------------------- # Odoo base URL for the bills/write-back adapter. ODOO_BASE_URL=https:// # --------------------------------------------------------------------------- # Email -- Resend (documented target, docs/08; no Resend call site found in # this pass -- treat as not-yet-implemented, not just unconfigured) # --------------------------------------------------------------------------- RESEND_API_KEY= # --------------------------------------------------------------------------- # Embedded analytics (documented target, docs/08; no call site found in this # pass) # --------------------------------------------------------------------------- METABASE_SITE_URL= METABASE_SECRET_KEY= NOCODB_BASE_URL= NOCODB_API_TOKEN= # --------------------------------------------------------------------------- # Test-only (tests/p0 real-infra suite; not needed to run the app) # --------------------------------------------------------------------------- # ST0RE_TEST_TENANT_ID= # --------------------------------------------------------------------------- # Document workbench (upload -> DOC0 -> INF0 -> validated export -> credits) # Wired in code today: app/api/0pt/documents/*, app/api/0pt/credits/*. # Every value is optional to boot and mandatory to USE: a missing integration # makes its route answer 503 "not configured" instead of fabricating success. # --------------------------------------------------------------------------- # DOC0 document service. URL must be absolute HTTPS; token is a service PAT. DOC0_URL=https://doc0.siax.io DOC0_SERVICE_TOKEN= # The DOC0 service identity's own actor id (token subject). DOC0 rejects an # x-actor-id it has no actor_grant for; 0pt keeps the end-user actor in its own # journal and lets the DOC0 call act as the service. DOC0_SERVICE_ACTOR_ID= # Maximum accepted upload size (bytes). Default 25 MiB. OPT_MAX_UPLOAD_BYTES=26214400 # Interim AI path: self-hosted Ollama on the SIAX decision server # (server-decision-01). Internal only; no provider key and no per-token cost. # Plain HTTP is accepted only for loopback/RFC1918/tailnet hosts. DECISION_OLLAMA_URL=http://100.96.133.104:11434 DECISION_OLLAMA_MODEL=hermes-qwen3.5:9b # INF0 AI gateway (model calls). No provider key is ever placed in this repo. # Live path today: the LiteLLM gateway (OpenAI-compatible). The native # /v1/inf0/* surface is contract-only; INF0_URL is used as a fallback. INF0_GATEWAY_URL=https://ai-gateway.siax.io/v1 INF0_GATEWAY_LITELLM_KEY= INF0_GATEWAY_PROVIDER_KIND=external INF0_URL=https://api.inf0.siax.io INF0_API_TOKEN= # PAY0 (optional; Stripe is used directly for credit checkout today). PAY0_URL=https://pay0.siax.io PAY0_API_TOKEN= # N0D / ST0RE (used by the broader pipeline; not required by the routes above). N0D_URL=https://api.n0d.siax.io N0D_API_TOKEN= ST0RE_URL=https://api.st0re.siax.io ST0RE_API_TOKEN= # OpenFGA project authorization. Without all four values, document routes fail # closed with PROJECT_AUTHORIZATION_NOT_CONFIGURED. OPENFGA_API_URL=https://fga.siax.io OPENFGA_STORE_ID= OPENFGA_MODEL_ID= OPENFGA_AUTH_TOKEN= # Transitional, server-owned project grants used only when OpenFGA is not # configured. JSON array; actorId "*" means every verified actor in the tenant. # OPT_PROJECT_GRANTS=[{"tenantId":"379976258701823235","projectId":"prj_demo","actorId":"*"}] OPT_PROJECT_GRANTS= # Model policy for the document workbench. Without this, structuring answers # MODEL_POLICY_NOT_CONFIGURED. OPT_ALLOWED_MODELS= OPT_ALLOWED_DATA_CLASSES=none,low OPT_MODEL_DESTINATION=external OPT_EXTERNAL_TRANSFER_ALLOWED=false OPT_MODEL_MAX_INPUT_TOKENS=60000 OPT_MODEL_MAX_OUTPUT_TOKENS=4000 # Server-owned, versioned credit price book (JSON, one line). No price is ever # read from a browser. Without it, credit packs/checkout/structuring answer # PRICE_BOOK_NOT_CONFIGURED. # {"version":"2026-11-01","currency":"SEK","packs":[{"id":"starter","credits":100,"amountMinor":9900}],"metering":{"pagesPerCredit":5,"inputTokensPerCredit":20000,"outputTokensPerCredit":4000}} OPT_CREDIT_PRICE_BOOK= # Pilot switch. `off` runs document structuring without a wallet; it is # explicit (never a silent fallback), the response reports `credits: null`, and # purchases stay disabled. Unset/`on` requires the price book + ledger. OPT_CREDITS_MODE= # Central Keycloak (SIAX Portal) KEYCLOAK_ISSUER=https://auth.portal.siax.dev/realms/portal KEYCLOAK_EXPECTED_AUDIENCE=account KEYCLOAK_AUTHORIZED_PARTY=siax-portal