| 🎯 OBJECTIVE | One transaction boundary across two database engines — without losing a single cent |
| 🧠 INTEL | Oracle 23ai owns the ACID money path · TimescaleDB owns telemetry · OCPP 1.6J gateway drives a simulator fleet |
| 💣 BOSS FIGHT | Race conditions — respawned in CI on every push until they stayed dead |
| 🏆 LOOT | CI + release pipelines · Codecov coverage · OpenSSF Scorecard · typography-led Next.js ops console |
| 🎮 DIFFICULTY | ★★★★★ · no hard mode, just engineering |
Two-engine EV charging management: Oracle for ACID billing/reservations, TimescaleDB for telemetry — driven by an OCPP 1.6J gateway + simulator fleet, race-tested in CI on both database engines, with a typography-led Next.js operations console.
New here? Pick a 5-minute path.
You are… Read, in this order 🧭 Recruiter / curious the 30-second demo below → What's actually here → the docs site → docs/resume-bullets.md🔧 Engineer Quickstart → ARCHITECTURE.md→ ADR index → run the race suite yourself🔍 Reviewer / sceptic ADRs (trade-offs named) → docs/verification.md(every claim has a receipt) →docs/perf.md(numbers, methodology) →SECURITY.md
| 72 | 2 | 7 | 16 | 6 | 0 |
|---|---|---|---|---|---|
| Spec'd API paths, OpenAPI drift-gated | DB engines behind one store port | PL/SQL packages own the writes | ADRs, trade-offs named | CI jobs on every push | known CVEs, npm audit gated |
Live session — real OCPP MeterValues tick the UI (re-record anytime: scripts/screenshots/ — see its README).
Real pixels from the running stack (API :4000 + OCPP 1.6J WebSocket session + web console). More at the documentation site.
Two terminals reserve the same connector → one
201 BOOKED, one409 OVERLAP→ plug-in → live kWh/kW/cost → itemized invoice → wallet pay. (Record the terminal race withscripts/demo.sh.)
| Choice | Why (and the trade-off accepted) |
|---|---|
| Oracle 23ai for the money path | row-lock semantics + PL/SQL packages own the writes (guard trigger blocks direct connector.status UPDATEs). Trade-off: a real Oracle dependency — CI runs it as a service container every push |
| TimescaleDB for telemetry | 90 %+ of rows are immutable time-ordered ticks — hypertables/caggs/compression are the right shape. Trade-off: caggs can only query one hypertable, so enrichment is query-time views + a relay-maintained station_map |
| outbox + relay between them | same-transaction outbox on Oracle + ack-after-COMMIT relay gives effectively-once without a broker. Trade-off: single-writer worker, not a fan-out bus |
| Express 5 + pino 10 | boring, dependency-light API core; request-ID error envelopes; drift-gated OpenAPI. Trade-off: hand-rolled middlewares instead of a framework galaxy |
| Next.js 16 + React 19 | App Router with real client components; CSP set at the Next layer. Trade-off: server/client boundary discipline is on the team |
| Node 22 LTS | CI runs a 20/22 matrix so engines >=20 stays honest; images ship 22 |
| eslint 10 flat config + prettier + npm audit gate | the lint gate is deliberately stricter than the pre-flat era (it immediately caught two latent issues); npm audit fails CI on either lockfile |
| c8 coverage → Codecov (informational) | the coverage badge is a receipt, not a target — unit-tier suites instrumented with V8 coverage on every push; DB-backed suites stay in db-tests on purpose |
| Compose + Caddy, one VM | honest deploy target (ADR-0001); graceful SIGTERM drains matched to compose stop-grace periods. Trade-off: no K8s story by design |
- Sessions/billing are ACID-relational (money path, overlap exclusion, ledger) → Oracle 23ai, with the money logic in PL/SQL packages (
RESERVATION_PKG,CHARGE_SESSION_PKG,BILLING_PKG) — not in app code. - 90%+ of rows are immutable, time-ordered meter ticks read as window aggregates — the wrong shape for a row-store → TimescaleDB hypertables + 1m/1h continuous aggregates + 7-day compression + 90-day retention.
- Same event, two resolutions: every OCPP
MeterValueswritesMETER_READING(billing record, Oracle) andmeter_tick(analytics record, Timescale) via an outbox + relay (at-least-once delivery, idempotent replay = effectively-once). - Race safety is proven, not claimed: CI fires parallel double-reserves and double-pays and asserts exactly one winner — against the in-process store and against real Oracle row locks.
flowchart TB
W["Next.js web console (Grid Current)"] --> REST
SIM["OCPP 1.6J simulator fleet<br/>normal · race · fault · no-show · burst"] --> GW
SIM --> REST
subgraph API["apps/api — one port :4000"]
REST["REST /api/v1 — 66 spec'd paths, OpenAPI drift-gated"]
GW["OCPP 1.6J gateway<br/>(WS · Basic auth · 10 msg/s)"]
end
REST --> ST
GW --> ST
subgraph ENG["the two engines (store port, ADR-0005)"]
ORA[("Oracle 23ai<br/>29 relations · 7 PL/SQL packages<br/>money path — reservations · billing · ledger")]
TS[("TimescaleDB<br/>hypertables · caggs · compression<br/>telemetry path — meter ticks")]
end
ST -- "write-through pkg calls" --> ORA
ORA -- "hydrate on boot" --> ST
ST --> OB["outbox (in-transaction)"]
OB --> REL["apps/worker · 2 s · batch · ack-after-COMMIT<br/>idempotent replay ⇒ effectively-once"]
REL --> TS
REST --> MX["/metrics · /health/deep"] --> GF["Grafana"]
Mermaid renders natively on GitHub. Full 1-page version + ADR table: ARCHITECTURE.md.
| Layer | Evidence |
|---|---|
| Oracle OLTP | 29-relation BCNF-honest schema, 7 PL/SQL packages, connector-state guard trigger, least-privilege role (no DELETE anywhere), MV + scheduler — db/oracle/V001–V006 |
| Concurrency | SELECT … FOR UPDATE / SKIP LOCKED bulk expiry, error bands -2050x…-209xx mapped to HTTP once (src/errors.js), race suites in CI on both engines — apps/api/test/race.js |
| OCPP 1.6J | WebSocket gateway with HTTP Basic auth on upgrade (Security Profile 1), per-CP credentials, 10 msg/s limit, 7 core messages + Smart Charging (SetChargingProfile/Clear/GetCompositeSchedule) + 10 awaited management commands (Reset, UnlockConnector, ChangeAvailability, Get/ChangeConfiguration, TriggerMessage, ReserveNow, CancelReservation, ClearCache, DataTransfer) with per-command accepted-status sets and audit — apps/api/src/ocpp/, docs/ocpp-1.6-conformance.md |
| AI (advisory) | 87-station pooled panel from three public datasets (Tennessee, Norway, Korea) · 754,032 station-hours · MLP MAE 1.906 kWh, MASE 0.640, 36.0 % skill vs seasonal-naive, conformal coverage 0.803 · model card, occlusion explanations, PSI/KS drift — apps/ai/, ADR-0008 |
| CarbonPilot | Deterministic model-v1 grid intensity (average for accounting, marginal for scheduling), 48 h forecasts, SHA-256 session receipts, site summaries, carbon-aware scheduling at weight 0 by default — apps/api/src/carbon/, apps/web/src/app/carbon/, ADR-0017 |
| Control loop (durable) | The loop is proven through the DB engine, not just the local store: test/e2e/control-plane.js drives certify → plan → actuate → typed outcome → durable audit over HTTP against the compose stack and refuses to run unless /health reports mode: oracle |
| Control loop | FC-HCC, closed end-to-end: sense → certify (worst-case admission, taper-aware) → schedule (acceptance-envelope, feasibility-first then price-aware) → compile → actuate with CALLRESULT ack correlation → verify against metered kW. Optimizer-independent envelope rejects cap-violating pushes (-20903), pushes are idempotent per (cp, decision) and rate-governed 6/min/CP; per-site mode defaults OFF until receipts exist — apps/api/src/control/, ADR-0010/0011/0014/0015 |
| Twin + experiments | Deterministic generative fleet (arrivals, SoC, CC-CV taper, ToU, non-compliant chargers) with a shared scoring harness — apps/simulator/src/twin.js. Receipts are reproducible from their seeds alone (scenarios are anchored to a fixed UTC epoch). E1b: controller −46.7 cost units vs static-cap (95% CI [−48.3, −45.0], 200 paired seeds), 0 calibration shortfall on 1,687 admitted vehicles. E3: admission tracks load; and the taper-binding family fails (42.6% shortfall, no deadline advantage over static-cap at ρ=0.9) — published as-is. E6 repairs it without editing E3: acceptance-envelope scheduling cuts shortfall 42.6% → 2.36% and deadline miss 56.6% → 15.7%, with plan-honesty gap falling from 1,280 kWh → 0.011 kWh per 100-seed run and zero change below the knee — docs/perf.md, ADR-0015 |
| DA3 pipeline | Outbox → 2s relay → hypertables → caggs; crash-after-COPY replays safely — apps/worker/, db/timescale/ |
| API | 82 spec'd paths, OpenAPI 3.0 with a CI drift gate + a generated-snapshot check, Idempotency-Key replay, keyset pagination, request-ID error envelopes — apps/api/src/ |
| Web | 18 routes, "Grid Current" design system (Bricolage Grotesque / IBM Plex Sans + Plex Mono tabular numerals), installable PWA with offline page, zero chart dependencies, unified PageState + toasts + URL-as-state — apps/web/ |
| CI | 6 jobs: lint (Node 20/22 matrix) · security (npm audit gate, both lockfiles) · quality · coverage (c8 → Codecov) · db-tests (Oracle + Timescale service containers) · e2e (full compose). The control-loop properties, the AI advisory contract, the twin properties, the OpenAPI snapshot freshness and the durable control-plane journey (npm run test:e2e:control) all gate on every push — .github/workflows/ci.yml |
| Releases | tag-driven GitHub Releases with notes extracted from a Keep-a-Changelog CHANGELOG.md; conventional commits throughout — .github/workflows/release.yml |
- The local profile (no Docker) runs an in-process store mirroring package semantics for fast iteration/tests;
ORACLE_HOST/TS_HOSTarms the durable two-engine path (ADR-0005). - Chargers are simulated (SteVe precedent — real hardware interop is out of scope); payments are a prepaid wallet (no card data, ever); single-VM Compose, not K8s (ADR-0001).
- OCPP scope is the 1.6J core profile + Smart Charging + the ten management commands in ADR-0018;
GetDiagnosticsand firmware update remain out of scope (no file store/signature/rollback contract). Online authorization only — no local auth list. Migration path to OCPP 2.0.1 (IEC 63584):TransactionEventreplaces Start/Meter/Stop — the store surface is protocol-neutral by design. Full statement:docs/ocpp-1.6-conformance.md. - No performance numbers are claimed here — see
docs/perf.md(methodology + 5 reproducible experiments; measured tables land frombench/scripts only).
# 1) local, no Docker: API :4000 + web :3000
npm install
npm run dev:api & # seeded demo data, /api/v1/health
cd apps/web && npm install && npm run dev
# 2) full stack (Oracle 23ai + TimescaleDB containers) — works with no .env at all
# If you created a root .env (see DEPLOY.md), pass it or it is silently ignored:
# docker compose --env-file .env -f infra/docker-compose.yml up --build
docker compose -f infra/docker-compose.yml up --build
# 3) races + tests + coverage
npm run test -w apps/api && npm run test:race -w apps/api
npm run coverage # c8 line coverage over the unit-tier suites
node apps/simulator/src/index.js --scenario race # expect exactly one 201 + one 409Demo logins: arjun@volthub.in / Operator@123 · any seeded driver / Driver@123. The admin credential is not published — set ADMIN_BOOTSTRAP_PASSWORD before seeding (see DEPLOY.md §8).
- Current release: v1.7.0 — tagged releases carry notes extracted from
CHANGELOG.md(Keep-a-Changelog / SemVer). - Engineering history is fully auditable: conventional-commit breadcrumbs (
fix(oracle): …,ci(e2e): …) — including the failures and their fixes. - Roadmap notes (OCPP 2.0.1 migration path, scale exits):
ARCHITECTURE.md·docs/masterplan/18-da-plans-and-roadmap.md.
| What | Where |
|---|---|
| Docs site (Pages) | humoge7502.github.io/VoltHub-CSMS — 1-page visual tour of this repo |
| Masterplan (DA1→DA2→DA3) | docs/masterplan/ + polished PDF docs/VoltHub-CSMS-Engineering-Masterplan.pdf |
| Architecture (1 page + diagram) | ARCHITECTURE.md |
| Decision records | docs/adr/0001 modular monolith · 0002 TimescaleDB (4.90/5) · 0003 outbox pipeline · 0004 plain-JS divergence · 0005 store adapter · 0006 connector FK-native · 0007 OCPP remote commands · 0008 AI advisory sidecar · 0009 multi-tenancy (deferred) · 0010 FC-HCC control loop · 0011 control-path integrity · 0012 twin profile compliance · 0013 benchmark methodology · 0014 control-loop closure · 0015 acceptance-envelope scheduling · 0016 wallet signature rail · 0017 carbon-aware charging · 0018 OCPP management commands |
| OCPP conformance | docs/ocpp-1.6-conformance.md — implemented messages, per-command status sets, explicit gaps, test evidence |
| Wallet rail (signed authorisations) | docs/wallet.md — EIP-191 personal_sign, single-use challenge, amount inside the signed text, server-side signer recovery, replay refused with 409 ALREADY_SETTLED; no card data, no keys, no gas (ADR-0016) |
| Security posture | SECURITY.md |
| Contributing (the full bar) | CONTRIBUTING.md — testing ladder, ground rules, ADR-first changes |
| Performance methodology | docs/perf.md |
| REST contract (OpenAPI 3.0) | docs/openapi.json — generated snapshot of the live /api/v1/docs spec (npm run openapi:gen); route coverage drift-gated and snapshot freshness byte-checked in CI |
| AI platform (advisory-only) | docs/ai-platform.md — three-dataset forecasts, anomaly triage, smart-charging LP, model card, explanations, drift; measured CPU receipts; guardrails in code (ADR-0008) |
| CarbonPilot (advisory-only) | docs/adr/0017-carbon-aware-charging.md — average vs marginal intensity, receipts and why carbon cannot block a session |
| Demo beats | docs/demo-script.md |
| ER / architecture / race diagrams | diagrams/*.mmd (Mermaid) |
| Verification receipts | docs/verification.md (every claim names what actually ran) |
MIT.
Also building Q-Trust — post-quantum cryptography migration & attestation: CBOM scanning, NIST/CNSA 2.0 scoring, GNN-ranked migration planning, attestations sealed on Base L2. The two projects share one discipline: trade-offs named, claims receipted, races proven in CI.

