Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 4 additions & 23 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,29 +75,10 @@ Each module, before moving to the next, must ship with:
| `shared` / `blockchain` foundations | ✅ Done (Phase 4) |
| `auth` | ✅ Done — register/login/refresh/logout/verify-email/password-reset, RBAC guard, 42 passing unit/infra tests + skip-gated Prisma/API integration tests |
| `users` | ✅ Done — profile read, wallet linking (challenge/signature via real Stellar ed25519 verification), wallet list/unlink, RBAC-ready |
| `indexer` | ✅ Done — full scope, all five contracts with a consuming module (`escrow_contract`, `delivery_contract`, `fleet_management_contract`, `dispute_resolution_contract`, `identity_reputation_contract`; `settlement_contract` permanently excluded — unimplemented stub, `PHASE_1_DOMAIN_ANALYSIS.md` §8), see `EVENT_INDEXER.md` — checkpointed idempotent polling, generic ScVal XDR decoder, BullMQ repeatable job + worker, `GET /health/indexer`, verified against real Postgres and the real public testnet RPC |
| `indexer` | ✅ Done (minimal scope: `escrow_contract` + `delivery_contract` only, see `EVENT_INDEXER.md`) — checkpointed idempotent polling, generic ScVal XDR decoder, BullMQ repeatable job + worker, `GET /health/indexer`, verified against real Postgres and the real public testnet RPC |
| `deliveries` | ✅ Done — read model synced from indexed events (with a supplementary `get_delivery` read call to hydrate the sparse `delivery_created` event), unsigned-XDR builders for all six `delivery_contract` calls, real ScVal struct/enum encoding verified by construction + round-trip (not yet against a live deployment — see `EVENT_INDEXER.md`) |
| `escrow` | ✅ Done — read model synced from indexed events (delivery id read from the event *topic*, not the payload — verified against `escrow_contract`'s distinct convention; `dispute_resolved`'s release/refund ambiguity resolved via a supplementary `get_escrow` read call), unsigned-XDR builders for `create_escrow`/`release_escrow`/`refund_escrow` (dispute-resolution calls deliberately deferred to the future `disputes` module), real ScVal struct/enum encoding verified by construction + round-trip |
| `fleet` | ✅ Done — read model synced from indexed events (every `fleet_management_contract` event carries everything needed directly, unlike escrow/deliveries — no supplementary read call required for sync), unsigned-XDR builders for all five mutating calls (`register_fleet`/`update_fleet_treasury`/`add_driver_to_fleet`/`accept_fleet_invite`/`remove_driver_from_fleet`), plus a live `get_payout_address` read (a derived on-chain view with no corresponding event); `fleet_id` verified as a bare `u64`, no tuple-struct wrapping |
| `disputes` | ✅ Done — read model reconciling **both** on-chain dispute layers (`dispute_resolution_contract`'s five events plus `escrow_contract`'s `delivery_disputed`) into one `Dispute` row per delivery (Phase 1 §5); unsigned-XDR builders for all five `dispute_resolution_contract` mutating calls; evidence upload (local-filesystem storage for v1, sha256 content hash) plus read-time cross-verification of each stored hash against a live `get_dispute` call. `delivery_id` verified as the tuple-wrapped `DeliveryId` struct, unlike `escrow_contract`'s bare `u64`. Documented gaps: `senderShareBps` is never observable from any on-chain event, and a dispute resolved purely through `escrow_contract`'s Layer A (bypassing `dispute_resolution_contract` entirely) stays `OPEN` in this read model — see `EVENT_INDEXER.md` |
| `reputation` | ✅ Done — canonical driver reputation read model sourced from `identity_reputation_contract` (Phase 1 §12 decision: canonical over `delivery_contract`'s own separate, legacy counter); every mutating event (`driver_registered`/`kyc_status_updated`/`reputation_increased`/`reputation_decreased`) triggers a full `get_driver_profile` refresh rather than reimplementing the on-chain `+5+3+2`/cap-at-100 scoring formula locally (ROADMAP §13's no-duplicated-business-logic rule); `tier` recomputed locally as a pure function of score (Bronze/Silver/Gold thresholds verified against `get_driver_tier`); `legacyDeliveriesCompleted` opportunistically refreshed via a second, independent read against `delivery_contract` on the same events, allowed to fail without regressing a previously-known value to 0. Unsigned-XDR builders for `register_driver`/`update_driver_kyc_status` only — `increase_reputation`/`decrease_reputation`/`register_user` deliberately have no builder (see `API_REFERENCE.md`) |
| `notifications` | ✅ Done — dispatches a `Notification` row (channel `EMAIL`) off a deliberately narrow set of blockchain events chosen for carrying a directly-available, worth-notifying actor address (`delivery.driver_assigned`, `escrow.delivery_disputed`, `escrow.escrow_released`, `dispute-resolution.dispute_raised`, all four `identity-reputation` events, five `fleet` events — see `EVENT_INDEXER.md` for exactly which events were excluded and why, which is three different reasons, not one); resolves the address to a local account via a direct (and deliberately documented-as-an-exception) read of `users`/`wallet_addresses`; enqueues a BullMQ delivery job the worker process consumes via the default `NotificationSender` (logs instead of sending real email, same genuinely-functional-dev-default pattern as `auth`'s `Mailer`). Fixed a real, previously-untested gap while wiring this module's own worker: every module's event-subscription wiring only ran in the `api` process, not the `worker` process where the indexer's poll job (the sole publisher) actually runs — see `src/workers/index.ts` and `EVENT_INDEXER.md`'s "Process-boundary correction." `GET /notifications`, `GET /notifications/:id` only — no build endpoints, nothing on-chain to build a transaction for |
| `analytics` | ✅ Done — four read-only aggregate endpoints (`GET /analytics/gmv`, `/completion-rate`, `/dispute-rate`, `/driver-tiers`), `ADMIN`-gated. The one module that reads `deliveries`/`escrows`/`disputes`/`driver_profiles` directly rather than through each owning module's use cases — documented by design (`ARCHITECTURE.md` §4/§10), not an exception. GMV is grouped by token, never summed across tokens; dispute-rate counts every delivery *ever* disputed (the `disputes` table, one row per delivery) rather than a `DISPUTED`-status snapshot, which would undercount once a dispute resolves and the delivery moves on. No time-range filtering in v1 — every figure is all-time |
| `fraud-detection` | ✅ Done — one endpoint (`GET /fraud-detection/actors/:address`, `ADMIN`-gated), evaluating three v1 rule-based velocity heuristics fresh on every call against a durable, append-only `ActorActivity` log this module's own event handler writes to (`DELIVERY_CREATION_VELOCITY`, `ESCROW_RELEASE_VELOCITY`, `DISPUTE_RAISE_VELOCITY` — chosen to match `ARCHITECTURE.md` §4's "delivery/escrow/dispute velocity per actor" as closely as the actually-available event payloads allow). Writes synchronously in its event handler (no BullMQ queue, unlike `notifications`) — a single fast `INSERT` has no failure-prone external channel to isolate from. ML-based scoring and configurable/tunable thresholds are both out of scope for v1, documented future work (`ROADMAP.md` §9) |
| `admin` | ✅ Done — three `ADMIN`-gated endpoints: `GET /admin/disputes` (open-dispute review list, reading `disputes`/`deliveries` directly — the same documented cross-module-read exception `analytics` established, not a new one), `POST /admin/users/:id/role` (off-chain-only role assignment, the third module to touch the shared `users` table directly after `auth`/`users` themselves), and `GET /admin/audit-log` (reads the `audit_logs` table `ARCHITECTURE.md` §4 planned back in Phase 3/4 but nothing had written to until now). Deliberately does **not** build a fourth `POST /admin/disputes/:deliveryId/resolve` path to the same on-chain calls `disputes` already exposes — `admin`'s frontend calls those directly once armed with the review list, avoiding duplicated business logic. No shared "audit-logging decorator" — `admin` is the only consumer so far, so audit-log writing stays module-local rather than speculatively generalized |

### Phase 6 — Hardening & Release Readiness ✅ Complete
Not part of the original task-brief phase gate (§5's Phases 1–5 are) — this formalizes what M8/M9 (§6) already named as the work left after every module shipped: the codebase is feature-complete but has never had a dedicated security pass, has no metrics endpoint despite `OBSERVABILITY.md` planning one since Phase 4, has never been load-tested, and `docker compose up` — the actual deployment runbook — was never verified end-to-end (Phase 4's own DoD flagged this explicitly: no Docker was available in the sandbox that scaffold was built in).

**DoD:**
- ✅ Security review pass completed, real findings fixed (`disputes` evidence IDOR + unrestricted upload — see `SECURITY.md`'s "Security Review History"), `SECURITY.md` reflects actual (not just intended) posture.
- ✅ `GET /metrics` (Prometheus format) and `GET /health/queue` implemented and tested — both were `OBSERVABILITY.md`-planned, not built until now.
- ✅ A local Prometheus + Grafana stack (`docker compose --profile observability up`) scrapes `/metrics` and renders a real starter dashboard against live data — verified visually via Prometheus's own target-health API and Grafana's datasource proxy, not just "the endpoint returns 200."
- ✅ A load test run against the real running server (the actual Docker deployment, not just `pnpm dev`), results documented in `OBSERVABILITY.md`.
- ✅ The full `docker compose up` stack (`api` + `worker` + `postgres` + `redis`, all four, built from the real `Dockerfile`) verified booting and serving traffic — the thing `DEPLOYMENT.md` had described since Phase 4 without ever having been run. Found and fixed four real, previously-latent bugs in the process (missing `.dockerignore`, a Prisma-client-copy step broken under pnpm, native build scripts silently skipped by a pnpm default, missing OpenSSL in the base image) — see `DEPLOYMENT.md`'s "Status" section for detail.
- ✅ `v1.0.0` tagged.

**Status:** Complete.
| `escrow`, `fleet`, `disputes`, `reputation` | Pending |
| `notifications`, `analytics`, `fraud-detection`, `admin` | Pending |

## 6. Milestones & Deliverables

Expand Down Expand Up @@ -189,4 +170,4 @@ Not part of the original task-brief phase gate (§5's Phases 1–5 are) — this

---

**Current status:** All twelve Phase 5 modules complete — `auth`, `users`, `indexer` (full scope — all five contracts with a consuming module), `deliveries`, `escrow`, `fleet`, `disputes`, `reputation`, `notifications`, `analytics`, `fraud-detection`, and `admin`. Phase 5's final listed step, "indexer completed for remaining event types," is satisfied as a consequence of the above — `indexer`'s tracked-contract scope has covered every contract with a consuming module since `disputes`/`reputation` shipped, not a separate remaining task (see `EVENT_INDEXER.md`'s "Current Scope" section). See §6 (Milestones & Deliverables, M8/M9) for what's still open before a v1.0.0 tag — security review, observability dashboards, a load test pass, and a deployment runbook validated on a real environment, none of which are per-module work.
**Current status:** Phase 5 in progress. `auth`, `users`, `indexer` (minimal scope), and `deliveries` modules complete. Next: `escrow`.
Loading
Loading