Quy ước DEFER
Một quy tắc duy nhất để track những việc cố ý hoãn lại — không bị thất lạc trong doc 200 dòng hay TODO list mục nát. Source of truth nằm ngay tại điểm defer; index tự động generate.
1. Khi nào tag một defer
Tag DEFER mỗi khi anh ship một feature với gap đã biết mà anh quyết định không đóng trong iteration này. Ví dụ đáng tag:
- Guest retry flow (đã ship customer-authed retry, chưa làm guest).
- Real SMTP/SendGrid impl (đã ship
LogEmailProvider, chưa wire production vendor). - Server-side automatic TRANSIENT retry (đã ship customer-click only).
- "Bump enum này thành Prisma enum khi có N+ usages" (P2-8).
KHÔNG tag là defer:
- Bug anh không fix vì không biết. Đó là bug, không phải defer.
- Feature tương lai chưa ai quyết build. Những thứ này nằm ở
progress/gaps-and-plan.md/ product backlog dạng[ ]checkbox. - Cleanup toàn codebase không gắn với 1 feature shipped cụ thể.
Tiêu chuẩn: nếu người tiếp theo đọc code/doc cần một sticker vàng nói "yes biết rồi, đây là lý do" — tag nó.
2. Định dạng tag
Trong Markdown docs
Dùng HTML comment đặt ngay trên đoạn prose giải thích gap. Comment lines vô hình khi render nhưng greppable.
<!-- DEFER:p1-4-guest-retry origin:2026-04-25 trigger:smtp-vendor-signoff owner:tuan -->
**Hoãn lại**: Guest retry (chưa có JWT) — cần magic-link via email…
Các trường bắt buộc: id, origin (ISO date), trigger (điều kiện cụ thể để unblock).
Optional: owner (mặc định = repo owner).
Trong code
Dùng comment 1 dòng với marker DEFER:<id>. Dòng sau giải thích tại sao defer và cái gì sẽ unblock. Luôn link doc:
// DEFER: p1-4-guest-retry — magic-link flow chưa nằm trong scope cho tới khi
// SMTP vendor sign-off (xem docs/flows/status-matrix/05-payment-driven.md §3).
if (!customer) throw new ForbiddenException('CUSTOMER_REQUIRED_FOR_RETRY');
id PHẢI khớp với 1 tag ở đâu đó trong docs/. Script flag orphan cả 2 chiều (code tag không có doc tag, doc tag không có code tag).
Quy ước đặt ID
<feature-prefix>-<short-slug>. Lowercase, hyphenated.
p1-4-guest-retry— status-matrix item P1-4, gap = guest retry.p1-4-smtp-vendor— cùng item, gap = real SMTP impl.payment-rls-phase8— payment architecture, defer tới Phase 8.p2-8-deposit-status-enum— bare P2 backlog item.
Hai tag có thể share cùng feature-prefix khi cùng feature shipped có nhiều known gap; slug phải distinguish chúng.
3. Index — docs/progress/DEFERRED.md
Auto-generated. Không sửa tay. Chạy:
yarn defer:list # in ra stdout
yarn defer:list --write # regenerate docs/progress/DEFERRED.md
Pre-commit hook gọi variant --check — commit fail nếu generated file sẽ thay đổi, nên anh không thể ship stale index.
4. Khi resolve một defer
Khi việc đã defer thực sự ship:
- Xoá tag
DEFER:<id>khỏi doc + code. - Replace doc paragraph bằng behaviour đã ship (hoặc xoá).
- Chạy
yarn defer:list --writeregenerateDEFERRED.md. - Mention resolution trong changelog entry của ship đó.
Nếu defer hoá ra không cần làm nữa (quyết định: "sẽ không bao giờ làm"), xoá tag và document quyết định inline hoặc trong ADR — đừng để tag với no plan to act on it.