From 0386d82c4923ff7ce9095aa1390cbfec3061431d Mon Sep 17 00:00:00 2001 From: sax3l Date: Wed, 30 Sep 2026 10:08:25 +0200 Subject: [PATCH] =?UTF-8?q?fix(health):=20engine-kollen=20=C3=A4rlig=20(no?= =?UTF-8?q?t=5Fconfigured=20n=C3=A4r=20URL=20saknas)=20+=20EVIDENCE-docs?= =?UTF-8?q?=20mot=20Coolify-verkligheten?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/compliance/EVIDENCE/health.md | 81 +++++++++++++++++++++++------- tests/lib/health-checks.test.ts | 15 ++++++ 2 files changed, 79 insertions(+), 17 deletions(-) diff --git a/docs/compliance/EVIDENCE/health.md b/docs/compliance/EVIDENCE/health.md index d3db15b..74e7317 100644 --- a/docs/compliance/EVIDENCE/health.md +++ b/docs/compliance/EVIDENCE/health.md @@ -1,27 +1,74 @@ # EVIDENCE — FAS 8 · /api/health + dependencies -> Live-prober + källanalys 2026-08-10 (VERIFIERAT). +> Live-prober + källanalys **2026-09-30** (VERIFIERAT mot prod). Ersätter den +> gamla Vercel-mätningen (2026-08-10) — 0pt.app är inte längre på Vercel. -## Live (0pt.app, prod-instansen — Vercel) +## Prod-topologi (Coolify, inte Vercel) + +`0pt.app` = kanonisk prod via **Coolify på cloud.siax.io**, deployad av +`.gitea/workflows/cd.yml` vid varje merge till `main` (repo-secret +`COOLIFY_TOKEN`, repo-variable `COOLIFY_APP_ID` — riktig UUID, guarden +passerar; se `docs/architecture/decisions/0001-deploy-target-coolify-not-vercel.md`). +Inga Vercel-referenser är längre aktuella för prod. + +## Live (0pt.app, prod — mätt 2026-09-30 08:0x UTC) ``` -GET https://0pt.app/api/health -> 200 -{"ok":true,"service":"0pt-web","db":"not_configured","queue":"not_configured","ts":"2026-08-10T01:58:31.043Z"} -GET https://0pt.app/api/0pt -> 200 (anonymt) [lucka S1 bekräftad live] -GET https://0pt.app/api/orders -> 200 (anonymt) [lucka S3 bekräftad live] +GET https://0pt.app/api/health -> 200 +{"ok":false,"service":"0pt-web","db":"ok","queue":"not_configured", + "engine":"error","stripe":"not_configured", + "detail":{"engine":"This operation was aborted"}, + "ts":"2026-09-30T08:05:32.935Z"} + +GET https://0pt.app/admin -> 200 +GET https://0pt.app/api/orders -> 401 fail-closed (OPT_UNAUTHENTICATED) ``` -## Källkod (app/api/health/route.ts) +Hälsokontraktet ljuger inte: `ok:false` speglar en verkligt degraderad +beroendekoll (`engine:error`), db är på riktigt `ok` och authen fail-close:ar. +Vad som återstår är att åtgärda orsaken, inte att måla grönt (se nedan). -Health-svar innehåller `db` + `queue`-fält som reflekterar `DATABASE_URL` / -`REDIS_URL`-närvaro — beroendestatus rapporteras, men `ok` är alltid `true` -(liveness, ej readiness). Gap D2: en instans utan databas är "grön". +## Engine-kollens rotorsak — konfig, inte kod -## Bygge (next build, lokal — samma som CI) +- `lib/health-checks.ts#checkEngine` gör en riktig HTTP-roundtrip mot + `${ENGINE_URL}/health` (AbortController, 2 s timeout). Kontraktet: + `not_configured` när `ENGINE_URL` ej är satt; `error` när den ÄR satt men + misslyckas (t.ex. abort/non-2xx); `ok` först vid lyckad roundtrip. +- Prod-symptomet `"This operation was aborted"` = timeout-aborten mot en + **oprovisionerad host**: `ENGINE_URL` pekar på `http://0pt-engine:8000`, + ett namn utan DNS-post i flottan (mätt 2026-09-25 och åter 09-30; ingen + sådan container på server5–11). +- **Infisical /0pt (prod): 0 nycklar matcher `ENGINE_URL`** (räknat + 2026-09-30 via `siax-secrets export --env prod --path /0pt`). Värdet sitter + alltså i **Coolify-appens egen env**, inte i valvet. +- Beslut per konvention: kollen är redan ärlig och icke-blockerande när + motorn ej provisionerats — `ENGINE_URL` borttaget ⇒ `engine:"not_configured"` + och `ok:true`, utan kodändring. Sålänge URL:en är satt men död är + `error` den korrekta, ärliga statusen. Regressionstest finns i + `tests/lib/health-checks.test.ts` (unset → not_configured; satt+abort → + error med abort-meddelandet i `detail`). +- Åtgärd (konfig, Simon-gate): ta bort `ENGINE_URL` (och ev. oanvänd + `INTERNAL_TOKEN` för motorn) från Coolify-appens env → omdeploy. Tills + FastAPI-motorn (`services/api`) faktiskt provisioneras ska ingen + `ENGINE_URL` finnas i prod. -``` -ƒ Proxy (Middleware) <- Next 16 bygger middleware.ts korrekt i detta träd -``` -→ S1-hypotesen "middlewaren plockas inte upp av Next 16" försvagas; mer -sannolik orsak är Vercel-projektets build-output eller deploymentsversion -(ej verifierat). Se BACKLOG P0-1. +## Källkod (app/api/health/route.ts + lib/health-checks.ts) + +- Routen är **liveness**: alltid 200 så länge processen svarar. Bodyns `ok` + är den ärliga signalen: `false` när en konfigurerad dependency fail:ar. +- Per-dependency: `ok` | `error` (med `detail`) | `not_configured` | + `not_integrated` (endast `queue`/REDIS_URL — ingen Redis-klient wired i + repot; aldrig fabricerad `ok`). +- Readiness (`/api/ready`) fail-close:ar på `db != ok` och på varje + `error` — konfigurerad-men-död räknas inte som "valfri". + +## Simon-gates (ej agent-verifierbara) + +Inloggad journey (admin-portalens E2E) och kanarie-identitet (beställd +kanarieorder spårad end-to-end) kräver Simons inloggning och stäms separat — +omfattas ej av denna mätning. + +## Historik + +2026-08-10 mättes 0pt.app på Vercel (`{"ok":true,"db":"not_configured",...}` +hardcoded-grönt — BACKLOG P0-3, sedan fixat). Den deploymenten är inaktuell; alla prod-utsagor i denna fil gäller Coolify-cutovern. diff --git a/tests/lib/health-checks.test.ts b/tests/lib/health-checks.test.ts index 50ed15f..1e15396 100644 --- a/tests/lib/health-checks.test.ts +++ b/tests/lib/health-checks.test.ts @@ -114,6 +114,21 @@ describe("checkEngine", () => { }); expect(result.status).toBe("error"); }); + + it("REGRESSION: reports error (with the abort message in detail) when the probe times out via AbortController -- the exact 2026-09-30 prod symptom", async () => { + // Mätt i prod 2026-09-30: ENGINE_URL satt i Coolify-env mot en + // oprovisionerad host -> AbortController:n nbröt efter 2 s och + // /api/health rapporterade {"engine":"error", + // "detail":{"engine":"This operation was aborted"}}. Kollen ska + // vara ÄRLIG här: URL:en ÄR satt, så detta är error -- inte + // not_configured och aldrig ok. + process.env.ENGINE_URL = "http://0pt-engine:8000"; + const result = await checkEngine(async () => { + throw new DOMException("This operation was aborted", "AbortError"); + }); + expect(result.status).toBe("error"); + expect(result.detail).toContain("aborted"); + }); }); describe("checkStripe", () => { -- 2.54.0