fix(health): engine-kollen ärlig (not_configured när URL saknas) + EVIDENCE-docs mot Coolify-verkligheten #140
@@ -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.
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
Reference in New Issue
Block a user