fix(health): engine-kollen ärlig (not_configured när URL saknas) + EVIDENCE-docs mot Coolify-verkligheten #140

Merged
admin merged 1 commits from feat/health-evidence-20260930 into main 2026-09-30 08:45:27 +00:00
2 changed files with 79 additions and 17 deletions
+64 -17
View File
@@ -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.
+15
View File
@@ -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", () => {