diff --git a/.env.example b/.env.example index be9397f..bec0d82 100644 --- a/.env.example +++ b/.env.example @@ -97,6 +97,9 @@ ENGINE_URL=http://localhost:8000 CL0UD_URL= N0D_URL= AUD0_URL= +# 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 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..e7a6cdc --- /dev/null +++ b/.gitattributes @@ -0,0 +1,37 @@ +# Normalize line endings for text files; keep binaries untouched. +* text=auto + +# Source / config +*.ts text eol=lf +*.tsx text eol=lf +*.js text eol=lf +*.mjs text eol=lf +*.cjs text eol=lf +*.css text eol=lf +*.json text eol=lf +*.jsonc text eol=lf +*.yaml text eol=lf +*.yml text eol=lf +*.py text eol=lf +*.md text eol=lf +*.sh text eol=lf +Dockerfile text eol=lf +*.dockerfile text eol=lf + +# Binaries / generated — never text-normalized +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.webp binary +*.ico binary +*.svg text eol=lf +*.woff binary +*.woff2 binary +*.ttf binary +*.otf binary +*.pyc binary +*.pyo binary + +# Lockfiles — keep exactly as generated +pnpm-lock.yaml -text diff --git a/.npmrc b/.npmrc index 21d37e3..8e42c68 100644 --- a/.npmrc +++ b/.npmrc @@ -1,3 +1,8 @@ verify-deps-before-run=false include-workspace-root=true confirmModulesPurge=false + +# SIAX Cloud publishes @siax/* packages from Gitea's own npm registry. +# Only scoped @siax/* names resolve here; everything else stays on the +# public npm registry. +@siax:registry=https://git.siax.io/api/packages/sax3l/npm/ diff --git a/app/globals.css b/app/globals.css index 5ac6dd8..bf952b2 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,9 +1,13 @@ +@import '@fontsource-variable/inter'; +@import '@fontsource-variable/jetbrains-mono'; @import 'tailwindcss'; @import 'tw-animate-css'; @custom-variant dark (&:is(.dark *)); :root { + --font-inter: 'Inter Variable', 'Inter', system-ui, sans-serif; + --font-jetbrains-mono: 'JetBrains Mono Variable', 'JetBrains Mono', ui-monospace, monospace; --background: #FFFFFF; --foreground: #0A0A0A; --card: #FFFFFF; diff --git a/app/layout.tsx b/app/layout.tsx index 45197b8..7c3ea82 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,16 +1,11 @@ import React from "react"; import type { Metadata } from "next"; -import { Inter, JetBrains_Mono } from "next/font/google"; -import { Analytics } from "@vercel/analytics/next"; import { Toaster } from "sonner"; import { LocaleProvider } from "@/components/locale-provider"; import { ThemeProvider } from "@/components/theme-provider"; import { RouteReveal } from "@/components/route-reveal"; import "./globals.css"; -const inter = Inter({ subsets: ["latin"], variable: "--font-inter" }); -const jetbrainsMono = JetBrains_Mono({ subsets: ["latin"], variable: "--font-jetbrains-mono" }); - export const metadata: Metadata = { title: "0pt — your data", description: @@ -47,7 +42,7 @@ export const metadata: Metadata = { export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) { return ( - + - ); diff --git a/contracts/openapi.yaml b/contracts/openapi.yaml new file mode 100644 index 0000000..4891b0b --- /dev/null +++ b/contracts/openapi.yaml @@ -0,0 +1,548 @@ +openapi: 3.1.0 +info: + title: 0pt Data OS API + version: 1.0.0 + description: > + SIAX Cloud Data OS — order-, estimat- och capability-API (0pt). + Single Source of Truth för HTTP-kontraktet (SSOT). Routehandlers i + app/api/* realiserar dessa kontrakt; se x-siax-status för vad som är + live vs kontrakt-first (target). + x-siax: + appId: 0PT + capabilityPrefix: 0pt + tenantBinding: header X-Tenant-Id (live), session (target — se BACKLOG P0-2) +servers: + - url: https://0pt.app + description: Production (edge plane) + - url: https://api.0pt.app + description: Target API-host (Caddy, EJ verifierad live) +security: + - bearerAuth: [] +paths: + /api/health: + get: + operationId: 0pt.health + summary: Liveness + dependency state + x-siax-status: live + responses: + "200": + description: OK — db/queue rapporterar "configured"/"not_configured" + content: + application/json: + schema: + $ref: "#/components/schemas/HealthResponse" + /api/estimates: + post: + operationId: 0pt.estimate_order + summary: Prisestimat för en datajob-konfiguration + x-siax-status: live + security: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/EstimateRequest" + responses: + "200": + description: Estimat (öre) + content: + application/json: + schema: + $ref: "#/components/schemas/EstimateResponse" + "400": + $ref: "#/components/responses/BadRequest" + /api/orders: + post: + operationId: 0pt.create_order + summary: Skapa draft-order (50 % deposition startar jobbet) + x-siax-status: live + security: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/CreateOrderRequest" + responses: + "201": + description: Order skapad + content: + application/json: + schema: + $ref: "#/components/schemas/OrderSummary" + "400": + $ref: "#/components/responses/BadRequest" + get: + operationId: 0pt.list_orders + summary: Lista ordrar (org-scoping saknas — se BACKLOG P0-3) + x-siax-status: live + x-siax-gap: S3 — ingen auth/org-scoping + security: [] + responses: + "200": + description: Orderlista + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/OrderSummary" + /api/orders/{id}: + get: + operationId: 0pt.get_order + summary: Hämta order + x-siax-status: live + security: [] + parameters: + - $ref: "#/components/parameters/OrderId" + responses: + "200": + description: Order + content: + application/json: + schema: + $ref: "#/components/schemas/Order" + "404": + $ref: "#/components/responses/NotFound" + patch: + operationId: 0pt.update_order + summary: Uppdatera order + x-siax-status: live + security: [] + parameters: + - $ref: "#/components/parameters/OrderId" + requestBody: + required: true + content: + application/json: + schema: + type: object + responses: + "200": + description: Uppdaterad order + content: + application/json: + schema: + $ref: "#/components/schemas/Order" + "404": + $ref: "#/components/responses/NotFound" + /api/0pt: + get: + operationId: 0pt.capability_index + summary: Capability-index + x-siax-status: live + responses: + "200": + description: Index med endpoints + content: + application/json: + schema: + type: object + additionalProperties: true + /api/0pt/sources/{sourceId}/query: + post: + operationId: 0pt.query_source + summary: Bunden källfråga (enrich-primitiv) + x-siax-status: live + x-siax-gap: S10 — connection_config ur body (SSRF-primitiv, BACKLOG P0-7) + parameters: + - name: sourceId + in: path + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/SourceQueryRequest" + responses: + "200": + description: Källdata med provenance per fält + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/EnrichedRecord" + "400": + $ref: "#/components/responses/BadRequest" + /api/0pt/documents/capture: + post: + operationId: 0pt.capture_document + summary: Ta emot dokument (PII) för extraktion + x-siax-status: live + x-siax-gap: S2 — tenant_id från body (BACKLOG P0-2) + requestBody: + required: true + content: + application/json: + schema: + type: object + additionalProperties: true + responses: + "201": + description: Dokument registrerat + "400": + $ref: "#/components/responses/BadRequest" + get: + operationId: 0pt.list_documents + summary: Lista dokument + x-siax-status: live + x-siax-gap: S2 — tenant_id från query (BACKLOG P0-2) + responses: + "200": + description: Dokumentlista + /api/0pt/documents/{documentId}/extraction: + post: + operationId: 0pt.extract_document + summary: Extrahera dokument (OCR/AI — env-guardad, 501 utan konfig) + x-siax-status: live + parameters: + - name: documentId + in: path + required: true + schema: + type: string + responses: + "200": + description: Extraktion — aldrig fabricerade värden + "404": + $ref: "#/components/responses/NotFound" + "501": + $ref: "#/components/responses/NotConfigured" + /api/0pt/lakehouse/mappings: + post: + operationId: 0pt.map_dataset_to_iceberg + summary: Registrera dataset↔Iceberg-mappning + x-siax-status: live + x-siax-gap: S2 — tenant_id från body (BACKLOG P0-2) + responses: + "201": + description: Mappning registrerad + "400": + $ref: "#/components/responses/BadRequest" + get: + operationId: 0pt.list_iceberg_mappings + summary: Lista mappningar + x-siax-status: live + x-siax-gap: S2 — tenant_id från query (BACKLOG P0-2) + responses: + "200": + description: Mappningslista + /api/0pt/lakehouse/mappings/{mappingId}/writes: + post: + operationId: 0pt.request_iceberg_write + summary: Iceberg-write (L4) — tung variant delegeras till N0D-jobb + x-siax-status: live + x-siax-gap: S5 — CL0UD fail-open (BACKLOG P0-5) + parameters: + - name: mappingId + in: path + required: true + schema: + type: string + responses: + "202": + description: Skrivning begärd (job_id eller synkront snapshot) + "400": + $ref: "#/components/responses/BadRequest" + /api/0pt/sources/rate-policies: + post: + operationId: 0pt.create_rate_policy + summary: Registrera käll-rate-policy + x-siax-status: live + x-siax-gap: S2 — tenant_id från body (BACKLOG P0-2) + responses: + "201": + description: Rate-policy skapad + get: + operationId: 0pt.list_rate_policies + summary: Lista rate-policies + x-siax-status: live + x-siax-gap: S2 — tenant_id från query (BACKLOG P0-2) + responses: + "200": + description: Rate-policylista + /api/screen: + get: + operationId: 0pt.screen_entity + summary: Sanktions-/PEP-screening via OpenSanctions + x-siax-status: live + security: [] + parameters: + - name: q + in: query + required: true + schema: + type: string + responses: + "200": + description: Screeningträffar + "400": + $ref: "#/components/responses/BadRequest" + /v1/0pt/enrichment/company-lists: + post: + operationId: 0pt.enrich_company_list + summary: > + (TARGET) Capability-formen av enrichment: lista → enrichade poster med + bridge_provenance. Kontrakt-first — implementeras av 0pt-motor/edge + via N0D-jobb (job_type 0pt.enrichment.company_list). + x-siax-status: target + security: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/EnrichCompanyListRequest" + responses: + "202": + description: Jobb begärt — poll via /v1/0pt/jobs/{id} (target) + "401": + $ref: "#/components/responses/Unauthorized" + /v1/0pt/resolve/entities: + post: + operationId: 0pt.resolve_entities + summary: (TARGET) Entity resolution/dedupe (rapidfuzz idag, spaCy planerat) + x-siax-status: target + security: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/ResolveEntitiesRequest" + responses: + "200": + description: Kluster med konfidens + "401": + $ref: "#/components/responses/Unauthorized" + /v1/0pt/provenance/records/{entityType}/{entityKey}: + get: + operationId: 0pt.get_provenance + summary: (TARGET) Provenance per fält — bridge_provenance (källa, färskhet, konfidens, level) + x-siax-status: target + security: [] + parameters: + - name: entityType + in: path + required: true + schema: + type: string + enum: [company, contact, vehicle, ubo] + - name: entityKey + in: path + required: true + schema: + type: string + responses: + "200": + description: Provenance per fält + content: + application/json: + schema: + $ref: "#/components/schemas/ProvenanceRecord" + "401": + $ref: "#/components/responses/Unauthorized" +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + description: Zitadel-utfärdad token; verifieras i middleware mot ZITADEL_ISSUER + parameters: + OrderId: + name: id + in: path + required: true + schema: + type: string + responses: + BadRequest: + description: Ogiltig input + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorEnvelope" + Unauthorized: + description: Saknad/ogiltig Bearer-token + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorEnvelope" + NotFound: + description: Resursen finns inte + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorEnvelope" + NotConfigured: + description: Integration ej konfigurerad (501) + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorEnvelope" + schemas: + ErrorEnvelope: + type: object + required: [error] + properties: + error: + type: object + required: [code] + properties: + code: + type: string + description: error.code-envelope (t.ex. bad_json, storage_not_configured) + message: + type: string + HealthResponse: + type: object + required: [ok, service, db, queue, ts] + properties: + ok: + type: boolean + service: + type: string + db: + type: string + enum: [configured, not_configured] + queue: + type: string + enum: [configured, not_configured] + ts: + type: string + format: date-time + EstimateRequest: + type: object + properties: + flow: + type: string + config: + type: object + additionalProperties: true + EstimateResponse: + type: object + properties: + estimate: + type: object + additionalProperties: true + CreateOrderRequest: + type: object + properties: + flow: + type: string + default: custom + config: + type: object + additionalProperties: true + OrderSummary: + type: object + required: [order_id, status] + properties: + order_id: + type: string + status: + type: string + deposit_ore: + type: integer + description: Deposition i öre + total_ore: + type: integer + description: Totalsumma i öre + Order: + type: object + properties: + order_id: + type: string + status: + type: string + config: + type: object + additionalProperties: true + SourceQueryRequest: + type: object + required: [tenant_id, source_id, query] + properties: + tenant_id: + type: string + description: (GAP) ska komma från session — BACKLOG P0-2 + project_id: + type: string + source_id: + type: string + query: + type: string + connection_config: + type: object + additionalProperties: true + description: (GAP) SSRF-primitiv — BACKLOG P0-7 + EnrichedRecord: + type: object + properties: + fields: + type: object + additionalProperties: true + provenance: + type: object + additionalProperties: + $ref: "#/components/schemas/FieldProvenance" + FieldProvenance: + type: object + required: [source_id, freshness, confidence, level] + properties: + source_id: + type: string + freshness: + type: string + format: date-time + confidence: + type: number + minimum: 0 + maximum: 1 + level: + type: integer + minimum: 0 + maximum: 3 + description: 0=ej tillgänglig · 1=licensierad · 2=authorized web · 3=officiell API/bulk + EnrichCompanyListRequest: + type: object + required: [market, entities] + properties: + market: + type: string + entities: + type: array + items: + type: object + additionalProperties: true + fields: + type: array + items: + type: string + output_artifact_type: + type: string + default: st0re.artifact + ResolveEntitiesRequest: + type: object + required: [records] + properties: + records: + type: array + items: + type: object + additionalProperties: true + threshold: + type: number + default: 0.85 + algorithm: + type: string + enum: [rapidfuzz, target_spacy] + default: rapidfuzz + ProvenanceRecord: + type: object + additionalProperties: + $ref: "#/components/schemas/FieldProvenance" diff --git a/package.json b/package.json index 725427b..8bdf905 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "my-v0-project", + "name": "@siax/0pt", "version": "0.1.0", "private": true, "packageManager": "pnpm@9.15.9", @@ -17,6 +17,8 @@ "typecheck:app": "tsc --noEmit -p tsconfig.json" }, "dependencies": { + "@fontsource-variable/inter": "^5.2.5", + "@fontsource-variable/jetbrains-mono": "^5.2.5", "@hookform/resolvers": "^3.10.0", "@radix-ui/react-accordion": "1.2.2", "@radix-ui/react-alert-dialog": "1.1.4", @@ -45,7 +47,6 @@ "@radix-ui/react-toggle": "1.1.1", "@radix-ui/react-toggle-group": "1.1.1", "@radix-ui/react-tooltip": "1.1.6", - "@vercel/analytics": "1.3.1", "autoprefixer": "^10.4.20", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b01d7b0..fe83c30 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,12 @@ importers: .: dependencies: + '@fontsource-variable/inter': + specifier: ^5.2.5 + version: 5.3.0 + '@fontsource-variable/jetbrains-mono': + specifier: ^5.2.5 + version: 5.3.0 '@hookform/resolvers': specifier: ^3.10.0 version: 3.10.0(react-hook-form@7.80.0(react@19.2.0)) @@ -92,9 +98,6 @@ importers: '@radix-ui/react-tooltip': specifier: 1.1.6 version: 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@vercel/analytics': - specifier: 1.3.1 - version: 1.3.1(next@16.0.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0) autoprefixer: specifier: ^10.4.20 version: 10.5.0(postcss@8.5.15) @@ -433,6 +436,12 @@ packages: '@floating-ui/utils@0.2.11': resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} + '@fontsource-variable/inter@5.3.0': + resolution: {integrity: sha512-OupL48va4JNofb97w6NYeF9S7W/kHNKM0Er8Dem5nqi4jeOLrVJDoE8tZEpnMJmtkvNbB1EIPPwHcdkF6b1oUA==} + + '@fontsource-variable/jetbrains-mono@5.3.0': + resolution: {integrity: sha512-F32xpS2NsGYoQi2ADSkKTgpJj7ozajsGgDJ8woTnqjmIB+dxDIqImjl4pXZVEExu8UFZ2ndhmX18EBS/hdz3Lw==} + '@hookform/resolvers@3.10.0': resolution: {integrity: sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==} peerDependencies: @@ -1643,17 +1652,6 @@ packages: resolution: {integrity: sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@vercel/analytics@1.3.1': - resolution: {integrity: sha512-xhSlYgAuJ6Q4WQGkzYTLmXwhYl39sWjoMA3nHxfkvG+WdBT25c563a7QhwwKivEOZtPJXifYHR1m2ihoisbWyA==} - peerDependencies: - next: '>= 13' - react: ^18 || ^19 - peerDependenciesMeta: - next: - optional: true - react: - optional: true - '@vitest/expect@3.2.7': resolution: {integrity: sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==} @@ -2423,9 +2421,6 @@ packages: engines: {node: '>=10'} hasBin: true - server-only@0.0.1: - resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==} - sharp@0.34.5: resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -2852,6 +2847,10 @@ snapshots: '@floating-ui/utils@0.2.11': {} + '@fontsource-variable/inter@5.3.0': {} + + '@fontsource-variable/jetbrains-mono@5.3.0': {} + '@hookform/resolvers@3.10.0(react-hook-form@7.80.0(react@19.2.0))': dependencies: react-hook-form: 7.80.0(react@19.2.0) @@ -3992,13 +3991,6 @@ snapshots: '@typescript-eslint/types': 8.65.0 eslint-visitor-keys: 5.0.1 - '@vercel/analytics@1.3.1(next@16.0.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0)': - dependencies: - server-only: 0.0.1 - optionalDependencies: - next: 16.0.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - react: 19.2.0 - '@vitest/expect@3.2.7': dependencies: '@types/chai': 5.2.3 @@ -4749,8 +4741,6 @@ snapshots: semver@7.8.5: {} - server-only@0.0.1: {} - sharp@0.34.5: dependencies: '@img/colour': 1.1.0 diff --git a/renovate.json b/renovate.json index 5db72dd..ffa30e5 100644 --- a/renovate.json +++ b/renovate.json @@ -2,5 +2,22 @@ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:recommended" + ], + "platform": "gitea", + "semanticCommits": "enabled", + "dependencyDashboard": true, + "schedule": ["before 6am on tuesday"], + "packageRules": [ + { + "description": "Batch minor/patch updates of the v0-generated UI stack", + "matchDepNames": ["@radix-ui/*", "react", "react-dom", "next", "tailwindcss", "typescript"], + "groupName": "web-stack", + "separateMajorMinor": true + }, + { + "description": "Do not auto-update the scraper stack (kept intact per deliberate user decision)", + "matchFiles": ["services/scrapers/requirements.txt", "services/api/requirements.txt"], + "enabled": false + } ] } diff --git a/services/scrapers/requirements.txt b/services/scrapers/requirements.txt index c16119a..47a8da7 100644 --- a/services/scrapers/requirements.txt +++ b/services/scrapers/requirements.txt @@ -7,3 +7,4 @@ requests>=2.33.0 beautifulsoup4>=4.12.0 lxml>=5.3.0 boto3>=1.35.0 # MinIO S3 sync +duckdb>=1.1.0 # DuckDBWriter alternative (lazy-imported in clickhouse_pipeline.py)