Chuyển đến nội dung
Phase 2: persistence và tenancy

Phase 2: persistence và tenancy

15 tháng 10, 2026

Đã xảy ra gì

Giai đoạn 2 thêm Postgres (pgx, goose migration nhúng trong binary), một scoped store có tenant lấy từ handle và từ chối khi thiếu (fail closed), API key lưu dưới dạng lookup cộng SHA-256, một auth middleware với limiter chạy trước bước xác thực, một ma trận authorization theo từng route, một audit trail chỉ thêm vào (append-only) mang theo trace id, platform bootstrap từ HANGO_BOOTSTRAP_ADMIN_KEY, và lệnh hango migrate. Test tích hợp chạy trên một Postgres testcontainers được dùng lại, mỗi package một database.

Kiểm chứng trực tiếp tìm ra hai lỗi mà test bỏ sót: build image thất bại vì Dockerfile không copy migrations/ cho embed, và dòng log request thiếu tenant id vì auth middleware chạy bên trong logging middleware. Cách sửa là một request-log holder có thể thay đổi, để middleware bên trong thêm attribute vào.

Review tìm thấy gì

  • Limiter chạy trước bước xác thực được khóa theo IP và key lookup, nên mỗi request dùng một key bịa khác nhau lại nhận một bucket mới: số lần tra database không bị chặn và map bucket phình ra vô hạn. Ai biết lookup của một key thật cũng có thể rút cạn bucket của key đó. Giờ limiter được khóa theo từng IP và giới hạn ở 16 x 4096 bucket, và một key đã xác thực trong mười phút gần nhất được qua dù bucket đã rỗng.
  • Platform có thể tạo key cho bất kỳ user chưa có key nào ở bất kỳ tenant nào. Giờ nó chỉ được tạo key đầu tiên của tenant, cho một owner.
  • “User đầu tiên” và “key đầu tiên” là các race kiểu kiểm tra rồi mới ghi. Giờ chúng chạy trong một transaction giữ select ... for no key update trên dòng tenant. Một test bắn 8 lần tạo owner đồng thời và nhận đúng một 201.
  • Comment của goose nói có advisory lock, nhưng goose chỉ khóa khi được truyền WithSessionLocker. Giờ nó được truyền: ba lần chạy migrate đồng thời trên database rỗng cho một applied=1 và hai applied=0.
  • Các sửa nhỏ hơn: một lần platform bị từ chối được audit trong tenant platform, một handler bị panic vẫn có một dòng audit, response của key gửi Cache-Control: no-store, và đối số CLI lạ thì thoát với mã 2.

Quyết định

  • Test helper là một package pgtest dùng chung thay vì mỗi package một bản sao, khác với plan. go list -deps chứng minh testcontainers không lọt vào binary.
  • Các false positive G101 được tắt inline, kèm lý do.

Bước tiếp theo

Merge PR, tag phase-02, rồi giai đoạn 3 (provider abstraction, Anthropic).

Bản ghi công việc lịch sử, không phải căn cứ lâu dài. Hãy ưu tiên docs/specs/ADR cho các quyết định hiện hành.