26 KiB
RentalDriveGo — Plan to Reach Production Ready
Purpose of this document: This is the execution plan to take the current codebase to production ready. It is not a feature roadmap and not an architecture rewrite. Success is measured only by the definition in §1 and the checkpoint in §13.
Status: Phases 0–4 application code / runbooks complete (12 Aug 2026). Remaining work is ops evidence (CI smoke, restore/alert drills, pen-test, soak) — not missing features. Phase 4 correctly does not extract microservices (ADR-003).
Code root: D:\1\management
Last re-verified against source: 12 Aug 2026 (Phases 0–4 code pass)
Inputs: Live repo state + Technical Architecture & Investor Due Diligence (v1.0, 9 Aug 2026) + prior hardening reports under docs/
Architecture decision: Keep the modular monolith. Do not split services until Phase 3 load/ownership evidence justifies it.
How this plan works
flowchart TD
now[Current_beta_pre_scale] --> sec[Close_security_P0]
sec --> p0[Finish_Phase0_evidence]
p0 --> p1[Phase1_replica_safe]
p1 --> p2[Phase2_operate_recover]
p2 --> p3[Phase3_prove_under_load]
p3 --> prod[Production_ready_gate]
| Stage | Outcome |
|---|---|
| Security P0 + Phase 0 close | Safe enough to run a controlled environment; envelope proven |
| Phase 1 | Safe to run two API replicas + worker |
| Phase 2 | Operable: observe, backup/restore, commercial/privacy baselines |
| Phase 3 + §13 checklist | Production ready — claims allowed only after exit evidence |
1. Definition of “production ready” (the finish line)
The project is production ready only when all of the following are true and evidenced:
- Install, generate, lint, type-check, test, and build from a clean checkout
- Deploy a known topology (API, frontends, PostgreSQL, Redis, storage, worker)
- Run two API replicas without duplicate bookings, duplicate cron side effects, or missing files
- Deliver notifications that were written to the outbox (email + realtime) without multi-replica double-send
- Detect failure, restore data within agreed RPO/RTO, and show no unresolved critical/high dependency vulnerabilities outside a dated exception
- Ship with Critical/High application-security findings closed (§3)
- §13 diligence checkpoint items are checked off with retained evidence
Until then, the accurate label remains capable beta / pre-scale, not production SaaS.
Out of scope for this plan: new marketplace features, KYC/OCR, per-tenant Docker isolation, microservices rewrite, marketing-only work.
Why this path
The core product and data architecture exist; the next milestone is to convert that breadth into a reproducible, secure, observable, horizontally safe production system.
2. Current state (re-verified after project update)
2.1 Progress since the original diligence snapshot
| Area | Previous gap | Status now | Evidence |
|---|---|---|---|
| Turbo pipeline | Missing | Done | turbo.json (build/dev/lint/type-check/db:*) |
| Shared TS base | Missing | Done | tsconfig.base.json |
| Scripts / ops helpers | Missing | Done | scripts/ (env, docker-prod-*, admin, security-static-check.mjs, backup guides) |
| Compose / Docker | Missing | Done | docker-compose.dev.yml, docker-compose.production.yml, Dockerfile.dev / .production / .test, production/ mirror |
.gitignore |
Missing | Done | Root .gitignore |
| Static security script | Missing | Done | scripts/security-static-check.mjs + npm run security:static |
| CI | No workflows | Partial | .gitea/workflows/ (test.yml, build-and-deploy.yml) + .gitlab-ci.yml; no .github/workflows |
| Notification outbox consumer | Missing | Partial | processNotificationOutbox() in notificationService.ts; cron every minute in apps/api/src/index.ts — still inside the API process; email/IN_APP delivery + DLQ; no redis.publish for realtime |
| Prior hardening passes | — | Documented | SECURITY_HARDENING_APPLIED_REPORT.md, leftover report, docs/SECURITY_HARDENING_* (June 2026) — API-key hash-only, Socket.IO actor verify, cookie session work, etc. |
2.2 What remains strong
| Area | Evidence |
|---|---|
| Product surfaces | apps/homepage, dashboard, admin, carplace, api |
| Domain depth | Prisma: fleet, reservations, billing, payments, notifications, collections, admin |
| API shape | Express modular monolith |
| Auth / tenancy | JWT actors, HttpOnly cookies, admin 2FA, company middleware, subscription gates |
| Payments foundation | Manual bank transfer and check for subscription and rental billing; WebhookEvent retained for other inbound webhooks |
| Upload validation | Magic bytes + MIME + size limits |
| Deploy intent | Compose + Traefik configs, backup/restore scripts present |
2.3 What still blocks a production claim
| Priority | Gap | Repo evidence (current) |
|---|---|---|
| P0 | App security Critical/High still open | Team API spreads passwordHash / reset tokens; plaintext invite tokens; open redirect; unrestricted payment return URLs on authenticated checkout; admin presenter scrub incomplete; container Docker-socket design still in tree — see §3 |
| P0 | Ghost / dangerous container feature | containerService.ts + apps/admin/.../containers/page.tsx still present |
| P0 | Dependency SCA not freshly proven | Diligence (9 Aug): 21 prod vulns; must re-run on CI/clean runner |
| P0 | Phase 0 exit not fully evidenced | Envelope files exist, but clean-runner green CI + container removal + SCA gate not closed |
| P1 | Outbox not multi-replica safe | Processor runs via node-cron in every API process; no lease/lock; no Redis realtime publish |
| P1 | Process-local coordination | In-memory express-rate-limit; Carplace idempotencyCache Map; all crons in API index.ts |
| P1 | Local file storage | FILE_STORAGE_ROOT disk — no S3/MinIO adapter found |
| P1 | Ops blind spots | Only GET /health; no /ready; no SIGTERM graceful shutdown |
| P1 | Catalog drift | Homepage pricing vs shared plan/entitlement constants (unchanged risk) |
flowchart LR
subgraph done [Restored_envelope]
Turbo[turbo_tsconfig]
Compose[compose_Dockerfiles]
Scripts[scripts_security_static]
Gitignore[gitignore]
end
subgraph open [Still_open]
Sec[App_security_Critical_High]
Containers[containerService_UI]
Shared[Redis_rate_limit_idempotency]
Worker[Separate_worker_lease]
Ready[ready_shutdown_object_store]
end
done --> open
3. Security assessment (re-checked 12 Aug 2026 after update)
Scope: Static re-verification of findings from the earlier application review against current source.
Not in this pass: Live pen-test, production secrets rotation, successful live npm audit (registry TLS may still block local runs).
3.1 Controls that remain solid
| Control | Evidence |
|---|---|
| Tenant scoping on core CRUD | Company routes use session companyId |
| JWT from env + HS256 pinned | security/tokens.ts |
| Session cookies HttpOnly / Secure / SameSite | sessionCookies.ts |
| Hashed company API keys (legacy plaintext removed) | Hardening reports + schema |
| Upload magic-byte validation | http/upload |
| Payment webhook signatures | Online payment webhooks removed; remaining webhooks still verify signatures |
| Forwarded-header scrubbing by default | sanitizeForwardedHeaders unless TRUSTED_FORWARD_HEADERS=true |
| Site payment redirect allowlist | assertAllowedPaymentRedirect in site.service.ts |
3.2 Findings status after project update
| Sev | ID | Status | Location | Finding |
|---|---|---|---|---|
| Critical | S1 | FIXED (Phase 0) | teamService.ts |
Team list/invite use safe presenter; no hash/token leakage |
| Critical | S2 | FIXED (Phase 0) | containerService.ts + admin containers page + ADR-001 |
Feature disabled / fail-closed; UI shows out-of-scope |
| High | S3 | FIXED (Phase 0) | teamService.ts invite |
Invite token stored as SHA-256 hash |
| High | S4 | FIXED (Phase 0) | Prisma Employee + auth repo |
@@unique([companyId, email]); login fails closed on ambiguity |
| High | S5 | FIXED (Phase 0) | SignInForm.tsx |
Safe relative-path redirect only |
| High | S6 | FIXED (Phase 0) | paymentRedirects.ts + payment/subscription services |
Authenticated checkout allowlists return URLs |
| High | S7 | FIXED (Phase 0) | admin.schemas / admin.repo |
Slug regex + slugify on update |
| High | S8 | FIXED (Phase 0) | admin.presenter.ts |
Strips reset/verification secrets |
| High | S9 | FIXED (docs + default scrub) | forwardedHeaders.ts + docs/ops/proxy-trust.md |
Default scrub; TRUSTED_FORWARD_HEADERS documented |
| Medium | S10 | FIXED (Phase 0) | employee/admin reset repos | Hash-only reset token lookup |
| Medium | S11 | FIXED (Phase 3) | reservation/review presenters | reviewToken omitted from API JSON |
| Medium | S12 | FIXED | site.repo / site.service |
Public booking token unused-only on payment consume |
| Medium | S13 | FIXED | requireFreshAdmin2FA |
Max-age TTL (ADMIN_FRESH_2FA_MAX_AGE_MS, default 30m) |
| Medium | S14 | FIXED | .gitignore |
Present |
| Medium | S15 | FIXED | scripts/security-static-check.mjs |
Present; CI wired; local pass after env scrub |
3.3 Dependency / SCA status
| Check | Result |
|---|---|
| Diligence SCA (9 Aug 2026) | 21 production findings: 1 critical, 14 high, 5 moderate, 1 low |
| Local re-audit | May fail on TLS to registry — do not treat as clean |
| Required | CI/clean-runner npm audit --omit=dev (or OSV) with fail-on critical/high |
3.4 Security remediations (ordered)
P0 — before any production traffic
- S1: Explicit safe select/presenter for team APIs — never return hashes/tokens
- S3: Hash invite tokens at rest; remove raw dual-match after migration (S10)
- S8: Expand admin presenter denylist
- S2: Remove/disable
containerService+ admin containers UI from GA - S14/S15: Already fixed — keep in CI
- Re-run and clear dependency critical/high
P1 — before multi-user GA
- S4:
@@unique([companyId, email])+ fail-closed login - S5: Allowlist relative same-origin post-login redirects
- S6: Apply payment redirect allowlist to authenticated checkout/subscription
- S7: Slugify/validate admin slug updates
- S9: Document proxy trust; never enable trusted forwards without edge scrubbing
- S11–S13: Hide review tokens; single-use public access; align fresh 2FA on money mutations
3.5 Security exit criteria
- No API response includes password hashes, TOTP secrets, or raw reset/invite tokens
- Invite/reset tokens hashed at rest; legacy raw match removed
- Container/Docker-socket feature absent from production scope
- Login redirect and payment return URLs allowlisted on all checkout paths
- Employee email uniqueness (or fail-closed login) enforced
- CI runs
security:static+ SCA; no unresolved critical/high outside dated exception - Independent pen-test after deploy envelope proven (Phase 3)
4. Guiding principles
- Evidence over claims — Phase exit criteria must be demonstrable.
- Close Critical/High security before scale work — Envelope restore is largely done; app-sec P0 is now the front of the queue.
- Shared state before more replicas — Redis/DB/object storage before scaling API.
- One worker plane — Notifications and scheduled jobs must not run identically on every API replica.
- Disable incomplete privileged features — Especially Docker-socket container management.
- One commercial source of truth — Marketing, checkout, and enforcement share typed entitlements.
- Extract services last — Only after measurement.
5. Phase 0 — Evidence recovery
Goal: Reproducible build/deploy envelope + security hygiene baseline.
Update: Core files are restored. Remaining work is prove + close security/container/SCA.
5.1 Build orchestration — DONE
| Deliverable | Status |
|---|---|
turbo.json |
Present |
tsconfig.base.json |
Present |
scripts/ (env, docker-prod, admin, security-static) |
Present |
5.2 Runtime topology — DONE (files present)
| Deliverable | Status |
|---|---|
docker-compose.dev.yml / .production.yml |
Present |
Dockerfile.dev / .production / .test |
Present |
| Env examples | Present (.env.example, docker env samples) |
| Backup/restore scripts | Present under scripts/ |
Still required as evidence: recorded smoke that Compose boots API + Postgres + Redis and /health succeeds on a clean machine.
5.3 Continuous integration — PARTIAL
| Deliverable | Status |
|---|---|
| Gitea workflows | Present (.gitea/workflows/test.yml, build-and-deploy.yml) |
| GitLab CI | Present (.gitlab-ci.yml) |
| GitHub Actions | Not present (optional if Gitea/GitLab is the system of record) |
| Proven green run + SCA fail gate | Not evidenced in this review |
5.4 Ghost container control plane — NOT DONE
| Action | Status |
|---|---|
| Remove admin containers UI | Still present |
Quarantine/delete containerService.ts |
Still present |
| ADR: out of GA | Missing |
5.5 Dependency hygiene — NOT EVIDENCED
Must re-run SCA on CI and clear critical/high.
Phase 0 exit criteria (updated checkboxes)
turbo.json+tsconfig.base.json+scripts/present in tree- Compose + Dockerfiles present in tree
.gitignore+security:staticpresent- Team API no longer returns password hashes / reset tokens; invite tokens hashed at rest (S1/S3)
- Admin presenter scrubs reset/verification secrets (S8)
- Container orchestration disabled + ADR (
docs/ADR-001-disable-per-tenant-containers.md) (S2) - Login redirect allowlisted; authenticated payment return URLs allowlisted; admin slug validated; employee
(companyId, email)unique (S4–S7) - Env example/dev templates scrubbed of real-looking secrets;
security:staticpasses locally - Gitea CI includes
security:static+npm audit --omit=dev --audit-level=high - Fresh clone:
npm ci→ generate → lint → type-check → test → build with no local repair (needs recorded evidence on a clean runner) - CI green on default branch with SCA policy (push/run evidence)
- Compose smoke: API + Postgres + Redis;
/healthok (recorded) - No unresolved critical/high vulns outside dated exceptions (await CI audit result)
6. Phase 1 — Correctness and shared state
Goal: Two API replicas + one dedicated worker are correct under retries and failover.
Status (12 Aug 2026): Core code landed. Still needs npm ci, migrate, and a two-replica Compose smoke for exit evidence.
6.1 Notification outbox — DONE in code
| Piece | Status |
|---|---|
| Process isolation | apps/api/src/workers/index.ts + Compose api-worker; API jobs only if ENABLE_EMBEDDED_JOBS=true |
| Leasing | lockedAt / lockedBy / attempts / availableAt + claim via updateMany |
| Realtime | redis.publish('notifications:' + userId, …) on IN_APP delivery |
| Metrics | Phase 2 /metrics + outbox counters (scrape/alerts still open) |
6.2 Shared rate limiting — DONE in code
Redis store in redisRateLimitStore.ts (memory when NODE_ENV=test or RATE_LIMIT_STORE=memory).
6.3 Durable booking idempotency — DONE in code
idempotencyStore.ts (Redis; memory in test) used by Carplace /reservations.
6.4 Externalize scheduled work — DONE in code
Cron moved to workers/jobs.ts with Redis leader lock. API no longer starts cron by default.
6.5 Object storage — DONE in code (optional)
FILE_STORAGE_DRIVER=local|s3 + @aws-sdk/client-s3 + MinIO Compose profile storage. Default remains local disk.
6.6 Readiness and graceful shutdown — DONE in code
| Item | Status |
|---|---|
GET /health |
Liveness |
GET /ready |
DB + Redis + storage probes |
SIGTERM drain |
API + worker close HTTP/Socket/Redis/Prisma |
Phase 1 exit criteria
- Worker entrypoint + outbox lease + Redis publish implemented
- Redis rate limits + durable Carplace idempotency implemented
- Cron externalized with leader lock;
/ready+ graceful shutdown implemented - S3/MinIO adapter + Compose worker/MinIO services present
- Two API replicas + one worker: recorded concurrency/retry smoke (needs runner)
- Outbox no double-send under two API replicas (API without embedded jobs)
- Files readable across replicas when
FILE_STORAGE_DRIVER=s3(optional smoke)
7. Phase 2 — Operational control
Code baselines landed (2026-08): metrics/logs endpoint, plan catalog + tests, OpenAPI coverage gate, backup smoke check, privacy + billing/notification source-of-truth docs. Remaining work is evidence (drills, owners, alerts).
| Item | Status | Location |
|---|---|---|
Structured JSON access logs + /metrics (latency, status, outbox) |
In code | apps/api/src/lib/opsMetrics.ts, app.ts, worker |
Readiness /ready |
In code (Phase 1) | DB / Redis / storage |
| Plan/entitlement catalog + contract tests | In code | packages/types/src/planCatalog.ts, homepage pricing import |
| OpenAPI completeness gate | In code | npm run openapi:coverage → scripts/check-openapi-coverage.mjs |
| Backup artifact smoke check + RPO/RTO checklist | In code / docs | scripts/backup-restore-smoke-check.sh, scripts/backup-restore-guide.md |
| Privacy data map | Doc baseline | docs/PRIVACY_DATA_MAP.md (owners / DSAR still open) |
| Billing & notification SoT | Doc baseline | docs/BILLING_NOTIFICATION_SOURCE_OF_TRUTH.md |
| Staging alert + restore drill evidence | Open | Ops exercise |
| Provider reconciliation sample | Open | Finance / eng |
Phase 2 exit criteria
- Staging incident detectable and recoverable via runbooks (metrics scraped + alert fired once)
- Restore exercise meets RPO/RTO (dated drill using backup smoke check)
- Provider reconciliation sample exists
- Plan catalog single-sourced with tests
- Privacy data map drafted (assign legal/ops owners before GA)
- OpenAPI coverage script in CI
- Billing/notification source-of-truth documented
8. Phase 3 — Scale and assurance
Goal: Prove the system holds under load/failure, tenant isolation, independent security testing, and operable release/secret drills. Keep the modular monolith until measurement says otherwise (ADR-002 defers RLS).
Code / runbook baselines landed (2026-08):
| Item | Status | Location |
|---|---|---|
| Cross-tenant negative suite | In code | apps/api/src/tests/integration/cross-tenant-isolation.test.ts |
| S11 reviewToken scrubbed from API payloads | FIXED | reservation presenter + review service presenters |
| Soak / load probe (+ optional k6) | In code | scripts/load/soak-probe.mjs, booking-smoke.k6.js → npm run test:soak |
| Failure injection helper | In code | scripts/chaos/failure-injection.sh |
| Canary / rollback runbook | Doc | docs/ops/canary-rollback.md |
| Key rotation drill | Doc | docs/ops/key-rotation-drill.md |
| Pen-test scope pack | Doc | docs/security/pen-test-scope.md |
| Postgres RLS | Deferred | docs/ADR-002-defer-postgres-rls.md |
Phase 3 exit criteria
- Soak + failure-injection drill completed on staging with dated metrics evidence
- Cross-tenant suite green in CI (
cross-tenant-isolation) — code landed; CI run is evidence - Independent pen-test report in
security-reports/; Critical/High closed or accepted - Canary promote or rollback drill recorded once
- Key rotation drill recorded on staging
- App-level isolation suite started; RLS deferred per ADR-002
- S11 review tokens not returned in reservation/review JSON
- S12 single-use public payment token consume
- S13 fresh admin 2FA TTL
Phase 4 (months 3–6) — extract only if measured
Default: stay on the modular monolith + api-worker (ADR-003). Phase 4 is not a microservices rewrite.
| Item | Status | Location |
|---|---|---|
| Extraction policy ADR | Accepted | docs/ADR-003-defer-service-extraction.md |
| Gates G1–G7 + candidates | Doc | docs/ops/phase4-extraction-gates.md |
| Measurement template | Doc | docs/ops/phase4-measurement-template.md |
| Module seam map | Doc | docs/ops/phase4-module-boundaries.md |
| Actual service split | Blocked until a candidate passes G1–G7 | — |
Phase 4 exit criteria (for deciding, not for “having microservices”)
- Extraction gates and candidates documented
- In-monolith-first alternatives listed (scale workers, async media, thin webhooks)
- At least one soak/profile attribution packaged with the measurement template (needs Phase 3 evidence)
- Explicit decision recorded per candidate: do not extract (default) or approved extract ADR
Candidates if gates ever pass: payments/webhooks, notification worker as independent deployable, media processing.
9. Risk register → plan mapping
| ID | Risk | Status | Addressed by |
|---|---|---|---|
| R1 | Unreproducible package | Mostly mitigated (files restored; prove CI smoke) | Phase 0 evidence |
| R2 | Dependency vulnerabilities | Open (CI policy; clear on runner) | Phase 0.5 / CI SCA |
| R3 | Privileged container feature | Mitigated in code (ADR-001) | Phase 0.4 + S2 |
| R4 | Outbox not dispatched | Mitigated in code (worker + leases) | Phase 1.1 |
| R5 | Process-local rate limit / idempotency | Mitigated in code (Redis stores) | Phase 1.2–1.3 |
| R6 | Embedded schedules | Mitigated in code (worker + lock) | Phase 1.4 |
| R7 | Local file persistence | Mitigated in code (S3 driver) | Phase 1.5 |
| R8 | Ops blind spots | Partial (metrics/logs in code; alerts/drills open) | Phase 1.6 + Phase 2 |
| R9–R14 | Privacy, catalog, OpenAPI, legacy overlap, FE drift, flaky tests | Partial / later (catalog, OpenAPI gate, privacy map, SoT docs) | Phase 2–3 |
| S1–S8 | App security Critical/High | Fixed in code (S9 documented) | §3 P0/P1 |
| S10–S15 | Medium app security | Fixed in code (S11–S13 included) | Hardening + Phase 3 |
| S16–S21 | Medium/Low (undefined IDs) | Track in pen-test | Phase 3 evidence |
10. Suggested ownership and evidence log
| Field | Example |
|---|---|
| Phase | 0 / Security P0 |
| Date | YYYY-MM-DD |
| Commit / tag | prod-ready-phase0 |
| Commands | npm ci && npm run type-check && npm run security:static && npm audit --omit=dev |
| Artifacts | CI URL, Compose smoke logs, audit report |
| Exceptions | CVE-xxxx until DATE by OWNER |
| Sign-off | Eng lead |
11. Explicit non-goals (until gates pass)
- Claiming production ready / HA / horizontally scaled before security P0 + Phase 1 exit evidence
- Shipping per-tenant container orchestration
- Guaranteeing SMS/push without provider paths + worker proof
- Marketing KYC / license authenticity beyond date/expiry validation
- Splitting the monolith for its own sake
12. Next backlog (when implementation resumes)
Immediate (security + Phase 0 close)
- Fix team API secret leakage (S1); hash invite tokens (S3); scrub admin presenter (S8)
- Remove/disable
containerService+ admin containers UI (S2) - Allowlist login redirect (S5) and authenticated payment return URLs (S6); slugify admin slugs (S7); employee email uniqueness (S4)
- Record clean-runner CI green + production SCA clear/critical policy
- Record Compose smoke evidence
Then Phase 1
- Move outbox + cron to dedicated worker with leases; add Redis publish for realtime
- Redis rate-limit store; durable Carplace idempotency
- Object storage (MinIO/S3);
/ready+ graceful shutdown
Then Phase 2–3
- Observability, restore drill, catalog convergence, privacy map (Phase 2 evidence still open)
- Soak/failure drills, cross-tenant CI green, pen-test report, canary + key-rotation evidence (Phase 3)
Then Phase 4 (only if measured)
- Fill
docs/ops/phase4-measurement-template.mdfrom Phase 3 soak data; default decision remains do not extract (ADR-003)
13. Diligence checkpoint (definition of done)
- Clean checkout builds/tests on documented Node/npm with no local repair
- Production SCA has no unresolved critical/high outside formal exception
- Two API replicas pass booking/payment/job/notification/file correctness with shared coordination
- Authoritative API contract + auth test matrix
- Restore exercise meets RPO/RTO
- Logs/metrics/alerts/runbooks demonstrated in a failure exercise
- Privacy controls cover classification, encryption plan, retention, privileged reads/exports, incident response
- Plan catalog / checkout / entitlements single-sourced
- Independent security testing closes critical/high or records explicit acceptance
- §3 Critical/High application findings closed
14. Thesis and kickoff
This plan is how RentalDriveGo gets to production ready. The monorepo envelope is largely restored; the remaining path is: close Critical/High app security → finish Phase 0 evidence → replica-safe Phase 1 → operate/recover Phase 2 → prove Phase 3 → pass §13.
When implementation starts, execute §12 in order. Do not skip security P0 for feature work. Do not claim production ready until §1 and §13 are evidenced.
End of production-readiness plan. Code root: D:\1\management. Companion diligence: RentalDriveGo_Technical_Architecture_and_Investor_Due_Diligence.docx. Re-verified: 12 Aug 2026.