Skip to content
GitLab
Projects Groups Topics Snippets
  • /
  • Help
    • Help
    • Support
    • Community forum
    • Submit feedback
    • Contribute to GitLab
  • Register
  • Sign in
  • A Ahad Mobile
  • Project information
    • Project information
    • Activity
    • Labels
    • Members
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributor statistics
    • Graph
    • Compare revisions
  • Issues 0
    • Issues 0
    • List
    • Boards
    • Service Desk
    • Milestones
  • Merge requests 1
    • Merge requests 1
  • CI/CD
    • CI/CD
    • Pipelines
    • Jobs
    • Schedules
  • Deployments
    • Deployments
    • Environments
    • Releases
  • Packages and registries
    • Packages and registries
    • Package Registry
    • Terraform modules
  • Monitor
    • Monitor
    • Incidents
  • Analytics
    • Analytics
    • Value stream
    • CI/CD
    • Repository
  • Wiki
    • Wiki
  • Snippets
    • Snippets
  • Activity
  • Graph
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
Collapse sidebar
  • holatech
  • Ahad Mobile
  • Merge requests
  • !18

fix: Phase 0 on/off-ramp hardening — vá 5 lỗ nghiêm trọng trong luồng tiền

  • Review changes

  • Download
  • Patches
  • Plain diff
Merged Khanh Pham requested to merge fix/onoff-ramp-p0-hardening into develop Aug 24, 2026
  • Overview 0
  • Commits 16
  • Pipelines 1
  • Changes 47

Phase 0 của lộ trình on/off-ramp (Epic 8): vá 5 lỗ nghiêm trọng trong luồng tiền để các phase sau có nền đứng. Không thêm tính năng ramp nào — đây là nhánh hardening.

Spec: docs/superpowers/specs/2026-08-23-onoff-ramp-design.md · Plan: docs/superpowers/plans/2026-08-23-onoff-ramp-p0-hardening.md (cả hai nằm trong docs/ đang gitignore, đọc ở local).


⚠️ BA ĐIỀU NGƯỜI DEPLOY PHẢI BIẾT TRƯỚC

1. Production sẽ TỪ CHỐI KHỞI ĐỘNG cho tới khi ops chạy yarn fireblocks:fetch-assets. FIREBLOCKS_ASSETS_PRODUCTION vẫn là mảng rỗng — có chủ ý. Không thể tự điền vì id/blockchainId là UUID do Fireblocks cấp, khác nhau giữa sandbox và production; điền sai còn tệ hơn để rỗng vì nó tạo giao dịch on-chain sai asset. Trước đây danh sách rỗng khiến service boot bình thường rồi hỏng lặng lẽ ở lần rút tiền đầu tiên. Nay nó hỏng to tiếng lúc boot. Script cần credential Fireblocks production.

2. Migration 1787320000002 sẽ CHẶN DEPLOY nếu staging/prod còn hàng accounts có card_id số dư khác 0. Đó là chốt chặn cố ý — nó đứng giữa migration và việc âm thầm giấu tiền của khách. Kiểm trước khi deploy:

SELECT count(*), sum(balance::numeric) FROM accounts WHERE card_id IS NOT NULL AND deleted_at IS NULL;

Khác 0 → phải đối soát tay với user_cards.balance trước.

3. Người giữ EUR/GBP/HKD/SGD nay nhận 400 khi nạp thẻ. Trước đây nạp 200 EUR bị trừ 200 EUR và được 200 USD tiêu ở thẻ (user_cards.balance là USD theo thiết kế). Vế cộng cũ rơi vào sổ chết nên sai lệch nằm im; nhánh này làm vế cộng sống nên phải chặn. FX là việc của Epic 8.


Năm lỗ đã bịt

# Lỗ Commit
1 Webhook Reap chưa bao giờ kiểm chữ ký. Controller gán cả object {isValid, traceId} vào biến tên isValid rồi test if (!isValid) — object luôn truthy nên nhánh từ chối chưa từng chạy. Mọi webhook giả mạo đều được xử lý như thật. bbda772
2 Danh sách asset production rỗng → xem mục ⚠️1. 1eeb80e 9199b9c
3 Sổ crypto không chứa nổi vị thế hệ thống. user_crypto_balances.user_id là NOT NULL và helper trả {0,0} cho user null, nuốt mọi bút toán đối ứng của hệ thống. a26ecf8 c68fb00
4 Nút in tiền của admin. Một admin CREDIT được số tiền crypto bất kỳ cho user bất kỳ: không trần, không đối ứng, không duyệt hai người — và số dư đó rút được on-chain. Nay: trần cấu hình được, bút toán đối ứng bắt buộc, duyệt bởi admin thứ hai, row lock chống duyệt trùng. 2434034 f474715 b4e05db
5 Nạp thẻ ghi vào sổ bóng. Credit rơi vào hàng accounts khoá theo card_id mà không ai đọc, trong khi app đọc user_cards.balance. 28ec8b9 1231986 4ff73a8 bc530f4
final CRITICAL mà cả 5 review từng task bỏ lọt (xem dưới) + 3 finding khác e3e54a8…65c0a4c

Con bug đáng chú ý nhất

Task 3 đổi index unique trên user_crypto_balances thành partial (WHERE user_id IS NOT NULL), nhưng nhánh user trong cùng file vẫn phát ON CONFLICT ("user_id","token_symbol") không kèm index_predicate. Postgres không tự suy ra partial index cho ON CONFLICT → mọi đường ghi số dư crypto đều hỏng (nạp, rút, hai vế chuyển, hoàn tiền, conversion, sync).

Nó lọt qua 5 vòng review vì test khẳng định đúng cái hình dạng đã hỏng, trên một query builder giả lập. 569 test xanh không nói gì về SQL thật. Chỉ lộ ra khi review toàn nhánh chạy SQL thật vào Postgres.

Đã sửa bằng indexPredicate và chứng minh bằng chạy thật: hình dạng cũ báo no unique or exclusion constraint matching the ON CONFLICT specification; hình dạng mới chạy hai lần mỗi đường để lại đúng một hàng user và một hàng hệ thống.


Kiểm chứng

  • yarn test → 580/580 pass, 89 suite
  • yarn build → sạch
  • Ba migration đã chạy thật vào Postgres 16, không chỉ compile-check
  • yarn lint → 254 vấn đề có sẵn từ trước ở file không liên quan, cố ý không đụng (nguyên tắc thay đổi tối thiểu). File nhánh này chạm vào đều sạch.

Hai điểm đã park, cần biết

fireblocks-ledger.service.ts:324-326 — nếu cả hai vế transfer là null-user, cả hai balanceAfter cùng lấy từ một lần đọc số dư hệ thống rồi ghi tuần tự, làm mất vế nợ. Và test mới đang khẳng định hành vi đó là đúng. Hôm nay không caller nào chạm tới được. Phase 1 seed hàng quỹ là chạm tới — phải xem lại test đó trước khi seed.

fireblocks-ledger.upsert.postgres.spec.ts chạy trong yarn test mặc định và INSERT vào bảng users thật của bất kỳ DB nào biến DB_* đang trỏ tới (có rollback). Local thì ổn. Trên CI có credential staging thì nó ghi vào staging. Đề nghị gate sau env flag opt-in trước khi nhánh này chạm CI.

Chưa test được end-to-end

Endpoint điều chỉnh số dư admin (POST fireblocks/balance/adjust và .../:id/approve) trả 403 với mọi tài khoản: permission fireblocks:write không được seed ở đâu, guard fail-closed. An toàn hôm nay, nhưng nghĩa là đường đó chưa từng chạy thật lần nào. Seed permission phải là quyết định có chủ đích, không phải tiện tay.


🤖 Generated with Claude Code

Assignee
Assign to
Reviewers
Request review from
Time tracking
Source branch: fix/onoff-ramp-p0-hardening