Bảo đảm bất biến: một thay đổi trạng thái nghiệp vụ và audit event của nó cùng commit hoặc cùng rollback.
Không thêm feature, không đổi API, không đổi audit model, không migration. Chỉ chuẩn hoá transaction boundary.
Nguồn sự thật code:provisioning/provisioning-service.ts,kernel/context-builder.ts(booking),adapters/prisma-audit-repository.ts.
ProvisioningService ghi audit sau khi state đã commit, mỗi lệnh prisma.X là một transaction ngầm riêng:
customer.update(...) // commit #1
provisioning.updateMany() // commit #2
writeAudit(...) // commit #3 ← nếu FAIL ⇒ state đã commit, audit MẤT (mồ côi)
Gói mọi state change + audit append của một thao tác vào MỘT prisma.$transaction. Audit repo ghi bằng **cùng tx client** ⇒ nguyên tử.
return this.prisma.$transaction(async (tx) => {
const customer = await tx.customer.create(...); // state
await tx.provisioning.create(...); // state
await this.writeAudit(tx, "partner.created", ...); // audit — CÙNG tx
return { partnerId: customer.id };
});
| Vùng | Boundary | Cơ chế |
|---|---|---|
| Provisioning (Partner/Tenant/Key) — Beta-6 | 1 transaction/thao tác, gồm mọi state change + 1 audit append | prisma.$transaction(async (tx) => …); writeAudit(tx, …) |
| Booking (Reservation) — đã có từ Beta-1/2 | 1 transaction/use case, gồm reservation + outbox + audit | KernelContextBuilder.runInTenantScope → prisma.$transaction; audit sink bound ctx.tx |
Các thao tác Provisioning được bọc tx: createPartner, activate/suspend/reactivate/revokePartner (transitionPartner), issueKey, rotateKey, revokeKey, provisionTenant, activate/suspend/reactivate/archiveTenant (transitionTenant).
| Bước fail trong tx | Hành vi |
|---|---|
State change ném (vd guard transition sai, PartnerNotFoundError) | Tx rollback: 0 state, 0 audit. Lỗi propagate (CLI → exit 2). |
| Audit append ném (DB lỗi / seam giả lập) | Tx rollback: state KHÔNG commit, 0 audit. Thao tác báo lỗi. |
AuditEvent.record trả ok:false (input không hợp lệ) | writeAudit NÉM (fail-closed) ⇒ rollback (không commit state không audit). |
| Mọi bước thành công | Commit nguyên tử: state + đúng 1 audit cùng tồn tại. |
AuditEvent (append-only) + PrismaAuditRepository. Beta-6 chỉ đổi *ai truyền client nào* (tx thay vì this.prisma).ProvisioningService nhận tuỳ chọn appendAudit(db, event) — mặc định new PrismaAuditRepository(db).append(event). Test tiêm hàm ném lỗi để giả lập "audit down" và khẳng định business rollback. Production dùng mặc định ⇒ hành vi không đổi.
createAuditFactory) hiện bỏ qua khi AuditEvent.record trả ok:false (input không hợp lệ). Booking đã nguyên tử với mọi lỗi ghi audit THẬT (append lỗi trong tx ⇒ rollback); trường ok:false chỉ xảy ra với input không hợp lệ — không phát sinh cho reservation.* (id/tenantId/action luôn hợp lệ). Đề xuất Beta-7: cho sink này fail-closed để đồng nhất với Provisioning. Xem AUDIT-FAILURE-MATRIX.md.