Validating Production Readiness
This skill is the single source of truth for the production-readiness gate in the lt-stack. Every consumer applies the same eight pillars, the same severity classification, and produces the same report block — so multiple runs across time can be compared.
Goal: Decide, with evidence, whether the system is safe to deploy to production. Anything Critical is a hard blocker. Anything Major must be tracked. Minor findings are advisory.
When to Use This Skill
| Caller |
Phase |
Trigger |
/lt-dev:production-ready |
Phase 4 |
Hard release gate |
lt-dev:production-readiness-orchestrator |
Phase 4 |
Owns the audit + remediation loop |
lt-dev:devops-reviewer |
(cross-reference) |
Cites this checklist when flagging deployment-relevant gaps |
| Manual user invocation |
Pre-deploy |
Final sign-off before tagging a release |
Severity Classification
| Severity |
Meaning |
Effect on the release |
| Critical |
Loss of data, security breach, or undetectable downtime is plausible |
BLOCK the release until fixed |
| Major |
Operability is degraded; recovery requires manual intervention |
Tracked finding; release may proceed only with explicit owner sign-off |
| Minor |
Cosmetic, missing nice-to-have, or hygienic gap with no operational impact |
Advisory only |
A pillar with even a single Critical finding makes the whole release NOT READY.
The Eight Pillars
Each pillar lists the concrete checks to perform, the file paths to inspect, and the typical fix when the check fails.
Pillar 1 — Configuration & Secrets
| Check |
How |
Severity if missing |
| Env validation at boot |
projects/api/src/config.env.ts (or equivalent) parses with class-validator / Valibot and crashes on missing required keys |
Critical |
.env* excluded from VCS |
.gitignore covers .env, .env.*, except .env.example |
Critical |
.env.example exists & is current |
All keys read by code are present with placeholder values |
Major |
| Secrets are runtime-injected (not baked) |
Production Dockerfile does NOT COPY .env; secrets come from CI vault |
Critical |
NODE_ENV taxonomy respected |
e2e (local tests), ci (pipeline tests), develop (dev server), test (customer staging — NOT a test env), production (live) |
Major |
| Per-env DB URI / database name |
nsc.mongoose.uri ends with the env-specific DB (no shared "test" DB across envs) |
Critical |
| Default-credentials guard |
Production refuses to boot with password === 'admin' / JWT_SECRET === 'change-me' etc |
Critical |
Pillar 2 — Observability & Logging
| Check |
How |
Severity if missing |
| Structured logs (JSON) in production |
pino-http or NestJS Logger configured with JSON transport when NODE_ENV=production |
Major |
No console.log in shipped server code |
grep -nR "console\.log" projects/api/src returns 0 (console.error for boot failures is acceptable) |
Major |
| Request ID / correlation ID |
Middleware emits and propagates x-request-id to outgoing calls |
Major |
| Error tracking wired up |
Sentry (or equivalent) initialised on both API and App when DSN is set |
Major |
| Metrics endpoint or APM |
/metrics (Prometheus) OR APM agent (NewRelic/Datadog) instrumented |
Minor |
| Frontend error boundary |
Nuxt error.vue renders a useful message and reports to Sentry |
Major |
| Log levels controlled by env |
LOG_LEVEL env var read; defaults to info in prod, debug in dev |
Minor |
Pillar 3 — Health & Lifecycle
| Check |
How |
Severity if missing |
| Liveness endpoint |
GET /health (or /meta) returns 200 without DB dependency, suitable for k8s liveness probe |
Critical |
| Readiness endpoint |
GET /ready performs DB ping; returns 503 when DB is unreachable |
Major |
| Graceful shutdown |
NestJS app.enableShutdownHooks() enabled; in-flight requests drain before process exit |
Critical |
| Frontend SSR health |
GET / returns 200 in SSR with mocked-or-real backend; no unhandled rejections in server log |
Major |
Docker HEALTHCHECK declared |
Production Dockerfile includes HEALTHCHECK instruction calling /health |
Major |
| Process isolation |
Container runs as non-root user (USER node or similar) |
Major |
Pillar 4 — Security Hardening
| Check |
How |
Severity if missing |
| Helmet (or equivalent) on API |
app.use(helmet()) for REST mode; CSP set explicitly |
Major |
| CORS allowlist |
cors.origin is an explicit list, NOT * in production |
Critical |
| HTTPS enforced |
Reverse proxy / Nuxt config redirects http → https in production |
Critical |
| Cookies secure |
Better-Auth cookies have secure: true, httpOnly: true, sameSite: 'lax' (or stricter) in production |
Critical |
| Rate limiting on auth |
@nestjs/throttler (or equivalent) on /auth/*, /forgot-password, etc |
Major |
| Restricted decorators on every controller |
@Restricted / @Roles / securityCheck() present per backend-dev guidelines |
Critical |
pnpm audit clean (or audit ladder exhausted) |
Per running-check-script skill rules |
Major |
| No secrets in client bundle |
grep -nR "JWT_SECRET|MONGO_URI|API_KEY" projects/app/.output/ returns 0 after build |
Critical |
Pillar 5 — Data Durability
| Check |
How |
Severity if missing |
| Indexes for hot queries |
All queries seen in load-test profiler (Pillar 6 / k6 phase) hit an index |
Major |
| Migrations / seed scripts deterministic |
pnpm run migrate is idempotent; replaying it on production data does not corrupt |
Critical |
| Backup strategy documented |
README.md or docs/runbook.md describes backup cadence + restore drill |
Major |
| No destructive migrations on boot |
Server does NOT run dropDatabase / dropCollection on startup in production |
Critical |
| Soft-delete or audit trail where data loss matters |
Models flagged as critical have deletedAt / version trail |
Minor |
| Connection retry / backoff |
Mongoose connect uses exponential backoff, not infinite tight loop |
Major |
Pillar 6 — Resilience Under Load
The k6 results from the running-load-tests-with-k6 skill feed directly into this pillar. Don't re-run k6 here — read the report block.
| Check |
How |
Severity if missing |
| k6 load scenario PASSED |
tests/load/summary-load.json thresholds all PASS |
Critical |
| k6 baseline regression < 15% |
p95 delta vs tests/load/baselines/ within bounds |
Major |
| No memory leak in soak (when run) |
summary-soak.json shows flat memory at end |
Major |
| Connection pool not exhausted |
Mongoose log shows < 80% of maxPoolSize at peak |
Major |
| Timeouts everywhere |
All HTTP / DB calls have explicit timeouts (no infinite waits) |
Major |
| Idempotency keys on critical writes |
Payment / email / external write endpoints have idempotency-key handling |
Major |
Pillar 7 — Deployment Hygiene
| Check |
How |
Severity if missing |
| Dockerfile multi-stage |
Builder + runtime stages; runtime image contains only built artefacts + prod deps |
Major |
| Image base pinned |
FROM node:22.11.0-alpine (concrete version), not node:latest |
Major |
.dockerignore complete |
.git, node_modules, tests/, .env* excluded |
Major |
| CI builds from main with tag |
Tag-triggered pipeline builds the production image; never latest-only |
Major |
| Build is reproducible |
Lockfiles (pnpm-lock.yaml) committed; CI uses --frozen-lockfile |
Major |
| Image scan in CI |
trivy / grype / GitLab container scan stage exists |
Major |
| Image size reasonable |
Final image < 500 MB (alpine) / < 1 GB (debian-slim) |
Minor |
| Build identity baked (drift detection) |
Both images receive APP_VERSION_COMMIT (= CI commit SHA) as a build arg; GET /meta.commit returns the real SHA (not unknown) and the App /app/admin/system compares App vs. API to catch a partial / stale rollout |
Major |
Pillar 8 — Runbook & Rollback
| Check |
How |
Severity if missing |
| Runbook exists |
docs/runbook.md (or equivalent) describes deploy, rollback, common incidents |
Major |
| Rollback path documented |
Tagged previous image is deployable in < 5 min |
Critical |
| On-call ownership clear |
README or runbook names the responsible team / Slack channel |
Minor |
| Migration reversibility |
Each migration has a documented rollback (or is annotated as one-way) |
Major |
| Feature flags for risky changes |
Risky paths gated by env var or runtime flag |
Minor |
| Monitoring alerts configured |
At least: API down, error rate > 5%, p95 > 1s, DB connection lost |
Major |
Audit Procedure
For each pillar, in order:
- Inspect the listed files / configs / endpoints. Use
Read, Grep, Glob only — no destructive Bash.
- Classify each finding as Critical / Major / Minor with file:line evidence.
- Record the verdict per pillar as
PASS (no findings ≥ Major), WARN (Major only), FAIL (any Critical).
- Roll up the global verdict:
READY (all pillars PASS), READY-WITH-NOTES (≥1 WARN, 0 FAIL), NOT-READY (any FAIL).
Remediation Loop (when called from production-ready command)
When called from /lt-dev:production-ready, the orchestrator MUST attempt remediation for each Critical / Major finding:
| Finding type |
Default fix |
Missing .env.example key |
Add the placeholder key with a comment describing the value |
Missing enableShutdownHooks |
Add to main.ts |
Missing helmet / CORS allowlist |
Add to bootstrap module |
Missing HEALTHCHECK in Dockerfile |
Add `HEALTHCHECK CMD wget -qO- http://localhost:3000/health |
Missing rate limiting on /auth/* |
Add @Throttle decorator with sane defaults |
console.log in shipped code |
Replace with NestJS Logger / pino instance |
| Missing index |
Add @Index decorator + migration to backfill |
| Missing runbook |
Scaffold docs/runbook.md from the runbook template |
If a finding cannot be auto-fixed (architectural change, missing infra access, business decision), classify it as needs-human and surface it in the report rather than guessing.
After every remediation pass, re-run the relevant pillar check.
Report Block (canonical)
Every consumer ends Phase 4 with this block:
### Production Readiness Report
| Pillar | Verdict | Critical | Major | Minor | Auto-fixed |
|--------|---------|----------|-------|-------|-------------|
| 1. Configuration & Secrets | <PASS|WARN|FAIL> | <n> | <n> | <n> | <n> |
| 2. Observability & Logging | …
| 3. Health & Lifecycle | …
| 4. Security Hardening | …
| 5. Data Durability | …
| 6. Resilience Under Load | …
| 7. Deployment Hygiene | …
| 8. Runbook & Rollback | …
Global verdict: <READY|READY-WITH-NOTES|NOT-READY>
Auto-fixed total: <n>
Needs-human findings: <n>
Blocking issues:
- <pillar/severity/file:line> — short description
Cross-Skill References
- Code-level security:
general-frontend-security (frontend XSS/CSRF) and lt-dev:security-reviewer agent (OWASP-aligned diff review)
- DevOps configuration:
lt-dev:devops-reviewer agent (Docker / Compose / CI/CD specifics)
- Load resilience evidence:
running-load-tests-with-k6 (k6 results feed Pillar 6)
- Runnability gate:
running-check-script (must already be GREEN before this skill runs)
- Server lifecycle for health checks:
managing-dev-servers
1---2name: validating-production-readiness3description: The lenne.tech fullstack production-readiness checklist. Defines the eight pillars (configuration and secrets, observability, health and lifecycle, security hardening, data durability, resilience under load, deployment hygiene, runbook and rollback) with file/line evidence, severity classification, and a machine-parseable report block. Activates on "production ready", "produktionsreif", "Go-Live", "kann das live", or when a release must be gated before shipping. NOT for OWASP code-level security review (use the security-reviewer agent). NOT for npm dependency audits (use maintaining-npm-packages).4---56# Validating Production Readiness78This skill is the **single source of truth** for the production-readiness gate in the lt-stack. Every consumer applies the same eight pillars, the same severity classification, and produces the same report block — so multiple runs across time can be compared.910> **Goal:** Decide, with evidence, whether the system is safe to deploy to production. Anything Critical is a hard blocker. Anything Major must be tracked. Minor findings are advisory.1112## When to Use This Skill1314| Caller | Phase | Trigger |15|--------|-------|---------|16| `/lt-dev:production-ready` | Phase 4 | Hard release gate |17| `lt-dev:production-readiness-orchestrator` | Phase 4 | Owns the audit + remediation loop |18| `lt-dev:devops-reviewer` | (cross-reference) | Cites this checklist when flagging deployment-relevant gaps |19| Manual user invocation | Pre-deploy | Final sign-off before tagging a release |2021## Severity Classification2223| Severity | Meaning | Effect on the release |24|----------|---------|------------------------|25| **Critical** | Loss of data, security breach, or undetectable downtime is plausible | **BLOCK** the release until fixed |26| **Major** | Operability is degraded; recovery requires manual intervention | Tracked finding; release may proceed only with explicit owner sign-off |27| **Minor** | Cosmetic, missing nice-to-have, or hygienic gap with no operational impact | Advisory only |2829A pillar with even a single Critical finding makes the whole release **NOT READY**.3031## The Eight Pillars3233Each pillar lists the concrete checks to perform, the file paths to inspect, and the typical fix when the check fails.3435### Pillar 1 — Configuration & Secrets3637| Check | How | Severity if missing |38|-------|-----|----------------------|39| Env validation at boot | `projects/api/src/config.env.ts` (or equivalent) parses with class-validator / Valibot and crashes on missing required keys | Critical |40| `.env*` excluded from VCS | `.gitignore` covers `.env`, `.env.*`, **except** `.env.example` | Critical |41| `.env.example` exists & is current | All keys read by code are present with placeholder values | Major |42| Secrets are runtime-injected (not baked) | Production Dockerfile does NOT `COPY .env`; secrets come from CI vault | Critical |43| `NODE_ENV` taxonomy respected | `e2e` (local tests), `ci` (pipeline tests), `develop` (dev server), `test` (customer staging — NOT a test env), `production` (live) | Major |44| Per-env DB URI / database name | `nsc.mongoose.uri` ends with the env-specific DB (no shared "test" DB across envs) | Critical |45| Default-credentials guard | Production refuses to boot with `password === 'admin'` / `JWT_SECRET === 'change-me'` etc | Critical |4647### Pillar 2 — Observability & Logging4849| Check | How | Severity if missing |50|-------|-----|----------------------|51| Structured logs (JSON) in production | `pino-http` or NestJS Logger configured with JSON transport when `NODE_ENV=production` | Major |52| No `console.log` in shipped server code | `grep -nR "console\.log" projects/api/src` returns 0 (`console.error` for boot failures is acceptable) | Major |53| Request ID / correlation ID | Middleware emits and propagates `x-request-id` to outgoing calls | Major |54| Error tracking wired up | Sentry (or equivalent) initialised on both API and App when DSN is set | Major |55| Metrics endpoint or APM | `/metrics` (Prometheus) OR APM agent (NewRelic/Datadog) instrumented | Minor |56| Frontend error boundary | Nuxt `error.vue` renders a useful message and reports to Sentry | Major |57| Log levels controlled by env | `LOG_LEVEL` env var read; defaults to `info` in prod, `debug` in dev | Minor |5859### Pillar 3 — Health & Lifecycle6061| Check | How | Severity if missing |62|-------|-----|----------------------|63| Liveness endpoint | `GET /health` (or `/meta`) returns 200 without DB dependency, suitable for k8s liveness probe | Critical |64| Readiness endpoint | `GET /ready` performs DB ping; returns 503 when DB is unreachable | Major |65| Graceful shutdown | NestJS `app.enableShutdownHooks()` enabled; in-flight requests drain before process exit | Critical |66| Frontend SSR health | `GET /` returns 200 in SSR with mocked-or-real backend; no unhandled rejections in server log | Major |67| Docker `HEALTHCHECK` declared | Production Dockerfile includes `HEALTHCHECK` instruction calling `/health` | Major |68| Process isolation | Container runs as non-root user (`USER node` or similar) | Major |6970### Pillar 4 — Security Hardening7172| Check | How | Severity if missing |73|-------|-----|----------------------|74| Helmet (or equivalent) on API | `app.use(helmet())` for REST mode; CSP set explicitly | Major |75| CORS allowlist | `cors.origin` is an explicit list, NOT `*` in production | Critical |76| HTTPS enforced | Reverse proxy / Nuxt config redirects http → https in production | Critical |77| Cookies secure | Better-Auth cookies have `secure: true`, `httpOnly: true`, `sameSite: 'lax'` (or stricter) in production | Critical |78| Rate limiting on auth | `@nestjs/throttler` (or equivalent) on `/auth/*`, `/forgot-password`, etc | Major |79| Restricted decorators on every controller | `@Restricted` / `@Roles` / `securityCheck()` present per backend-dev guidelines | Critical |80| `pnpm audit` clean (or audit ladder exhausted) | Per `running-check-script` skill rules | Major |81| No secrets in client bundle | `grep -nR "JWT_SECRET\|MONGO_URI\|API_KEY" projects/app/.output/` returns 0 after build | Critical |8283### Pillar 5 — Data Durability8485| Check | How | Severity if missing |86|-------|-----|----------------------|87| Indexes for hot queries | All queries seen in load-test profiler (Pillar 6 / k6 phase) hit an index | Major |88| Migrations / seed scripts deterministic | `pnpm run migrate` is idempotent; replaying it on production data does not corrupt | Critical |89| Backup strategy documented | `README.md` or `docs/runbook.md` describes backup cadence + restore drill | Major |90| No destructive migrations on boot | Server does NOT run `dropDatabase` / `dropCollection` on startup in production | Critical |91| Soft-delete or audit trail where data loss matters | Models flagged as critical have `deletedAt` / version trail | Minor |92| Connection retry / backoff | Mongoose connect uses exponential backoff, not infinite tight loop | Major |9394### Pillar 6 — Resilience Under Load9596The k6 results from the `running-load-tests-with-k6` skill feed directly into this pillar. Don't re-run k6 here — read the report block.9798| Check | How | Severity if missing |99|-------|-----|----------------------|100| k6 load scenario PASSED | `tests/load/summary-load.json` thresholds all PASS | Critical |101| k6 baseline regression < 15% | p95 delta vs `tests/load/baselines/` within bounds | Major |102| No memory leak in soak (when run) | `summary-soak.json` shows flat memory at end | Major |103| Connection pool not exhausted | Mongoose log shows < 80% of `maxPoolSize` at peak | Major |104| Timeouts everywhere | All HTTP / DB calls have explicit timeouts (no infinite waits) | Major |105| Idempotency keys on critical writes | Payment / email / external write endpoints have idempotency-key handling | Major |106107### Pillar 7 — Deployment Hygiene108109| Check | How | Severity if missing |110|-------|-----|----------------------|111| Dockerfile multi-stage | Builder + runtime stages; runtime image contains only built artefacts + prod deps | Major |112| Image base pinned | `FROM node:22.11.0-alpine` (concrete version), not `node:latest` | Major |113| `.dockerignore` complete | `.git`, `node_modules`, `tests/`, `.env*` excluded | Major |114| CI builds from main with tag | Tag-triggered pipeline builds the production image; never `latest`-only | Major |115| Build is reproducible | Lockfiles (`pnpm-lock.yaml`) committed; CI uses `--frozen-lockfile` | Major |116| Image scan in CI | `trivy` / `grype` / GitLab container scan stage exists | Major |117| Image size reasonable | Final image < 500 MB (alpine) / < 1 GB (debian-slim) | Minor |118| Build identity baked (drift detection) | Both images receive `APP_VERSION_COMMIT` (= CI commit SHA) as a build arg; `GET /meta.commit` returns the real SHA (not `unknown`) and the App `/app/admin/system` compares App vs. API to catch a partial / stale rollout | Major |119120### Pillar 8 — Runbook & Rollback121122| Check | How | Severity if missing |123|-------|-----|----------------------|124| Runbook exists | `docs/runbook.md` (or equivalent) describes deploy, rollback, common incidents | Major |125| Rollback path documented | Tagged previous image is deployable in < 5 min | Critical |126| On-call ownership clear | README or runbook names the responsible team / Slack channel | Minor |127| Migration reversibility | Each migration has a documented rollback (or is annotated as one-way) | Major |128| Feature flags for risky changes | Risky paths gated by env var or runtime flag | Minor |129| Monitoring alerts configured | At least: API down, error rate > 5%, p95 > 1s, DB connection lost | Major |130131## Audit Procedure132133For each pillar, in order:1341351. **Inspect** the listed files / configs / endpoints. Use `Read`, `Grep`, `Glob` only — no destructive Bash.1362. **Classify each finding** as Critical / Major / Minor with file:line evidence.1373. **Record the verdict per pillar** as `PASS` (no findings ≥ Major), `WARN` (Major only), `FAIL` (any Critical).1384. **Roll up the global verdict:** `READY` (all pillars PASS), `READY-WITH-NOTES` (≥1 WARN, 0 FAIL), `NOT-READY` (any FAIL).139140## Remediation Loop (when called from production-ready command)141142When called from `/lt-dev:production-ready`, the orchestrator MUST attempt remediation for each Critical / Major finding:143144| Finding type | Default fix |145|--------------|-------------|146| Missing `.env.example` key | Add the placeholder key with a comment describing the value |147| Missing `enableShutdownHooks` | Add to `main.ts` |148| Missing `helmet` / CORS allowlist | Add to bootstrap module |149| Missing `HEALTHCHECK` in Dockerfile | Add `HEALTHCHECK CMD wget -qO- http://localhost:3000/health || exit 1` |150| Missing rate limiting on `/auth/*` | Add `@Throttle` decorator with sane defaults |151| `console.log` in shipped code | Replace with NestJS `Logger` / `pino` instance |152| Missing index | Add `@Index` decorator + migration to backfill |153| Missing runbook | Scaffold `docs/runbook.md` from the runbook template |154155If a finding cannot be auto-fixed (architectural change, missing infra access, business decision), classify it as **needs-human** and surface it in the report rather than guessing.156157After every remediation pass, re-run the relevant pillar check.158159## Report Block (canonical)160161Every consumer ends Phase 4 with this block:162163```164### Production Readiness Report165166| Pillar | Verdict | Critical | Major | Minor | Auto-fixed |167|--------|---------|----------|-------|-------|-------------|168| 1. Configuration & Secrets | <PASS|WARN|FAIL> | <n> | <n> | <n> | <n> |169| 2. Observability & Logging | …170| 3. Health & Lifecycle | …171| 4. Security Hardening | …172| 5. Data Durability | …173| 6. Resilience Under Load | …174| 7. Deployment Hygiene | …175| 8. Runbook & Rollback | …176177Global verdict: <READY|READY-WITH-NOTES|NOT-READY>178Auto-fixed total: <n>179Needs-human findings: <n>180Blocking issues:181- <pillar/severity/file:line> — short description182```183184## Cross-Skill References185186- **Code-level security:** `general-frontend-security` (frontend XSS/CSRF) and `lt-dev:security-reviewer` agent (OWASP-aligned diff review)187- **DevOps configuration:** `lt-dev:devops-reviewer` agent (Docker / Compose / CI/CD specifics)188- **Load resilience evidence:** `running-load-tests-with-k6` (k6 results feed Pillar 6)189- **Runnability gate:** `running-check-script` (must already be GREEN before this skill runs)190- **Server lifecycle for health checks:** `managing-dev-servers`