Files
0pt/docs/08-infrastructure.md
T
adminandClaude Sonnet 5 2902a26bac docs: full accuracy overhaul, grounded claim-by-claim in real code (#4)
* docs: full accuracy overhaul, grounded claim-by-claim in real code

Every doc in this repo (CLAUDE.md, README.md, SECURITY.md, docs/*.md) was
audited and rewritten by a multi-pass process: independent rewrite per
cluster, adversarial re-verification of that rewrite against the actual
code, then a final cross-doc consistency pass — each stage re-checking
claims against the repo directly rather than trusting the previous stage.

Two corrections matter most:

1. docs/ICEBERG_WRITE_BOUNDARY.md, docs/11-lakehouse-write-plan.md, and
   docs/P0_QUERY_SOURCE_FLOW.md asserted src/services/quality-check-service.ts,
   quality-report-service.ts, src/data-platform/services/catalog-service.ts,
   and sql/002_p0_dataset_quality.sql/sql/003_*.sql as real, tested code.
   git log --all across every branch shows zero commits for any of these
   paths, ever. 0pt's data-quality/QA enforcement does not exist in code.
   Corrected everywhere, with a new docs/P0_DATASET_QUALITY_FLOW.md added
   as an explicit target-spec (not-yet-built) replacement.

2. github.com/sax3l/0pt (this repo) and git.siax.io/sax3l/0pt (Gitea) are
   two diverged remotes sharing part of the same history (services/api/
   workers/adaptive_router.py is byte-identical between them). Gitea's main
   independently accumulated a WAF-bypass/proxy-rotation scraper stack in
   three 2026-07-13 commits, since removed via a Gitea PR; GitHub's main
   never had it. Which remote is canonical/deployed is unresolved — flagged
   consistently across the corpus, not decided here.

Also newly and consistently documented: the tenant_id/project_id-read-from-
client-body gap on the P0 lakehouse/rate-policy/source-query routes (no
auth/session system exists anywhere in this repo yet); that every CL0UD/
N0D/AUD0/ST0RE platform client is real but mock-only (zero process.env
reads for any of their *_URL vars); that two P0 mutation routes
(lakehouse/mappings, sources/rate-policies) always fail over HTTP today
because their prerequisite entity can never be created via any route; a
schema bug in sql/004 (FK references to opt_datasets/opt_sources, which
don't exist); and that 0pt.app's live deployment was independently
confirmed (curled this pass) to be running this exact codebase with
DATABASE_URL/REDIS_URL unset in production.

New file: docs/12-edge-capability-api.md — the edge-native P0 TypeScript
capability contract (app/api/0pt/*, /api/screen, /api/ai/parse), previously
undocumented and distinct from docs/04-api.md's FastAPI-engine-only scope.

Verified before commit: tsc --noEmit clean; vitest run 88 passed, 1 todo,
0 failures (13 files) — docs-only change, no source touched.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* docs: reflect PR #5's lakehouse-mapping-registry + status-code fixes

Follow-up to keep docs/12-edge-capability-api.md accurate once both PRs
land: fix/0pt-full-bug-hunt (#5) fixed the shared-instance bug in the
lakehouse mapping routes and added proper status-code mapping to the
document extraction route. This doc was written against the pre-fix
state in the same pass that added it, so it needs the same correction.

Does not claim the lakehouse write path fully works now -- the deeper
dataset-registration gap (no route ever calls DatasetService.registerDataset)
is unchanged and still blocks it end-to-end; only the shared-instance half
of the bug is fixed.

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-22 18:49:01 +02:00

20 KiB

08 · Infrastructure

Verified against actual repo state 2026-07-19 (branch main, plus .env.example read from feature/0pt-env-example). Where this doc previously described a target that doesn't match what's committed, that's called out explicitly rather than silently corrected — see the repo's honesty convention in docs/ICEBERG_WRITE_BOUNDARY.md.

Repo structure (actual, not the original apps/web sketch)

The monorepo did not converge on the apps/web/ + packages/shared/ layout this doc originally sketched. What's actually on main today:

0pt/
├─ CLAUDE.md
├─ README.md · SECURITY.md
├─ docs/                      # this documentation set
├─ app/                       # Next.js App Router (Vercel) — the "edge" plane, AT THE REPO ROOT
│  ├─ api/                    # route handlers: orders, auth, admin, catalog, connections,
│  │                          #   vehicles, subscriptions, sources, estimates, health, uploads,
│  │                          #   payments, 0pt/*, screen, webhooks, ai
│  ├─ bestall/ katalog/ kallor/ order/ portal/ admin/ tjanster/   # pages
├─ lib/                       # edge-side helpers: db.ts, engine.ts, auth.ts, stripe.ts,
│  │                          #   orders-store.ts, subscriptions-store.ts, data.ts, services.ts
├─ src/                       # SIAX-platform-facing TS layer (NOT under app/ or lib/)
│  ├─ clients/                # cl0ud-, n0d-, aud0-, st0re-, st0re-lakehouse-client.ts
│  ├─ services/                # dataset-, source-, source-query-, iceberg-write-,
│  │                          #   document-capture-, document-extraction-, external-writeback-,
│  │                          #   source-rate-policy-, dataset-iceberg-mapping-service.ts
│  ├─ adapters/                # local-blob-store.ts, odoo-bills-adapter.ts, graph-mailbox-adapter, ...
│  └─ types/
├─ components/ hooks/ styles/  # design system in code (docs/05) — data-artifact.tsx,
│  │                          #   provenance-badge.tsx, embed.tsx, use-reveal.ts, globals.css
├─ packages/schemas/           # JSON Schemas for 0pt.* event/entity contracts (not a pnpm workspace
│  │                          #   package today — pnpm-workspace.yaml has no `packages:` list)
├─ services/api/               # FastAPI orchestrator + workers (the "engine" plane, on a VPS)
│  ├─ orchestrator/main.py
│  ├─ workers/                 # adaptive_router.py, pipeline.py, resolve.py, queue.py, run.py
│  ├─ sources/                 # sources.yaml + connectors.py (docs/06)
│  └─ Dockerfile · requirements.txt
├─ sql/                        # migrations — see "Schema state" below
├─ infra/
│  ├─ docker-compose.yml       # real, committed (not a sketch — see below)
│  └─ Caddyfile                # real, committed
├─ tests/p0/                   # vitest, TS-side (services/clients/adapters)
└─ (no .github/workflows/ directory exists — see "CI/CD" below)

Known open question (not resolved this pass, Simon's call): CLAUDE.md and README.md both now note that github.com/sax3l/0pt (this checkout's origin) and git.siax.io/sax3l/0pt (Gitea) are two remotes that share part of the same history but have independently diverged — Gitea's main picked up a WAF-bypass/proxy-rotation scraper stack (since removed via a Gitea PR) that never touched GitHub's main. Which remote is authoritative/deployed, and whether they should be kept in sync, is undecided. This doc describes what's on this checkout's main only.

Schema state (sql/)

Only two migration files exist in the repo's history, on any branch: sql/001_init.sql (the core schema — dim_company, dim_contact, fact_financials, orders, etc., per docs/03-data-model.md) and sql/004_iceberg_write_boundary.sql (opt_dataset_iceberg_mappings + source-rate-policy tables, per docs/ICEBERG_WRITE_BOUNDARY.md). There is no sql/002_* or sql/003_* anywhere in this repo's git history (git log --all for those paths returns zero commits) — despite docs/ICEBERG_WRITE_BOUNDARY.md, docs/11-lakehouse-write-plan.md, and docs/P0_QUERY_SOURCE_FLOW.md citing sql/002_p0_dataset_quality.sql and a sql/003_* as if they were already-applied migrations. They are not real. See "Security & compliance" below for what this means for QA/quality-check claims.

Hosting topology

  • Vercel — app/ (this repo's root, not a separate apps/web) → 0pt.app. Verified this pass (network check performed): https://0pt.app returns HTTP 200, serves a Next.js/Turbopack app titled "0pt — your data" with SIAX branding, and GET https://0pt.app/api/health returns {"ok":true,"service":"0pt-web","db":"not_configured","queue":"not_configured",...} — an exact shape match for this repo's own app/api/health/route.ts. So README's "Live: https://0pt.app" is confirmed accurate, and the live deployment confirms DATABASE_URL/REDIS_URL are not set in production — i.e. the live site is running on the in-memory order/subscription stores described throughout this doc, not against a real Postgres. This does not confirm which git remote/commit is deployed, or that every route behaves as documented — only that the deployment exists and matches this repo's shape.
  • One VPS (Hetzner, per docs/02) — infra/docker-compose.yml (see below) runs FastAPI, worker, NocoDB, Metabase, Postgres, Redis, Caddy. This compose file and its Caddyfile are real, committed files — not a sketch — but this pass found no evidence (no CI/CD, no deploy log, no accessible host) that they have ever actually been run against a live VPS. Treat as "written, unexecuted."
  • Cloudflare R2 — documented target for file storage. app/api/uploads/route.ts checks for R2_ACCESS_KEY_ID/R2_BUCKET/R2_ENDPOINT and returns 501 storage_not_configured when absent; when "present" it still returns a hardcoded put_url: null — there is no actual presigned-URL generation implemented yet. R2 upload is an env-guarded stub, not a working integration.
  • Postgres — lib/db.ts resolves DATABASE_URL || POSTGRES_URL || POSTGRES_PRISMA_URL (Vercel Postgres integration sets the latter two automatically) and falls back to null (in-memory stores) when none is set. This part is real and wired.

docker-compose (infra/docker-compose.yml — real file, reproduced here for reference)

services:
  postgres:
    image: pgvector/pgvector:pg16
    environment: { POSTGRES_USER: opt, POSTGRES_PASSWORD: ${PG_PASSWORD}, POSTGRES_DB: opt }
    volumes: ["pgdata:/var/lib/postgresql/data", "../sql:/docker-entrypoint-initdb.d:ro"]
  redis:
    image: redis:7
  api:
    build: ../services/api
    command: uvicorn orchestrator.main:app --host 0.0.0.0 --port 8000
    environment: { DATABASE_URL: "postgres://opt:${PG_PASSWORD}@postgres:5432/opt", COMPANIES_HOUSE_API_KEY: "${COMPANIES_HOUSE_API_KEY:-}", INTERNAL_TOKEN: "${INTERNAL_TOKEN}" }
    depends_on: [postgres, redis]
  worker:
    build: ../services/api
    command: python -m workers.run
    environment: { DATABASE_URL: "postgres://opt:${PG_PASSWORD}@postgres:5432/opt", COMPANIES_HOUSE_API_KEY: "${COMPANIES_HOUSE_API_KEY:-}" }
    depends_on: [postgres, redis]
  nocodb:
    image: nocodb/nocodb:latest
    environment: { NC_DB: "pg://postgres:5432?u=opt&p=${PG_PASSWORD}&d=opt", NC_PUBLIC_URL: "https://data.0pt.app" }
    depends_on: [postgres]
  metabase:
    image: metabase/metabase:latest
    environment: { MB_DB_TYPE: postgres, MB_DB_HOST: postgres, MB_DB_PORT: "5432", MB_DB_USER: opt, MB_DB_PASS: "${PG_PASSWORD}", MB_DB_DBNAME: opt }
    depends_on: [postgres]
  caddy:
    image: caddy:2
    ports: ["80:80", "443:443"]
    volumes: ["./Caddyfile:/etc/caddy/Caddyfile", "caddydata:/data"]
    depends_on: [api, nocodb, metabase]
volumes: { pgdata: {}, caddydata: {} }

Note vs. an earlier draft of this doc: the api/worker containers here only receive DATABASE_URL, COMPANIES_HOUSE_API_KEY, and INTERNAL_TOKEN — not STRIPE_*, RESEND_API_KEY, or R2_*. That's correct as written: Stripe, R2, and (a documented but unbuilt) Resend integration are all called from app/api/* route handlers, which run on Vercel, not inside this compose stack.

Caddyfile (real, committed):

api.0pt.app { reverse_proxy api:8000 }
data.0pt.app { reverse_proxy nocodb:8080 }
dash.0pt.app { reverse_proxy metabase:3000 }

Environment variables

.env.example now exists — added on branch feature/0pt-env-example (github.com/sax3l/0pt/pull/3, not yet merged to main as of this pass). It was previously impossible to commit: .gitignore's blanket .env* rule silently matched .env.example too, so it was never added despite docs/09-build-plan.md Phase 0 requiring it since day one. The same PR adds !.env.example to .gitignore to fix that. README's "Env inventory: .env.example" link is therefore currently a broken/future reference on main until that PR merges.

The table below is grep-verified against main this pass, not transcribed from .env.example's own comments — in one case (ST0RE_URL, see note) that turned up a discrepancy worth flagging.

Var Status on main Where
DATABASE_URL / POSTGRES_URL / POSTGRES_PRISMA_URL Wired — priority-ordered fallback lib/db.ts
ENGINE_URL Wired — edge defers to FastAPI engine when set, else computes locally lib/engine.ts
INTERNAL_TOKEN Wired — sent as X-Internal-Token on every engineFetch() call lib/engine.ts
AUTH_SECRET Wired, but only for a lightweight HMAC session scaffold — not Auth.js. lib/auth.ts's own comment: "Production auth = Auth.js + Postgres adapter... wired once the database is provisioned" lib/auth.ts, app/api/auth/login/route.ts
DEV_LOGIN_KEY Wired — gates the dev-only login route (403 without a match); not a real auth system app/api/auth/login/route.ts
AUTH_URL Documented target — no read found —
STRIPE_SECRET_KEY Wired — real Stripe Checkout session creation (test or live key both work; not test-mode-locked in code) app/api/payments/[order_id]/deposit/route.ts
STRIPE_WEBHOOK_SECRET Wired — real signature verification, advances order status app/api/webhooks/stripe/route.ts
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY Documented target — no read found —
R2_ACCESS_KEY_ID, R2_BUCKET, R2_ENDPOINT Presence-checked only — gates a 501 vs stub 200; no actual R2 client/presign call exists yet app/api/uploads/route.ts
R2_ACCOUNT_ID, R2_SECRET_ACCESS_KEY Documented target — no read found (even the stub above doesn't check these) —
OPT_LOCAL_BLOB_DIR Wired — local-disk fallback artifact store, used when no object storage is configured src/adapters/local-blob-store.ts
RESEND_API_KEY Documented target — no Resend call site anywhere in this repo; not "unconfigured", genuinely not implemented —
METABASE_SITE_URL, METABASE_SECRET_KEY, NOCODB_BASE_URL, NOCODB_API_TOKEN Documented target — no read found. components/embed.tsx renders an iframe from a src prop the caller must supply; no server-side signed-URL generator exists to produce that src —
DOC_INTELLIGENCE_ENDPOINT, DOC_INTELLIGENCE_KEY, DOC_INTELLIGENCE_API_VERSION Wired src/services/document-extraction-service.ts
GRANT_ENC_KEY Presence-checked only — gates 501 vs a stub 201 that returns { grant_id, status: "pending" }. No AES-GCM encryption, no read-only validation, no actual storage happens yet despite the route's own comment describing that flow app/api/connections/db-grant/route.ts
NEXT_PUBLIC_APP_URL Wired — used as a fallback origin for building Stripe redirect URLs app/api/payments/[order_id]/deposit/route.ts
NEXT_PUBLIC_API_URL Documented target — no read found —
REDIS_URL Presence-checked only, in /api/health — no queue code in this repo reads it (Celery/queue usage, if any, is services/api's own concern, out of this repo's TS surface) app/api/health/route.ts
COMPANIES_HOUSE_API_KEY Real key referenced by services/api (Python engine, sources.yaml/connectors.py) and passed through by infra/docker-compose.yml — not part of the Next.js edge app's own env surface services/api/

Platform-client URLs — corrected from a stronger claim

CL0UD_URL, N0D_URL, AUD0_URL, and ST0RE_URL are all documented targets only — none of the four is read via process.env.* anywhere in this repo's committed code, grep-verified this pass (grep -rn "process\.env\.\(ST0RE_URL\|CL0UD_URL\|N0D_URL\|AUD0_URL\)" returns zero matches outside comments and one it.todo(...) placeholder in tests/p0/iceberg-write-service.test.ts).

This corrects a claim that appears in .env.example itself (on feature/0pt-env-example) and was carried into an earlier version of this doc: that comment states "ST0RE_URL IS read (src/clients/st0re-client.ts, src/clients/st0re-lakehouse-client.ts)". That is not accurate as of this verification. Both of those client files — like cl0ud-client.ts, n0d-client.ts, and aud0-client.ts — only export a create*Client(baseUrl?: string) factory that falls back to an in-memory mock when no baseUrl argument is passed. Every call site in this repo (app/api/0pt/** routes, src/services/*.ts) either passes no baseUrl at all or forwards an opts?.st0reBaseUrl-style constructor option that nothing upstream ever populates from process.env.ST0RE_URL. This matches docs/ICEBERG_WRITE_BOUNDARY.md's own "Verification status" section, which lists wiring ST0RE_URL → IcebergWriteService({ st0reBaseUrl: process.env.ST0RE_URL }) as future work, not something already done.

Net effect: setting any of CL0UD_URL/N0D_URL/AUD0_URL/ST0RE_URL in a deployed environment today does nothing by itself. A future pass has to wire each route/service to actually read its corresponding env var before the value has any effect. Until then, every 0pt→platform call in this repo runs against the deterministic in-memory mock built into each client file — real, tested TS, but never exercised against a live CL0UD/N0D/AUD0/ST0RE instance from this codebase.

Security & compliance — corrected against actual code, not the target design

The items below were previously described as already-built behavior. Grep/read-verified this pass, several are env-guarded stubs that return a plausible-shaped response without doing the described work. Stating this plainly, per this repo's own honesty convention:

  • Customer DB grants — app/api/connections/db-grant/route.ts returns 501 when GRANT_ENC_KEY is unset, and a stub 201 { grant_id, status: "pending" } when it is set. There is no AES-GCM encryption, no read-only-role validation, and no persistence implemented yet — the route's own comment describes the target behavior ("validate read-only, AES-GCM encrypt conn_string, store, audit") but the code doesn't do it. This is a real gap, not a doc typo.

  • File delivery / presigned R2 URLs — same pattern: app/api/uploads/route.ts is a 501 stub with a hardcoded put_url: null. No presigned URL is ever actually generated in this repo today.

  • Tenant/org isolation — OPEN SECURITY GAP, not a deliberate two-tier design. Several app/api/0pt/* routes read tenant_id/project_id directly from the request body instead of from a server-bound session:

    • app/api/0pt/lakehouse/mappings/route.ts
    • app/api/0pt/lakehouse/mappings/[mappingId]/writes/route.ts
    • app/api/0pt/sources/rate-policies/route.ts
    • app/api/0pt/sources/[sourceId]/query/route.ts
    • src/services/document-capture-service.ts

    This is not a defensible internal-vs-external boundary — it's because no auth/session system exists anywhere in this repo yet. app/api/orders/route.ts's own GET handler carries the literal comment // org scoping added with auth — i.e., not yet. lib/auth.ts is a lightweight HMAC session scaffold gated by a dev-only login key, unconnected to any of the 0pt/* routes above. Any caller can currently claim any tenant_id for these endpoints. Fixing this is Phase 3 of docs/09-build-plan.md (Auth & portal shell) — it is Simon's call how that auth design binds tenant_id server-side; it is not built anywhere in this repo today.

  • PII (contacts/UBO) / DPA — no code found in this pass implementing lawful-basis recording, deletion-on-request, or a published DPA. Treat as target design, per CLAUDE.md §5, not shipped.

  • Secrets rotation / least privilege — no rotation tooling found in this repo (expected — that's an operational practice, not application code — but noting it's unverified either way).

  • Quality/QA gate on delivered data — src/services/quality-check-service.ts, src/services/quality-report-service.ts, src/types/quality-check.ts, src/types/quality-report.ts, src/data-platform/services/catalog-service.ts, and sql/002_p0_dataset_quality.sql/sql/003_*.sql were at one point referenced in docs/ICEBERG_WRITE_BOUNDARY.md, docs/11-lakehouse-write-plan.md, and docs/P0_QUERY_SOURCE_FLOW.md as if built and tested. None of these files has ever existed in this repo's git history, on any branch (git log --all confirms zero commits for every one of those paths). All three of those docs have since been corrected (verified this pass) — each now carries its own "fabricated, not built" callout and none currently asserts those files as real; this section restates the underlying fact because it directly bears on the "Security & compliance" claims below: there is no lakehouse-side data-quality/QA enforcement built in this repo today. (The Python engine's services/api/workers/pipeline.py does have a real, working confidence-threshold drop — delivered = [r for r in deduped if r["confidence"] >= threshold] — but that's a separate, narrower mechanism inside the FastAPI pipeline, not the TS/lakehouse quality-check layer those three docs describe.)

Embedding NocoDB / Metabase

components/embed.tsx (EmbeddedView) is real, committed code — a sandboxed <iframe> that renders whatever src URL its caller passes, with a placeholder state when src is null. What's missing: the server-side code that generates a signed Metabase/NocoDB embed URL from METABASE_SECRET_KEY / NOCODB_API_TOKEN. No such generator was found in this pass. So the embed shell exists; the signed-URL plumbing that would make it show real customer data does not yet.

CI/CD

There is no .github/workflows/ directory anywhere in this repo. No lint/typecheck/build/test pipeline runs in CI today. On the TS side, pnpm lint, pnpm typecheck, and pnpm test (vitest, tests/p0/*.test.ts) exist as local scripts in package.json, but nothing invokes them automatically on push/PR. On the Python side there is no lint/test tooling at all — grep-verified this pass: ruff and pytest appear nowhere in services/api/requirements.txt or anywhere else in this repo (no pyproject.toml, no pytest.ini, no test files). services/api/README.md's own "Run locally" section only lists pip install -r requirements.txt + uvicorn/python -m workers.run — no lint or test step. An earlier version of this doc claimed ruff/pytest "exist as local scripts" for services/api; that was unverified and is corrected here. This is a real gap against docs/09-build-plan.md Phase 0's "CI skeleton" deliverable — not yet built, not a doc omission.

Monitoring

  • Vercel Analytics — genuinely wired: app/layout.tsx imports and renders <Analytics /> from @vercel/analytics/next (package.json dependency @vercel/analytics@1.3.1).
  • Sentry — not found. No sentry package in package.json, no Sentry init/config file anywhere in the repo. This is a documented target only, not shipped.
  • Uptime ping on /health — app/api/health/route.ts exists and returns { ok, db, queue, ts } based on env presence checks; no external uptime monitor wired to it was found in this repo (that would live outside this codebase regardless, e.g. an external pinger — unverified either way).
  • Worker/job health in admin — app/admin/jobs/page.tsx exists as a page; this pass did not trace whether it reads real job state or placeholder data (out of this doc's grounding scope — flagged for whoever owns docs/01-product.md/admin-portal docs to verify).