Files
0pt/docs/architecture/ARCHITECTURE.md
T

443 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 0PT — Arkitektur
**Kategori:** platform (K1) **Underkategori:** data-os **Status:** under utveckling (deployad frontend i drift, capability-lagret ej produktionsklart)
**Senast verifierad mot kod:** 2026-08-07 (commit `6531384`, main) · **Klassificeringsuppdatering:** 2026-08-10 (`siax.repo.json`)
> **Läsanvisning.** Detta dokument beskriver vad koden i repot **gör**, inte vad
> `README.md`, `CLAUDE.md` eller `docs/01–11` säger att den borde göra. Där de
> avviker står avvikelsen utskriven — se §7. Påståenden märkta *(live-verifierat
> 2026-08-07)* är kontrollerade mot den körande instansen på `https://0pt.app`.
> Allt annat är läst ur källkoden. Där något inte gick att belägga står **OKÄNT**.
---
## 1. Syfte
0pt är SIAX Clouds **Data OS**: plattformen som skaffar, strukturerar, härleder
och levererar företags-, kontakt- och fordonsdata för Norden/Europa, med
proveniens (källa, färskhet, konfidens) per fält.
### 1.1 Klassificering (SSOT: `siax.repo.json` / `siax.config.json`, VERIFIERAT 2026-08-10)
| Fält | Värde |
|---|---|
| category / subcategory | `platform` (K1) / `data-os` |
| appId / packageName | `0PT` / `@siax/0pt` |
| capabilityPrefix | `0pt` |
| node | `null` (ingen SIAX-nod tilldelad — ingen drift på flottan än) |
| domains | `0pt.app` (live) · `api.0pt.app` (target per `.env.example`) |
| consumes | CL0UD · ST0RE · N0D · AUD0 · INF0 |
| replacesSaaS | Apollo · Cognism · ZoomInfo · Clearbit (målet för Data OS: ersätta paid enrichment-SaaS) |
| decoupled | `false` |
| designSystem | `null` (eget mono/editorial-system, konsumerar inte @siax/ui — öppen fråga §8.8) |
Ersätter krav på betald enrichment-SaaS (Apollo/Cognism/ZoomInfo/Clearbit) med
egen compliant sourcing + provenance — målet är att Datahub/VehIQ/data-avdelningar
konsumerar `0pt.*`-capabilities i stället för att köpa listor. *(ANTAGANDE om
konsumentledet: inga inkommande integrationer är verifierade i detta repo.)*
Repot innehåller i praktiken **två separata system** i samma träd:
| Plan | Var | Vad koden faktiskt är |
|---|---|---|
| **Webbkonsol + edge-API** | repo-roten (`app/`, `lib/`, `components/`) | Next.js 16.0.10 App Router. 20 sidor (svensk UI-copy), 28 API-routehandlers. Marknadsföring, katalog, orderflöde, kund-/adminportal. |
| **Capability-lager** | `src/` + `app/api/0pt/*` | TypeScript-tjänster för `0pt.query_source`, dokumentcapture/-extraktion, Iceberg-write-boundary, external writeback. **All state ligger i `Map`-objekt i minnet** (`src/services/*.ts`), inte i databas. |
| **Data-engine** | `services/api/` | FastAPI-orkestrator, 12 endpoints, 162 rader. Workers (`adaptive_router`, `pipeline`, `queue`, `resolve`, `run`). |
| **Skrapmotor** | `services/scrapers/` | Python. `engine.py`, `engine_v2.py`, `waf_bypass.py`, ClickHouse-pipeline. |
### Vad 0pt uttryckligen INTE gör
- **Ingen egen IdP.** Bearer-tokens verifieras mot Zitadel (`src/auth/zitadel.ts`),
utfärdas aldrig här. Cookie-sessionen i `lib/auth.ts` är ett HMAC-scaffold med
dev-login bakom `DEV_LOGIN_KEY`, uttryckligen inte produktionsauth.
- **Ingen BigQuery.** Ingen referens till BQ finns i koden (verifierat med grep).
- **Ingen egen policymotor.** Beslut delegeras till CL0UD (`src/clients/cl0ud-client.ts`).
- **Ingen tung batch i webblagret.** Långa jobb ska gå till engine/N0D.
### Uttryckligt förbud som koden BRYTER MOT
`CLAUDE.md` §1 och `README.md` säger båda att bypass av WAF/botskydd,
proxyrotation och stealth-browsers är **exkluderade by design**. Det stämmer inte:
`services/scrapers/waf_bypass.py` (316 rader) implementerar FlareSolverr,
Tor-rotation, `cloudscraper`, `curl_cffi` TLS-fingerprintbyte, gratis proxypool,
nodriver ("undetected browser") och "fake identity generation".
`services/infra/docker-compose.data-stack.yml` startar `flaresolverr` och
`tor-proxy` som tjänster. Commit `4a164c5` (Simon Axelsson, 2026-07-13) heter
ordagrant *"refactor: remove compliance restrictions"*. Se §7 lucka **S9**.
---
## 2. Plats i estatet
### Beroenden uppåt (0pt anropar)
| Beroende | Klient | Env-var | Beteende när env saknas |
|---|---|---|---|
| Zitadel (IdP) | `src/auth/zitadel.ts` | `ZITADEL_ISSUER` (default `https://id-customers.siax.io`) | Använder default-issuern; JWKS hämtas via OIDC-discovery, fallback `<issuer>/oauth/v2/keys` |
| CL0UD (policy) | `src/clients/cl0ud-client.ts` | `CL0UD_URL` | **Fail-open** — returnerar alltid `allow` utan nätanrop (§7 S5) |
| AUD0 (audit) | `src/clients/aud0-client.ts` | `AUD0_URL` | **No-op** — inga auditevent skrivs (§7 S6) |
| ST0RE (artefakter/lakehouse) | `src/clients/st0re-client.ts`, `st0re-lakehouse-client.ts` | `ST0RE_URL` | Returnerar deterministisk mockartefakt |
| N0D (jobb) | `src/clients/n0d-client.ts` | `N0D_URL` | Returnerar deterministiskt mockjobb |
| Egen data-engine | `lib/engine.ts` | `ENGINE_URL`, `INTERNAL_TOKEN` | `engineFetch` returnerar `null`, edge räknar lokalt |
| Stripe | `lib/stripe.ts` | `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET` | Betalroutes svarar `501` |
| Azure Document Intelligence | `src/adapters/document-intelligence-adapter.ts` | `DOC_INTELLIGENCE_*` | Extraktion svarar "not configured" |
| Cloudflare R2 | `app/api/uploads/route.ts` | `R2_ENDPOINT`, `R2_BUCKET`, `R2_ACCESS_KEY_ID` | `501`, fallback `OPT_LOCAL_BLOB_DIR` |
| OpenSanctions (extern, publik) | `app/api/screen/route.ts` | — | Anropas direkt utan nyckel |
| Odoo | `src/adapters/odoo-bills-adapter.ts` | `ODOO_BASE_URL` | Adapter inaktiv |
Mönstret är genomgående: **saknad env ⇒ tyst degradering, inte fel.** Det är
bekvämt lokalt och farligt i produktion (§7 S5, S6).
### Beroenden nedåt (vem konsumerar 0pt)
Enligt `CLAUDE.md` matar 0pt interna produkter (Protosell/FiveCRM, VehIQ). **Inga
sådana anrop finns i det här repot** — ingen inkommande integration är verifierad
här. Betrakta konsumentlistan som odokumenterad tills motparten visats.
### Vad 0pt inte får bero på
- **BigQuery** — hård estate-regel. Ej överträdd.
- **Hogia** — skrivning förbjuden. Ingen referens finns.
- **Protosell-tenantdata** — tenantisolering, se §7 S2.
- Nya SaaS-beroenden utan dokumenterat skäl (`CLAUDE.md` §4, "one tool per job").
---
## 3. API-yta
Risknivå = SIAX L0–L5-taxonomin, bedömd av mig utifrån vad handlern gör.
"Kräver godkännande" = huruvida koden **faktiskt** kräver auth/godkännande idag.
### 3.1 `/api/0pt/*` — capability-lagret (bakom `middleware.ts`-matchern)
| Operation | Metod/sökväg | Risknivå | Kräver godkännande |
|---|---|---|---|
| Capability-index | `GET /api/0pt` | L0 | Nej — *(live-verifierat: 200 anonymt)* |
| Registrera dataset↔Iceberg-mappning | `POST /api/0pt/lakehouse/mappings` | L3 | Nej i kod; `tenant_id` läses ur body |
| Lista mappningar | `GET /api/0pt/lakehouse/mappings` | L2 | Nej; `tenant_id` ur query |
| Hämta mappning | `GET /api/0pt/lakehouse/mappings/{mappingId}` | L2 | Nej |
| Utför Iceberg-write | `POST /api/0pt/lakehouse/mappings/{mappingId}/writes` | **L4** | Nej i kod (CL0UD fail-open, §7 S5) |
| Registrera rate-policy | `POST /api/0pt/sources/rate-policies` | L2 | Nej |
| Lista/hämta rate-policy | `GET /api/0pt/sources/rate-policies[/{id}]` | L1 | Nej |
| Kör källfråga | `POST /api/0pt/sources/{sourceId}/query` | **L4** | Nej; `connection_config` tas ur body (§7 S10) |
| Ta emot dokument | `POST /api/0pt/documents/capture` | L3 (PII) | Nej |
| Lista dokument | `GET /api/0pt/documents/capture` | L3 (PII) | Nej |
| Extrahera dokument | `POST /api/0pt/documents/{documentId}/extraction` | L3 (PII) | Nej |
### 3.2 Övriga API-routes — **helt utanför middleware-matchern**
`middleware.ts` har `matcher: ['/api/0pt/:path*']`. Allt nedan har därför
**noll autentisering i koden**.
| Operation | Metod/sökväg | Risknivå | Kräver godkännande |
|---|---|---|---|
| Liveness | `GET /api/health` | L0 | Nej (avsiktligt) |
| Prisestimat | `POST /api/estimates` | L1 | Nej |
| Katalog / källor / fordon | `GET /api/{catalog,sources,vehicles}` | L1 | Nej |
| Sanktions-/PEP-screening | `GET /api/screen?q=` | L2 | Nej — proxar mot OpenSanctions |
| AI-parsning av fritext | `POST /api/ai/parse` | L2 | Nej |
| Skapa order | `POST /api/orders` | L2 | Nej |
| **Lista alla ordrar** | `GET /api/orders` | **L3** | **Nej** — ingen org-scoping *(live: 200, `[]`)* |
| Läs/ändra order | `GET,PATCH /api/orders/{id}` | L3 | Nej |
| **Adminmetrik (orderpipeline, belopp)** | `GET /api/admin/metrics` | **L3** | **Nej** *(live-verifierat: 200 anonymt)* |
| Deposition (Stripe checkout) | `POST /api/payments/{order_id}/deposit` | **L4** | Nej |
| **Frigör escrow-slutbetalning** | `POST /api/payments/{order_id}/release` | **L4** | **Nej** — sätter dessutom status `delivered` (§7 S4) |
| Stripe-webhook | `POST /api/webhooks/stripe` | L3 | Signaturkontroll via `STRIPE_WEBHOOK_SECRET` |
| Prenumerationer | `GET,POST /api/subscriptions` | L2 | Nej |
| Filuppladdning | `GET,POST /api/uploads` | L3 | Nej (`501` utan R2) |
| Ta emot kund-DB-grant | `POST /api/connections/db-grant` | **L4** | Nej (`501` utan `GRANT_ENC_KEY`; §7 S7) |
| Dev-login | `POST /api/auth/login` | L2 | Ja — kräver `AUTH_SECRET` + matchande `DEV_LOGIN_KEY` |
| Logout / me | `POST /api/auth/logout`, `GET /api/auth/me` | L1 | Cookiebaserat |
### 3.3 FastAPI-engine (`services/api/orchestrator/main.py`)
12 endpoints: `GET /health`, `/sources`, `/sources/{market}`, `/vehicles/{plate}`,
`/screen`, `POST /estimates`, `POST /orders`, `GET /orders`, `GET /orders/{oid}`,
`PATCH /orders/{oid}`, `POST /orders/{oid}/run`, `GET /orders/{oid}/status`.
**Ingen autentisering alls** — grep efter `Depends`, `auth`, `token`,
`X-Internal-Token` i filen ger noll träffar, trots att `lib/engine.ts` skickar
`X-Internal-Token`. Se §7 S8.
---
## 4. Datamodell
### 4.1 Postgres-schema (DDL finns, används inte av `/api/0pt/*`)
`sql/001_init.sql` definierar 13 tabeller:
- **Dataentiteter:** `dim_source`, `dim_company`, `dim_contact`, `dim_ubo`,
`dim_vehicle`, `fact_financials`
- **Proveniens:** `bridge_provenance` (`entity_type`, `entity_key`, `field_name`,
`source_id`, `freshness`, `confidence`, `level` 0–3, `agreed_sources`) — unikt
index per fält. Detta är produktens kärnsignatur.
- **Plattform:** `orgs`, `users`, `orders`, `jobs`, `deliveries`, `subscriptions`,
`files`, `db_grants`, `payments`, `audit_log`
- Kräver `pgvector` + `pgcrypto`. Belopp i **öre** (integer).
`sql/004_iceberg_write_boundary.sql` + `005_..._gap_closure.sql` lägger till
`opt_dataset_iceberg_mappings` och `opt_source_rate_policies`.
**Migrationskedjan har hål:** `sql/002_*` och `sql/003_*` **saknas i repot** men
refereras av tre filer (`app/api/0pt/sources/[sourceId]/query/route.ts:14`,
`src/services/source-service.ts:8`, `docs/P0_QUERY_SOURCE_FLOW.md:98`) som
"`sql/002_p0_dataset_quality.sql` definierar redan `opt_sources`". Tabellen
`opt_sources` har alltså ingen migration här. Det finns heller ingen
migrationsrunner — SQL:en appliceras manuellt. **OKÄNT** var/om den körts.
### 4.2 Var data faktiskt ligger
| Data | Faktisk lagring i kod |
|---|---|
| Ordrar, jobb, betalningar | `lib/orders-store.ts`: Postgres **om** `getSql()` ger en klient, annars `const mem = new Map()` per instans |
| Sources, datasets, mappningar, rate-policies, dokument, extraktioner, writebacks | `Map` i minnet, en per tjänsteinstans (`src/services/*.ts`, 10 st) |
| Dokumentbytes | R2 om konfigurerat, annars `LocalBlobStore` mot `OPT_LOCAL_BLOB_DIR` |
| Auditspår | AUD0 om `AUD0_URL` satt, annars ingenstans |
Två konsekvenser, båda dokumenterade i koden själv:
1. **Ingen delad state mellan routemoduler.** Varje route-fil instansierar sin
egen tjänst, så en källa registrerad via `POST /api/0pt/sources` är inte
synlig för `POST /api/0pt/sources/{id}/query`. Detta står utskrivet i
route-filens egen kommentar.
2. **Ingen durabilitet.** *(live-verifierat 2026-08-07: `GET /api/0pt/lakehouse/mappings`
och `/api/0pt/sources/rate-policies` svarar `[]`, och `GET /api/health` på
0pt.app rapporterar `"db":"not_configured"`.)* Produktionsinstansen har alltså
**ingen databas** — all state försvinner vid varje kallstart.
### 4.3 Persondata
- `dim_contact`: namn, roll, e-post, telefon, LinkedIn — persondata under GDPR.
- `dim_ubo`: verklig huvudman — persondata, potentiellt känslig.
- `db_grants.conn_encrypted`: kunds DB-anslutningssträng (se §7 S7).
- Dokumentcapture (`app/api/0pt/documents/capture`) tar emot godtyckliga
dokument, inkl. fakturor/mailbilagor via Graph-adaptern — persondata.
### 4.4 Retention
**Ingen retention är implementerad.** `CLAUDE.md` §5 utlovar "deletion-after-job";
inget sådant jobb, ingen TTL, ingen `DELETE`-väg och ingen radering finns i koden.
`audit_log`-tabellen finns men skrivs aldrig av TypeScript-lagret.
---
## 5. Deploy
| Fält | Verifierat läge |
|---|---|
| **Nod** | Ingen SIAX-nod. *(live-verifierat 2026-08-07: `https://0pt.app/api/health` → `Server: Vercel`, `X-Vercel-Id: arn1::iad1::…`)* Frontenden körs alltså på **Vercel**, inte på flottan. |
| **Coolify-app** | Ingen verifierad — fortfarande **OKÄNT** om någon Coolify-app pekar på repot (2026-08-09: `git.siax.io/sax3l/0pt` genomsökt, ingen egen fleet-åtkomst att fråga Coolify API direkt denna pass). `Dockerfile` är skriven för Coolify (node:24-alpine, multistage, `HEALTHCHECK` mot `/api/health`, kör som `node`-användare). `siax.config.json` deklarerar nu `deploy.target: "coolify"` (ändrat från `"vercel"` 2026-08-09, PR i `fix/remove-vercel-deploy-target`) och ett nytt `.gitea/workflows/deploy.yml` finns, ported från n0d/st0re-mönstret — men appens UUID i den filen är en medveten platshållare som får jobbet att faila högljutt tills en riktig Coolify-app skapats och tokens/uuid fyllts i. Config-flippen ändrar alltså **avsikt**, inte trafik. |
| **Domän** | `0pt.app` (live). Docs nämner även `api.0pt.app`, `data.0pt.app`, `dash.0pt.app` via Caddy — **ej verifierade**. |
| **Bygg** | `next build` via pnpm@9. `next.config.mjs` sätter `typescript.ignoreBuildErrors: true` — bygget typkontrollerar alltså **inte** `app/`, `components/`, `lib/` eller `middleware.ts`. |
| **Engine-deploy** | `infra/docker-compose.yml` (postgres, redis, api, worker, nocodb, metabase, caddy) och `services/infra/docker-compose.data-stack.yml` (clickhouse, trino, flaresolverr, tor-proxy, superset, postgres). **Ingen av dem är verifierad som körande.** |
| **Hemligheter** | Rena miljövariabler (`.env.example` listar 20+). **Infisical är inte inkopplat** — ingen referens till Infisical/vault finns i repot. Vercel-projektets env är källan för webblagret. |
| **CI** | `.gitea/workflows/ci.yml`. `quality`-jobb: typecheck (blockerande, endast `src/` + `tests/`), vitest (blockerande, 17 testfiler), eslint (rådgivande), `next build` (blockerande). `security`-jobb: Trivy `--exit-code 0` (rådgivande). Kör på `ubuntu-latest`. |
> **Konflikt att fatta beslut om:** estate-projektet *GCP/Vercel-exit* säger att
> flottan ska bort från Vercel. 0pt:s enda körande instans ligger på Vercel. Se §8.
---
## 6. Drift
### Hälsokontroll
`GET /api/health` → `{ok, service:"0pt-web", db, queue, ts}`. Den svarar **alltid
`ok:true`** oavsett om databasen finns — den rapporterar bara `"not_configured"`
i ett fält. En uptime-monitor som tittar på HTTP 200 kommer därför visa grönt
för en instans utan databas, vilket är exakt vad som gäller idag. Engine har ett
separat `GET /health`.
### Loggar
Ingen strukturerad loggning. `console.warn` i `src/auth/context.ts` är den enda
avsiktliga loggpunkten. Ingen korrelation-id-propagering till logg (ett
`requestId` skapas i `readRequestContext` men loggas aldrig). Ingen
Sentry/GlitchTip/OTel-koppling.
### Backup + senast testad restore
**Ingen backup.** Det finns ingen databas i produktion att säkerhetskopiera
(§4.2), inget backupskript, ingen dumproutine. **Restore har aldrig testats.**
Om/när Postgres provisioneras måste detta lösas separat — jfr estate-blockeraren
"ingen offsite backup".
### Kända fällor
1. **`pnpm install` kräver `--ignore-workspace`.** `pnpm-workspace.yaml` är en
v0-exportartefakt med bara `allowBuilds:` och inget `packages:`-fält, vilket
får pnpm att avbryta. `Dockerfile` skickar flaggan; **`.gitea/workflows/ci.yml`
gör det inte** — CI:s `pnpm install --frozen-lockfile` kan alltså falla på
samma sak som Dockerfilen redan fått ett workaround för. Ej verifierat vilken
pnpm-version som räddar CI idag.
2. **Node-versionsglapp:** CI kör Node 22, Dockerfile bygger på Node 24.
3. **In-memory-state** (§4.2) gör att varje kallstart och varje ny Vercel-lambda
nollställer capability-lagret. Testresultat i CI säger ingenting om detta.
4. `next.config.mjs` sätter `images.unoptimized: true` — inte en bugg, men
förklarar varför bildoptimering saknas.
5. `services/scrapers/output/*.json` innehåller committad skrapdata (brreg-sidor)
i repot.
6. `__pycache__/*.pyc` är committade i `services/`.
---
## 7. Säkerhet
### Vad som faktiskt finns
- **Riktig kryptografisk tokenverifiering.** `src/auth/zitadel.ts` gör äkta
JWKS-baserad `jwtVerify` med issuer-kontroll och `exp`/`nbf` via `jose`.
Tenanthärledning har dokumenterad precedens
(`urn:zitadel:iam:org:id` → `urn:zitadel:iam:user:resourceowner:id` →
`tenant_id` → `tenant`), och ett token utan tenantclaim underkänns.
- **Fail-closed default i koden.** `authEnforced()` returnerar `true` för allt
utom literalen `"false"`, och `assertAuthConfigSafe()` kastar vid
`NODE_ENV=production` + auth av. Designen är korrekt.
- **Stripe-webhooken** verifierar signatur mot `STRIPE_WEBHOOK_SECRET`.
- `SECURITY.md` finns med rapporteringsväg (`security@siax.io`).
### KÄNDA LUCKOR
**S1 — `/api/0pt/*` svarar anonymt i produktion, trots "fail-closed". 🔴**
*(live-verifierat 2026-08-07)* `GET https://0pt.app/api/0pt` returnerar **200**
utan `Authorization`-header. Samma sak med ett medvetet ogiltigt token
(`Bearer aaa.bbb.ccc`) → **200**. `GET /api/0pt/lakehouse/mappings` och
`/api/0pt/sources/rate-policies` returnerar **200 + `[]`** anonymt. Eftersom
lakehouse-routen bara finns i nyare kod är deployen **inte** gammal — middlewaren
kör helt enkelt inte. `README.md` rad 49–52 och merge-commit `6531384`
("make the AUTH_ENFORCE default fail-closed — 0pt.app was answering anonymously")
påstår motsatsen. **Orsaken är OKÄND.** Ej uteslutna hypoteser, ingen verifierad:
(a) `middleware.ts` plockas inte upp av Next 16 i den här filplaceringen när ett
`src/`-träd finns, (b) Vercel-projektet har ingen middleware i sin
build-output, (c) något annat. **Detta måste rotorsaksanalyseras innan repot
beskrivs som skyddat någonstans.**
**S2 — Ingen handler läser den verifierade tenanten. 🔴**
`middleware.ts:56-57` sätter `x-0pt-verified-sub` / `x-0pt-verified-tenant` på
den vidarebefordrade requesten. En grep över hela repot visar att **ingen fil
utanför `middleware.ts` läser de headrarna**. Samtliga `/api/0pt/*`-handlers tar
i stället `tenant_id` från request-body eller querysträng
(`app/api/0pt/sources/[sourceId]/query/route.ts:49`,
`documents/capture/route.ts:27`, `lakehouse/mappings/route.ts:27,49`,
`sources/rate-policies/route.ts:26`, m.fl.). Även med fungerande middleware kan
alltså en innehavare av ett giltigt token för tenant A skicka
`{"tenant_id": "t_b"}` och arbeta i tenant B. Middlewarens 403 vid
`X-Tenant-Id`-konflikt hjälper inte — den kontrollerar bara *headern*, inte
*bodyn*. **Tenantisolering är inte implementerad.**
**S3 — Hela det icke-`/api/0pt`-prefixade API:t saknar auth.**
Matchern täcker bara `/api/0pt/:path*`. Ordrar, adminmetrik, betalningar,
prenumerationer, uppladdningar, AI-parsning och DB-grants är oskyddade i koden.
*(live-verifierat: `GET /api/admin/metrics` och `GET /api/orders` svarar 200
anonymt.)* De är tomma idag enbart för att produktionsinstansen saknar databas.
**S4 — Anonym anropare kan trigga escrow-slutbetalning och flagga leverans.**
`POST /api/payments/{order_id}/release` kräver ingen identitet. Den skapar en
Stripe-checkout på `order.balance_ore` och kör därefter
`setStatus(order_id, "delivered")` **oavsett om betalningen genomförs**.
Statusövergången sker alltså på begäran, inte på betalning. (Skyddas i praktiken
idag bara av att `getOrder` inte hittar något utan databas.)
**S5 — CL0UD-policyklienten är fail-open utan produktionsspärr.**
`src/clients/cl0ud-client.ts:45` — saknas `CL0UD_URL` returneras alltid
`{decision: "allow"}` utan nätanrop. Till skillnad från auth finns **ingen**
`assertAuthConfigSafe`-motsvarighet, ingen `NODE_ENV`-kontroll och ingen varning.
En produktionsdeploy som tappar variabeln godkänner tyst varje
sidoeffektsbärande capability, inklusive Iceberg-writes (L4).
**S6 — AUD0-audit är no-op utan produktionsspärr.**
`src/clients/aud0-client.ts` — saknas `AUD0_URL` skrivs inga auditevent och inga
lineage-event, tyst. Kombinerat med S5 innebär det: inga beslut fattas, inga
spår lämnas. `sql/001_init.sql` har en `audit_log`-tabell som TypeScript-lagret
aldrig skriver till.
**S7 — `db-grant`-routen påstår kryptering som inte finns.**
`app/api/connections/db-grant/route.ts` kommenterar att anslutningssträngen
"lagras KRYPTERAT (AES-GCM via `GRANT_ENC_KEY`), aldrig i klartext". Koden gör
ingenting av detta: utan `GRANT_ENC_KEY` returneras `501`, och **med** nyckeln
returneras ett slumpat `grant_id` — ingen validering av read-only, ingen
kryptering, ingen lagring, ingen audit. Kommentaren beskriver en oimplementerad
funktion i presens.
**S8 — FastAPI-engine har ingen autentisering.**
`services/api/orchestrator/main.py` (12 endpoints, inkl. `POST /orders`,
`PATCH /orders/{oid}`, `POST /orders/{oid}/run`) saknar varje form av
auth-dependency. `lib/engine.ts` skickar `X-Internal-Token`, men servern läser
den aldrig. Var engine än exponeras är den öppen för alla som når porten.
**S9 — Compliance-koden motsäger produktlöftet. 🔴**
`services/scrapers/waf_bypass.py` implementerar FlareSolverr, Tor-rotation,
cloudscraper, TLS-fingerprintbyte, gratis proxypool, "undetected browser"
(nodriver) och "fake identity generation".
`services/infra/docker-compose.data-stack.yml` startar `flaresolverr` och
`tor-proxy`. `services/api/workers/adaptive_router.py` inleds med
`"""Adaptive router — all methods, no compliance restrictions."""`.
Detta står i direkt konflikt med `CLAUDE.md` §1, `README.md` ("Bypass/stealth/
proxy-rotation/fake-account paths are excluded by design") och med hela den
juridiska positionering som säljs på `0pt.app`. Det är samtidigt en
**affärsrisk**: att marknadsföra laglig sourcing medan bypass-kod ligger i repot
är svårt att försvara vid en granskning.
**S10 — Godtycklig anslutningskonfiguration från request-body.**
`POST /api/0pt/sources/{sourceId}/query` tar `connection_config` direkt ur bodyn
och skickar den till adapterbygget (`src/services/source-query-service.ts:95`).
Utan fungerande auth (S1) innebär det att en anonym anropare kan få tjänsten att
öppna utgående anslutningar mot en angiven värd — SSRF-liknande primitiv, och en
väg att låta 0pt-instansen agera proxy.
**S11 — Typkontrollen täcker inte auth-koden.**
`next.config.mjs` sätter `typescript.ignoreBuildErrors: true`, och
`tsconfig.check.json` inkluderar endast `src/**/*.ts` + `tests/**/*.ts`.
**`middleware.ts` ligger i roten och typkontrolleras därför i praktiken inte i
CI** — trots att det är hela authseamen. `eslint` är dessutom rådgivande
(`continue-on-error: true`).
**S12 — Sessions-scaffoldet är svagt.**
`lib/auth.ts` signerar `{email, iat}` med HMAC-SHA256 mot `AUTH_SECRET`, utan
`exp`-kontroll vid verifiering (bara cookiens `maxAge`), utan rotation och utan
bindning till en användarpost. Kommentaren erkänner att det är ett scaffold; det
är ändå den mekanism `GET /api/auth/me` litar på.
---
## 8. Öppna frågor
1. **Varför kör inte middlewaren i produktion (S1)?** Måste rotorsaksanalyseras.
Ingen dokumentation får påstå att `/api/0pt/*` är skyddat innan en anonym
request faktiskt får 401 från `0pt.app`.
2. **Ska `waf_bypass.py`, FlareSolverr och tor-proxy raderas (S9)?**
`CLAUDE.md` säger uttryckligen "remove or disable it". Commit `4a164c5` gick
åt motsatt håll. Detta är ett Simon-beslut med både juridisk och
marknadsföringsmässig innebörd — inte något en session ska avgöra ensidigt.
3. **Vercel eller flottan?** *(config-delen löst 2026-08-09, trafik-delen kvarstår.)*
0pt:s enda **körande** instans ligger fortfarande på Vercel — den ändringen är
medvetet INTE gjord här (skulle kräva en egen cutover-plan: verifiera att inget
riktigt kunddata/trafik ligger bakom `0pt.app` — vilket är fallet idag, se §5/§6:
`/api/health` visar `db/queue: not_configured`, ordrar lever bara in-memory —
provisionera en Coolify-app, peka DNS om, och avveckla Vercel-projektet, i den
ordningen). Det som ÄR gjort: `siax.config.json` deklarerar nu `target: "coolify"`
(matchar varje annat SIAX Cloud OS-repo) och `.gitea/workflows/deploy.yml` finns
med en medveten platshållar-UUID som failar tills en riktig Coolify-app skapats.
Ingen nod är tilldelad (`node: null`, matchar st0re/aud0:s mönster). Se README
"Deploy" för samma sammanfattning.
4. **Ska capability-lagret vara i drift alls?** Det är exponerat publikt men helt
in-memory och utan delad state mellan routemoduler (§4.2). Antingen backas det
av Postgres, eller så bör routes stängas tills dess.
5. **Var är `sql/002_*` och `sql/003_*`?** Tre filer refererar dem. Fanns de
någonsin, eller är referenserna aspirationella? `opt_sources` har ingen
migration här.
6. **Ingen databas i produktion.** Är `0pt.app` avsedd att vara en publik
marknadsföringssajt tills vidare, eller är det ett driftfel att
`POSTGRES_URL` aldrig satts? Ordrar som skapas idag försvinner.
7. **`package.json` heter fortfarande `"my-v0-project"`** — v0-exportartefakt.
Byte påverkar inget bygge men bör göras för spårbarhet.
8. **Designsystem:** repot har ett eget dokumenterat mono/editorial-system
(`docs/05-design-system.md`) + shadcn/ui och konsumerar **inte** `@siax/ui`.
Ska 0pt konvergera mot estate-standarden eller är avvikelsen medveten?
`siax.config.json` sätter därför `designSystem: null` snarare än att påstå
något osant.
9. **Tenantmodell:** är en 0pt-tenant 1:1 med en Zitadel-org? Koden antar det i
`deriveTenantId` men det är inte bekräftat mot CL0UD:s tenantregister.
10. **Infisical:** hemligheter ligger i Vercel-env, inte i credential-SSOT.
Behöver bootstrappas.
11. **`GET /api/screen`** proxar mot OpenSanctions utan avtal — kommentaren i
koden säger själv "VERIFY commercial terms before production volume".
12. **Konsumentledet är odokumenterat.** `CLAUDE.md` säger att 0pt matar
Protosell/FiveCRM och VehIQ. Inget i repot visar en sådan integration.