Self-Validation
Runtime gates. These let a running service or an agent verify the system is actually working — not just that the build passed.
Health and readiness endpoints
Every NestJS app exposes two endpoints under /api/:
| Endpoint | Purpose | Status codes |
|---|---|---|
GET /api/health |
Liveness — "am I running?" | 200 always (unless the process is dead) |
GET /api/ready |
Readiness — "can I serve traffic?" | 200 healthy, 503 degraded |
Backend (apps/backend)
/api/ready checks Mongoose connection state:
curl http://localhost:3001/api/health
# {"status":"ok","timestamp":"2026-04-20T...","uptime":123.45,"environment":"production"}
curl http://localhost:3001/api/ready
# {"status":"ready","timestamp":"...","checks":{"mongo":"connected"}}
If Mongo is disconnected, /api/ready returns 503 with "checks": {"mongo": "disconnected"}.
Device Service (apps/device-service)
/api/ready checks serial port + backend socket. In NODE_ENV=local (emulator mode) it reports "serial":"emulator" because there is no real USB device.
curl http://localhost:3003/api/ready
# {"status":"ready","timestamp":"...","checks":{"serial":"emulator","backend":"connected"}}
Structured JSON logs
Backend and device-service use nestjs-pino instead of the default NestJS logger.
- Production (
NODE_ENV=production): raw JSON on stdout. Log aggregators (Loki, ELK) ingest without transformation. - Local dev (
NODE_ENV=local):pino-prettyis wired, so you get human-readable colored output.
Tail a container:
docker logs backend-container 2>&1 | jq 'select(.level >= 40)' # warnings and above
docker logs device-service-container 2>&1 | jq 'select(.req)' # just HTTP request logs
The frontend and Electron apps still use console.* — they run in browsers/Electron renderer, where pino adds weight without matching upside.
Smoke script
scripts/smoke/run.ts is a Playwright-based probe. It checks backend health + readiness, device service health + readiness (if provided), then loads the frontend and takes a screenshot.
Install
npm --prefix scripts/smoke ci # installs playwright + tsx
npm --prefix scripts/smoke run smoke:install # downloads chromium
Invoke
# Defaults: localhost:3000 frontend, localhost:3001 backend
npm --prefix scripts/smoke run smoke
# Explicit targets
npm --prefix scripts/smoke run smoke -- \
--frontend https://smoker-dev-cloud.tail74646.ts.net \
--backend https://smoker-dev-cloud.tail74646.ts.net:8443 \
--device http://localhost:3003 \
--artifacts /tmp/smoke-artifacts \
--timeout 30000
# Env var fallbacks
SMOKE_FRONTEND_URL=... SMOKE_BACKEND_URL=... npm --prefix scripts/smoke run smoke
Exit codes
0— all checks passed1— one or more probes failed2— unexpected error (browser launch, config parse, etc.)
Artifacts
On success: frontend-ok.png saved to --artifacts directory.
On failure: frontend-fail.png or frontend-error.png plus a stack trace in the console.
Output format
[HEALTH] backend ... PASS (42ms)
[READY] backend ... PASS (15ms)
[HEALTH] device ... PASS (11ms)
[READY] device ... PASS (8ms)
[LOAD] frontend ... PASS (2141ms) screenshot=frontend-ok.png
smoke: PASS (5/5)
The final smoke: PASS (n/n) or smoke: FAIL (k/n failed) line is parsed by Ralph's validator (see below).
Ralph self-validation
Ralph (scripts/ralph/ralph-prompt.md) runs the smoke script after finishing TDD on an issue. The result lands in the commit body as a trailer:
feat(backend): add /api/smokes endpoint
... body ...
smoke: PASS — 5/5 probes green
Possible trailer values:
smoke: PASS — <detail>— all probes passed against a localdocker compose up -dtargetsmoke: FAIL — <detail>— one or more probes failed; Ralph will also self-label the issueralph:blockedso the next iteration skips itsmoke: SKIPPED — <reason>— environment cannot run the smoke (no docker, offline, etc.); Ralph reports the reason rather than silently passing
This trailer is required. A PR reviewer can scan commit bodies and see that every Ralph-authored change had its smoke reported.
Deploy workflow integration
The smoke script runs post-deploy in these workflows:
| Workflow | Target | Runs after | Artifact name |
|---|---|---|---|
cloud-deploy.yml |
localhost:3000 / localhost:3001 (runner-local) |
60s startup sleep | cloud-deploy-smoke-artifacts |
dev-deploy.yml |
smoker-dev-cloud.tail74646.ts.net via Tailscale |
Tailscale Serve config | dev-deploy-smoke-artifacts |
These steps are currently continue-on-error: true (advisory). They upload screenshots as GitHub Actions artifacts with 7–14 day retention so you can debug a failed probe without redeploying.
Each step is tagged id: smoke so the Week-3 blocking flip is a grep-and-edit across those files.
When a probe fails
- Check the artifact — screenshot tells you if the frontend rendered at all
- SSH in and curl the endpoint manually —
/api/readyreturns structuredchecksexplaining which dep is failing - Check the container logs — pino JSON is searchable with
jq - If it is a transient Mongo/Tailscale hiccup, re-run the workflow; if it reproduces, investigate the dep that
/api/readyflagged
Related
- Backpressure — commit + PR gates (the layer above these)
- Infrastructure — infra-level validation (post-provision smoke, compose healthchecks)
- Deployment / Health Checks — the deploy-side of the same story