fix production issues
Build & Push / Pipeline Tests (push) Failing after 59s
Build & Push / Build & Push Docker Image (push) Has been skipped
Test / Type Check (all packages) (push) Failing after 51s
Test / API Unit Tests (push) Has been skipped
Test / Homepage Unit Tests (push) Has been skipped
Test / Carplace Unit Tests (push) Has been skipped
Test / Admin Unit Tests (push) Has been skipped
Test / Dashboard Unit Tests (push) Has been skipped
Test / API Integration Tests (push) Has been skipped

This commit is contained in:
root
2026-08-12 16:48:41 -04:00
parent 53de25120a
commit 8fc88ffc14
117 changed files with 4717 additions and 1443 deletions
@@ -0,0 +1,20 @@
# ADR-001: Disable per-tenant Docker container orchestration
**Status:** Accepted (Phase 0 production readiness)
**Date:** 2026-08-12
## Decision
Per-tenant Docker Compose / Docker-socket management is **out of production scope**. The previous `containerService` and admin Containers UI must not run in GA.
## Context
- The admin UI called `/admin/containers*` while matching API routes / `CompanyContainer` model were incomplete.
- `containerService` built Compose YAML via string interpolation and expected Docker socket access — a near-host RCE trust boundary inside the business data plane.
- Production readiness requires removing privileged incomplete features before scaling.
## Consequences
- `apps/api/src/services/containerService.ts` fails closed (`501 container_feature_disabled`).
- Admin containers page shows an explicit out-of-scope notice.
- Any future isolation requirement needs a separate controller with signed templates, quotas, audit, and no tenant-facing Docker socket.
+24
View File
@@ -0,0 +1,24 @@
# ADR-002 — Defer Postgres RLS until app-level isolation is proven
## Status
Accepted (Phase 3) — **RLS not enabled yet**
## Context
Phase 3 lists optional Postgres row-level security after app-level cross-tenant tests. The API already scopes queries with `companyId` from the authenticated session. Enabling RLS without a complete policy matrix and migration path risks breaking admin, workers, migrations, and reporting jobs that use elevated DB roles.
## Decision
1. Land and keep expanding the app-level suite (`apps/api/src/tests/integration/cross-tenant-isolation.test.ts`).
2. Do **not** enable `FORCE ROW LEVEL SECURITY` in production until:
- Cross-tenant suite covers vehicles, customers, reservations, payments, team, billing reads
- Worker and migration DB roles are designed (`BYPASSRLS` or dedicated policies)
- A staging soak proves no latent `findMany` without tenant predicates
3. Revisit RLS as a defense-in-depth layer in a dedicated change set — not as a gate to start Phase 3 assurance work.
## Consequences
- Tenant safety remains an application responsibility in the near term.
- Pen-testers should still treat missing `companyId` filters as Critical.
- Future RLS work tracks under Phase 3 optional / post-GA hardening.
+21
View File
@@ -0,0 +1,21 @@
# ADR-003 — Do not extract microservices until measurement gates pass
## Status
Accepted (Phase 4 planning) — **modular monolith remains the default**
## Context
The production-readiness plan and diligence review both forbid a microservices rewrite for its own sake. Phase 1 already isolated the **notification/job worker process** inside the same deployable (`api-worker`). Phase 4 candidates (payments/webhooks, notification worker as a separate *service*, media processing) are optional extractions only after Phase 3 load/ownership evidence shows a real bottleneck or blast-radius problem.
## Decision
1. **Default:** keep one API codebase + dedicated worker process(es) sharing the same package and schema.
2. **Do not** create new deployable services for payments, webhooks, or media until **all** gates in `docs/ops/phase4-extraction-gates.md` are met for that candidate.
3. Prefer in-monolith hardening first: clearer module boundaries, queue isolation, separate worker replicas, object-storage media pipeline — without new network hops.
4. Any approved extraction must ship with: ownership, SLO, independent deploy/rollback, contract tests, and dual-run evidence before cutting over.
## Consequences
- Phase 4 work in-repo is **gates, measurement templates, and module-boundary notes** — not a service split.
- Claiming “we moved to microservices” without gate evidence is a non-goal (§11 of the readiness plan).
@@ -0,0 +1,34 @@
# Billing & notification source-of-truth map
**Status:** Phase 2 convergence guide
**Date:** 2026-08-12
## Billing
| Generation | Models | Rule |
|------------|--------|------|
| **Canonical** | `BillingAccount`, `BillingInvoice`, line items, intents, attempts, credits, tax, refunds | Write path for new money movement |
| **Legacy compat** | `SubscriptionInvoice`, older payment-attempt shapes linked via `billingInvoiceId` | Read/migrate only; do not create new orphans |
**Invariant:** One money-moving command → one canonical `BillingInvoice` (or intent) → provider idempotency key → webhook reconciliation → auditable outcome.
Until legacy rows are archived:
1. Prefer canonical reads in admin/finance UI
2. Keep dual-read adapters only where needed for historical invoices
3. Block new writes that create legacy-only invoice rows
## Notifications
| Generation | Models | Rule |
|------------|--------|------|
| **Canonical** | `NotificationEvent``NotificationRecipient``NotificationDelivery` + `NotificationOutbox` | All new product notifications |
| **Legacy** | Flat `Notification` | Migrate reads; no new writes after GA |
**Dispatch:** Outbox worker (Phase 1) is the only delivery executor. Direct `sendTransactionalEmail` remains allowed for auth/security messages that must not wait on the outbox.
## Exit evidence for convergence
- [ ] Inventory counts of legacy vs canonical rows in staging/prod
- [ ] No new legacy-only writes in code paths covered by tests
- [ ] Retention policy applied consistently across both generations during migration
+37
View File
@@ -0,0 +1,37 @@
# Privacy data map (Phase 2 baseline)
**Status:** Baseline inventory for production-readiness — not a legal opinion or DPIA.
**Owner:** Engineering + ops (assign legal owner before GA)
**Date:** 2026-08-12
## Data classes
| Class | Examples | Storage | Access | Retention target (TBD / approve) |
|-------|----------|---------|--------|-----------------------------------|
| Account identity | Employee/admin/renter email, name, phone | PostgreSQL | Tenant roles / admin | Account life + 30 days |
| Auth secrets | Password hashes, TOTP secrets, hashed reset tokens | PostgreSQL | Auth services only | Until rotated/cleared |
| Customer PII | Customer name, DOB, nationality, address | PostgreSQL | Tenant employees | Contract life + local legal minimum |
| License evidence | License images, numbers, expiry | Private storage + DB refs | Authenticated customer routes | Contract life + dispute window |
| Rental evidence | Reservation photos, contracts/PDFs, damage inspections | Private/public storage + DB | Tenant + limited public tokens | Contract life + dispute window |
| Billing | Invoices, payment intents, manual payment evidence | PostgreSQL + private storage | Finance roles + fresh admin 2FA where required | 710 years (finance — confirm) |
| Notifications | Notification events, deliveries, preferences | PostgreSQL | Actor inbox APIs | 90180 days operational |
| Audit | Admin `AuditLog` rows | PostgreSQL | Admin roles | 12 years minimum |
## Controls in code today
- Private storage split + blocked anonymous customer/reservation storage paths
- HttpOnly cookies; admin 2FA; hashed API keys / invite tokens (Phase 0)
- Admin audit log for privileged platform actions
- Ops metrics do **not** include PII payloads
## Gaps to close before claiming privacy compliance
- [ ] Field-level encryption for highest-risk PII (license numbers, government IDs)
- [ ] Automated retention/deletion jobs with legal hold exceptions
- [ ] DSAR export/delete runbooks with evidence
- [ ] Privileged-read logging for license images and payment evidence downloads
- [ ] Processor inventory + DPA list
## Privileged-read audit expectation
Every successful read of license images, contract PDFs, damage photos, or payment evidence by support/admin impersonation must write an `AuditLog` (or equivalent immutable record) with actor, subject, resource id, and request id.
+6 -12
View File
@@ -66,25 +66,19 @@ Layer 4: api.RentalDriveGo.com — REST API
rental-car-site/
├── README.md
├── docs/
│ ├── design/ ← Production-readiness design updates (Phases 04)
│ ├── project-design/ ← Feature / API / schema design
│ ├── ops/ ← Runbooks, readiness plan, drills
│ ├── ARCHITECTURE.md ← Payment providers, carplace redirect model, photo flow
│ ├── DESIGN_SYSTEM.md
│ ├── PAGES.md ← All pages. Carplace = discovery only. Booking = company site.
│ ├── FEATURES.md
│ └── DEPLOYMENT.md
└── skills/
├── rental-car-website/
├── rental-car-components/
├── rental-car-backend/
│ └── references/
│ ├── schema.md ← AmanPay/PayPal fields, no Stripe
│ ├── api-routes.md ← All routes
│ ├── payment-service.md ← AmanPay + PayPal implementation ← KEY FILE
│ ├── subscription-service.md ← Manual renewal, plan prices in MAD/USD/EUR
│ ├── subdomain-service.md ← Carplace redirect + company site
│ └── notification-service.md ← All 5 channels
└── rental-car-i18n/
└── apps / packages / scripts ← See monorepo root README
```
See **`docs/design/README.md`** for what changed during the production-readiness program.
---
## 🛠 Tech Stack
+37
View File
@@ -0,0 +1,37 @@
# Commercial and data design updates (Phase 2)
## Plan / entitlement catalog
**Single source of truth:** `packages/types/src/planCatalog.ts`
Exports (via `@rentaldrivego/types` / `api.ts` re-exports):
- `PLAN_PRICES`, `PLAN_ENTITLEMENTS`, capabilities, public marketing map (`launch``STARTER`, etc.)
- Helpers: `getVehicleLimit`, `getPublicMonthlyMajorUnits`, `planHasCapability`
**Consumers:**
- Homepage marketing prices → `apps/homepage/src/content/pricing-config.ts`
- API site/subscription fallbacks → `@rentaldrivego/types`
- Vehicle fleet limits → catalog first (no duplicate hardcoded fallback table)
Contract tests: `packages/types/src/planCatalog.test.ts` (`npm run test:types`).
## Billing & notifications source of truth
See `docs/BILLING_NOTIFICATION_SOURCE_OF_TRUTH.md`:
| Domain | Canonical | Legacy |
|--------|-----------|--------|
| Money | `BillingAccount` / `BillingInvoice` / intents | `SubscriptionInvoice` (compat reads only) |
| Notifications | `NotificationEvent` → recipients → deliveries + **outbox** | Flat `Notification` |
Dispatch design: outbox worker is the delivery executor; direct transactional email remains allowed for auth/security messages.
## Privacy baseline
`docs/PRIVACY_DATA_MAP.md` inventories data classes, storage, access, and open gaps (field encryption, DSAR automation, privileged-read audit). Ops metrics intentionally exclude PII payloads.
## OpenAPI completeness
Design rule: ops endpoints `/health`, `/ready`, `/metrics` must appear in OpenAPI. CI runs `npm run openapi:coverage` (`scripts/check-openapi-coverage.mjs`).
@@ -0,0 +1,106 @@
# Production readiness — what was implemented
**Date:** 2026-08-12
**Code root:** monorepo `apps/*`, `packages/*`
**Plan:** `docs/ops/RentalDriveGo_Production_Readiness_Plan.md`
This document is the design-facing summary of work already landed. It is not a backlog.
---
## Architecture decision (unchanged)
Keep a **modular Express monolith** plus a dedicated **job worker process**. Do not split payments/webhooks/media into microservices until measurement gates pass (ADR-003).
```
[Traefik] → [API × N] → PostgreSQL
↓ ↑
[Redis] [api-worker]
[local disk | S3/MinIO]
```
---
## Phase 0 — Security & envelope
| Area | Design outcome | Primary paths |
|------|----------------|---------------|
| Team API secrets | Safe presenters; no `passwordHash` / raw reset tokens in responses | `apps/api/src/services/teamService.ts` |
| Invite tokens | Stored as SHA-256 hashes | `teamService.ts` |
| Admin presenters | Scrub reset/verification secrets | `admin.presenter.ts` |
| Per-tenant Docker | Feature **fail-closed**; UI out-of-scope | `containerService.ts`, ADR-001 |
| Login redirects | Relative same-origin allowlist only | dashboard `SignInForm` |
| Payment return URLs | Authenticated checkout allowlists | `paymentRedirects.ts` |
| Admin slugs | Validated / slugified | admin schemas/repo |
| Employee email | `@@unique([companyId, email])`; ambiguous login fails closed | Prisma + auth repo |
| Reset / email verify tokens | Hash-only lookup (no raw dual-match) | employee/admin repos |
| Env templates | Scrubbed of real-looking secrets | `.env.example`, docker env samples |
| CI hygiene | `security:static` + production `npm audit` gate | `.gitea/workflows/test.yml` |
---
## Phase 1 — Replica-safe shared state
| Area | Design outcome | Primary paths |
|------|----------------|---------------|
| Job plane | Dedicated `api-worker`; API embeds jobs only if `ENABLE_EMBEDDED_JOBS=true` | `apps/api/src/workers/` |
| Notification outbox | DB leases (`lockedAt` / `lockedBy` / `attempts` / `availableAt`) | `notificationService` + migration |
| Realtime | Redis publish on IN_APP delivery | worker / notification service |
| Rate limits | Shared Redis store | `redisRateLimitStore.ts` |
| Carplace idempotency | Redis-backed store | `idempotencyStore.ts` |
| Files | `FILE_STORAGE_DRIVER=local\|s3` (+ MinIO compose profile) | `lib/storage`, `objectStorage` |
| Readiness | `GET /ready` checks DB, Redis, storage | `app.ts` |
| Shutdown | SIGTERM drains API and worker | `index.ts`, `workers/index.ts` |
---
## Phase 2 — Operational control (code baselines)
| Area | Design outcome | Primary paths |
|------|----------------|---------------|
| Metrics / logs | Structured JSON access logs; Prometheus text at `GET /metrics` | `opsMetrics.ts`, `app.ts` |
| Worker metrics | Optional `WORKER_METRICS_PORT` exposes `/metrics` + `/health` | `workers/index.ts` |
| Plan catalog | Single source in `@rentaldrivego/types` | `packages/types/src/planCatalog.ts` |
| Homepage pricing | Imports catalog (no hard-coded MAD amounts) | `apps/homepage/.../pricing-config.ts` |
| OpenAPI gate | Coverage script + CI step | `scripts/check-openapi-coverage.mjs` |
| Backup smoke | Artifact checker + RPO/RTO checklist | `scripts/backup-restore-*` |
| Privacy / SoT docs | Baseline maps | `docs/PRIVACY_DATA_MAP.md`, `docs/BILLING_NOTIFICATION_SOURCE_OF_TRUTH.md` |
**Still evidence-only:** staging alert fire, dated restore drill, provider reconciliation sample, privacy owners.
---
## Phase 3 — Scale & assurance (code / runbooks)
| Area | Design outcome | Primary paths |
|------|----------------|---------------|
| Cross-tenant suite | Vehicles, customers, reservations, offers, team, payments negatives | `cross-tenant-isolation.test.ts` |
| S11 | `reviewToken` omitted from API JSON | reservation presenter, review service |
| S12 | Public booking token: GET does not burn; payment **consumes** once | `site.service.ts` / `site.repo.ts` |
| S13 | Fresh admin 2FA max-age (default 30 minutes) | `requireFreshAdmin2FA` |
| Soak / chaos | Node soak probe + optional k6; failure-injection helper | `scripts/load/`, `scripts/chaos/` |
| Canary / keys / pen-test | Runbooks and scope pack | `docs/ops/`, `docs/security/pen-test-scope.md` |
| Postgres RLS | Explicitly deferred | ADR-002 |
**Still evidence-only:** soak/chaos drill records, pen-test report, canary + key-rotation drills.
---
## Phase 4 — Extract only if measured
| Area | Design outcome |
|------|----------------|
| Default | Stay on monolith + `api-worker` |
| Gates | G1G7 in `docs/ops/phase4-extraction-gates.md` |
| Candidates | Notifications worker deployable, payments/webhooks, media — **blocked** until measurement |
| Code change | None required for “phase complete”; ADR-003 + templates only |
---
## Explicit non-goals (still)
- Claiming production ready without §13 evidence
- Shipping per-tenant container orchestration
- Microservices rewrite without gate evidence
- Marketing KYC / OCR authenticity beyond license date checks
+32
View File
@@ -0,0 +1,32 @@
# Design docs — production-readiness updates
This folder records **architecture and design changes** landed while taking RentalDriveGo to production readiness (Phases 04). It complements the longer plans under `docs/ops/` and `docs/project-design/`.
## Investor diligence (Word)
| Document | Notes |
|----------|--------|
| [RentalDriveGo_Technical_Architecture_and_Investor_Due_Diligence.docx](./RentalDriveGo_Technical_Architecture_and_Investor_Due_Diligence.docx) | **v1.1 (12 Aug 2026)** — updated executive status, risk/roadmap annotations, and **Addendum A** for Phases 04 implementation. Original v1.0 backup: `RentalDriveGo_Technical_Architecture_and_Investor_Due_Diligence.v1.0.backup.docx` |
## Markdown design notes
| Doc | Purpose |
|-----|---------|
| [PRODUCTION_READINESS_IMPLEMENTED.md](./PRODUCTION_READINESS_IMPLEMENTED.md) | What was implemented per phase (code + runbooks) |
| [RUNTIME_AND_OPS_SURFACE.md](./RUNTIME_AND_OPS_SURFACE.md) | Worker, Redis, storage, `/ready`, `/metrics`, env knobs |
| [SECURITY_DESIGN_UPDATES.md](./SECURITY_DESIGN_UPDATES.md) | Security findings S1S15 design outcomes |
| [COMMERCIAL_AND_DATA_DESIGN.md](./COMMERCIAL_AND_DATA_DESIGN.md) | Plan catalog, billing/notification SoT, privacy map |
## Related ADRs
- `docs/ADR-001-disable-per-tenant-containers.md`
- `docs/ADR-002-defer-postgres-rls.md`
- `docs/ADR-003-defer-service-extraction.md`
## Canonical execution plan
- `docs/ops/RentalDriveGo_Production_Readiness_Plan.md`
## Status (2026-08-12)
**Application code and in-repo runbooks for Phases 04 are complete.** Remaining production-ready gates are **ops evidence** (CI smoke, restore/alert drills, soak, pen-test), not missing product features. Phase 4 does **not** extract microservices by default (ADR-003).
+56
View File
@@ -0,0 +1,56 @@
# Runtime and ops surface (post Phases 12)
Design note for operators and engineers. Reflects the **current** intended production topology.
## Processes
| Process | Role | Notes |
|---------|------|--------|
| `api` | HTTP API | Do **not** set `ENABLE_EMBEDDED_JOBS=true` when running multiple API replicas |
| `api-worker` | Outbox + scheduled jobs | Uses DB leases + Redis leader lock for cron |
| Frontends | homepage, carplace, dashboard, admin | Unchanged modular apps |
| postgres | System of record | |
| redis | Rate limit, idempotency, pub/sub, locks | Required for replica-safe behavior |
| object storage | Optional S3/MinIO | Prefer over local disk for multi-replica files |
## HTTP ops endpoints
| Method | Path | Purpose |
|--------|------|---------|
| GET | `/health` | Liveness |
| GET | `/ready` | Readiness: database, redis, storage |
| GET | `/metrics` | Prometheus-style counters/gauges (latency, status, outbox pending/published) |
| GET | `/api/v1/openapi.json` | OpenAPI document |
| GET | `/docs` | Swagger UI (when enabled) |
Worker (optional):
| Env | Behavior |
|-----|----------|
| `WORKER_METRICS_PORT=<port>` | Worker listens for `GET /metrics` and `GET /health` |
| `0` / unset | No worker HTTP listener |
## Important environment knobs
Documented in `.env.example`:
- `ENABLE_EMBEDDED_JOBS` — default false in production examples
- `RATE_LIMIT_STORE` / `IDEMPOTENCY_STORE` — prefer `redis`
- `FILE_STORAGE_DRIVER``local` or `s3` (+ `S3_*`)
- `TRUSTED_FORWARD_HEADERS` — default false; see `docs/ops/proxy-trust.md`
- `ADMIN_FRESH_2FA_MAX_AGE_MS` — default `1800000` (30 minutes)
- `WORKER_METRICS_PORT` — optional
## Observability design
- Access logs: structured JSON via morgan in `app.ts`
- In-process series: `apps/api/src/lib/opsMetrics.ts`
- Durable outbox gauges: API `/metrics` also counts PENDING / PUBLISHED rows from PostgreSQL so scrapes remain useful when jobs run only on the worker
## Failure / release runbooks
- Chaos helper: `scripts/chaos/failure-injection.sh`
- Soak: `npm run test:soak``scripts/load/soak-probe.mjs`
- Canary / rollback: `docs/ops/canary-rollback.md`
- Key rotation: `docs/ops/key-rotation-drill.md`
- Backup smoke: `scripts/backup-restore-smoke-check.sh`
+54
View File
@@ -0,0 +1,54 @@
# Security design updates (Phases 0 & 3)
Summary of application-security design changes from the production-readiness program. Full finding table lives in `docs/ops/RentalDriveGo_Production_Readiness_Plan.md` §3.
## Closed in code (S1S15)
| ID | Design rule now enforced |
|----|---------------------------|
| S1 | Team list/invite responses use safe presenters — never return password hashes or raw reset tokens |
| S2 | Per-tenant Docker/Compose orchestration is **out of production scope** (fail-closed + ADR-001) |
| S3 | Invite tokens hashed at rest (SHA-256) |
| S4 | Employee email unique per company; login fails closed on ambiguity |
| S5 | Post-login redirects limited to safe relative paths |
| S6 | Authenticated payment/subscription return URLs allowlisted |
| S7 | Admin company slugs validated / slugified |
| S8 | Admin presenters strip reset/verification secrets |
| S9 | Forwarded headers scrubbed by default; `TRUSTED_FORWARD_HEADERS=true` only behind a scrubbing edge (`docs/ops/proxy-trust.md`) |
| S10 | Password reset token lookup is hash-only |
| S11 | `reviewToken` is never returned in reservation/review API JSON (still used server-side for email links) |
| S12 | Public booking access: **read** does not burn the token; **payment init** atomically consumes an unused token |
| S13 | Admin money / privileged mutations require 2FA proof newer than `ADMIN_FRESH_2FA_MAX_AGE_MS` |
| S14 | `.gitignore` present for secrets/build artifacts |
| S15 | `npm run security:static` in CI |
## Public booking token (S12) flow
```
createBooking → mint publicAccessToken (hash stored)
├─ GET booking?token=… → validate (used or unused OK until expiry) — do not consume
└─ initPayment(token) → require usedAt IS NULL → updateMany set usedAt → proceed
(second payment attempt with same token → 404)
```
## Fresh admin 2FA (S13)
`requireFreshAdmin2FA` rejects when:
- TOTP not enrolled, or
- JWT lacks `last2faAt`, or
- `now - last2faAt > ADMIN_FRESH_2FA_MAX_AGE_MS` (default 30 minutes)
Wired on finance/support money and high-privilege admin mutations.
## Tenant isolation design
- App-level: every company resource query includes `companyId` from the authenticated session
- Regression suite: `apps/api/src/tests/integration/cross-tenant-isolation.test.ts`
- Postgres RLS: **deferred** (ADR-002) until the app-level suite and worker/migration roles are ready
## Pen-test
Scope pack: `docs/security/pen-test-scope.md`. Reports belong under `security-reports/` (evidence, not design).
+357
View File
@@ -0,0 +1,357 @@
from docx import Document
from pathlib import Path
import shutil
src = Path(r"D:\1\management\docs\design\RentalDriveGo_Technical_Architecture_and_Investor_Due_Diligence.docx")
backup = src.with_suffix(".v1.0.backup.docx")
if not backup.exists():
shutil.copy2(src, backup)
print("backup:", backup)
doc = Document(str(src))
def set_runs_text(paragraph, text: str) -> None:
if paragraph.runs:
paragraph.runs[0].text = text
for r in paragraph.runs[1:]:
r.text = ""
else:
paragraph.add_run(text)
# --- Title / version updates ---
for i, p in enumerate(doc.paragraphs[:15]):
t = p.text.strip()
if t.startswith("Version 1.0"):
set_runs_text(
p,
"Version 1.1 • 12 August 2026 (addendum update of v1.0 dated 9 August 2026)",
)
print(f"updated version para {i}")
if "Prepared from the supplied" in t:
set_runs_text(
p,
"Prepared from the car_management / management monorepo; v1.1 reflects Phases 04 "
"production-readiness implementation status as of 12 August 2026",
)
print(f"updated prepared para {i}")
# --- Soft-update executive bottom line ---
for i, p in enumerate(doc.paragraphs):
if p.text.startswith("Bottom line"):
set_runs_text(
p,
"Bottom line (updated 12 Aug 2026) RentalDriveGo remains a serious multi-application "
"rental SaaS with strong domain depth. Since the 9 August 2026 archive review, the monorepo "
"has been restored and Phases 04 of the production-readiness program have landed in "
"application code and in-repo runbooks: security P0/P1 fixes, dedicated api-worker with "
"leased outbox, Redis rate-limit/idempotency, object-storage driver, /ready and /metrics, "
"single plan catalog, OpenAPI coverage gate, cross-tenant tests, and extraction deferred "
"pending measurement (ADR-003). The platform is still not “production ready” as an investor "
"claim until clean-runner CI/SCA evidence, Compose multi-replica smoke, restore/alert drills, "
"soak/pen-test, and §13 checkpoint items are recorded. The accurate frame is now: product + "
"operational foundations exist in code; remaining capital/ops work is evidence and assurance, "
"not a greenfield rebuild.",
)
print(f"updated bottom line para {i}")
break
# Update investment interpretation cells
for table in doc.tables:
for row in table.rows:
cells = [c.text.strip() for c in row.cells]
if not cells:
continue
dim = cells[0]
if dim == "Delivery reproducibility" and len(cells) >= 3:
row.cells[1].text = "Mostly restored in tree"
row.cells[2].text = (
"turbo.json, tsconfig.base.json, scripts, Compose, backup/restore present; "
"clean-runner CI green still needs recorded evidence."
)
elif dim == "Scale readiness" and len(cells) >= 3:
row.cells[1].text = "Improved (code)"
row.cells[2].text = (
"api-worker, Redis stores, S3/MinIO driver, readiness/shutdown landed; "
"two-replica soak evidence still open."
)
elif dim == "Operational maturity" and len(cells) >= 3:
row.cells[1].text = "Baseline in code"
row.cells[2].text = (
"/metrics, structured logs, backup smoke, runbooks present; "
"alert/restore/pen-test drills still open."
)
elif dim == "Security design" and len(cells) >= 3:
row.cells[1].text = "Strengthened"
row.cells[2].text = (
"S1S15 application findings closed or documented; containers fail-closed; "
"fresh 2FA TTL; review tokens scrubbed; pen-test still required."
)
# Update material findings glance
for table in doc.tables:
for row in table.rows:
texts = [c.text for c in row.cells]
joined = " | ".join(texts)
if "turbo.json and tsconfig.base.json are absent" in joined and len(row.cells) >= 3:
row.cells[0].text = "P0→Mitigated"
row.cells[1].text = (
"Monorepo envelope (turbo.json, tsconfig.base.json, scripts, Compose) "
"restored in the management tree."
)
row.cells[2].text = "Remaining: recorded clean-runner CI/smoke evidence."
elif "21 vulnerabilities" in joined and len(row.cells) >= 3:
row.cells[0].text = "P0→Open evidence"
row.cells[1].text = (
"CI now runs production npm audit (fail on high); "
"local/registry results still time-sensitive."
)
row.cells[2].text = "Clear critical/high on a clean runner or file dated exceptions."
elif ("Per-tenant container" in joined or "container orchestration" in joined.lower()) and len(
row.cells
) >= 3:
row.cells[0].text = "P0→Mitigated"
row.cells[1].text = "Container feature fail-closed; admin UI out-of-scope; ADR-001 accepted."
row.cells[2].text = "Do not re-enable Docker-socket tenant isolation."
elif "no dispatcher/worker was found" in joined and len(row.cells) >= 3:
row.cells[0].text = "P1→Mitigated in code"
row.cells[1].text = "Dedicated api-worker with leased outbox dispatch and Redis realtime publish."
row.cells[2].text = "Prove under two API replicas + worker in staging."
elif "Rate limits" in joined and "process-local" in joined.lower() and len(row.cells) >= 3:
row.cells[0].text = "P1→Mitigated in code"
row.cells[1].text = (
"Redis rate-limit store, Redis Carplace idempotency, cron leader lock, S3/local storage driver."
)
row.cells[2].text = "Multi-replica correctness evidence still required."
elif (
"No complete CI/CD, observability" in joined
or "graceful shutdown, backup/restore" in joined
) and len(row.cells) >= 3:
row.cells[0].text = "P1→Partial"
row.cells[1].text = (
"CI security/OpenAPI gates, /ready, /metrics, backup smoke scripts and ops runbooks landed."
)
row.cells[2].text = "Alert/restore/pen-test/soak evidence still open."
# Update recommendation bullets
for p in doc.paragraphs:
if p.text.startswith("Treat the current asset as a capable beta"):
set_runs_text(
p,
"Treat the current asset as a late beta / hardening-stage platform: application foundations "
"for production readiness are largely implemented; do not claim production-hardened SaaS until "
"exit evidence (§13) is complete.",
)
if p.text.startswith("Condition any production or scale claim"):
set_runs_text(
p,
"Condition any production or scale claim on remaining evidence gates: clean CI/SCA, "
"two-replica Compose smoke, restore meeting RPO/RTO, alert/failure drill, soak, "
"independent pen-test, and provider reconciliation sample.",
)
if p.text.startswith("Fund a 90-day production-readiness program"):
set_runs_text(
p,
"Continue the funded production-readiness program through evidence close-out; Phase 4 "
"service extraction remains deferred until measurement gates pass (ADR-003). Reevaluate "
"extraction only from measured load and ownership boundaries.",
)
# Risk register updates
risk_updates = {
"R1": ("Mostly mitigated", "Envelope restored in tree; prove clean-runner CI/smoke."),
"R2": ("Open (CI policy)", "SCA gate in CI; clear critical/high on clean runner."),
"R3": ("Mitigated in code", "ADR-001; containerService fail-closed; UI out-of-scope."),
"R4": ("Mitigated in code", "api-worker + DB leases + Redis publish; prove multi-replica."),
"R5": ("Mitigated in code", "Redis rate-limit and idempotency stores."),
"R6": ("Mitigated in code", "Schedules on worker with Redis leader lock."),
"R7": ("Mitigated in code", "FILE_STORAGE_DRIVER local|s3; prefer object storage in prod."),
"R8": ("Partial", "Metrics/logs/runbooks in repo; drills still open."),
"R9": ("Partial", "Privacy data map drafted; owners/DSAR/encryption plan open."),
"R10": ("Mitigated in code", "packages/types planCatalog + homepage/API consumers + tests."),
"R11": ("Mitigated in code", "OpenAPI coverage CI gate; /ready and /metrics documented."),
"R12": ("Partial", "Billing/notification SoT documented; migration convergence ongoing."),
"R13": ("Open / later", "Shared clients still a Phase 3+ concern."),
"R14": ("Open / later", "Stabilize CI runners; bounded completion still evidence."),
}
for table in doc.tables:
for row in table.rows:
first = row.cells[0].text.strip()
for rid, (status, action) in risk_updates.items():
if first.startswith(rid + " ") or first.startswith(rid + "") or first.startswith(rid + " ·") or first.startswith(rid + ""):
if len(row.cells) >= 4:
ev = row.cells[2].text.strip()
if "STATUS (12 Aug 2026)" not in ev:
row.cells[2].text = f"{ev}\nSTATUS (12 Aug 2026): {status}"
row.cells[3].text = action
elif len(row.cells) >= 3:
row.cells[2].text = f"STATUS (12 Aug 2026): {status}. {action}"
# Phase status annotations
phase_status = {
"Phase 0 — Evidence recovery": (
"STATUS (12 Aug 2026): Application/envelope code largely complete; "
"remaining is recorded CI green + Compose smoke evidence."
),
"Phase 1 — Correctness and shared state": (
"STATUS (12 Aug 2026): Core code landed (worker, Redis stores, S3 driver, /ready, shutdown). "
"Two-replica correctness evidence still open."
),
"Phase 2 — Operational control": (
"STATUS (12 Aug 2026): Metrics, catalog, OpenAPI gate, backup smoke, privacy/SoT docs landed. "
"Alert/restore/reconciliation drills still open."
),
"Phase 3 — Scale and assurance": (
"STATUS (12 Aug 2026): Cross-tenant suite, soak/chaos scripts, canary/key/pen-test runbooks, "
"S11S13 fixes landed. Staging drills and pen-test report still open. RLS deferred (ADR-002)."
),
"Phase 4 — Selective evolution": (
"STATUS (12 Aug 2026): Extraction gates documented (ADR-003). Default decision remains "
"do not extract microservices until G1G7 measurement passes."
),
}
paras = list(doc.paragraphs)
for idx, p in enumerate(paras):
for title, status in phase_status.items():
if p.text.startswith(title):
insert_after = idx
for j in range(idx, min(idx + 12, len(paras))):
if paras[j].text.startswith("Exit evidence"):
insert_after = j
break
if insert_after + 1 < len(paras) and not paras[insert_after + 1].text.strip():
set_runs_text(paras[insert_after + 1], status)
elif paras[insert_after].text.startswith("Exit evidence"):
set_runs_text(paras[insert_after], paras[insert_after].text + " " + status)
else:
set_runs_text(p, p.text + "" + status)
print("phase status:", title[:48])
# Claims not supportable — soft updates
for p in doc.paragraphs:
if p.text.startswith("CI-green or reproducibly deployable from the supplied package"):
set_runs_text(
p,
"CI-green or reproducibly deployable without recorded clean-runner evidence "
"(envelope restored in tree, but attestation still required).",
)
if p.text.startswith("Complete OpenAPI coverage or a stable external API contract"):
set_runs_text(
p,
"Perfect OpenAPI coverage (coverage gate exists; completeness still approximate "
"and must stay green in CI).",
)
if p.text.startswith("Guaranteed email, push, SMS, or realtime notification delivery through the modeled outbox"):
set_runs_text(
p,
"Guaranteed multi-channel notification delivery in production without worker soak "
"evidence (dispatcher exists in code).",
)
if p.text.startswith("Per-tenant container isolation or functioning tenant container orchestration"):
set_runs_text(
p,
"Per-tenant container isolation (explicitly disabled and out of production scope — ADR-001).",
)
# --- Append full addendum ---
doc.add_page_break()
doc.add_heading("Addendum A — Implementation status (12 August 2026)", level=1)
intro = doc.add_paragraph()
intro.add_run(
"This addendum updates Version 1.0 (9 August 2026) after execution of the production-readiness "
"program against the management monorepo. It does not replace the original evidence-based findings; "
"it records what has since been implemented in application code and in-repo runbooks, and what remains "
"evidence-only before a production-ready claim."
)
doc.add_heading("A.1 Verdict shift", level=2)
doc.add_paragraph(
"9 Aug 2026: capable beta / pre-scale; package incomplete; scale-critical paths process-local or missing."
)
doc.add_paragraph(
"12 Aug 2026: late beta / hardening stage. Phases 04 application code and runbooks are complete. "
"Investor “production ready” still requires recorded ops evidence (CI smoke, multi-replica proof, "
"DR/alert drills, soak, pen-test)."
)
doc.add_heading("A.2 Phase implementation summary", level=2)
phases = [
(
"Phase 0 — Security & envelope",
"Team/admin secret scrubbing; hashed invites; containers fail-closed (ADR-001); "
"redirect/payment allowlists; employee email uniqueness; hash-only reset/verify tokens; "
"env scrub; CI security:static + npm audit gate.",
),
(
"Phase 1 — Replica-safe shared state",
"Dedicated api-worker; outbox DB leases; Redis publish; Redis rate-limit & Carplace idempotency; "
"FILE_STORAGE_DRIVER local|s3; GET /ready; graceful shutdown; ENABLE_EMBEDDED_JOBS gated.",
),
(
"Phase 2 — Operational control",
"Structured logs + GET /metrics; planCatalog single source; OpenAPI coverage CI; "
"backup smoke + RPO/RTO checklist; privacy map; billing/notification source-of-truth docs.",
),
(
"Phase 3 — Scale & assurance",
"Cross-tenant isolation suite; S11 reviewToken scrub; S12 single-use payment consume; "
"S13 fresh 2FA TTL; soak/chaos scripts; canary/key-rotation/pen-test docs; RLS deferred (ADR-002).",
),
(
"Phase 4 — Selective evolution",
"No microservice extract. ADR-003 + extraction gates G1G7 + measurement templates. "
"Default: stay modular monolith + api-worker.",
),
]
for title, body in phases:
p = doc.add_paragraph()
run = p.add_run(title + ". ")
run.bold = True
p.add_run(body)
doc.add_heading("A.3 Architecture topology (current intent)", level=2)
doc.add_paragraph(
"Traefik → API × N → PostgreSQL; API and worker share Redis (rate limit, idempotency, pub/sub, locks); "
"api-worker owns outbox/cron; files via local disk or S3/MinIO. Ops endpoints: /health, /ready, /metrics."
)
doc.add_heading("A.4 Security findings S1S15", level=2)
doc.add_paragraph(
"Application findings S1S15 from the follow-on security review are fixed or documented in code "
"(including S9 proxy-trust documentation, S11S13). Independent penetration testing remains an open "
"Phase 3 evidence item. See docs/design/SECURITY_DESIGN_UPDATES.md and "
"docs/ops/RentalDriveGo_Production_Readiness_Plan.md §3."
)
doc.add_heading("A.5 Remaining evidence before production-ready claim", level=2)
for item in [
"Clean-runner: npm ci → generate → lint → type-check → test → build with CI green and SCA clear/exceptioned.",
"Compose smoke: two API replicas + worker + Postgres + Redis; booking/payment/notification/file correctness.",
"Phase 2 drills: staging alert fired once; dated restore meeting RPO/RTO; provider reconciliation sample; privacy owners.",
"Phase 3 drills: soak + failure injection; pen-test report under security-reports/; canary or rollback record; key-rotation drill.",
"Phase 4: fill measurement template; default decision remains do not extract.",
]:
doc.add_paragraph(item, style="List Bullet")
doc.add_heading("A.6 Design documentation pointers", level=2)
for item in [
"docs/design/README.md — index of production-readiness design updates",
"docs/design/PRODUCTION_READINESS_IMPLEMENTED.md — phase-by-phase landed work",
"docs/design/RUNTIME_AND_OPS_SURFACE.md — worker, metrics, env knobs",
"docs/ops/RentalDriveGo_Production_Readiness_Plan.md — living execution plan and exit checkboxes",
"docs/ADR-001-disable-per-tenant-containers.md",
"docs/ADR-002-defer-postgres-rls.md",
"docs/ADR-003-defer-service-extraction.md",
]:
doc.add_paragraph(item, style="List Bullet")
doc.add_paragraph(
"End of Addendum A. Original sections 117 remain the baseline diligence narrative from 9 August 2026, "
"as amended inline above where statuses were refreshed."
)
doc.save(str(src))
print("saved", src, "size", src.stat().st_size)
@@ -0,0 +1,501 @@
# RentalDriveGo Manual UI Test Plan
**Version:** v1.0
**Scope:** Phase 16 unified UI across homepage, Carplace/storefront, renter area, operator dashboard, platform admin, authentication pages, localization, theming, responsiveness, accessibility, and cross-app navigation.
**Repository baseline:** `RentalDriveGo_Phase16_Dashboard_Admin_Unified_v1.0.zip`
**Test type:** Manual functional UI, visual consistency, accessibility spot checks, responsive behavior, localization, and regression testing.
---
## 1. Objective
Verify that all public and authenticated RentalDriveGo applications use the Phase 16 homepage design system consistently and remain usable across English, French, Arabic RTL, light mode, dark mode, desktop, tablet, and mobile.
The testing must prove three things:
1. The new design is applied consistently across all major pages.
2. Existing user flows still work after the visual refactor.
3. Shared navbar, footer, dashboard shell, admin shell, forms, tables, cards, and page headings behave correctly across contexts.
---
## 2. Applications in scope
| Area | Application | Manual coverage required |
|---|---|---|
| Marketing website | `homepage` | Public homepage, navigation, language switcher, theme switcher, CTAs, demo flow, responsive layout, localized routes |
| Marketplace / Carplace | `storefront` | Public marketplace, explore/search, company pages, vehicle detail, booking form, footer pages, renter auth and renter account pages |
| Operator dashboard | `dashboard` | Public auth pages, dashboard shell, all operational routes, forms, tables, detail pages, modals, billing, settings, print surfaces |
| Platform admin | `admin` | Admin auth, admin shell, all admin management routes, company management, pricing, billing, containers, menu/config pages |
| API-connected behavior | `api` through UI | Confirm UI states for loading, success, validation errors, authorization errors, and network failures where available |
---
## 3. Test environments
Run manual UI testing in at least these environments:
| Environment | Required? | Notes |
|---|---:|---|
| Local development | Yes | Primary smoke and visual review environment |
| Staging / preview deployment | Yes | Required before production release |
| Production-like deployment | Recommended | Use production build settings, HTTPS, real domains, and realistic API URLs |
Do not approve release from local testing only. That would be optimism wearing a fake mustache.
---
## 4. Browser and device matrix
### Desktop browsers
| Browser | OS | Priority |
|---|---|---:|
| Chrome latest | Windows | P0 |
| Edge latest | Windows | P0 |
| Safari latest | macOS | P1 |
| Firefox latest | Windows or macOS | P1 |
### Mobile and tablet
| Device class | Width target | Priority |
|---|---:|---:|
| Small mobile | 360 x 800 | P0 |
| Large mobile | 390 x 844 | P0 |
| Tablet | 768 x 1024 | P1 |
| Small laptop | 1280 x 800 | P0 |
| Desktop | 1440 x 900 | P0 |
| Wide desktop | 1920 x 1080 | P1 |
Use real devices where possible for mobile navigation, keyboard behavior, touch target size, and scroll locking. Browser dev tools are useful, but they are not a phone, no matter how hard the viewport dropdown tries.
---
## 5. Test users and roles
Prepare these accounts before testing:
| Role | Needed for |
|---|---|
| Public visitor | Homepage, marketplace, footer pages, sign-in/create-account flows |
| Renter | Carplace renter dashboard, profile, saved companies, notifications |
| Operator / agency admin | Main dashboard, reservations, contracts, fleet, customers, team, billing, settings |
| Operator team member with limited permissions | Permission visibility and blocked routes |
| Platform super admin | Admin dashboard and platform management pages |
| Invalid/expired user | Auth error states, reset password, invite, verification edge cases |
---
## 6. Required test data
Create or seed the following before full UI testing:
| Data | Minimum requirement |
|---|---|
| Companies/agencies | At least 3 active companies with different logos, statuses, locales, and subscription states |
| Vehicles | At least 10 vehicles across available, reserved, maintenance, inactive, and incomplete states |
| Reservations | Draft, confirmed, active, overdue, cancelled, completed, online-requested |
| Contracts | At least one printable contract with customer, vehicle, dates, photos, and charges |
| Customers/renters | Customers with complete, partial, and missing information |
| Team members | Admin, manager, agent, inactive user, invited user |
| Reviews/complaints | Open, resolved, escalated, empty-state capable |
| Billing/subscription | Active plan, trial, past due, cancelled or expired state where possible |
| Notifications | Read, unread, action-required, empty state |
| Admin data | Companies, renters, platform admins, pricing plans, containers, audit logs, menu entries, site config |
| Localization content | English, French, Arabic strings for public and authenticated areas |
---
## 7. Global design acceptance criteria
Every page must satisfy these checks:
- Uses Phase 16 blue/orange token system.
- Blue is used for primary operational actions.
- Orange is reserved for conversion, emphasis, selected states, focus, or warnings.
- Page background, panels, cards, forms, tables, badges, and shadows match the homepage visual language.
- No old `FleetOS` branding appears anywhere.
- No mixed brand names such as `RentalCarDrive`, `RentalDrive`, or legacy storefront naming appear in visible UI unless intentionally part of old data.
- Light and dark mode both remain readable.
- Arabic uses true RTL layout, not just Arabic text shoved into left-to-right furniture.
- Public navbar and footer appear on standalone public/auth pages where expected.
- Embedded auth pages using `embedded=1` do not show duplicated navbar/footer.
- Mobile layouts avoid horizontal scrolling.
- Buttons, links, inputs, selects, and controls have visible hover, active, disabled, loading, and focus-visible states.
- Interactive controls are approximately 44 px minimum height where practical.
- Tables, modals, drawers, menus, and dropdowns remain usable on mobile.
- Loading, empty, success, validation-error, permission-error, and server-error states are visually consistent.
---
## 8. Global navigation and layout tests
| ID | Area | Steps | Expected result | Priority |
|---|---|---|---|---:|
| NAV-001 | Public navbar | Open homepage desktop. Inspect logo, nav links, language switcher, theme switcher, CTAs. | Navbar matches Phase 16 design, no clipping, active/hover states visible. | P0 |
| NAV-002 | Public mobile navbar | Open homepage at 360 px. Open and close mobile menu. | Menu opens cleanly, traps visual focus appropriately, scroll does not break, links are usable. | P0 |
| NAV-003 | Public footer | Open homepage, marketplace home, sign-in, create account. | Shared footer appears consistently on standalone public pages. | P0 |
| NAV-004 | Auth public layout | Open dashboard sign-in, sign-up, forgot password, reset password, verify email, onboarding. | Shared navbar/footer are visible unless `embedded=1` is present. | P0 |
| NAV-005 | Embedded auth layout | Add `?embedded=1` to auth URLs. | Navbar/footer are hidden and page does not show nested chrome. | P0 |
| NAV-006 | Dashboard shell | Log in as operator. Move through all dashboard routes. | Sidebar, topbar, active states, page width, and spacing remain consistent. | P0 |
| NAV-007 | Admin shell | Log in as platform admin. Move through all admin routes. | Admin sidebar/topbar match dashboard design system, with admin-specific nav. | P0 |
| NAV-008 | Mobile operational shell | Open dashboard/admin at mobile width. Open sidebar/menu. | Drawer works, content is not hidden behind topbar, no horizontal scrolling. | P0 |
| NAV-009 | Logout/account controls | Use user/account menu in dashboard/admin. | Menu opens, closes, keyboard works, logout/account actions are visible. | P1 |
| NAV-010 | Active route state | Visit every sidebar route. | Correct nav item uses active state and accessible current-page indication. | P1 |
---
## 9. Homepage manual tests
Test locales: `/en`, `/fr`, `/ar`.
| ID | Page/feature | Steps | Expected result | Priority |
|---|---|---|---|---:|
| HOME-001 | Homepage load | Open each locale homepage. | Hero, proof section, comparison, workflow, feature sections, pricing/CTA areas load without broken layout. | P0 |
| HOME-002 | Hero CTAs | Click primary demo CTA and secondary tour/explore CTA if present. | Correct dialog, route, or section opens. No dead links. | P0 |
| HOME-003 | Demo dialog/form | Open demo request flow. Submit empty form, invalid email, then valid data. | Validation messages are clear; valid submission shows success or expected API result. | P0 |
| HOME-004 | Language switcher | Switch EN to FR to AR and back. | User remains on matching route; copy changes; Arabic direction flips to RTL. | P0 |
| HOME-005 | Theme switcher | Switch light, dark, system. Refresh page. | Theme applies consistently and persists if persistence is supported. | P0 |
| HOME-006 | Header scroll behavior | Scroll through page. | Sticky/header behavior is stable and does not obscure content. | P1 |
| HOME-007 | Public content pages | Open localized slug pages available in the site. | Header/footer render; not-found and loading states are localized and styled. | P1 |
| HOME-008 | Component lab | Open component lab if enabled. | Components render without layout breakage and match tokens. | P2 |
| HOME-009 | SEO/basic metadata | Inspect tab title, favicon, social preview if available. | Branding is RentalDriveGo and no old brand appears. | P2 |
| HOME-010 | Public accessibility pass | Keyboard through homepage from top to bottom. | Focus order is logical, focus rings visible, menus/dialogs usable. | P0 |
---
## 10. Carplace / storefront public tests
| ID | Page/feature | Steps | Expected result | Priority |
|---|---|---|---|---:|
| CP-001 | Marketplace home | Open storefront public home. | Uses Phase 16 navbar/footer and visual tokens. | P0 |
| CP-002 | Explore page | Open `/explore`. Search/filter if controls exist. | Filters are usable; results grid/cards align with design; empty state works. | P0 |
| CP-003 | Company workspace page | Open company workspace creation/info page. | Public layout, CTA hierarchy, and form controls are consistent. | P0 |
| CP-004 | Company profile | Open `/explore/[slug]` for at least 3 companies. | Branding, vehicles, contact/booking areas load correctly. | P0 |
| CP-005 | Vehicle detail | Open `/explore/[slug]/vehicles/[id]`. | Vehicle photos/details/pricing/availability render without overflow. | P0 |
| CP-006 | Booking form | Submit booking with empty, invalid, and valid fields. | Validation, date handling, price display, and success/error states are clear. | P0 |
| CP-007 | Public sign-in | Open storefront sign-in. | Styling matches public auth design and routes correctly. | P0 |
| CP-008 | Policy pages | Open EN/FR/AR privacy and terms pages. | Footer/header are consistent; copy direction is correct; long content is readable. | P1 |
| CP-009 | Footer content pages | Open footer dynamic pages. | Content loads; no broken layout or missing footer. | P1 |
| CP-010 | Marketplace mobile | Test home, explore, company, vehicle detail at 360 px. | Cards stack properly, no horizontal scroll, booking CTA remains usable. | P0 |
---
## 11. Renter account tests
| ID | Page/feature | Steps | Expected result | Priority |
|---|---|---|---|---:|
| RENT-001 | Renter sign-in | Open renter sign-in and test invalid credentials. | Validation/error state is styled and readable. | P0 |
| RENT-002 | Renter sign-up | Create account with missing, invalid, and valid data. | Form states and success path are clear. | P0 |
| RENT-003 | Renter dashboard | Log in as renter and open dashboard. | Shell is consistent with marketplace/renter context; key data appears. | P0 |
| RENT-004 | Saved companies | Save and remove company where supported. | State updates visually and persists after refresh if expected. | P1 |
| RENT-005 | Renter profile | Update profile fields. | Field styling, validation, save state, and success message are consistent. | P0 |
| RENT-006 | Renter notifications | Open notifications with read/unread data. | Notification cards/states are readable in light/dark. | P1 |
| RENT-007 | Permission boundary | Try accessing renter pages while logged out. | Redirect or guard behavior is correct. | P0 |
---
## 12. Dashboard public authentication tests
| ID | Page/feature | Steps | Expected result | Priority |
|---|---|---|---|---:|
| AUTH-001 | Sign in | Open dashboard sign-in. Test empty, invalid, and valid credentials. | Public navbar/footer show; errors are clear; success redirects to dashboard. | P0 |
| AUTH-002 | Create account | Open sign-up/create account. Test required fields and valid registration. | Form matches design; validation and success path are clear. | P0 |
| AUTH-003 | Forgot password | Request reset with empty, invalid, unknown, and valid email. | Safe, clear messaging; no layout break. | P0 |
| AUTH-004 | Reset password | Open reset page with missing, invalid, expired, and valid token. | Correct error/success states and password field behavior. | P0 |
| AUTH-005 | Verify email | Open valid, invalid, and expired verification states. | Styled message and next action are clear. | P1 |
| AUTH-006 | Onboarding | Open onboarding flow as invited/new user. | Layout, steps, forms, and final redirect work. | P0 |
| AUTH-007 | Accept invite | Open invitation acceptance with valid/expired invite. | Correct messaging and route behavior. | P0 |
| AUTH-008 | Embedded auth | Open each auth URL with `?embedded=1`. | No duplicated navbar/footer; form remains centered and usable. | P0 |
---
## 13. Operator dashboard tests
### Dashboard overview
| ID | Page/feature | Steps | Expected result | Priority |
|---|---|---|---|---:|
| DASH-001 | Overview | Open dashboard home. | Hero/heading, metrics, quick actions, and recent activity match Phase 16 style. | P0 |
| DASH-002 | Empty state | Use company/account with minimal data. | Empty panels are intentional, helpful, and styled. | P1 |
| DASH-003 | Loading state | Throttle network and reload. | Skeleton/loading states do not cause layout jumps or unreadable text. | P1 |
### Reservations
| ID | Page/feature | Steps | Expected result | Priority |
|---|---|---|---|---:|
| RES-001 | Reservation list | Open reservations page. Search/filter/sort if available. | Table/cards are readable and responsive; statuses are clear. | P0 |
| RES-002 | New reservation | Create reservation with missing, invalid, and valid data. | Form fields, validation, customer/vehicle/date selection, and save states work. | P0 |
| RES-003 | Reservation detail | Open confirmed, active, cancelled, completed reservation. | Detail layout, actions, status badges, panels, and timeline are consistent. | P0 |
| RES-004 | Reservation photos | Upload/view/remove photos if supported. | Upload UI, previews, errors, and mobile behavior work. | P1 |
| RES-005 | Vehicle condition | Complete damage/condition inspection if supported. | Cards, controls, required fields, and summary are usable. | P1 |
### Contracts
| ID | Page/feature | Steps | Expected result | Priority |
|---|---|---|---|---:|
| CON-001 | Contract list | Open contracts page and inspect table/actions. | Table style, filters, and empty states match design. | P0 |
| CON-002 | Contract detail | Open contract detail. | Panels, customer/vehicle/rate sections, and actions align with design. | P0 |
| CON-003 | Print contract | Use print preview. | Printable contract remains document-like and does not include broken app chrome. | P0 |
### Fleet
| ID | Page/feature | Steps | Expected result | Priority |
|---|---|---|---|---:|
| FLEET-001 | Fleet list | Open fleet page with many vehicles. | Cards/table/grid are readable; status colors include text/icons. | P0 |
| FLEET-002 | Vehicle detail | Open vehicle detail for available, reserved, maintenance vehicles. | Detail panels, pricing, availability, and actions are consistent. | P0 |
| FLEET-003 | Vehicle calendar | Open calendar view if present. | Calendar cells, selected date, availability, and overflow are usable. | P1 |
| FLEET-004 | Vehicle pricing | Edit pricing rules if supported. | Inputs, tables, add/remove states, and save behavior are clear. | P1 |
### Customers and team
| ID | Page/feature | Steps | Expected result | Priority |
|---|---|---|---|---:|
| CUST-001 | Customers | Open customers page, search, inspect profiles/actions. | Table/cards, empty states, and row actions match design. | P0 |
| TEAM-001 | Team list | Open team page with active/invited/inactive members. | Roles/statuses are clear and not color-only. | P0 |
| TEAM-002 | Invite modal | Open invite member modal and submit invalid/valid data. | Modal, focus, validation, and success/error states work. | P0 |
| TEAM-003 | Edit member modal | Change role/permissions where allowed. | Permission matrix is readable and keyboard usable. | P1 |
| TEAM-004 | Limited permission user | Log in as limited member. | Restricted nav/actions are hidden or disabled consistently. | P0 |
### Online reservations, reviews, complaints, offers
| ID | Page/feature | Steps | Expected result | Priority |
|---|---|---|---|---:|
| OPS-001 | Online reservations | Open page with pending/accepted/rejected requests. | Statuses, action buttons, details, and empty states are consistent. | P0 |
| OPS-002 | Reviews | Open reviews page with data and empty state. | Review cards/table and response controls are readable. | P1 |
| OPS-003 | Complaints | Open complaints page with open/resolved items. | Priority/status treatment is accessible and consistent. | P1 |
| OPS-004 | Offers | Create/edit/disable an offer where supported. | Orange is used only for promotional emphasis; operational saves are blue. | P1 |
### Reports, notifications, billing, subscription, settings
| ID | Page/feature | Steps | Expected result | Priority |
|---|---|---|---|---:|
| MGT-001 | Reports | Open reports page with and without data. | Charts/tables/cards align and stay readable in dark mode. | P1 |
| MGT-002 | Notifications | Open notifications, mark read/unread if supported. | Read/unread states are visible and accessible. | P1 |
| MGT-003 | Billing | Open billing page with active/past-due/trial states. | Plan cards, invoices, payment CTAs, and warnings are clear. | P0 |
| MGT-004 | Subscription | Open subscription page and test plan state display. | Current plan, upgrade/downgrade/cancel paths are visually clear. | P0 |
| MGT-005 | Settings | Open all settings sections. | Sections are organized, forms are consistent, and save states work. | P0 |
| MGT-006 | Dashboard route refresh | Refresh every dashboard page directly. | No route loses layout, auth, locale, or styling after refresh. | P0 |
---
## 14. Platform admin tests
### Admin authentication
| ID | Page/feature | Steps | Expected result | Priority |
|---|---|---|---|---:|
| AD-AUTH-001 | Admin login | Open admin login and test invalid/valid credentials. | Page matches design; errors are readable; success redirects to admin dashboard. | P0 |
| AD-AUTH-002 | Admin forgot/reset password | Test forgot and reset flows. | Validation and success/error states are clear. | P1 |
| AD-AUTH-003 | Auth redirect | Open admin root and auth redirect routes. | Correct redirect behavior for logged-in/logged-out users. | P0 |
### Admin dashboard and management pages
| ID | Page/feature | Steps | Expected result | Priority |
|---|---|---|---|---:|
| AD-001 | Admin overview | Open admin dashboard home. | Hero/metrics/actions match Phase 16 style. | P0 |
| AD-002 | Companies list | Open companies page with many companies. Search/filter/sort where available. | Table/cards, statuses, and actions are consistent. | P0 |
| AD-003 | Company detail | Open company detail for active, trial, suspended/past-due companies. | Panels, admin actions, metadata, and billing/status sections are clear. | P0 |
| AD-004 | Renters | Open renters page. Inspect search/filter/detail actions. | Layout and status treatment are consistent. | P1 |
| AD-005 | Admin users | Open admin users page. Create/edit/disable if supported. | Forms/modals/buttons use shared design and permissions are clear. | P0 |
| AD-006 | Billing | Open admin billing page. | Invoices/subscriptions/payment states are readable and consistent. | P0 |
| AD-007 | Pricing | Open pricing management. Add/edit plans where supported. | Form fields, conversion accents, and save buttons follow token rules. | P0 |
| AD-008 | Containers | Open containers page. | Container status, controls, tables, and warnings are clear. | P1 |
| AD-009 | Audit logs | Open audit logs with dense data. Search/filter if available. | Dense table remains readable, scrollable, and accessible. | P1 |
| AD-010 | Notifications | Open admin notifications page. | Creation/list/status/empty states are consistent. | P1 |
| AD-011 | Menu management | Open menu management page. Edit/reorder/toggle items if supported. | Drag/drop or controls work and are usable on mobile. | P1 |
| AD-012 | Site config | Open site configuration page. Edit fields and save. | Field styling, section hierarchy, validation, and success states work. | P0 |
| AD-013 | Admin mobile | Test all admin pages at 360 px. | Drawer, tables, modals, and forms remain usable without horizontal page scroll. | P0 |
| AD-014 | Admin route refresh | Refresh every admin page directly. | No page loses shell, auth state, or styling. | P0 |
---
## 15. Localization and RTL tests
Run these checks across homepage, Carplace, dashboard auth, dashboard, and admin where language switching is available.
| ID | Scenario | Steps | Expected result | Priority |
|---|---|---|---|---:|
| LOC-001 | English | Use English UI. | Copy fits controls; dates/numbers are readable. | P0 |
| LOC-002 | French | Switch to French. | Longer French labels do not overflow buttons, cards, tables, or nav. | P0 |
| LOC-003 | Arabic RTL | Switch to Arabic. | Layout direction flips; sidebar, nav, icons, paddings, and text alignment are RTL-aware. | P0 |
| LOC-004 | Mixed content | View emails, names, phone numbers, prices, and vehicle names in Arabic UI. | LTR data remains readable inside RTL layout. | P1 |
| LOC-005 | Mobile Arabic | Test public and authenticated mobile pages in Arabic. | Drawers open from the correct side; content does not clip. | P0 |
| LOC-006 | Missing translation | Force/identify missing string where possible. | No raw translation keys leak into UI. | P1 |
---
## 16. Theme tests
| ID | Scenario | Steps | Expected result | Priority |
|---|---|---|---|---:|
| THEME-001 | Light mode | Switch to light mode and inspect all major routes. | Contrast, shadows, borders, and panels match Phase 16. | P0 |
| THEME-002 | Dark mode | Switch to dark mode and inspect all major routes. | Text, inputs, badges, tables, dialogs, and menus remain readable. | P0 |
| THEME-003 | System mode | Set system theme and change OS preference. | App follows system setting if supported. | P1 |
| THEME-004 | Theme persistence | Change theme, refresh, open new tab. | Theme persists consistently where expected. | P1 |
| THEME-005 | Dark mode modals/dropdowns | Open menus, dialogs, date pickers, selects. | Overlays do not appear with mismatched light/dark styling. | P0 |
---
## 17. Responsive testing checklist
For each major route group, test 360 px, 390 px, 768 px, 1280 px, and 1440 px widths.
| ID | Check | Expected result |
|---|---|---|
| RESP-001 | No horizontal page scroll | Content fits viewport except intentional internal table scrolling. |
| RESP-002 | Sidebar/drawer behavior | Desktop sidebar becomes usable mobile drawer. |
| RESP-003 | Topbar behavior | Topbar does not cover content or controls. |
| RESP-004 | Tables | Tables either stack, scroll inside a panel, or remain readable. |
| RESP-005 | Forms | Labels, inputs, helper text, errors, and buttons fit. |
| RESP-006 | Modals | Dialogs fit viewport and scroll internally if needed. |
| RESP-007 | Cards/grids | Cards stack with reasonable spacing. |
| RESP-008 | Footer | Footer columns collapse cleanly. |
| RESP-009 | CTAs | Primary actions remain reachable without awkward scrolling. |
| RESP-010 | Touch targets | Controls are large enough for touch interaction. |
---
## 18. Accessibility spot-check plan
This is not a full WCAG audit, but these checks are mandatory before release.
| ID | Check | Steps | Expected result | Priority |
|---|---|---|---|---:|
| A11Y-001 | Keyboard navigation | Use Tab/Shift+Tab through every major page. | Focus order is logical and visible. | P0 |
| A11Y-002 | Skip/repeated navigation | Test repeated nav areas. | User can reach main content without excessive tabbing where supported. | P1 |
| A11Y-003 | Menus/dialogs | Open and close nav menus, dropdowns, modals. | Focus enters, remains usable, and returns predictably. | P0 |
| A11Y-004 | Forms | Trigger validation errors. | Errors are associated with fields and readable. | P0 |
| A11Y-005 | Color contrast | Inspect text, badges, disabled states, links in light/dark. | Text and states are legible. | P0 |
| A11Y-006 | Color-only status | Inspect statuses. | Status is not communicated by color alone. | P0 |
| A11Y-007 | Zoom | Test browser zoom at 200%. | Content remains usable without major clipping. | P1 |
| A11Y-008 | Reduced motion | Enable reduced motion. | No essential interaction depends on animation. | P1 |
| A11Y-009 | Forced colors | Test high-contrast/forced-colors mode if available. | Focus, text, and controls remain usable. | P1 |
| A11Y-010 | Screen reader smoke | Use VoiceOver/NVDA on key pages. | Headings, buttons, form labels, links, and menus are understandable. | P1 |
---
## 19. Error, empty, loading, and permission states
Verify these states across homepage forms, Carplace booking, dashboard pages, and admin pages.
| ID | State | How to test | Expected result | Priority |
|---|---|---|---|---:|
| STATE-001 | Loading | Throttle network or reload API-heavy pages. | Loading UI uses shared styling and avoids layout collapse. | P0 |
| STATE-002 | Empty data | Use account with no records. | Empty state explains what to do next and uses matching card/panel style. | P0 |
| STATE-003 | Validation error | Submit required forms empty or invalid. | Error messages are clear, close to fields, and accessible. | P0 |
| STATE-004 | Server error | Simulate 500/API failure where possible. | Error message is readable and not a raw stack trace. | P0 |
| STATE-005 | Unauthorized | Access dashboard/admin while logged out. | Redirect or blocked state is correct. | P0 |
| STATE-006 | Forbidden | Access admin-only route with non-admin user. | User sees safe forbidden state or redirect. | P0 |
| STATE-007 | Not found | Open invalid public, dashboard, storefront, and admin URLs. | Styled not-found behavior, no broken shell. | P1 |
| STATE-008 | Offline | Disable network after page load and perform action. | User receives clear failure feedback. | P1 |
---
## 20. Cross-app route and brand consistency tests
| ID | Check | Steps | Expected result | Priority |
|---|---|---|---|---:|
| XAPP-001 | Homepage to dashboard auth | Use homepage CTA that leads to sign-in/create account. | Correct app opens with public navbar/footer and same design language. | P0 |
| XAPP-002 | Homepage to Carplace | Use marketplace/explore links if present. | Carplace opens with shared public design. | P0 |
| XAPP-003 | Dashboard to public site | Use logo/help/home links where present. | Correct destination; no old domain/path if not intended. | P1 |
| XAPP-004 | Admin to company detail | From admin companies, open company detail. | Route works and design remains consistent. | P0 |
| XAPP-005 | Branding | Search visible UI for old names. | Only `RentalDriveGo` and intentional `Carplace` marketplace naming appear. | P0 |
| XAPP-006 | Favicon/logo assets | Inspect browser tab and logos across apps. | Assets are not broken, stretched, or legacy-branded. | P1 |
---
## 21. Manual regression checklist before release
Run this checklist after every UI/design change:
- Homepage loads in EN, FR, AR.
- Public navbar works on desktop and mobile.
- Public footer appears on homepage, Carplace, sign-in, create-account, forgot-password, reset-password, verify-email, onboarding.
- Embedded auth hides navbar/footer with `embedded=1`.
- Dashboard login works.
- Dashboard overview loads.
- Dashboard reservations list, new reservation, and reservation detail load.
- Dashboard contracts list, contract detail, and print preview work.
- Dashboard fleet list and vehicle detail load.
- Dashboard customers, team, billing, subscription, reports, notifications, settings load.
- Admin login works.
- Admin overview, companies, company detail, renters, admin users, billing, pricing, containers, audit logs, notifications, menu management, site config load.
- Carplace home, explore, company page, vehicle page, booking form, renter dashboard load.
- Light/dark mode checked on at least homepage, dashboard overview, admin companies, Carplace explore.
- Arabic RTL checked on at least homepage, dashboard overview, admin companies, Carplace explore.
- Mobile checked on at least homepage, dashboard overview, admin companies, Carplace vehicle detail.
- Keyboard navigation checked on homepage, dashboard sign-in, reservation form, admin companies.
- No visible old branding.
- No horizontal scroll at mobile width.
- No raw errors, stack traces, broken images, or unstyled default controls.
---
## 22. Defect reporting format
Every UI defect should include:
```text
Title:
Environment:
App:
Route:
Role/account:
Browser/device:
Language:
Theme:
Viewport size:
Steps to reproduce:
Expected result:
Actual result:
Severity:
Screenshot/video:
Console errors:
Network/API errors:
Regression? yes/no:
```
Severity definitions:
| Severity | Meaning | Example |
|---|---|---|
| S0 Blocker | User cannot complete a core flow | Cannot sign in, dashboard crashes, booking cannot submit |
| S1 Critical | Major page/flow broken or misleading | Wrong layout on mobile, save action hidden, admin table unusable |
| S2 Major | Important UI defect with workaround | Modal overflow, dark mode unreadable badge, RTL alignment bug |
| S3 Minor | Cosmetic or low-impact issue | Slight spacing mismatch, non-critical hover inconsistency |
| S4 Trivial | Polish only | Tiny copy or icon alignment issue |
---
## 23. Release exit criteria
Manual UI testing may be accepted when:
- All P0 tests pass.
- No S0 or S1 defects remain open.
- S2 defects have approved fixes or explicit release acceptance.
- Homepage, Carplace, dashboard, and admin have been tested in light, dark, and Arabic RTL.
- Mobile smoke tests pass at 360 px width.
- Keyboard navigation smoke tests pass for public auth, dashboard, and admin.
- Public navbar/footer behavior is verified on all standalone public/auth pages.
- Embedded auth behavior is verified with `embedded=1`.
- Visual consistency review confirms Phase 16 design language across public and authenticated pages.
- Stakeholder signs off with screenshots or recordings attached for the main flows.
---
## 24. Suggested execution order
1. Smoke test all apps and authentication first.
2. Test homepage and public navigation/footer.
3. Test Carplace public marketplace and renter flows.
4. Test operator dashboard routes.
5. Test platform admin routes.
6. Run localization and RTL passes.
7. Run dark mode pass.
8. Run mobile pass.
9. Run accessibility spot checks.
10. Re-test fixed defects and complete release checklist.
This order catches structural failures early. Testing every button color before confirming login works.
@@ -0,0 +1,492 @@
# 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 04 **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 04 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
```mermaid
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:
1. Install, generate, lint, type-check, test, and build from a clean checkout
2. Deploy a known topology (API, frontends, PostgreSQL, Redis, storage, worker)
3. Run **two API replicas** without duplicate bookings, duplicate cron side effects, or missing files
4. Deliver notifications that were written to the outbox (email + realtime) without multi-replica double-send
5. Detect failure, restore data within agreed RPO/RTO, and show no unresolved critical/high dependency vulnerabilities outside a dated exception
6. Ship with Critical/High application-security findings closed (§3)
7. §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 | Stripe / PayPal / AmanPay; webhook signature verify; `WebhookEvent` |
| 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) |
```mermaid
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 | Stripe / PayPal / AmanPay paths |
| 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**
1. **S1:** Explicit safe select/presenter for team APIs — never return hashes/tokens
2. **S3:** Hash invite tokens at rest; remove raw dual-match after migration (**S10**)
3. **S8:** Expand admin presenter denylist
4. **S2:** Remove/disable `containerService` + admin containers UI from GA
5. **S14/S15:** Already fixed — keep in CI
6. Re-run and clear dependency critical/high
**P1 — before multi-user GA**
7. **S4:** `@@unique([companyId, email])` + fail-closed login
8. **S5:** Allowlist relative same-origin post-login redirects
9. **S6:** Apply payment redirect allowlist to authenticated checkout/subscription
10. **S7:** Slugify/validate admin slug updates
11. **S9:** Document proxy trust; never enable trusted forwards without edge scrubbing
12. **S11S13:** 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
1. **Evidence over claims** — Phase exit criteria must be demonstrable.
2. **Close Critical/High security before scale work** — Envelope restore is largely done; app-sec P0 is now the front of the queue.
3. **Shared state before more replicas** — Redis/DB/object storage before scaling API.
4. **One worker plane** — Notifications and scheduled jobs must not run identically on every API replica.
5. **Disable incomplete privileged features** — Especially Docker-socket container management.
6. **One commercial source of truth** — Marketing, checkout, and enforcement share typed entitlements.
7. **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)
- [x] `turbo.json` + `tsconfig.base.json` + `scripts/` present in tree
- [x] Compose + Dockerfiles present in tree
- [x] `.gitignore` + `security:static` present
- [x] Team API no longer returns password hashes / reset tokens; invite tokens hashed at rest (**S1/S3**)
- [x] Admin presenter scrubs reset/verification secrets (**S8**)
- [x] Container orchestration disabled + ADR (`docs/ADR-001-disable-per-tenant-containers.md`) (**S2**)
- [x] Login redirect allowlisted; authenticated payment return URLs allowlisted; admin slug validated; employee `(companyId, email)` unique (**S4S7**)
- [x] Env example/dev templates scrubbed of real-looking secrets; `security:static` passes locally
- [x] 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; `/health` ok (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
- [x] Worker entrypoint + outbox lease + Redis publish implemented
- [x] Redis rate limits + durable Carplace idempotency implemented
- [x] Cron externalized with leader lock; `/ready` + graceful shutdown implemented
- [x] 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
- [x] Plan catalog single-sourced with tests
- [x] Privacy data map drafted (assign legal/ops owners before GA)
- [x] OpenAPI coverage script in CI
- [x] 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
- [x] 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
- [x] App-level isolation suite started; RLS deferred per ADR-002
- [x] S11 review tokens not returned in reservation/review JSON
- [x] S12 single-use public payment token consume
- [x] S13 fresh admin 2FA TTL
### Phase 4 (months 36) — 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 G1G7 + 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 G1G7 | — |
#### Phase 4 exit criteria (for *deciding*, not for “having microservices”)
- [x] Extraction gates and candidates documented
- [x] 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.21.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 |
| R9R14 | Privacy, catalog, OpenAPI, legacy overlap, FE drift, flaky tests | **Partial / later** (catalog, OpenAPI gate, privacy map, SoT docs) | Phase 23 |
| **S1S8** | App security Critical/High | **Fixed in code** (S9 documented) | §3 P0/P1 |
| **S10S15** | Medium app security | **Fixed in code** (S11S13 included) | Hardening + Phase 3 |
| **S16S21** | 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)**
1. Fix team API secret leakage (**S1**); hash invite tokens (**S3**); scrub admin presenter (**S8**)
2. Remove/disable `containerService` + admin containers UI (**S2**)
3. Allowlist login redirect (**S5**) and authenticated payment return URLs (**S6**); slugify admin slugs (**S7**); employee email uniqueness (**S4**)
4. Record clean-runner CI green + production SCA clear/critical policy
5. Record Compose smoke evidence
**Then Phase 1**
6. Move outbox + cron to dedicated worker with leases; add Redis publish for realtime
7. Redis rate-limit store; durable Carplace idempotency
8. Object storage (MinIO/S3); `/ready` + graceful shutdown
**Then Phase 23**
9. Observability, restore drill, catalog convergence, privacy map (Phase 2 evidence still open)
10. Soak/failure drills, cross-tenant CI green, pen-test report, canary + key-rotation evidence (Phase 3)
**Then Phase 4 (only if measured)**
11. Fill `docs/ops/phase4-measurement-template.md` from Phase 3 soak data; default decision remains **do not extract** (ADR-003)
---
## 13. Diligence checkpoint (definition of done)
1. Clean checkout builds/tests on documented Node/npm with no local repair
2. Production SCA has no unresolved critical/high outside formal exception
3. Two API replicas pass booking/payment/job/notification/file correctness with shared coordination
4. Authoritative API contract + auth test matrix
5. Restore exercise meets RPO/RTO
6. Logs/metrics/alerts/runbooks demonstrated in a failure exercise
7. Privacy controls cover classification, encryption plan, retention, privileged reads/exports, incident response
8. Plan catalog / checkout / entitlements single-sourced
9. Independent security testing closes critical/high or records explicit acceptance
10. **§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.*
+7
View File
@@ -0,0 +1,7 @@
{
"message": "request to https://registry.npmjs.org/-/npm/v1/security/advisories/bulk failed, reason: unable to get local issuer certificate",
"error": {
"summary": "",
"detail": ""
}
}
+47
View File
@@ -0,0 +1,47 @@
# Canary deploy & rollback (Phase 3)
**Status:** Ops runbook — evidence required before claiming production ready
**Date:** 2026-08-12
## Goal
Ship a new API image to a small fraction of traffic, watch `/metrics` + `/ready`, and roll back within minutes if error rate or latency regresses.
## Preconditions
- Release image tagged and pulled (`scripts/docker-prod-deploy.sh` path)
- Metrics scraped; alerts for 5xx and readiness exist (Phase 2)
- Previous known-good image tag recorded
## Canary procedure
1. Record baseline: `p95` latency, 5xx rate, `notification_outbox_pending` for 15 minutes.
2. Deploy new tag to **one** API replica (or Traefik weight 10% if configured).
3. Watch 1530 minutes under real or soak traffic (`npm run test:soak`).
4. **Promote** only if error rate and p95 within agreed budget vs baseline.
5. **Rollback** immediately if:
- `/ready` flapping
- 5xx rate > 2× baseline
- Outbox pending unbounded growth
- Payment/webhook failures spike
## Rollback
```bash
# Set IMAGE_TAG to last known-good and redeploy
export IMAGE_TAG=<previous-good>
bash scripts/docker-prod-deploy.sh
```
Verify `/health`, `/ready`, and a booking smoke after rollback.
## Evidence to attach
| Field | Value |
|-------|-------|
| Date | |
| Canary tag | |
| Previous tag | |
| Decision | promote / rollback |
| Metrics summary | |
| Operator | |
+45
View File
@@ -0,0 +1,45 @@
# Key rotation drill (Phase 3)
**Status:** Ops runbook
**Date:** 2026-08-12
## Secrets in scope
| Secret | Used by | Rotation impact |
|--------|---------|-----------------|
| `JWT_SECRET` | API token signing | Invalidates existing employee/admin/renter sessions |
| Company API keys | Partner/Carplace integrations | Per-company reissue via admin/API |
| Payment provider webhooks | Stripe/PayPal/AmanPay | Update dashboard + env together |
| DB / Redis passwords | Compose / managed services | Coordinated restart |
| Object storage keys | S3/MinIO driver | Dual-key period preferred |
## JWT_SECRET drill (staging)
1. Announce maintenance window (sessions will drop).
2. Generate a new high-entropy secret; store in secret manager **before** env change.
3. Update staging `.env` / secret store; rolling-restart API + worker.
4. Confirm:
- Old bearer tokens return 401
- Fresh login issues valid tokens
- `/ready` stays healthy
5. Record time-to-rotate and any customer-facing impact.
## Company API key drill
1. Create a replacement key for a test company.
2. Switch the client to the new key.
3. Revoke/disable the old key.
4. Confirm old key fails; new key succeeds.
## Evidence
| Field | Value |
|-------|-------|
| Date | |
| Environment | staging |
| Secrets rotated | |
| Duration | |
| Issues | |
| Operator | |
Do **not** rotate production secrets without dual-control and a tested rollback path for webhook signing secrets.
+46
View File
@@ -0,0 +1,46 @@
# Phase 4 — Extraction gates & candidates
**Status:** Planning / measurement only (ADR-003)
**Date:** 2026-08-12
**Prerequisite:** Phase 3 exit evidence (soak, pen-test, canary, key rotation) should be underway before any extraction design review.
## Rule
Extract a bounded context into its **own deployable** only if measurement shows the monolith cannot meet SLOs or blast-radius requirements **and** ownership is staffed. Otherwise scale the existing `api` + `api-worker` topology.
## Candidates (from diligence / readiness plan)
| Candidate | Today | Extract when… | Prefer first |
|-----------|--------|---------------|--------------|
| **Notification worker** | Already a separate **process** (`apps/api` worker / Compose `api-worker`) sharing the API package | Worker CPU/memory or release cadence is blocked by API deploys; or need multi-language worker fleet with independent scaling | Scale `api-worker` replicas; keep shared package |
| **Payments / webhooks** | Modules inside API (`payments`, `billing`, `subscriptions`, webhook routes) | Webhook lag or payment CPU dominates API p95; or PCI/provider isolation requires a smaller trust boundary | Dedicated webhook ingress + queue inside monolith; idempotent consumers |
| **Media processing** | Upload + storage drivers in API; local/S3 | Image/PDF processing blocks request threads or storage I/O saturates API | Async job on `api-worker`; object storage (MinIO/S3) already preferred |
## Hard gates (all required per candidate)
Copy to an evidence note before any extract ADR:
| # | Gate | Evidence |
|---|------|----------|
| G1 | Phase 3 soak baseline exists | Dated `test:soak` / k6 + `/metrics` snapshot |
| G2 | Bottleneck attributed | Profile or metrics: p95, queue lag, CPU by route/job — not anecdote |
| G3 | Blast radius / compliance need | Written threat or compliance reason (e.g. webhook storm, media malware scan) |
| G4 | Ownership | Named eng + on-call for the new service |
| G5 | Contract | OpenAPI or event schema + consumer contract tests |
| G6 | Dual-run | Shadow or dual-publish ≥ 1 release cycle with zero silent divergence |
| G7 | Rollback | Documented cutback to monolith path in &lt; 30 minutes |
If any gate fails → **do not extract**; file an in-monolith improvement instead.
## In-monolith improvements (do these before/instead of extract)
1. Keep payment/webhook handlers thin; push side effects through outbox/jobs.
2. Run N `api-worker` replicas with DB leases (already Phase 1).
3. Move heavy media work to worker jobs + S3; never resize on the request path.
4. Publish domain events internally (same DB outbox) so a future service can subscribe without a big-bang rewrite.
## Explicit non-goals for Phase 4
- Rewriting the monorepo into many services
- Separate databases per module “for purity”
- Per-tenant container orchestration (still ADR-001)
+54
View File
@@ -0,0 +1,54 @@
# Phase 4 — Extraction measurement template
Fill one copy per candidate (`notifications` | `payments-webhooks` | `media`). Store dated copies under `docs/ops/evidence/` (create as needed). Do not extract without G1G7.
## Candidate
- Name:
- Date:
- Author:
- Environment measured: staging / production
## G1 — Soak baseline
- Command / duration:
- Error rate / p95:
- Link or paste `/metrics` summary:
## G2 — Bottleneck
- Symptom (SLO miss):
- Attribution (route, job, DB, Redis, storage):
- Supporting graph or log sample:
## G3 — Blast radius / compliance
- Why a separate deployable (not just more workers):
## G4 — Ownership
- Eng owner:
- On-call:
- Budget for dual-run:
## G5 — Contract
- Sync API or async events:
- Contract test location (planned):
## G6 — Dual-run plan
- Shadow period:
- Diff/alert strategy:
## G7 — Rollback
- Cutback steps:
- Drill date (must be practiced once):
## Decision
- [ ] **Do not extract** — pursue in-monolith item: _______________
- [ ] **Extract** — write service ADR and implementation plan (separate change)
Sign-off: _______________ date: _______________
+41
View File
@@ -0,0 +1,41 @@
# Module boundaries for possible Phase 4 extracts
**Purpose:** Document current monolith seams so a future extract does not require archaeology. Not a license to split today (ADR-003).
## Notification / jobs
| Piece | Path |
|-------|------|
| Worker entry | `apps/api/src/workers/index.ts` |
| Job loop | `apps/api/src/workers/jobs.ts` |
| Outbox processing | `apps/api/src/services/notificationService` (and related) |
| Compose | `api-worker` in `docker-compose*.yml` |
**Seam:** DB outbox + Redis pub for IN_APP. Extract later = move worker package + keep outbox schema shared or via events.
## Payments / webhooks
| Piece | Path |
|-------|------|
| Webhook router | `apps/api/src/modules/webhooks/` |
| Payments | `apps/api/src/modules/payments/` |
| Billing | `apps/api/src/modules/billing/` |
| Subscriptions | `apps/api/src/modules/subscriptions/` |
**Seam:** Provider signature verify → idempotent intent/invoice update → outbox side effects. Extract later = webhook ingress service writing to the same billing tables or a command queue.
## Media
| Piece | Path |
|-------|------|
| Storage drivers | `apps/api/src/lib/storage` |
| Upload HTTP | `apps/api/src/http/upload` (and module callers) |
| Compose MinIO | `minio` profile in dev compose |
**Seam:** API accepts upload → stores object key → optional worker job for virus scan / derivatives. Extract later = media service owning bucket + callback to API.
## Shared that must not fork casually
- Prisma schema / migrations (`packages/database`)
- Auth tokens / tenant `companyId` middleware
- Plan catalog (`packages/types` planCatalog)
+22
View File
@@ -0,0 +1,22 @@
# Proxy trust & forwarded headers (S9)
**Default:** `TRUSTED_FORWARD_HEADERS` is unset/false. The API **deletes** client-supplied forwarding headers (`x-forwarded-for`, `x-real-ip`, etc.) so rate limits and logs cannot be spoofed.
## When to set `TRUSTED_FORWARD_HEADERS=true`
Only when:
1. Traefik / nginx / cloud LB is the **only** ingress to the API.
2. That edge **overwrites** (not appends blindly from the client) trusted client IP / proto headers.
3. The API `trust proxy` hop count matches the real proxy chain.
If any of those are false, leave the flag off.
## Operator checklist
- [ ] Edge strips spoofed `X-Forwarded-*` from the public internet
- [ ] Documented hop count for Express `trust proxy`
- [ ] Rate-limit keys still meaningful after deploy
- [ ] Change reviewed in incident/runbook (misconfiguration → IP allowlist / rate-limit bypass)
Related: `apps/api/src/middleware/forwardedHeaders.ts`
+18 -1
View File
@@ -35,6 +35,7 @@ There is no separate frontend app for a white-label company public site in this
- Company-scoped vehicles, customers, reservations, offers, payments, complaints, and notifications
- API tenant isolation through employee auth plus `companyId` scoping
- Cross-tenant negative regression suite (`cross-tenant-isolation` integration tests)
- Company profile, brand, contract settings, insurance policies, pricing rules, and accounting settings
- Public API key generation/regeneration for company integrations
@@ -76,10 +77,12 @@ There is no separate frontend app for a white-label company public site in this
- SaaS trial and subscription lifecycle
- Subscription status handling including `TRIALING`, `ACTIVE`, `PAYMENT_PENDING`, `PAST_DUE`, `SUSPENDED`, `CANCELLED`, `EXPIRED`, `PAUSED`, `UNPAID`
- Plan pricing loaded from DB or fallback config
- Plan pricing and entitlements from a single catalog (`packages/types` `planCatalog`) with DB overrides where configured
- Homepage marketing prices derived from the same catalog
- AmanPay and PayPal provider support for SaaS subscription checkout
- Subscription invoice history
- Platform billing accounts, billing invoices, billing events, refunds, credit notes, and tax records
- Canonical billing models preferred over legacy subscription-invoice-only writes (see `docs/BILLING_NOTIFICATION_SOURCE_OF_TRUTH.md`)
### Rental payments
@@ -97,6 +100,16 @@ There is no separate frontend app for a white-label company public site in this
- In-app notifications
- Notification templates and per-channel preferences
- Notification history for company users
- Durable notification outbox with leased dispatch on `api-worker` (Redis publish for IN_APP)
- Canonical event/recipient/delivery models preferred over legacy flat notifications
### Ops and production-readiness baselines
- `GET /ready` and `GET /metrics` on the API
- Shared Redis rate limiting and Carplace idempotency
- Optional S3/MinIO file storage driver
- OpenAPI coverage CI gate
- Design summary: `docs/design/`
### Reservation-adjacent advanced operations
@@ -112,6 +125,7 @@ There is no separate frontend app for a white-label company public site in this
### Admin app
- Admin login and password reset
- Admin 2FA enrollment; fresh 2FA required for money / high-privilege mutations (TTL)
- Admin dashboard
- Company management
- Renter management
@@ -121,6 +135,7 @@ There is no separate frontend app for a white-label company public site in this
- Pricing configuration and promotions
- Notification review
- Carplace/site config management
- Per-tenant Docker/container orchestration UI is **out of scope** (ADR-001)
## Present But Not Active Product Features
@@ -135,6 +150,8 @@ These exist partially in code or schema, but they are not active end-user produc
- Admin impersonation through Clerk sessions
- Multi-currency SaaS checkout beyond `MAD`
- Separate white-label company-site frontend app
- Per-tenant Docker container orchestration (explicitly disabled — ADR-001)
- Microservice extraction of payments/webhooks/media (deferred — ADR-003)
## Explicitly Out Of Scope For Current Docs
+12 -3
View File
@@ -13,19 +13,24 @@ Source of truth for the runtime wiring:
The API is an Express application mounted under `/api/v1` with a few non-versioned utility endpoints:
- `GET /health` returns process health.
- `GET /health` returns process liveness.
- `GET /ready` returns readiness (PostgreSQL, Redis, storage). Returns `503` when a dependency fails.
- `GET /metrics` returns Prometheus-style ops metrics (request counts/latency, outbox gauges).
- `GET /docs` serves Swagger UI.
- `GET /api/v1/openapi.json` returns the generated OpenAPI document.
- `/storage/*` serves uploaded assets such as logos, hero images, vehicle photos, and reservation/customer documents.
A dedicated **`api-worker`** process runs notification outbox dispatch and scheduled jobs (Phase 1). The API process must not embed those jobs when multiple replicas are used (`ENABLE_EMBEDDED_JOBS` stays false). See `docs/design/RUNTIME_AND_OPS_SURFACE.md`.
The app boot sequence in `apps/api/src/app.ts` is intentionally ordered:
1. CORS is applied first.
2. storage guards are applied before static serving so private customer license files are never anonymously retrievable.
3. Swagger UI is mounted before Helmet so the UI assets are not blocked by CSP.
4. webhook routes that require raw payload handling are mounted before `express.json()`.
5. Helmet, request logging, JSON parsing, and the module routers are mounted.
5. Helmet, request logging (structured JSON), metrics middleware, JSON parsing, and the module routers are mounted.
6. the centralized error middleware converts validation, Prisma, and application errors into consistent JSON responses.
7. Spoofable forwarded headers are scrubbed unless `TRUSTED_FORWARD_HEADERS=true` (see `docs/ops/proxy-trust.md`).
## Request Lifecycle
@@ -382,6 +387,8 @@ Successful route handlers usually return:
Common deviations:
- `/health`
- `/ready`
- `/metrics`
- `/api/v1/docs`
- webhook receipt payloads
- file downloads such as admin invoice PDFs
@@ -393,4 +400,6 @@ There are two documentation layers in the codebase:
1. runtime API docs from Swagger/OpenAPI at `/docs` and `/api/v1/openapi.json`
2. this markdown document, which explains design decisions, route grouping, and request flow
The OpenAPI file is useful for request/response contracts, but it is not yet a perfect mirror of every newer route. This document should be updated together with `app.ts`, route files, and the OpenAPI generator whenever route groups change.
Production-readiness design updates (Phases 04) are summarized under `docs/design/`.
The OpenAPI file is useful for request/response contracts. CI enforces a minimum coverage gate via `npm run openapi:coverage`. This document should be updated together with `app.ts`, route files, and the OpenAPI generator whenever route groups change.
+39
View File
@@ -0,0 +1,39 @@
# Penetration test scope (Phase 3)
**Status:** Scope pack for an independent tester
**Date:** 2026-08-12
**In-repo evidence folder:** `security-reports/` (attach PDF/HTML outputs here; do not commit secrets)
## In scope
- Public marketing site, Carplace booking, company dashboard, admin console
- API ` /api/v1/* `, `/health`, `/ready`, `/metrics` (metrics should not expose PII)
- Auth: employee, renter, admin (incl. 2FA), company API keys
- Multi-tenant isolation (IDOR across companies)
- Payments / webhooks (signature bypass attempts)
- File upload / private media paths
- Session cookies, CSRF on cookie mutations, redirect/open-redirect
## Out of scope (unless separately contracted)
- Physical / social engineering
- Third-party Clerk/payment provider infrastructure itself
- DoS that risks shared staging cost blowups (use agreed soak limits)
## Priority assertions to verify
1. No password hashes, TOTP secrets, raw invite/reset/review tokens in API JSON (**S11 closed in code**)
2. Cross-tenant resource access returns 404 without leakage
3. Webhook endpoints reject unsigned/forged payloads
4. Admin money-moving actions require fresh 2FA where designed
5. Container/Docker orchestration remains disabled (ADR-001)
## Exit for Phase 3
- [ ] Independent report attached under `security-reports/`
- [ ] All Critical/High findings fixed or formally accepted with owner + date
- [ ] Retest of fixed Critical/High completed
## Suggested tooling (tester choice)
OWASP ZAP / Burp, authenticated scripted checks mirroring `cross-tenant-isolation.test.ts`, dependency SCA already in CI.