Märkesspecifika kopplingar — kunden lägger in sina egna credentials
Verkstaden har redan sina avtal: Volvo-verkstaden har VIDA, VAG-verkstaden har erWin, den fria verkstaden har en fordonsdataleverantör. Kopplingarna konfigureras därför av kunden själv under Inställningar, med sina egna uppgifter — vi tillhandahåller ramen, inte kontot. Uppgifterna når aldrig webbläsaren. De krypteras med AES-256-GCM (INTEGRATION_NYCKEL) innan de skrivs till tabellen integrationer, och API:t returnerar hemliga fält maskerade. Alla uppslag görs av servern. Saknas krypteringsnyckeln sparas ingenting alls — 503 och en förklaring i gränssnittet i stället för klartext i databasen. Endast systemadministratören hanterar uppgifterna; kopplingarna är organisationsknutna som all annan ärendedata. Leverantörer är data, inte kod: URL-mall, autentiseringstyp (bearer/header/basic/query) och svarsmappning beskrivs i services/plattform/integrationer.json, utbytbar via ConfigMap (INTEGRATIONER_FIL). Nya märken läggs till utan att appen byggs om. Varje uppslag skriver senast_testad och senaste_status på kopplingen, så ett utgånget abonnemang syns i inställningarna i stället för att ge tysta tomma svar. Två latenta krascher hittade av klicktestet och åtgärdade: TextFalt och UNDANTAGSORSAKER användes utan import. vite build typkontrollerar inte, så de passerade bygget — därav nya npm-skriptet typkontroll, nu del av verifieringen. Verifierat: 80 vitest-tester, typkontroll, eslint, OpenAPI-validering, integrationstest mot riktig Postgres (rollstyrning, kryptering i vila, maskering, organisationsisolering, fail closed, borttagning) och klickgenomgång mot en körande plattform. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012EQg3rJsrQ1ZNTvkzmQAtt
This commit is contained in:
+26
-2
@@ -22,7 +22,7 @@ flowchart LR
|
||||
| `plattform` | Självhostad backend (`services/plattform`): **multi-tenant** — registrering skapar organisation + systemadministratör, admin hanterar användare (tekniker/arbetsledare/admin), all ärendedata organisationsisolerad. Inloggning (bcrypt via pgcrypto, HS256-JWT med roll + org i anspråken), append-only händelse-API, publik delningsendpoint | Deployment + Service + HPA + PDB |
|
||||
| `ai-orkester` | AI-orkestern (`services/ai-orkester`): fyra uppgifter routade till Sonnet 5 / Opus 5 / Haiku 4.5 — verifierar plattformens JWT (delad hemlighet) | Deployment + Service + HPA + PDB |
|
||||
| `postgres` | Händelselogg + användare; **append-only garanterat med databastriggers** — historik kan inte ändras eller raderas oavsett roll | StatefulSet + PVC (10 Gi). Produktion: CloudNativePG-operatorn för backup/failover/PITR |
|
||||
| Hemligheter | `anthropic-api-key`, `jwt-secret` (delas av plattform + orkester), `postgres-losenord` | Secret `felsokning-hemligheter` — aldrig i bilder eller manifest |
|
||||
| Hemligheter | `anthropic-api-key`, `jwt-secret` (delas av plattform + orkester), `postgres-losenord`, `integration-nyckel` (krypterar kundernas märkesspecifika credentials) | Secret `felsokning-hemligheter` — aldrig i bilder eller manifest |
|
||||
|
||||
**Klienten har två driftlägen**, valda vid bygget: med `VITE_PLATTFORM_URL` går inloggning, synk, Live Share och AI mot klustret (helt självhostat); utan den används Supabase-läget (edge-funktion + managerad Postgres/Auth) som tidigare. Samma händelsemodell, samma orkester — låst av paritetstester.
|
||||
|
||||
@@ -43,7 +43,8 @@ kubectl create namespace guidad-felsokning
|
||||
kubectl -n guidad-felsokning create secret generic felsokning-hemligheter \
|
||||
--from-literal=anthropic-api-key='sk-ant-…' \
|
||||
--from-literal=jwt-secret="$(openssl rand -base64 48)" \
|
||||
--from-literal=postgres-losenord="$(openssl rand -base64 24)"
|
||||
--from-literal=postgres-losenord="$(openssl rand -base64 24)" \
|
||||
--from-literal=integration-nyckel="$(openssl rand -hex 32)"
|
||||
|
||||
# 3. Applicera manifesten (Postgres initieras med schema + append-only-triggers)
|
||||
kubectl apply -k infra/k8s
|
||||
@@ -56,6 +57,29 @@ curl https://app.exempel.se/api/openapi.yaml # API-first: hela API-specen
|
||||
|
||||
Byt domän och cert-issuer i `infra/k8s/ingress.yaml`. Att skapa nya organisationer är öppet i beta — stäng med `REGISTRERING_OPPEN=false` på plattformens Deployment; användare inom en organisation skapas alltid av dess systemadministratör.
|
||||
|
||||
## Märkesspecifika kopplingar
|
||||
|
||||
Varje verkstad har sina egna avtal med tillverkare och dataleverantörer.
|
||||
Kopplingarna konfigureras därför av kunden själv under **Inställningar →
|
||||
Märkesspecifika kopplingar**: systemadministratören väljer leverantör och
|
||||
fyller i sina credentials.
|
||||
|
||||
* **Uppgifterna når aldrig webbläsaren.** De krypteras med AES-256-GCM
|
||||
(`INTEGRATION_NYCKEL`, 32 byte hex eller base64) innan de skrivs till
|
||||
tabellen `integrationer`, och API:t returnerar hemliga fält maskerade
|
||||
(`••••3456`). Alla uppslag mot leverantören görs av servern.
|
||||
* **Fail closed.** Saknas `INTEGRATION_NYCKEL` sparas ingenting — API:t
|
||||
svarar 503 och inställningssidan säger varför. Inga uppgifter hamnar
|
||||
någonsin i klartext.
|
||||
* **Leverantörer är data, inte kod.** Registret ligger i
|
||||
`services/plattform/integrationer.json` och kan bytas mot en
|
||||
ConfigMap-mount via `INTEGRATIONER_FIL`. Nya märken läggs till genom
|
||||
att beskriva URL-mall, autentiseringstyp och svarsmappning — ingen
|
||||
ombyggnad av applikationen krävs.
|
||||
* **Testresultat loggas på kopplingen.** Varje uppslag skriver
|
||||
`senast_testad` och `senaste_status`, så ett trasigt abonnemang syns i
|
||||
inställningarna i stället för att tyst ge tomma svar.
|
||||
|
||||
## Multi-tenant och roller
|
||||
|
||||
Enligt Master Prompt: varje kund är en egen tenant, ingen data blandas mellan kunder.
|
||||
|
||||
+2
-1
@@ -43,6 +43,7 @@ Demomanus för visning: [DEMO.md](DEMO.md). Knappen **Skapa demoärende** på st
|
||||
| Ärendeidentitet | ✅ Fordonsobjektet som röd tråd: identiteten (AO-nummer, claim-/garantinummer, skadenummer, regnr, VIN, miltal, kund) registreras en gång — normalt via arbetsorderskanningen — och återanvänds i identitetsraden i arbetsytan (med ärendetypsval), låst panel överst i Live Share, slutrapportens första sida (Ärendeinformation + Fordonsinformation) och exporten. |
|
||||
| Instrumentavläsning (visual-first) | ✅ Kameran som universellt gränssnitt: `📷 Instrument` i Dokumentera-panelen fotograferar multimetrar, diagnosskärmar, batteritestare m.m. — bildtolkningen identifierar instrumenttyp och extraherar värden/enheter/felkoder med konfidens per värde; teknikern bekräftar innan något loggas. Originalbilden loggas alltid tillsammans med de strukturerade mätvärdena — strukturerad data ersätter aldrig originalevidensen. Ingen integration mot diagnossystem krävs. |
|
||||
| Utskrift | ✅ Kundrapport och Live Share-vy skrivs ut svart på vitt; interaktiva element döljs automatiskt. Utskriften går genom ECM-kvalitetsgrinden. |
|
||||
| Märkesspecifika kopplingar | ✅ Verkstaden konfigurerar sina egna OEM-/fordonsdataleverantörer under Inställningar med sina egna credentials ([moduler/markesspecifika-kopplingar.md](moduler/markesspecifika-kopplingar.md)): uppgifterna krypteras med AES-256-GCM i vila, returneras alltid maskerade (`••••3456`) och **alla uppslag görs av servern** — leverantörsnycklar når aldrig webbläsaren. Endast systemadministratören hanterar dem, kopplingarna är organisationsknutna och saknas krypteringsnyckeln sparas ingenting alls (fail closed). Leverantörer är data, inte kod: URL-mall, autentiseringstyp (bearer/header/basic/query) och svarsmappning beskrivs i `integrationer.json` (ConfigMap-utbytbar via `INTEGRATIONER_FIL`) — nya märken läggs till utan ombyggnad. Varje uppslag loggar teststatus, så ett utgånget abonnemang syns i inställningarna i stället för att ge tysta tomma svar. Verifierat i integrationstestet (rollstyrning, maskering, kryptering i databasen, organisationsisolering, fail closed). |
|
||||
| Öppet API | ✅ Plattforms-API:t är dokumenterat med OpenAPI 3.0 (`services/plattform/openapi.yaml`) — auth, användare, ärenden/händelser (append-only), översikt, publik delning och AI-orkestern, med scheman för alla händelsetyper. Specen valideras maskinellt, paritetstestas mot serverns rutter och serveras live på `GET /api/openapi.yaml`. |
|
||||
|
||||
## Arkitekturprinciper i koden
|
||||
@@ -57,4 +58,4 @@ Demomanus för visning: [DEMO.md](DEMO.md). Knappen **Skapa demoärende** på st
|
||||
|
||||
## Medvetna avgränsningar
|
||||
|
||||
- VIN-avkodning mot fordonsdatabaser (utrustningsnivå, återkallelser, TSB:er) och tillverkarintegrationer ingår inte ännu — arbetsorderskanningen och QR-/VIN-avläsningen ger strukturen de kopplas in i.
|
||||
- Ramen för tillverkarintegrationer finns (kunden lägger in sina egna credentials och slår upp fordonsuppgifter), men de anrikade datamängderna — utrustningsnivå, återkallelser, TSB:er — mappas inte ännu; registret behöver fler svarsfält och leverantörsprofiler innan det är meningsfullt.
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
# Märkesspecifika kopplingar
|
||||
|
||||
Verkstaden har redan sina avtal. Volvo-verkstaden har VIDA, VAG-verkstaden
|
||||
har erWin, den fria verkstaden har en fordonsdataleverantör. Ingen av dem
|
||||
vill att vi ska vara mellanhand för deras abonnemang — och ingen av dem
|
||||
har samma uppsättning som grannen.
|
||||
|
||||
Därför konfigurerar **kunden själv** sina kopplingar under
|
||||
**Inställningar → Märkesspecifika kopplingar**, med sina egna credentials.
|
||||
Vi tillhandahåller ramen, inte kontot.
|
||||
|
||||
## Principer
|
||||
|
||||
**Uppgifterna når aldrig webbläsaren.** Samma regel som för
|
||||
plattformens egna API-nycklar: hemligheter bor på servern. Credentials
|
||||
krypteras med AES-256-GCM innan de skrivs till databasen, och API:t
|
||||
returnerar hemliga fält maskerade (`••••3456`). Klienten kan se *att* en
|
||||
koppling finns och när den senast fungerade — aldrig vad nyckeln är.
|
||||
|
||||
**Alla uppslag görs av servern.** Klienten skickar en identifierare
|
||||
(VIN eller regnr); servern hämtar uppgifterna, dekrypterar dem i minnet,
|
||||
anropar leverantören och returnerar bara de mappade fordonsfälten.
|
||||
|
||||
**Fail closed.** Saknas krypteringsnyckeln (`INTEGRATION_NYCKEL`) sparas
|
||||
ingenting — API:t svarar 503 och inställningssidan förklarar varför.
|
||||
Alternativet, att lagra i klartext "så länge", finns inte.
|
||||
|
||||
**Endast systemadministratören.** Att lägga till, ändra och ta bort
|
||||
kopplingar kräver rollen `admin`. Teknikern kan läsa registret över
|
||||
tillgängliga leverantörer (annars kan inställningssidan inte visa dem)
|
||||
men aldrig någon organisations uppgifter.
|
||||
|
||||
**Organisationsknutet.** Kopplingarna hör till organisationen, precis
|
||||
som ärendedata. Ingen tenant ser en annans.
|
||||
|
||||
## Leverantörer är data, inte kod
|
||||
|
||||
Registret ligger i `services/plattform/integrationer.json` och kan bytas
|
||||
mot en ConfigMap-mount via `INTEGRATIONER_FIL`. En leverantör beskrivs
|
||||
helt deklarativt:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "volvo_vida",
|
||||
"namn": "Volvo VIDA",
|
||||
"falt": [
|
||||
{ "nyckel": "bas_url", "etikett": "Bas-URL (använd {vin} som platshållare)", "hemlig": false },
|
||||
{ "nyckel": "api_nyckel", "etikett": "API-nyckel", "hemlig": true }
|
||||
],
|
||||
"uppslag": {
|
||||
"urlFalt": "bas_url",
|
||||
"auth": "header",
|
||||
"authHeader": "X-Api-Key",
|
||||
"authFalt": "api_nyckel",
|
||||
"svarsfalt": { "marke": "make", "modell": "model", "arsmodell": "year" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* `falt` — vad administratören ska fylla i. `hemlig: true` styr både
|
||||
kryptering och maskering.
|
||||
* `uppslag.auth` — `bearer`, `header`, `basic` eller `query`. Inga
|
||||
leverantörsspecifika kodgrenar; all variation ligger i registret.
|
||||
* `svarsfalt` — mappning från leverantörens JSON (punktnotation stöds)
|
||||
till våra fordonsfält.
|
||||
* `nyckeltyp: "regnr"` — uppslaget sker på registreringsnummer i stället
|
||||
för VIN. `{vin}`/`{regnr}` i URL-mallen ersätts URL-kodat.
|
||||
|
||||
Ett nytt märke läggs alltså till genom att beskriva det — inte genom att
|
||||
bygga om applikationen.
|
||||
|
||||
## Vad ett uppslag gör och inte gör
|
||||
|
||||
Uppslaget fyller i **fordonsbeskrivningen** (märke, modell, årsmodell,
|
||||
motor, växellåda). Det är kontextdata, inte evidens: ett svar från en
|
||||
leverantör är aldrig en utförd kontroll och räknas inte i
|
||||
[evidensmotorn](evidensmotor.md). Returnerar leverantören inga kända fält
|
||||
säger systemet det rakt ut i stället för att visa tomma rader.
|
||||
|
||||
Varje uppslag skriver `senast_testad` och `senaste_status` på
|
||||
kopplingen. Ett utgånget abonnemang syns därför i inställningarna som ett
|
||||
felmeddelande från leverantören, inte som tysta tomma svar.
|
||||
|
||||
## API
|
||||
|
||||
| Väg | Metod | Roll | Vad |
|
||||
| --- | --- | --- | --- |
|
||||
| `/api/integrationer/leverantorer` | GET | inloggad | Registret (fältdefinitioner, inga uppgifter) |
|
||||
| `/api/integrationer` | GET | admin | Organisationens kopplingar, hemligheter maskerade |
|
||||
| `/api/integrationer` | POST | admin | Spara/uppdatera credentials (krypteras) |
|
||||
| `/api/integrationer/{leverantor}` | DELETE | admin | Ta bort |
|
||||
| `/api/integrationer/{leverantor}/uppslag` | POST | inloggad | Slå upp VIN/regnr via servern |
|
||||
|
||||
Fullständigt dokumenterat i `services/plattform/openapi.yaml`.
|
||||
|
||||
## Drift
|
||||
|
||||
`INTEGRATION_NYCKEL` är 32 byte hex eller base64 (`openssl rand -hex 32`),
|
||||
levererad via secret:en `felsokning-hemligheter` — se
|
||||
[DRIFT.md](../DRIFT.md). Byts nyckeln måste kopplingarna sparas om;
|
||||
tjänsten visar då inga värden i stället för att gissa.
|
||||
Reference in New Issue
Block a user