Files
admin a1557b2a7d
CI (SIAX Cloud) / integration (push) Has been skipped
masterplan-lock / check (push) Successful in 4s
CI (SIAX Cloud) / security (push) Successful in 14s
masterplan-lock / check (pull_request) Successful in 4s
CI (SIAX Cloud) / security (pull_request) Successful in 14s
Security Scan (SIAX Cloud) / security (push) Successful in 30s
CI (SIAX Cloud) / quality (push) Successful in 35s
CI (SIAX Cloud) / integration (pull_request) Successful in 25s
Security Scan (SIAX Cloud) / security (pull_request) Successful in 54s
CI (SIAX Cloud) / quality (pull_request) Successful in 1m31s
feat(payments): bind orders to verified tenant and buyer
2026-09-15 19:53:58 +02:00

32 KiB
Raw Permalink Blame History

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) · v2.0-standardmigrering (§5 tillagd, §6–§9 omnumrerade; §6 Deploy-fältet nedan uppdaterat mot lib/health-checks.ts): 2026-08-11 — scoped till dessa avsnitt, ingen fullständig radrad-omverifiering av hela dokumentet gjord denna pass

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 §8. 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 §9.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 §8 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 (§8 S5)
AUD0 (audit) src/clients/aud0-client.ts AUD0_URL No-op — inga auditevent skrivs (§8 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 (§8 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 §8 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, §8 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 (§8 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)
Readiness GET /api/ready 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 Verifierad buyer-principal i source; UI-SSO återstår
Lista buyer-ordrar GET /api/orders L3 Verifierad tenant och tenant-scoped SQL i source
Läs/ändra buyer-order GET,PATCH /api/orders/{id} L3 Verifierad tenant och tenant-scoped SQL i source
Adminmetrik (orderpipeline, belopp) GET /api/admin/metrics L3 Route-auth i source; äldre liveobservation ej omverifierad
Deposition (Stripe checkout) POST /api/payments/{order_id}/deposit L4 Verifierad tenant + ägd order i source; live-UI återstår
Frigör escrow-slutbetalning POST /api/payments/{order_id}/release L4 Ägd order; delivered först efter betald webhook; live-UI återstår
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; §8 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 §8 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 §8 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. Ytor och paritet

Migrering till standard v2.0 (BACKLOG P1-20/DOC-02): denna sektion är ny — v1.0-mallen hade 8 rubriker, v2.0 har 9 (Deploy/Drift/Säkerhet/Öppna frågor numrerade om §6–§9 nedan, tidigare §5–§8).

Källa: cl0ud packages/capability-registry/src/registry.ts (samma verifiering som docs/02-architecture.mds "Six-surface capability model"-avsnitt, ej omgjord här). 14 0pt.*-kapabiliteter är registrerade. Registret har fält för api/cli/mcp/ui/job; "CL0UD-konsol" nedan är en separat fråga (om kapabiliteten är inbäddad i den delade cl0ud-konsolen) som INTE går att verifiera från detta repo — markerad OKÄNT genomgående, inte gissad.

"Egen konsol" är ANTAGANDE baserat på sidnamn/routestruktur under app/, inte spårat anrop-för-anrop mot varje handler — verifiera innan den läses som hårt bevisad.

Kapabilitet API CLI MCP Egen konsol CL0UD-konsol
0pt.order_create ✅ ❌ ❌ ✅ (app/bestall/*) OKÄNT
0pt.order_payment ✅ ❌ ❌ ✅ (app/bestall/forskott) OKÄNT
0pt.estimate_compute ✅ ❌ ❌ ✅ (app/bestall/beskriv, ANTAGANDE) OKÄNT
0pt.catalog_list ✅ ❌ ❌ ✅ (app/katalog) OKÄNT
0pt.source_registry_list ✅ ❌ ❌ ✅ (app/kallor, app/admin/sources) OKÄNT
0pt.query_source ✅ ❌ ❌ ❌ (ingen dedikerad sida funnen) OKÄNT
0pt.vehicle_lookup ✅ ❌ ❌ ❌ (ingen dedikerad sida funnen) OKÄNT
0pt.screen_sanctions ✅ ❌ ❌ ❌ (ingen dedikerad sida funnen) OKÄNT
0pt.document_ocr ✅ ❌ ❌ ❌ (ingen dedikerad sida funnen) OKÄNT
0pt.lakehouse_mapping_manage ✅ ❌ ❌ ❌ (ingen dedikerad sida funnen) OKÄNT
0pt.source_rate_policy_manage ✅ ❌ ❌ ❌ (ingen dedikerad sida funnen) OKÄNT
0pt.lakehouse_write ✅ (+job) ❌ ❌ ❌ (ingen dedikerad sida funnen) OKÄNT
0pt.write_iceberg_table ✅ (+job) ❌ ❌ ❌ (ingen dedikerad sida funnen) OKÄNT
0pt.write_external_record ❌ (job-only, apiEnabled: false) ❌ ❌ ❌ OKÄNT

Luckor, sammanfattat: CLI = 0/14, MCP = 0/14 (matchar BACKLOG P1-5/P1-6: ingen CLI-yta, ingen MCP-yta alls finns i detta repo ännu). API = 13/14 (allt utom det renodlade job-kapabiliteten). Egen konsol ≈ 5/14, koncentrerat till order-/katalog-/källflödet; kapabiliteter under app/api/0pt/* (lakehouse, rate-policy, källfråga, sanktionsscreening, dokument-OCR) har ingen synlig UI alls — matchar BACKLOG P1-8 ("/api/0pt/*-kapabiliteterna saknar UI").


6. 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 §9.


7. Drift

Hälsokontroll

Fixat (BACKLOG P0-3): GET /api/health → {ok, service:"0pt-web", db, queue, engine, stripe, ts}. db/engine/stripe rapporterar nu en riktig beroendeprobe (lib/health-checks.ts): "ok" = lyckad live-anrop, "error" = konfigurerad men onåbar (se detail), "not_configured" = env-variabel ej satt. queue kan bara vara "not_configured"/"not_integrated" — ingen Redis-klient finns i repot, så en satt REDIS_URL rapporteras ärligt i stället för att låtsas testa en anslutning som aldrig görs. ok är true endast när ingen konfigurerad dependency ligger i "error". HTTP-statusen är fortsatt alltid 200 (liveness — processen svarar) oavsett dependency-läge; en extern beroendekrasch ska inte omstarta instansen. Ny GET /api/ready failar stängt (503) när databasen (den enda lastbärande dependencyn idag) inte både är konfigurerad och nåbar — peka en Coolify/lastbalanserar-readiness-probe dit, inte mot /api/health. Engine har ett separat GET /health som checkEngine() nu faktiskt anropar (tidigare kollades bara ENGINE_URL-närvaro).

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/.

8. 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. Om Stripe-konfiguration saknas svarar den nu 503 i stället för att kvittera ett obearbetat event med 200; saknad signatur ger 400. Deposit-checkout bär nu order-id/kind i sessionsmetadata. Webhooken flyttar bara en order efter verifierad signatur, payment_status=paid, matchande belopp och valuta. En skapad slutbetalnings-Checkout markerar inte längre ordern delivered innan betalt event har verifierats. Deposit-sidans demo-fallback kan inte längre märka en misslyckad betalning som lyckad. Klient-PATCH kräver nu verifierad auth, följer tillåtna statusövergångar och kan aldrig skriva in_production/delivered; concurrent writes använder jämför-och-sätt. Slutbetalningsrouten kräver också verifierad auth. Ny order-POST kräver nu en verifierad Zitadel-principal; dennes tenant och subject lagras i orders.tenant_id/buyer_subject via sql/011. Buyer GET/list/PATCH och deposit/release använder tenant-predikat i SQL; legacy-ordrar utan owner är osynliga och obetalbara. Fortfarande öppet före live-läge: migrationen har bara smoke-testats på engångs-Postgres och måste appliceras i målmiljön före deploy. Browser-UI saknar produktions-SSO/Bearer och stagingköp/refund är ännu inte verifierat. Beslut och nästa steg finns i ADR-001-order-ownership.md.
  • 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 — Historiskt anonymt icke-/api/0pt-API; delvis åtgärdat i source. Matchern täcker bara /api/0pt/:path*. Orderkö/adminmetrik, enskild order-GET, deposit- och release-checkout samt order-PATCH har nu explicita route-auth-grindar i source. De äldre live-observationerna av anonymt 200 beskriver tidigare deploy, inte verifierat nuläge. Övriga icke-prefixade API:er kräver egen inventering. Buyer-order-API:t har nu tenant-predikat i source, men migration, UI-SSO och liveverifiering återstår enligt ADR-001.

S4 — Historisk escrow-bugg åtgärdad i source; liveköp återstår. POST /api/payments/{order_id}/release kräver nu route-auth och skapar Checkout först efter accepted. En skapad session sätter inte delivered; endast en signerad, betald och matchande Stripe-webhook får göra det. Cross-tenant-skydd av buyer-ordern finns nu i SQL-predikat, men är inte produktionsbevisat innan sql/011 har applicerats, UI-SSO är kopplat och stagingköp/refund är verifierat. Livebetalning får därför inte aktiveras.

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å.


9. Ö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 §6/§7: /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.