﻿# Trip Management System: Resume & Interview Portfolio

## User-confirmed context — 2026-10-18

Professional context: this work was performed in my current Consultant Data Analyst role at ALTEN, September 2025–present.

> Source: direct user answers in the portfolio review. These are user-confirmed statements, not independently rerun repository or production measurements.

- **Ownership:** Docker/containerization, Kubernetes infrastructure and GitLab CI/CD only. Backend, frontend and application-test authorship are not attributed to the user. Authorship of adjacent operator tooling remains unconfirmed.
- **Operational status:** both development and production Kubernetes environments are deployed and used.
- **Adoption:** approximately 82 application users.
- **Unmeasured:** deployment duration, uptime, image-size reduction, rollback time and security findings. Backup restoration testing remains unconfirmed.
- **Scope of this edit:** existing backend/frontend bullet variants below are retained as requested, but remain application context and are not approved personal-achievement claims.

> Existing draft variants and unanswered placeholders are retained. This confirmed context governs personal ownership, adoption and outcomes; unresolved draft claims are not newly verified.


> Everything here comes from the repository's code, manifests, and pipeline files. Numbers were counted directly from the source.
> Bracketed placeholders such as `[X%]` mark real-world metrics that you need to fill in. The code alone can't measure them.

---

## 0. My Contribution (Read This First)

My work on this project was the **platform/DevOps layer**:

| Area | Files I own | What it does |
|---|---|---|
| **Containerization** | `backend/Dockerfile`, `frontend/Dockerfile`, `frontend/nginx.conf`, `backend/.dockerignore`, `frontend/.dockerignore`, `docker-compose.yml`, `backend/docker-compose.yml` | Non-root, layer-cached Python image (uv). Multi-stage Node→Nginx SPA image. Compose stack with a Postgres healthcheck gate and a one-shot `seed` profile. |
| **Kubernetes: dev + prod** | `k8s/dev/*`, `k8s/prod/*` (Kustomize) | Namespaces, Deployments, Services, a Postgres StatefulSet, a migration Job, a nightly backup CronJob, default-deny NetworkPolicies, a Gateway API `HTTPRoute`, and secret bootstrap scripts. |
| **GitLab CI/CD** | `.gitlab-ci.yml` + 9 includes under `.gitlab/ci/` | 16 jobs across 5 stages: lint/commit gates, dependency CVE audits, Postgres-backed tests, a "build once, promote on release" image pipeline, K8s deploys, and semantic-release. |

**Adjacent tooling that supports this work** (say these are yours only if you wrote them): `./make` + `deployment/` (plan/apply deploy CLI that talks to the GitLab API), plus `backend/cli/k8s_connect.py`, `k8s_logs.py`, `dbshell.py`, `sec_scan.py`, and `next_release.py`.

---

## 1. High-Level Project Overview

### One-Line Pitch
This is a full-stack, role-based **business-travel approval and cost-forecasting platform**. It takes a consultant's mission order (ODM, *Ordre de Mission*) through a multi-actor approval workflow: Consultant → PL1 → RC → "Antonin" cost approver. Along the way it produces auto-calculated re-billing forecasts, Excel/PDF exports, and transactional email notifications. It runs on Kubernetes across separate dev and prod environments.

### Problem & Solution
**Problem:** Consultant travel at a large engineering services company involves several stakeholders. Consultants request the trip, project leads (PL1) validate it, resource coordinators (RC) source cost quotes and book, and a cost approver picks the option. Doing this over email and spreadsheets leads to no audit trail, inconsistent cost math, and lost requests.

**How the architecture addresses it:**
- **An explicit state machine in the service layer.** ODMs have 4 statuses and Trips have 7. Every transition is guarded server-side (for example, `book_trip` requires `COST_VALIDATED`). See `backend/app/services/trip_service.py` and `odm_service.py`.
- **Fine-grained RBAC with row-level visibility.** 7 roles map to 20 permission strings. On top of that, query-level filters limit each user to the ODMs/Trips they are actually assigned to.
- **Derived financials.** When a cost option is selected, a Forecast row is created or updated with "Proposal ALTEN" and "Refacturation" amounts. A different billing rule applies to missions longer than 30 days (`forecast_service.calculate_forecast_amounts`).
- **Asynchronous notifications with an audit log.** Every workflow transition sends a templated email in the background. Each send is persisted to `email_logs`, and failed sends can be resent.
- **Production-grade delivery.** Immutable, promoted container images go to a hardened Kubernetes namespace per environment. Schema migrations run automatically and backups run nightly.

---

## 2. Comprehensive Tech Stack & Tooling

### Languages & Runtimes
- **Python 3.12** (`backend/.python-version`, `requires-python >=3.12`)
- **JavaScript (ES modules) / JSX**, React 19
- **Node.js 24** (build image and CI), **Node 22** (release job)
- **Bash/POSIX sh** (secret bootstrap, CI scripts), **YAML** (K8s, CI)

### Frontend / UI
| Concern | Technology |
|---|---|
| Framework | **React 19** (`react`, `react-dom`), `StrictMode` |
| Build tool | **Vite 8** + `@vitejs/plugin-react`, with path aliases (`@`, `components`, `hooks`, `services`, `pages`) |
| Routing | **React Router DOM 7**, with permission-aware `ProtectedRoute` / `PublicRoute` wrappers |
| State | **Redux Toolkit 2** (3 slices: `auth`, `notifications`, `theme`) + **redux-persist** (theme persisted through a custom localStorage adapter) |
| Styling | **CSS Modules** (57 `*.module.css` files) + global CSS variables (`styles/variables.css`) for light/dark theming via `data-theme` |
| Icons | **lucide-react** |
| Data layer | Custom **Repository pattern** (`apiRepository` / `mockRepository`), switched by the `VITE_DATA_SOURCE` env var, + 9 response **normalizers** |
| HTTP | Native `fetch` wrapper (`services/api/apiClient.js`) with Bearer injection, 401 session-expiry handling, blob downloads, and FormData uploads |

### Backend / API
| Concern | Technology |
|---|---|
| Framework | **FastAPI** (REST, OpenAPI at `/docs` and `/redoc`), served by **Uvicorn[standard]** |
| Validation | **Pydantic v2** + **pydantic-settings** (12-factor env config) |
| Auth | **PyJWT** (HS256, RFC 8693-style `act` claim for impersonation), **passlib[bcrypt]**, **zxcvbn** password-strength scoring |
| Background work | FastAPI **`BackgroundTasks`** for non-blocking email dispatch |
| Email | **aiosmtplib** (async SMTP + STARTTLS) + **Jinja2** templates (autoescaped HTML) |
| Documents | **PyMuPDF (`fitz`)** for coordinate-based PDF form filling, **openpyxl** for Excel export and bulk-import parsing, `csv` |
| Also declared | `reportlab`, `matplotlib`, `python-multipart` |
| Middleware | `CORSMiddleware` (env-driven origins); global exception handlers that normalize all errors into a `{success, message, errors}` envelope |
| CLI | Custom Django-style `manage.py` with **auto-discovered subcommands** (17 commands in `backend/cli/`) |

### Database & Storage
- **PostgreSQL 16** (prod/dev/CI), with **SQLite** and **MySQL** supported via the `DATABASE_BACKEND` setting
- **SQLAlchemy 2.0** (typed `Mapped[...]` declarative models, `selectinload` eager loading)
- **Alembic** (7 migration scripts)
- **psycopg2-binary**
- K8s **PersistentVolumeClaims**: 5 Gi data (StatefulSet `volumeClaimTemplates`) + 10 Gi backup volume (prod)

### DevOps, Testing & Tooling
| Category | Tools |
|---|---|
| Containers | **Docker** (multi-stage builds), **Docker Compose** (healthcheck-gated startup, `profiles: [seed]`), **Nginx (alpine)** for SPA serving |
| Orchestration | **Kubernetes** (Rancher-managed, Canal CNI), **Kustomize**, **Gateway API `HTTPRoute`**, NetworkPolicies, StatefulSet, Job, CronJob |
| CI/CD | **GitLab CI** (Docker-in-Docker `docker:24-dind` over TLS), GitLab Container Registry, GitLab Environments (`dev`, `prod`), pipeline schedules |
| Release | **semantic-release 24** + `@semantic-release/gitlab`, Conventional Commits, MR auto-labeling via the GitLab REST API |
| Dependency mgmt | **uv** (lockfile `uv.lock`, `--frozen`, `exclude-newer = "7 days"` supply-chain cooldown), npm lockfile drift check |
| Security scanning | **pip-audit**, **npm audit** (`--audit-level=high`), **gitleaks**, `detect-private-key` (Semgrep job scaffolded but commented out) |
| Lint/format | **Ruff** (lint + format, rules E/F/I/W/B/UP), **mypy**, **ESLint 10** (react-hooks, react-refresh), **Prettier**, **pre-commit** (16 hooks), **commitizen** |
| Testing | **pytest**, **pytest-asyncio** (`asyncio_mode=auto`), **pytest-cov**, **httpx `AsyncClient` + `ASGITransport`** |

---

## 3. Architecture & Technical Highlights

### 3.1 Build-Once / Promote-on-Release Container Pipeline *(my work)*
**Files:** `.gitlab/ci/build.yml`, `.gitlab/ci/deploy.yml`, `.gitlab/ci/release.yml`, `.releaserc.json`

- **Merge to `main` builds from source** and pushes only a rolling `:dev` tag. Images get **OCI labels** (`revision`, `source`, `created`, pipeline id/url) and build args (`IMAGE_TAG`, `COMMIT_SHA`) that are baked into the runtime environment for traceability.
- **Rollback slots:** before each push, the previous images rotate `dev → dev-prev → dev-prev2`, which keeps 2 rollback points with no extra storage tooling.
- **Releases are promoted, not rebuilt.** A manual `release` job runs semantic-release. It reads Conventional Commits (`fix`→patch, `feat`→minor, `!`→major) and pushes a `vX.Y.Z` tag. The tag pipeline then **pulls `:dev` and retags it**, so prod runs exactly the bytes that were tested in dev.
- **The retag trick:** a plain `docker tag` would leave `IMAGE_TAG=dev` inside the image. To fix that, `retag_image()` pipes `FROM <img>\nENV IMAGE_TAG=<tag>` into `docker build -`. That adds a single metadata layer, so `IMAGE_TAG` is accurate while `COMMIT_SHA` still points at the original build commit.
- **MR build-checks** use `rules:changes`, so they run only when a Dockerfile, a lockfile, `nginx.conf`, or `vite.config.js` changes. They build and discard the image: no registry login, no push.

### 3.2 Hardened, Environment-Parity Kubernetes Platform *(my work)*
**Files:** `k8s/{dev,prod}/*.yaml`, `k8s/*/secrets/create-secrets*.sh`

- **Zero-trust networking:** 7 NetworkPolicies in prod. A `default-deny` policy covers Ingress and Egress, then explicit allows are added: DNS to `kube-system`, ingress to frontend:80 and backend:8000, backend → Postgres:5432, backend → SMTP:25, migration → Postgres, backup → Postgres. Postgres accepts connections only from pods labelled `backend|migration|backup`.
- **Pod hardening on every workload:** `runAsNonRoot`, `seccompProfile: RuntimeDefault`, `allowPrivilegeEscalation: false`, `capabilities.drop: [ALL]` (with minimal `add` only where Nginx/Postgres need it), and fixed non-root UIDs (10001 backend, 1001 frontend, 999 postgres).
- **Health model:** HTTP readiness and liveness probes on `/api/v1/health`. The frontend also has a `startupProbe` (30 × 5 s budget). Postgres uses `pg_isready` exec probes.
- **Schema migrations as a K8s Job.** An `initContainer` blocks until `pg_isready` succeeds, then the Job runs `alembic upgrade head && python manage.py seed` (`backoffLimit: 5`, `ttlSecondsAfterFinished: 86400`).
- **Disaster recovery (prod only):** a `CronJob` runs `pg_dump | gzip` daily at 02:00 to a 10 Gi PVC. It keeps the last 14 dumps and sets `concurrencyPolicy: Forbid` so runs never overlap.
- **Single-host L7 routing** via a Gateway API `HTTPRoute`: `/api`, `/docs`, and `/redoc` go to the backend; `/` goes to the frontend. Because the API and SPA share one origin, the browser makes no cross-origin calls in-cluster.
- **Dev/prod parity through Kustomize.** The two environments differ only in what they should: replicas (1 vs 2), requests/limits, image tag, namespace, hostname, and the backup resources in prod.
- **Secrets never touch Git.** A single base64 `ENV_B64` CI variable is decoded to a `mktemp` file (deleted by `trap … EXIT`) and applied idempotently with `kubectl create secret … --dry-run=client -o yaml | kubectl apply -f -`. The same step creates the `docker-registry` pull secret.

### 3.3 Layered Quality & Security Gates in CI *(my work)*
**Files:** `.gitlab-ci.yml`, `.gitlab/ci/{pre-commit,commit-lint,auto-label,security,package-lock-check,test}.yml`, `.pre-commit-config.yaml`

- **5-stage DAG:** `pre-commit → security → test → build → deploy`. `needs: []` lets the security jobs fan out in parallel. `backend-tests` depends explicitly on pre-commit and all three audit jobs.
- **Supply-chain security:**
  - `pip-audit` scans the *exact* production dependency set (`uv export --frozen --no-dev`).
  - `npm audit --audit-level=high` scans the frontend.
  - A **lockfile-drift check** fails if `npm install --package-lock-only` would change `package-lock.json`.
  - A **weekly scheduled pipeline** re-runs only the audits, so new CVEs in unchanged dependencies still get caught.
- **Real database in tests:** the `postgres:16-alpine` service container runs `alembic upgrade head`, then `pytest`. That validates the migrations themselves, not just `create_all`.
- **Repo hygiene:** pre-commit runs Ruff, mypy, Prettier, ESLint, gitleaks, private-key detection, and a 1 MB large-file guard. The MR title is linted against Conventional Commits and auto-labelled through the GitLab API; `feat` also gets a `minor` label.
- **Caching:** the uv `.venv`, the pre-commit cache, and the npm cache are keyed per branch (`$CI_COMMIT_REF_SLUG`).

### 3.4 Defense-in-Depth Authorization: RBAC + Row-Level Scoping + Audited Impersonation
**Files:** `backend/app/core/deps.py`, `core/permissions.py`, `core/security.py`, `core/impersonator.py`, `policies/*`, `services/odm_service.py`, `services/trip_service.py`

- **Composable FastAPI dependencies:** the factories `require_permission`, `require_any_permission`, `require_role`, and `require_first_superuser` plug into routes as `Depends(...)`.
- **Permission namespace:** `resource:action[:scope]` strings (e.g. `odm:read:own` vs `odm:read:all`), with an `ADMIN: ["*"]` wildcard.
- **Row-level security in SQL.** `get_trips()` / `get_odms()` add `JOIN … WHERE pl1_id = me OR rc_id = me OR consultant.pl1_id = me …`, so a user cannot list what they aren't assigned to. Unauthorized single-record access returns **404, not 403**, so the API doesn't reveal that a record exists.
- **Impersonation ("Login as") built like a standard:**
  - It uses a JWT **`act` (actor) claim** and a **shorter 30-minute TTL** (vs 1440 minutes for normal sessions).
  - While impersonating, the session is **read-only**: every non-GET/HEAD/OPTIONS request is rejected except `logout` and `impersonate/stop`.
  - Only the seeded first superuser can impersonate, and only if their email matches a **hard-coded SHA-256 digest**, compared with `hmac.compare_digest` (constant-time). Changing an env var alone can't grant the capability.
- **Password policy:** bcrypt hashing, a minimum length of 8, no all-numeric passwords, and a **zxcvbn score ≥ 3** that uses the user's name and email as penalized inputs.

### 3.5 Workflow State Machine with Derived Financial Forecasting
**Files:** `backend/app/services/{odm,trip,cost_option,forecast}_service.py`, `models/*`

- **ODM** (`INITIATED → VALIDATED | REJECTED | CANCELED`). Validating an ODM **atomically creates its Trip**, which auto-fills dates, inclusive day count, the responsible person, and the auto-assigned Antonin approver. Only the consultant's *assigned* PL1 (or an ADMIN) can validate.
- **Trip** (`TRIP_VALIDATED → COST_PROVIDED → COST_VALIDATED → TRIP_BOOKED → TRIP_COMPLETED`, plus `REJECTED`/`CANCELED` as terminal states). Every mutation runs through guard functions, and `FINAL_TRIP_STATUSES` blocks edits once a trip is closed.
- **Cost selection is exclusive.** A single `UPDATE … SET is_selected=false WHERE trip_id=? AND id<>?` runs before the chosen option is flagged. The code then calls `flush()` and recomputes the Forecast inside the same transaction.
- **Billing rule:** `Decimal`-precise money math. Missions longer than 30 days bill `days × IGD`; shorter ones bill `days × IGD + taxi + car rental + transport`.
- **Forecast status mirrors the Trip** on every transition (`sync_forecast_status_with_trip`).
- **N+1 avoidance:** each service has a `_xxx_query()` builder that always eager-loads relationships with `selectinload` (e.g. ODM → consultant → rc/pl1, plus trip).

### 3.6 Observable, Resendable Async Email Subsystem
**Files:** `backend/app/core/email.py`, `services/notifications/*`, `services/email_log_service.py`, `templates/emails/*`

- An `Email` class assembles multipart MIME: Jinja2 HTML/text templates, CC/BCC, MIME-guessed attachments, and a plain-text fallback. It sends over **aiosmtplib** with STARTTLS. A console mode is available for development.
- **12 notification functions** (ODM, Trip, and user lifecycle) are dispatched through `BackgroundTasks`, so the HTTP response never waits on SMTP.
- **Best-effort audit trail:** each send (sent or failed, with the error) is written to `email_logs` in its own DB session, inside `try/except` so tracking failures never break the sending path.
- **Resend:** for resend-enabled emails, the render context is stored as JSON after a `json.dumps(default=str)` pass, so ORM objects degrade to strings. Admins can resend from the UI; resend tracks `send_times` and `send_dates` and returns **502** if the SMTP upstream fails.

### 3.7 Frontend: Swappable Data Layer and Permission-Driven UI
**Files:** `frontend/src/services/dataSource/*`, `services/normalizers/*`, `hooks/data/useRepositoryQuery.js`, `hooks/actions/*`, `components/auth/*`

- **Repository pattern.** `repository = isApiDataSource ? apiRepository : mockRepository`. Because a ~26 KB in-memory mock backend is included, the whole UI can be built and demoed without the API, controlled by the `VITE_DATA_SOURCE` build arg.
- **Normalizer layer:** 9 normalizers turn API payloads into stable UI shapes (money, roles, trips, and so on).
- **Hook architecture:** 28 custom hooks split into `data/` (queries via `useRepositoryQuery`), `actions/` (mutations via `useActionRunner`), and `auth/` (login, logout, impersonation, change password).
- **Permission-aware routing.** `ProtectedRoute` checks a permission list and `requireFirstSuperuser`. The frontend `can()` mirrors the backend permission map. `statusGuardService` encodes the same status rules as the backend, so the UI hides actions that the API would reject.
- **Cross-tab logout:** `authTokenService` emits a custom DOM event when the token is removed, and the 401 handler clears the token so session expiry is consistent.

---

## 4. Quantifiable Metrics & Scope (From the Codebase)

### API & Backend
| Metric | Value | Source |
|---|---|---|
| REST endpoints | **45** across **9 routers** (auth 7, odms 8, trips 8, users 6, cost-options 5, email-logs 5, forecast 4, roles 1, health 1) | `app/api/v1/endpoints/*` |
| Roles / permissions | **7 roles**, **20 distinct permission strings** + wildcard | `core/permissions.py` |
| Workflow states | ODM **4**, Trip **7** | `odm_service.py`, `trip_service.py` |
| Email notifications / templates | **12** notification senders, **14** templates (13 HTML + 1 text) | `services/notifications/`, `templates/emails/` |
| Management CLI commands | **17** auto-discovered commands (seed, demo-seed, k8s-connect, k8s-logs, dbshell, sec-scan, next-release, …) | `backend/cli/` |
| Backend LOC | ~**4.7k** app + ~**1.9k** CLI | `backend/app`, `backend/cli` |

### Data Model
| Metric | Value |
|---|---|
| Tables | **9**: `users`, `roles`, `user_role` (M:N), `odm`, `trips`, `cost_options`, `cost_transport`, `forecasts`, `email_logs` |
| ORM models | **8** + 1 association table |
| Alembic migrations | **7** |
| Key relationships | `ODM 1:1 Trip` (unique FK) · `Trip 1:N CostOption 1:N CostTransport` (cascade delete-orphan) · `Trip 1:1 Forecast` (unique FK) · `Trip` has **4 FKs to `users`** (consultant, pl1, rc, antonin) · `User` is **self-referential** (`rc_id`, `pl1_id` hierarchy) · `User M:N Role` |
| Indexed columns | All FKs, plus `status` on odm/trips/forecasts, `is_selected`, `email_logs.subject/status/created_at` |

### Frontend
| Metric | Value |
|---|---|
| Pages / routes | **11** page components, 11 routes + a catch-all redirect |
| Reusable components | **49** component files (19 shared in `components/common`, 3 in `components/forms`, the rest domain-specific) |
| Custom hooks | **28** |
| Redux slices | **3** |
| CSS Modules | **57** |
| Frontend LOC | ~**9.5k** JS/JSX |

### Testing
| Metric | Value |
|---|---|
| Test functions | **191** (**125 unit**, **66 integration**) across **27** test files, plus 13 `parametrize` blocks |
| Test LOC | ~**2.3k** |
| Strategy | Each integration test runs inside a **transaction that is rolled back** (`tests/integration/conftest.py`). The test DB is auto-created if missing, and a safety `assert` refuses to run against a non-test DB. `get_db` is swapped through `dependency_overrides`. |

### Infrastructure & Delivery *(my work)*
| Metric | Value |
|---|---|
| CI jobs / stages / include files | **16 jobs**, **5 stages**, **9** modular include files (~**470** YAML LOC) |
| Pre-commit hooks | **16** |
| K8s objects (prod) | **19**: 2 Deployments, 3 Services, 1 StatefulSet, 1 Job, 1 CronJob, 1 PVC, **7 NetworkPolicies**, 1 HTTPRoute, 1 Namespace, 1 Kustomization (dev: 16) |
| K8s manifests + scripts LOC | ~**1.2k** |
| Deploy CLI (`./make` + `deployment/`) | ~**500** LOC Python |
| Rollback depth | **2** previous dev images (`dev-prev`, `dev-prev2`) |
| Backups | Daily at **02:00**, **14** retained, **10 Gi** PVC |
| Rollout gate | `kubectl rollout status --timeout=5m` × 3 workloads; migration wait **600 s** timeout / **5 s** poll (CLI) |
| Prod capacity | **2** replicas each for backend and frontend; backend `250m/256Mi` request → `1 CPU/512Mi` limit; Postgres up to `2 CPU/2Gi`, **5 Gi** volume |
| Security audit cadence | Every MR + **weekly** (Mon 00:00 Europe/Paris) |
| Supply-chain cooldown | `uv exclude-newer = "7 days"` (ignores packages published in the last week) |

### Tunables Visible in Code
- JWT session TTL **1440 min**; impersonation TTL **30 min**
- Password: min **8** chars, zxcvbn score **≥ 3**; generated passwords are **16** chars from `secrets.choice`
- Email-log pagination: default **50**, max **200** (`Query(ge=1, le=200)`)
- Excel export: frozen header row, auto column width clamped to **10–50** chars
- GitLab API client timeout: **15 s**; Docker-daemon readiness wait: **30 s**
- Migration Job `backoffLimit: 5`, `ttlSecondsAfterFinished: 86400`

---

## 5. Ready-to-Use Resume Bullet Points (XYZ Format)

### Variation A: DevOps / Platform / Full-Stack *(best fit for my contribution)*
1. **Containerized** a FastAPI + React monorepo into hardened, non-root Docker images (multi-stage Node→Nginx; uv-locked Python with dependency-layer caching), cutting local onboarding to a single `docker compose up` with health-gated Postgres startup and an opt-in seed profile, and reducing image size by **[X%]**.
2. **Architected** Kustomize-based **dev and prod Kubernetes environments** (19 prod objects: Deployments, a Postgres StatefulSet, a migration Job, a backup CronJob, and a Gateway API HTTPRoute), with dev/prod parity limited to replicas, resources, and tags. Both environments are operational; the application serves **approximately 82 users**, with uptime **[99.X%]** still unmeasured.
3. **Engineered** a **zero-trust network model** with 7 default-deny NetworkPolicies and Pod Security hardening (`runAsNonRoot`, seccomp `RuntimeDefault`, `drop: [ALL]` capabilities), so the database accepts traffic only from 3 labelled workloads.
4. **Built** a 16-job, 5-stage **GitLab CI/CD pipeline** with a "build-once, promote-on-release" strategy: tagged releases retag the exact dev-tested image instead of rebuilding, which removes build drift between environments and cuts release time to **[Y min]**.
5. **Automated** semantic versioning and release notes with semantic-release, Conventional Commit MR-title linting, and API-driven MR auto-labeling, replacing manual versioning across **[N] releases**.
6. **Shifted security left** by adding CI gates for pip-audit (on the exact locked prod dependencies), npm audit, gitleaks, lockfile-drift detection, and a weekly scheduled CVE re-scan. These have caught **[N] vulnerable dependencies** before production.
7. **Implemented** zero-touch database operations: an init-container-gated Alembic migration Job runs on every deploy and blocks rollout until it succeeds, and a nightly `pg_dump` CronJob keeps 14 compressed restore points (RPO ≤ **24 h**).
8. **Designed** GitOps-style secret management: one masked, environment-scoped CI variable is decoded to an ephemeral file and applied idempotently (`--dry-run=client | kubectl apply`), so no secrets live in the repo.
9. **Developed** a `plan`/`apply` operator CLI (`./make`) that mirrors the pipeline locally. It runs `kubectl diff` previews, requires typed confirmation for prod, and pulls secrets from the GitLab API, which shortened emergency/manual deploys to **[X min]**.
10. **Cut** rollback time to **[< X min]** by rotating 2 rollback image tags (`dev-prev`, `dev-prev2`) on every merge and embedding OCI provenance labels (commit SHA, pipeline URL) in each image.

### Variation B: Backend / Systems Architecture
1. **Architected** a 45-endpoint FastAPI REST service with a layered router → service → policy → model design that drives a multi-role travel-approval workflow (ODM: 4 states, Trip: 7 states) for **[N] consultants**.
2. **Designed** fine-grained RBAC (7 roles, 20 `resource:action:scope` permissions) with SQL-level row filtering and existence-hiding 404s, so users see only records they are assigned to.
3. **Engineered** a standards-based impersonation feature using the JWT `act` claim, a 30-minute TTL, read-only enforcement at the dependency layer, and a constant-time SHA-256 allow-list, cutting support-debugging time by **[X%]**.
4. **Automated** financial forecasting: selecting a cost option triggers `Decimal`-precise re-billing calculations (with a separate rule for missions over 30 days) in the same transaction, replacing **[N hrs/month]** of manual spreadsheet work.
5. **Built** a non-blocking email subsystem (aiosmtplib + Jinja2, 12 workflow notifications via BackgroundTasks) with persistent delivery logs and one-click resend, reaching a **[X%]** delivery success rate.
6. **Implemented** bulk user onboarding from CSV/XLSX with per-row validation, dependency-ordered inserts (managers before consultants), and in-batch reference resolution, onboarding **[N] users** in one upload.
7. **Strengthened** credential security with bcrypt plus zxcvbn entropy scoring (≥ 3) that penalizes the user's own name and email, and 16-char CSPRNG passwords for provisioned accounts.
8. **Wrote** 191 pytest tests (125 unit / 66 integration) using per-test transaction rollback against real PostgreSQL in CI, reaching **[X%]** coverage.

### Variation C: Frontend / Product Engineering
1. **Built** an 11-page React 19 + Vite dashboard with 49 reusable components and 28 custom hooks, covering the full trip lifecycle for **[N] daily users** across 7 roles.
2. **Architected** a swappable Repository data layer (API vs in-memory mock, chosen at build time), which let frontend work proceed independently of the backend and cut demo setup to **[0 dependencies]**.
3. **Implemented** permission-driven routing and UI gating (`ProtectedRoute`, `PermissionGate`, a client-side `can()` that mirrors backend RBAC), so users never see actions the API would reject. This reduced 403 errors by **[X%]**.
4. **Designed** a normalizer layer (9 normalizers) and a uniform `{success, message, data/errors}` API contract, so field-level validation errors map directly onto form fields.
5. **Delivered** a theme-able design system (CSS Modules + CSS variables, persisted dark/light mode via redux-persist) and an admin impersonation banner, improving **[UX metric]**.
6. **Enabled** one-click Excel and PDF exports (pre-filled official mission-order PDF from a coordinate map) with server-side filtering by status, country, month, and free text, saving **[X hrs/week]** of reporting.

---

## 6. Interview Prep: Deep-Dive Talking Points

### 6.1 "Prod should run the exact image we tested", but the image says it's `dev`
**Files:** `.gitlab/ci/build.yml` (`retag_image`, `rotate_dev_rollback_tags`, `promote-*`), `.gitlab/ci/deploy.yml`, `deployment/deploy.py`

- **Situation:** Merges to `main` deploy continuously to dev. Releases are cut manually. Rebuilding for prod would risk a different image (new base-image patch, non-deterministic `npm install`) from what QA validated in dev.
- **Task:** Guarantee that prod runs the same artifact as dev, keep accurate version metadata inside the container, and keep a fast rollback path.
- **Action:**
  - Split the pipeline into a **build** path (merges push a rolling `:dev` tag) and a **promote** path (a `vX.Y.Z` tag pipeline pulls `:dev` and retags it with no rebuild).
  - A plain `docker tag` would leave `ENV IMAGE_TAG=dev` baked in. So I wrote `retag_image()`, which builds a one-line Dockerfile from stdin (`FROM img` + `ENV IMAGE_TAG=vX`). It adds one metadata-only layer and keeps the original `COMMIT_SHA`.
  - Before each push, the previous images rotate into `dev-prev` and `dev-prev2`.
  - On deploy, K8s Jobs are immutable, so the migration Job is deleted and recreated. In prod, `sed` pins the release tag into the Job manifest *before* apply.
  - Dev reuses the same tag, so `kubectl rollout restart` forces new pods.
- **Result:** Releases have zero rebuild drift, carry traceable provenance (OCI labels + SHA), and have two instant rollback points. Deploys fail fast via `rollout status --timeout=5m` and an explicit migration-success check.
- **Trade-off to own:** If someone merges between "tested on dev" and "cut release", the promote step picks up the newer `:dev`. We documented this instead of adding digest-pinning; digest pinning would be the next step. Also, `deploy-prod` is currently `when: never` in CI, so prod is deployed with `./make deploy prod apply`, which runs a `kubectl diff` plan and requires typed confirmation. That was a deliberate human-in-the-loop choice for prod.

### 6.2 Locking down a multi-tier app on a shared Rancher cluster
**Files:** `k8s/prod/network-policies.yaml`, `k8s/prod/*.yaml` (securityContext), `k8s/prod/postgres-backup.yaml`, `k8s/*/secrets/create-secrets-ci.sh`

- **Situation:** The app runs in a shared corporate cluster, and Postgres runs in-cluster as a StatefulSet. By default any pod could reach the database, and containers ran with default privileges.
- **Task:** Apply least privilege at the network, process, and secret layers without breaking migrations, backups, or outbound email.
- **Action:**
  - **Network:** a `default-deny` policy for Ingress and Egress, then per-component allows. Everything needs DNS. Backend needs Postgres:5432 and SMTP:25. The migration Job and backup CronJob reach only Postgres. Postgres ingress is restricted to `component ∈ {backend, migration, backup}`.
  - **Process:** `runAsNonRoot`, seccomp `RuntimeDefault`, and `drop: [ALL]` capabilities everywhere. Only Nginx and Postgres re-add the specific capabilities they need. Dockerfiles create fixed-UID users, and the Nginx PID file moves to `/tmp`.
  - **Secrets:** one environment-scoped `ENV_B64` CI variable is decoded to a `mktemp` file with a `trap` cleanup and applied idempotently. The registry pull secret is created the same way.
  - **Backups:** a nightly `pg_dump | gzip` CronJob keeps 14 dumps and uses `concurrencyPolicy: Forbid`.
- **Result:** The database's attack surface is 3 workload types. No container runs as root, no secret is committed, and there are 14 days of point-in-time restores.
- **Trade-offs to own (interviewers will probe):**
  - SMTP egress is `0.0.0.0/0:25`; it should be narrowed to the relay's CIDR.
  - Frontend and backend ingress allow `namespaceSelector: {}`; this should be narrowed to the gateway namespace.
  - Backups live on the same cluster's PVC; next step is off-cluster/object storage.
  - Postgres is a single replica; HA would mean an operator such as CloudNativePG or a managed DB.
  - The frontend Dockerfile has `npm config set strict-ssl false` (a corporate-proxy workaround) and uses `npm install` rather than `npm ci`.
  - `uv:latest` is unpinned. Pin a version.

### 6.3 Secure "Login as" without creating a privilege-escalation hole
**Files:** `backend/app/core/deps.py`, `core/security.py`, `core/impersonator.py`, `services/impersonation_service.py`, `api/v1/endpoints/auth.py`, `tests/integration/test_auth/test_impersonation.py`

- **Situation:** Support staff needed to see exactly what a specific PL1, RC, or consultant sees in order to debug visibility issues. Visibility is driven by row-level relationships, not just roles.
- **Task:** Let an admin view the app as another user without knowing their password, without making changes on their behalf, and without letting a config change grant the power to someone else.
- **Action:**
  - The impersonation token puts the target in `sub` and the admin in a standard JWT `act` claim, with a 30-minute expiry.
  - `get_impersonator_email` re-validates `act` on every request.
  - `get_current_user` rejects any non-read method while impersonating, except `logout` and `impersonate/stop`.
  - The capability needs *two* factors: the account must be the seeded first superuser **and** its email must match a SHA-256 digest compiled into the code, compared with `hmac.compare_digest`.
  - The frontend shows an `ImpersonationBanner` and restores the admin session on stop.
- **Result:** Support can reproduce user-specific views safely. Every impersonated session is short-lived, read-only, and attributable. The behaviour is covered by a dedicated integration test module.
- **Trade-off:** The allow-list is hard-coded, so adding an impersonator requires a code change and a deploy. That was chosen on purpose to make the feature auditable through Git history rather than runtime config.

---

### Quick Reference: 30-Second Elevator Answer
> "It's a FastAPI/React travel-approval platform with RBAC, a 7-state trip workflow, auto-calculated billing forecasts, and async email. I containerized it, wrote the dev and prod Kubernetes environments with default-deny networking, non-root pods, migration Jobs, and nightly backups, and built a 16-job GitLab pipeline. The pipeline audits dependencies, tests against real Postgres, and promotes the exact dev-tested image to prod through semantic-release instead of rebuilding it."
