Source document
docs/platform/AUDIT-TRANSACTION-DESIGN.md
Source version
0.0.0
Generated time
2026-07-06T14:59:21.727Z
Owner
Unassigned
Audience
Internal
Status
Approved

Audit Transaction Design (Beta-6)

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.

Vấn đề (từ Beta-4 / Beta-5)

ProvisioningService ghi audit sau khi state đã commit, mỗi lệnh prisma.Xmộ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)

Giải pháp

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 };
});

Transaction boundary

VùngBoundaryCơ chế
Provisioning (Partner/Tenant/Key) — Beta-61 transaction/thao tác, gồm mọi state change + 1 audit appendprisma.$transaction(async (tx) => …); writeAudit(tx, …)
Booking (Reservation) — đã có từ Beta-1/21 transaction/use case, gồm reservation + outbox + auditKernelContextBuilder.runInTenantScopeprisma.$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).

Failure scenario & rollback behavior

Bước fail trong txHà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ôngCommit nguyên tử: state + đúng 1 audit cùng tồn tại.

Invariants

  1. Atomicity: state change ⇔ audit event — cùng commit hoặc cùng rollback (không có trạng thái giữa chừng bền vững).
  2. No orphan state: không tồn tại state đã commit mà thiếu audit của nó.
  3. No orphan audit: không tồn tại audit của một thao tác mà state của nó bị rollback.
  4. Exactly-once per op: một thao tác thành công sinh đúng 1 audit event (không trùng, không thiếu).
  5. Fail-closed: không dựng/không ghi được audit ⇒ không commit state.
  6. Không đổi audit model: vẫn AuditEvent (append-only) + PrismaAuditRepository. Beta-6 chỉ đổi *ai truyền client nào* (tx thay vì this.prisma).

Seam kiểm thử (không đổi hành vi production)

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.

Ngoài phạm vi (ghi nhận trung thực)

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.