Skip to content

feats #29 #30 #31 #32 - #143

Merged
dev-fani merged 5 commits into
fanilabs:mainfrom
williamspatrivk-rgb:fix/remove-claude-coauthor
Aug 31, 2026
Merged

feats #29 #30 #31 #32#143
dev-fani merged 5 commits into
fanilabs:mainfrom
williamspatrivk-rgb:fix/remove-claude-coauthor

Conversation

@williamspatrivk-rgb

Copy link
Copy Markdown
Contributor

Summary

Remove Claude Sonnet 5 co-authorship from four key feature commits:

  • feat(auth): implement local accounts, JWT sessions, and RBAC
  • feat(users): profile read + Stellar wallet linking via ed25519 signatures
  • feat(indexer): checkpointed, idempotent Soroban event polling (escrow + delivery)
  • feat(deliveries): read model, get_delivery hydration, unsigned XDR builders

Each commit had "Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com" removed from the commit message.

Closes #29
Closes #30
Closes #31
Closes #32

First fully implemented module (Phase 5), following the Clean Architecture
layering from ARCHITECTURE.md end to end: domain ports/entities/errors,
application use cases, Prisma/bcrypt/JWT infrastructure adapters, and thin
Fastify routes composed in a single module root.

- register/login/refresh (rotation-on-use)/logout, email verification, and
  password reset (self-invalidating via a password-hash fingerprint embedded
  in the token, no revocation table needed).
- Shared src/shared/jwt (access/refresh sign+verify) and a route-level
  authenticate/requireRole guard in src/shared/http, reusable by every
  future module's protected routes.
- eslint-plugin-boundaries rules extended: domain may depend on the shared
  error hierarchy (pure TS, no framework coupling), and a new `module-root`
  element type is the only place per module allowed to see all four layers.
- Test strategy: in-memory port fakes for fast application-layer unit tests
  (fixed a real fake-encoding bug this caught), real bcrypt/JWT unit tests
  (no DB needed), and Prisma-repository + full HTTP integration tests that
  auto-skip (not fail) without a reachable database via a new
  src/shared/testing/database.ts helper — CI's Postgres service container
  runs them for real.
- Two real bugs found and fixed by actually booting the compiled server and
  curling it: (1) the Redis-backed rate limiter had no skipOnError, so a
  Redis outage 500'd every route including /health; (2) an unused
  `export { TokenExpiredError } from 'jsonwebtoken'` crashed the whole
  process at startup under Node's ESM/CJS interop.
- docs/AUTHENTICATION.md and docs/API_REFERENCE.md updated to describe the
  shipped behavior instead of the earlier design sketch.
…ures

Second Phase 5 module, same Clean Architecture pattern as auth: domain
ports/entities/errors, application use cases with in-memory-fake unit
tests, real infrastructure adapters, thin Fastify routes, one composition
root registered in app.ts.

- GET /users/me, GET /users/me/wallets, POST .../wallets/challenge,
  POST .../wallets/confirm, DELETE .../wallets/:id — all behind the shared
  authenticate guard from the auth module's work.
- Wallet linking is a stateless challenge/response flow (a short-TTL signed
  JWT is the challenge string itself, no extra DB table) verified with a
  real ed25519 signature check via stellar-sdk's Keypair.fromPublicKey(...)
  .verify(...) — the same primitive SEP-10 web-auth uses. Never touches a
  private key.
- `users` keeps its own narrow UserRecord type rather than importing auth's
  domain User, per the "no module imports another module's domain layer"
  rule.
- Tests: fast in-memory-port unit tests for every use case, real-crypto
  unit tests for the Stellar verifier (genuine Keypair.random() sign/verify
  round-trips, forged-signature and wrong-address rejection), and
  Prisma/API integration tests that auto-skip without a reachable database.
- Verified end-to-end against the compiled server: register -> login ->
  request challenge -> sign with a real generated keypair -> confirm ->
  list -> unlink, plus a forged-signature rejection, all over real HTTP.
… + delivery)

Third Phase 5 module. Minimal scope per ROADMAP.md: escrow_contract and
delivery_contract only, enough to unblock the deliveries/escrow modules
next — the polling engine itself is fully contract-agnostic, so tracking
the remaining four contracts later is a config addition, not new
architecture.

- pollContractEvents: resume from a persisted checkpoint (or the current
  chain tip on first run — no genesis backfill), fetch, idempotently store
  each event keyed by the Soroban RPC's own globally-unique event id, and
  only advance the checkpoint after every event in the batch is durably
  persisted.
- A generic ScVal->native XDR decoder (src/blockchain/xdr/sc-val.ts) —
  stellar-sdk 12.x doesn't ship scValToNative like later majors do, so this
  is a scoped equivalent covering the variants FaniLab's contracts actually
  use, verified against real constructed ScVal values including a genuine
  Keypair-derived address and signed i128 hi/lo-word reconstruction.
- Adjusted the BlockchainEvent schema before any migration existed to key
  on (contractName, network, rpcEventId) — the RPC's own monotonic event id
  — instead of a hand-rolled (txHash, eventIndex) composite.
- BullMQ repeatable-job scheduling + worker (src/modules/indexer/infrastructure/queue.ts),
  registered into the shared worker process entrypoint.
- GET /health/indexer: per-contract lag against the live chain tip,
  503 when any tracked+configured contract exceeds INDEXER_LAG_ALERT_LEDGERS.
- New shared/events in-process pub/sub — the indexer publishes every newly
  ingested event; no subscribers yet since deliveries/escrow don't exist,
  which is normal, not premature.
- Tests: fake-based unit tests for the polling/health use cases, a Prisma
  integration suite for checkpoint/event-store idempotency, and — since no
  FaniLab contracts are deployed anywhere reachable from this environment —
  a suite that exercises the full client -> RPC -> decode pipeline against
  the real public Soroban testnet RPC (verified live: real ledger reads,
  a real RPC rejection of the all-zero contract address caught and fixed,
  and a well-formed empty result for a random valid-but-undeployed one).
  All DB/RPC-dependent suites skip honestly, never falsely pass or fail,
  when nothing is reachable.
…ilders

Fourth Phase 5 module and the first to actually invoke a Soroban contract
(the previous three either had no chain interaction or only read chain
metadata). Same layering as prior modules, plus:

- Extended SorobanClient with getAccount/prepareTransaction/simulateTransaction
  and added two generic, contract-agnostic helpers every future contract-
  calling module builds on: buildInvokeTransaction (write path: build ->
  simulate -> assemble -> unsigned XDR) and simulateReadCall (read-only
  queries, no signing/account required).
- A generic ScVal encoder (src/blockchain/xdr/sc-val.ts) implementing the
  documented Soroban #[contracttype] conventions stellar-sdk 12.x doesn't
  ship a helper for: tuple/newtype structs as one-element Vecs, unit enums
  as a Vec of their Symbol name, named-field structs as a Map sorted by
  field name — plus delivery_contract-specific mapping on top
  (DeliveryId/DeliveryMetadata/CargoDescriptor/DeliveryRecord).
- delivery_created's on-chain event only carries (delivery_id, sender) — no
  metadata — so the sync handler makes a supplementary get_delivery read
  call to hydrate the full record; every other delivery_contract event
  carries enough in its own payload to update the read model directly.
  confirm_delivery's event has no timestamp either, so deliveredAt uses the
  indexer's ledger-close time instead.
- Threaded on-chain ledger-close time (closedAt) through the indexer's
  event pipeline end to end (RawContractEvent -> StoredEvent ->
  BlockchainEvent.ledgerClosedAt -> BlockchainEventEnvelope) since no
  migration existed yet to make retrofitting it costly.
- 6 unsigned-transaction-build endpoints (create/assign/mark-in-transit/
  confirm/cancel/raise-dispute) plus public GET /deliveries(/:id) — auth
  required on the build endpoints (anti-abuse: each does a real RPC account
  fetch + simulate), reads are public like a block explorer. An
  unconfigured DELIVERY_CONTRACT_ID degrades to a clear 502 rather than a
  boot-time crash, matching the indexer's "skip if not configured" pattern.
- Tests: real Keypair-signed round-trip verification of every encoder
  (including negative i128 hi/lo reconstruction and a real ed25519 address
  round-trip), a hand-built get_delivery response decoded end-to-end, and
  fake-based unit tests for the sync/build use cases. No FaniLab contract
  is deployed anywhere reachable from this environment, so the encoding is
  verified by construction and self round-trip, not against a live
  delivery_contract — flagged prominently in code comments and
  EVENT_INDEXER.md/API_REFERENCE.md as the first thing to verify once a
  real testnet deployment exists.
@dev-fani

Copy link
Copy Markdown
Contributor

Resolve conflicts @williamspatrivk-rgb

@williamspatrivk-rgb williamspatrivk-rgb changed the title Remove Claude as co-author from commits feats #29 #30 #31 #32 Aug 31, 2026
@drips-wave

drips-wave Bot commented Aug 31, 2026

Copy link
Copy Markdown

@williamspatrivk-rgb Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@dev-fani
dev-fani merged commit 48917d7 into fanilabs:main Aug 31, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment