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
+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)