1. Bối cảnh doanh nghiệp: tại sao kiểm thử phát hiện gian lận là bài toán khó
Hãy hình dung một ví điện tử fintech phục vụ mười triệu người dùng, xử lý khoảng ba nghìn giao dịch mỗi giây vào giờ cao điểm, với thoả thuận mức dịch vụ yêu cầu quyết định chặn hay cho qua trong dưới hai trăm mili-giây. Hệ thống phát hiện gian lận đứng giữa người dùng và tiền: nếu nó chặn nhầm khách thật, ta mất doanh thu và uy tín; nếu nó cho lọt kẻ gian, ta mất tiền mặt và có thể vi phạm quy định chống rửa tiền. Đây là một hệ thống mà cả hai kiểu sai đều đắt, nên kiểm thử phải đo được cả hai chiều chứ không chỉ 'hệ thống chạy'.
Vì sao bài toán khó? Thứ nhất, dữ liệu thật cực kỳ nhạy cảm: số thẻ, số dư, thông tin định danh KYC, không thể bê nguyên vào môi trường test. Thứ hai, gian lận là kẻ thù thích nghi: mẫu tấn công thay đổi liên tục, nên tập test tĩnh nhanh chóng lỗi thời. Thứ ba, tín hiệu rất mất cân bằng: tỉ lệ giao dịch gian lận thực sự có thể dưới một phần nghìn, khiến độ chính xác tổng thể trở thành thước đo vô nghĩa. Đây chính là nơi một AI agent có ích: nó giỏi sinh ra nhiều biến thể kịch bản nghi vấn và dữ liệu tổng hợp để mở rộng vùng phủ, miễn là con người giữ chặt oracle.
2. Oracle trước tiên: định nghĩa đúng-sai trước khi cho agent chạy
Sai lầm phổ biến nhất là thả agent vào hệ thống rồi hỏi 'giao dịch có bị chặn không'. Câu hỏi đó không phải oracle, vì bản thân việc chặn hay không chưa nói lên đúng-sai. Oracle thật phải neo vào sự thật đã biết: với một giao dịch ta phải biết trước nó nên bị chặn (nhãn gian lận) hay nên cho qua (nhãn sạch), rồi so sánh quyết định của hệ thống với nhãn đó. Nếu không có nhãn, ta không thể phân biệt true positive với false positive, và mọi con số đều là ảo tưởng.
Vì vậy chúng ta liệt kê các bất biến nghiệp vụ làm oracle chính. Giới hạn KYC: tài khoản chưa xác minh không được vượt hạn mức nạp/rút hằng ngày. Velocity: quá n giao dịch trong t giây từ cùng một thiết bị phải kích hoạt cờ nghi vấn. Geo: đăng nhập ở hai quốc gia cách nhau vô lý trong thời gian ngắn là bất khả thi vật lý. Device: thiết bị lạ chưa từng thấy cho một khoản lớn cần bước xác thực thêm. Bảo toàn số dư: tổng tiền vào bằng tổng tiền ra cộng chênh lệch số dư, không được tự sinh hay bốc hơi. Idempotency: cùng một khoá giao dịch gửi lại nhiều lần chỉ tạo đúng một hiệu ứng.
// oracle.ts — bất biến nghiệp vụ là NGUỒN CHÂN LÝ; agent không được sửa file này.
export interface Txn {
id: string; accountId: string; amount: number; currency: "VND";
deviceId: string; ip: string; countryFromIp: string; ts: number;
kycVerified: boolean; idempotencyKey: string;
}
export interface Decision { blocked: boolean; reasons: string[]; }
// Ngưỡng do RỦI RO/COMPLIANCE ký duyệt — người sở hữu, không phải agent.
export const LIMITS = {
unverifiedDailyTopUp: 20_000_000, // 20tr VND
velocityCount: 10, velocityWindowSec: 60,
impossibleTravelKmPerHour: 900, // nhanh hơn máy bay => bất khả thi
stepUpAmount: 50_000_000,
};
// Oracle KYC: tài khoản chưa xác minh KHÔNG được vượt hạn mức ngày.
export function oracleKyc(txn: Txn, todayTopUp: number): boolean {
if (txn.kycVerified) return true; // không áp trần này
return todayTopUp + txn.amount <= LIMITS.unverifiedDailyTopUp;
}
// Oracle bảo toàn số dư: không tiền nào tự sinh/bốc hơi.
export function oracleBalanceConserved(before: number, after: number, delta: number): boolean {
return Math.abs((before + delta) - after) < 1e-6;
}3. Mô hình dữ liệu và luồng quyết định của engine gian lận
Để kiểm thử tử tế, ta cần hiểu rõ mô hình dữ liệu và luồng ra quyết định. Mỗi giao dịch tham chiếu tới tài khoản, thiết bị, phiên đăng nhập và một khoá idempotency. Engine gian lận tính một điểm rủi ro từ nhiều tín hiệu: lịch sử velocity, khoảng cách địa lý so với lần trước, độ quen của thiết bị, và một số quy tắc cứng như trần KYC. Điểm rủi ro so với ngưỡng cho ra quyết định chặn, cho qua, hay yêu cầu xác thực thêm. Điểm mấu chốt: quyết định phải xác định và có thể tái lập với cùng đầu vào, nếu không ta không thể viết test ổn định.
// engine.ts — luồng quyết định (đơn giản hoá). QUYẾT ĐỊNH PHẢI XÁC ĐỊNH.
import { Txn, Decision, LIMITS } from "./oracle";
export function velocityFlag(recent: Txn[], now: number): boolean {
const inWindow = recent.filter(t => now - t.ts <= LIMITS.velocityWindowSec * 1000);
return inWindow.length >= LIMITS.velocityCount;
}
// khoảng cách/thời gian => tốc độ di chuyển ngụ ý; nhanh bất khả thi => cờ.
export function impossibleTravel(prevKm: number, dtHours: number): boolean {
if (dtHours <= 0) return prevKm > 0;
return (prevKm / dtHours) > LIMITS.impossibleTravelKmPerHour;
}
export function decide(txn: Txn, ctx: {
recent: Txn[]; prevKm: number; dtHours: number; deviceKnown: boolean;
}): Decision {
const reasons: string[] = [];
if (velocityFlag(ctx.recent, txn.ts)) reasons.push("VELOCITY");
if (impossibleTravel(ctx.prevKm, ctx.dtHours)) reasons.push("GEO");
if (!ctx.deviceKnown && txn.amount >= LIMITS.stepUpAmount) reasons.push("DEVICE_STEPUP");
// quy tắc cứng: trần KYC là chặn tuyệt đối
// (todayTopUp lấy từ ledger; ở đây minh hoạ)
return { blocked: reasons.length > 0, reasons };
}- Mỗi giao dịch có khoá idempotency duy nhất để chống double-charge khi retry.
- Điểm rủi ro là hàm xác định của tín hiệu — không dùng thời gian ngẫu nhiên bên trong.
- Ngưỡng và quy tắc nằm trong cấu hình có phiên bản, thay đổi phải qua duyệt.
💬 Bình luận (0)
Chưa có bình luận nào. Hãy là người đầu tiên!