spec-lite 1.1.12 → 1.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spec-lite",
3
- "version": "1.1.12",
3
+ "version": "1.1.13",
4
4
  "description": "Spec-driven development kit for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {
@@ -128,6 +128,40 @@ Nếu không có tests → AC section sẽ là NEEDS_CLARIFY.
128
128
  - Transaction boundaries nếu explicit (`@Transaction`, `queryRunner`)
129
129
  - Retry / timeout config nếu detect được
130
130
 
131
+ **4e. Verification Rules — reverse-engineer từ code:**
132
+
133
+ Áp dụng cho mọi input field user nhập trong feature (request body, query param, form field).
134
+
135
+ Scan và extract rule từ các nguồn sau, ưu tiên từ trên xuống:
136
+ - **Schema declarations**: Zod, Joi, Yup, Pydantic, class-validator decorators (`@IsEmail()`, `@MinLength(8)`...), Mongoose/TypeORM column constraints
137
+ - **Validator middleware / guard**: custom validation function trong middleware, NestJS pipes, FastAPI dependencies
138
+ - **Inline check trong handler/use case**: `if (!email.includes('@')) throw ...`
139
+ - **DB constraint**: migration files với `UNIQUE`, `NOT NULL`, `CHECK`, length limits
140
+ - **Test assertion**: `expect(() => validate(input)).toThrow(...)` → reverse-engineer rule từ test
141
+
142
+ Convert mỗi rule sang **human-readable diễn đạt** (không copy regex hoặc code nguyên):
143
+ - `@IsEmail()` → "email format hợp lệ"
144
+ - `@MinLength(8)` → "≥ 8 ký tự"
145
+ - `regex: /^[a-zA-Z0-9_]{3,20}$/` → "3-20 ký tự, alphanumeric + `_`"
146
+ - `UNIQUE` constraint → "unique"
147
+ - `@IsOptional()` → đánh `Required: no`
148
+
149
+ **Type**: derive semantic type (string/integer/email/date/...) từ field type, KHÔNG copy tech type (`varchar(255)`).
150
+
151
+ **Đánh dấu source** trong note hoặc inline comment: `(extracted from Zod schema)`, `(from DB constraint)`, `(from test assertion)`.
152
+
153
+ **Auto-assign Rule ID** `VR-F{NNN}-{seq}` cho mỗi rule extract được — tăng dần toàn feature, không reset theo field. Sub-rule phức tạp → tách flat thành nhiều rule riêng, mỗi cái 1 ID.
154
+
155
+ **Quan trọng — flag cho BA review:**
156
+ - Rule trong code có thể **thiếu** (validation chỉ chạy ở 1 layer, không cover hết) → list những field xuất hiện trong US nhưng không có validation → mark `NEEDS_CLARIFY`.
157
+ - Rule trong code có thể **lệch business intent** → toàn bộ Verification Rules section nên có header note: *"Rules dưới đây reverse-engineer từ code hiện tại. BA cần review và confirm có match business intent không."*
158
+ - Nếu field nào không có validation → fallback sang flow suggest options (xem `spec-new` SKILL.md → "Verification Rules — quy ước sinh") để BA quyết định khi review.
159
+
160
+ **Cấm:**
161
+ - ❌ Đưa regex / validator code nguyên xi vào bảng (đó là HOW, thuộc `fdd.md`).
162
+ - ❌ Đưa error message / user-facing copy vào bảng (UI copy, không thuộc FRD).
163
+ - ❌ Bỏ qua field có validation chỉ vì rule "có vẻ tầm thường" — vẫn phải ghi để BA confirm.
164
+
131
165
  ---
132
166
 
133
167
  ### Bước 5: Hỏi clarification — batch
@@ -161,6 +195,7 @@ Với mỗi feature, generate 2 files dùng templates tương ứng:
161
195
  - User flows: Mermaid flowchart cho happy case và edge cases (infer từ test chains / UI navigation, NEEDS_CLARIFY nếu không detect được)
162
196
  - User stories (đánh dấu source)
163
197
  - Acceptance criteria (extract từ tests nếu có, NEEDS_CLARIFY nếu không)
198
+ - **Verification Rules**: bảng `Field | Type | Required | Rule | Refs` từ Bước 4e. Thêm header note nhắc BA review match business intent. Field thiếu validation → NEEDS_CLARIFY.
164
199
  - Scope: In scope từ detected routes/actions; Out of scope → NEEDS_CLARIFY
165
200
  - Dependencies:
166
201
  - Feature Dependencies: feature-to-feature dependencies nếu detect được
@@ -246,5 +281,6 @@ grep "NEEDS_CLARIFY" để tìm và fill in khi có thêm context.
246
281
  - [ ] Components consumed list reference đúng C-XXX IDs từ Component Index
247
282
  - [ ] User stories và AC detect được đánh dấu source `(inferred)` hoặc `(from tests)`
248
283
  - [ ] Feature không có tests → AC section có NEEDS_CLARIFY rõ ràng
284
+ - [ ] Verification Rules: rule reverse-engineer từ code đã đánh dấu source; field thiếu validation đã NEEDS_CLARIFY; không chứa regex/code/error message
249
285
  - [ ] prd.md Feature Index đã được update
250
286
  - [ ] NEEDS_CLARIFY items được list đầy đủ trong summary
@@ -68,8 +68,9 @@ Sections hiện có:
68
68
  [2] User Flows
69
69
  [3] Feature-level Acceptance Criteria
70
70
  [4] User Stories
71
- [5] Scope (In Scope / Out of Scope)
72
- [6] Dependencies (Feature Dependencies + Components Used)
71
+ [5] Verification Rules
72
+ [6] Scope (In Scope / Out of Scope)
73
+ [7] Dependencies (Feature Dependencies + Components Used)
73
74
  [A] All — viết lại toàn bộ từ đầu
74
75
  [S] Lên spec từ frd.md hiện tại — frd.md đã đầy đủ, không cần chỉnh
75
76
 
@@ -222,7 +223,8 @@ Với từng feature có đề xuất thay đổi:
222
223
  **Tạo mới** `specs/main/feature/{F-XXX}-{feature-slug}/frd.md`
223
224
  - Dùng khi integration này là lần đầu tiên implement feature này
224
225
  - Thư mục đặt tên: `{F-XXX}-{feature-slug}` (ví dụ: `F-001-authentication`)
225
- - Nội dung đề xuất: User Stories, AC, Components consumed (reference C-XXX) — từ spec.md
226
+ - Nội dung đề xuất: User Flows, Feature-level AC, User Stories, **Verification Rules**, Scope, Components consumed (reference C-XXX) — từ spec.md
227
+ - **Verification Rules**: xem hướng dẫn ở mục "Verification Rules — quy ước sinh" bên dưới
226
228
 
227
229
  **Cập nhật** `specs/main/feature/{F-XXX}-{feature-slug}/frd.md`
228
230
  - Dùng khi frd.md đã tồn tại và integration này bổ sung thêm stories hoặc criteria
@@ -236,8 +238,9 @@ Sections hiện có:
236
238
  [2] User Flows
237
239
  [3] Feature-level Acceptance Criteria
238
240
  [4] User Stories
239
- [5] Scope (In Scope / Out of Scope)
240
- [6] Dependencies (Feature Dependencies + Components Used)
241
+ [5] Verification Rules
242
+ [6] Scope (In Scope / Out of Scope)
243
+ [7] Dependencies (Feature Dependencies + Components Used)
241
244
  [A] All — viết lại toàn bộ từ đầu
242
245
 
243
246
  Chọn section cần cập nhật (có thể chọn nhiều, ví dụ: 2 3):
@@ -248,6 +251,57 @@ Sau khi user chọn → chỉ sinh nội dung cho section đó. Nếu chọn `A`
248
251
  Không được bỏ qua feature nào — kể cả feature `Done` vẫn phải đánh giá xem spec mới có làm thay đổi scope không.
249
252
  ```
250
253
 
254
+ #### Verification Rules — quy ước sinh
255
+
256
+ Áp dụng khi sinh hoặc cập nhật section `Verification Rules` trong `frd.md`.
257
+
258
+ **Nguyên tắc:**
259
+ - Verification Rules là **business decision** — BA owns. Agent **KHÔNG** được tự assert rule.
260
+ - Agent đóng vai **option-suggester**, không phải decision-maker.
261
+
262
+ **Quy trình:**
263
+
264
+ 1. Scan các User Stories vừa confirm. Detect mọi input field user nhập (form field, query param, request body...).
265
+
266
+ 2. Với mỗi field, **suggest 2-4 phương án validation** dựa trên field name + semantic type + common patterns. Luôn kèm option `Custom` và `TBD`.
267
+
268
+ Ví dụ với field `password`:
269
+ ```
270
+ Verification rules cho `password` (type: string)?
271
+ [A] ≥ 8 ký tự, có chữ hoa + số (baseline common)
272
+ [B] ≥ 12 ký tự, có chữ hoa + số + ký tự đặc biệt (strict)
273
+ [C] ≥ 6 ký tự, không yêu cầu thêm (loose, internal tool)
274
+ [D] Custom — BA mô tả tay
275
+ [E] TBD — quyết định sau
276
+ ```
277
+
278
+ 3. BA chọn → agent **auto-assign Rule ID** `VR-F{NNN}-{seq}` và ghi vào bảng `Verification Rules` của frd.md. Chọn `Custom` → hỏi tiếp. Chọn `TBD` → ghi `TBD` ở cột Rule (vẫn assign ID để referencable) và list ra ở cuối draft để BA follow-up.
279
+
280
+ **Quy ước Rule ID (`VR-F{NNN}-{seq}`):**
281
+ - `NNN` = feature ID, `seq` = số thứ tự 3 chữ số tăng dần toàn feature, **không reset** theo field.
282
+ - Khi **tạo mới frd.md**: bắt đầu `VR-F{NNN}-001`, tăng tuần tự theo thứ tự rule được BA confirm.
283
+ - Khi **update frd.md hiện có**: đọc bảng Verification Rules hiện tại, lấy `max(seq)` + 1 làm seq tiếp theo. **Không tái sử dụng** ID đã từng tồn tại (kể cả rule đã bị xóa). **Không renumber** rule cũ.
284
+ - Sub-rule phức tạp (ví dụ password complexity 4 điều kiện) → **tách flat** thành nhiều rules riêng, mỗi cái 1 ID (`VR-F001-005`, `VR-F001-006`...). Không dùng hierarchy (`-005a`, `-005b`).
285
+
286
+ **Heuristic suggest theo field name (gợi ý, không bắt buộc):**
287
+
288
+ | Field name pattern | Categories cần hỏi |
289
+ |---|---|
290
+ | `email`, `*_email` | format, required, unique (nếu là identifier) |
291
+ | `password`, `pwd` | min length, complexity, max length |
292
+ | `username`, `login` | length range, allowed chars, unique |
293
+ | `*_at`, `*_date`, `birthday` | format, range (past/future), timezone |
294
+ | `age`, `quantity`, `*_count` | type (integer), range (min/max), positive |
295
+ | `phone`, `mobile` | format (E.164 / local), length |
296
+ | `url`, `link` | format, allowed protocols |
297
+ | `*_id`, `id` | type (UUID/integer), exists in domain |
298
+
299
+ **Cấm:**
300
+ - ❌ Tự điền rule vào bảng mà chưa hỏi BA.
301
+ - ❌ Suggest chỉ 1 option và mặc định chấp nhận.
302
+ - ❌ Đưa regex, validator code, hoặc error message vào bảng (regex/validator là HOW thuộc `fdd.md`; error/hint message là UI copy ở artifact khác).
303
+ - ❌ Dùng tech type (`varchar(255)`, `int32`...) — phải dùng semantic type (`string`, `integer`, `email`, `enum<...>`...).
304
+
251
305
  Hiển thị draft cho user xem — **chưa ghi file**.
252
306
 
253
307
  ---
@@ -85,6 +85,7 @@ Load các file sau:
85
85
  - `specs/main/sad.md` — đọc Tech Stack và Architectural Guardrails
86
86
  - `specs/main/domain.md` — đọc Glossary và Shared Entities
87
87
  - `specs/main/component/{C-XXX}-{slug}/cdd.md` — cho mỗi component trong frontmatter `components` (nếu cdd.md tồn tại)
88
+ - `specs/main/feature/{F-XXX}-{slug}/frd.md` — cho mỗi feature trong frontmatter `features` (đọc `Verification Rules` để sinh `Verification Implementation` trong fdd.md)
88
89
  - `specs/main/feature/{F-XXX}-{slug}/fdd.md` — cho mỗi feature trong frontmatter `features` (nếu fdd.md tồn tại)
89
90
 
90
91
  Tóm tắt context và surface assumptions:
@@ -190,10 +191,12 @@ Liệt kê **tất cả** features liên quan trong frontmatter và đánh giá:
190
191
 
191
192
  **Tạo mới** `specs/main/feature/{F-XXX}-{feature-slug}/fdd.md`
192
193
  - Khi feature gắn với integration nhưng chưa có fdd.md
193
- - Nội dung: Inter-component data flow, Orchestration, Cross-component invariants — từ tech.md (chỉ phần inter-component, không lặp internal design của component)
194
+ - Nội dung: Inter-component data flow, Orchestration, Cross-component invariants, **Verification Implementation** — từ tech.md (chỉ phần inter-component, không lặp internal design của component)
195
+ - **Verification Implementation**: với mỗi rule trong `frd.md > Verification Rules` của feature này, sinh row trong bảng `Rule ID | Rule (short) | Layer | Owning component | Error propagation`. Reference rule bằng `VR-F{NNN}-{seq}` ID (lấy nguyên từ FRD, không tạo ID mới). Rule (short) copy ngắn gọn từ FRD để row tự đọc được. Layer = client/gateway/service (có thể combine). Owning component reference C-XXX. KHÔNG đưa regex/validator code/error message wording vào — đó thuộc `cdd.md` của owning component (cascade riêng) hoặc UI copy artifact. Bỏ qua nếu FRD không có Verification Rules.
194
196
 
195
197
  **Cập nhật** `specs/main/feature/{F-XXX}-{feature-slug}/fdd.md`
196
198
  - Khi fdd.md đã tồn tại, ghi rõ thay đổi flow nào
199
+ - Nếu integration thêm/đổi rule trong FRD → cập nhật bảng Verification Implementation tương ứng
197
200
 
198
201
  Bỏ block fdd.md nếu integration không gắn với feature nào (`features: []`).
199
202
  ```
@@ -135,7 +135,7 @@ Component Index và Feature Index **để placeholder** — sẽ được điề
135
135
 
136
136
  **Bước 1 — Discover:** scan use case files, route groups, test describe blocks, page/screen dirs → detect feature candidates → present cho user confirm/adjust → assign F-XXX IDs.
137
137
 
138
- **Bước 2 — Deep scan từng feature:** user stories (infer từ use case + routes + tests) · AC (extract từ test assertions, đánh dấu source) · components consumed (map sang C-XXX) · inter-component call chain (cho fdd.md).
138
+ **Bước 2 — Deep scan từng feature:** user stories (infer từ use case + routes + tests) · AC (extract từ test assertions, đánh dấu source) · components consumed (map sang C-XXX) · inter-component call chain (cho fdd.md) · **verification rules** (reverse-engineer từ Zod/Joi/Pydantic/class-validator schemas, DB constraints, validator middleware, test assertions — convert sang human-readable, flag cho BA review).
139
139
 
140
140
  **Bước 3 — Generate batch:** frd.md + fdd.md cho tất cả. Batch clarification tối đa 5 câu.
141
141
 
@@ -204,7 +204,7 @@ Tạo `specs/integrations/{slug}/tech.md` cho một integration. Chạy bởi DE
204
204
 
205
205
  Nếu integration đã có `tech.md` → hỏi confirm trước khi ghi đè.
206
206
 
207
- Context load: `spec.md` + `sad.md` + `domain.md` + `cdd.md` (cho mỗi component bị touched) + `fdd.md` (cho mỗi feature liên quan).
207
+ Context load: `spec.md` + `sad.md` + `domain.md` + `cdd.md` (cho mỗi component bị touched) + `frd.md` (đọc Verification Rules để sinh Verification Implementation trong fdd.md) + `fdd.md` (cho mỗi feature liên quan).
208
208
 
209
209
  **Interview** (4 phần, tuần tự): Solution Overview · Data Model Changes · Interface Changes · Implementation Notes
210
210
 
@@ -50,3 +50,22 @@ Các invariant cần đảm bảo xuyên component — consistency, transactiona
50
50
 
51
51
  - {ví dụ: Order chỉ được đánh dấu `paid` sau khi Payment component confirm thành công}
52
52
  - {ví dụ: Inventory phải được reserve trước khi Order được tạo, rollback nếu Payment fail}
53
+
54
+ ## Verification Implementation
55
+
56
+ Map mỗi rule trong `frd.md > Verification Rules` sang implementation strategy ở cấp inter-component. Section này KHÔNG chứa internal detail của validation (regex cụ thể, validator library, error code) — những thứ đó thuộc về `cdd.md` của component sở hữu logic. Section này chỉ trả lời: rule chạy **ở đâu** trong flow, do component nào enforce, và lỗi propagate ra sao.
57
+
58
+ Bỏ section nếu feature không có Verification Rules.
59
+
60
+ | Rule ID | Rule (short) | Layer | Owning component | Error propagation |
61
+ | --- | --- | --- | --- | --- |
62
+ | `VR-F001-001` | username 3-20 ký tự, alphanumeric + `_` | client + gateway | `{C-XXX}-api-gateway` | 400 `INVALID_USERNAME` → UI inline error |
63
+ | `VR-F001-002` | username unique | service | `{C-YYY}-user-service` | 409 `USERNAME_TAKEN` → UI inline error |
64
+ | `VR-F001-003` | password ≥ 8 ký tự, có chữ hoa + số | client + gateway | `{C-XXX}-api-gateway` | 400 `WEAK_PASSWORD` → UI inline error |
65
+
66
+ Quy ước:
67
+ - **Rule ID**: reference tới `VR-F{NNN}-{seq}` trong `frd.md > Verification Rules`. ID là source of truth — nếu lệch giữa FRD và FDD, FRD đúng.
68
+ - **Rule (short)**: copy ngắn gọn từ FRD để row tự đọc được, không phải nhảy file. Nếu rule dài → giữ short ở đây, full description ở FRD.
69
+ - **Layer**: `client` (UI validation trước submit), `gateway` (API gateway / request validator), `service` (business logic component), hoặc kết hợp (`client + gateway` cho double-check). Layer ảnh hưởng UX (inline vs server round-trip).
70
+ - **Owning component**: component nào chịu trách nhiệm enforce. Implementation chi tiết (regex, validator code, error class) nằm trong `cdd.md` của component đó.
71
+ - **Error propagation**: HTTP status + error code semantic + cách render cho user. Wording cụ thể của error message thuộc UI copy artifact, không ở đây.
@@ -24,19 +24,19 @@ referenced_by:
24
24
 
25
25
  ```mermaid
26
26
  flowchart TD
27
- A([{Điểm bắt đầu}]) --> B[{Bước 1}]
28
- B --> C{Điều kiện?}
29
- C -- Happy case --> D[{Bước 2 — thành công}]
30
- C -- Edge case --> E[{Xử lý lỗi / nhánh phụ}]
31
- D --> F([{Kết thúc thành công}])
32
- E --> G([{Kết thúc thất bại / fallback}])
27
+ A(["Điểm bắt đầu"]) --> B["Bước 1"]
28
+ B --> C{"Điều kiện?"}
29
+ C -- Happy case --> D["Bước 2 — thành công"]
30
+ C -- Edge case --> E["Xử lý lỗi / nhánh phụ"]
31
+ D --> F(["Kết thúc thành công"])
32
+ E --> G(["Kết thúc thất bại / fallback"])
33
33
  ```
34
34
 
35
35
  ### Flow 2: {Tên luồng phụ / edge case lớn} *(nếu có)*
36
36
 
37
37
  ```mermaid
38
38
  flowchart TD
39
- A([{Điểm bắt đầu}]) --> B[{...}]
39
+ A(["Điểm bắt đầu"]) --> B["..."]
40
40
  ```
41
41
 
42
42
  ## Feature-level Acceptance Criteria
@@ -87,6 +87,25 @@ US ID format: `US-F{NNN}-{seq}` — đánh số tăng dần toàn feature.
87
87
 
88
88
  ---
89
89
 
90
+ ## Verification Rules
91
+
92
+ Constraint logic áp dụng cho input/data của feature — derive từ các input action mô tả trong User Stories ở trên. Pure WHAT, không chứa UI copy (error message, hint message thuộc về presentation, đặt ở artifact khác).
93
+
94
+ VR ID format: `VR-F{NNN}-{seq}` — đánh số tăng dần toàn feature (không reset theo field). ID **stable** — đã assign rồi giữ nguyên kể cả khi rule bị xóa (không tái sử dụng seq, không renumber). Nếu xóa rule → ID đó retire, seq mới tiếp tục tăng từ max.
95
+
96
+ Quy ước:
97
+ - **ID**: `VR-F{NNN}-{seq}` — stable identifier để FDD và test plan reference.
98
+ - **Type**: semantic type, không phải concrete tech type. Dùng `string`, `number`, `integer`, `boolean`, `date`, `datetime`, `email`, `url`, `enum<A|B|C>`, `array<T>`. KHÔNG dùng `varchar(255)`, `int32`, v.v. — đó là HOW.
99
+ - **Required**: `yes` / `no` — field bắt buộc hay optional. Product quyết định.
100
+ - **Rule**: diễn đạt human-readable. KHÔNG viết regex/code cụ thể (đó là HOW, thuộc `fdd.md`). Một field có thể có nhiều rules → mỗi rule một dòng, mỗi dòng 1 ID riêng.
101
+ - **Refs**: link tới US/AC yêu cầu rule này.
102
+
103
+ Sub-rule phức tạp (vd: password complexity với nhiều điều kiện) → **tách flat** thành nhiều rules riêng biệt, mỗi cái 1 ID, không hierarchy.
104
+
105
+ | ID | Field | Type | Required | Rule | Refs |
106
+ | --- | --- | --- | --- | --- | --- |
107
+ | `VR-{F_ID}-001` | `{field_name}` | `{semantic_type}` | yes / no | {rule diễn đạt human-readable} | `US-{F_ID}-{seq}` |
108
+
90
109
  ## Scope
91
110
 
92
111
  ### In Scope
@@ -0,0 +1,60 @@
1
+ ---
2
+ id: "003-assign-todo"
3
+ slug: "assign-todo"
4
+ title: "Assign Todo — Test Plan"
5
+ features: ["F-004"]
6
+ status: draft
7
+ created: 2026-04-21
8
+ ---
9
+
10
+ ## Test Strategy
11
+
12
+ Approach tổng quan: test pyramid distribution, environment setup, fixtures.
13
+
14
+ ## AC Coverage Matrix
15
+
16
+ | AC ID | Primary Level | Test Case ID(s) | Status |
17
+ |-------|--------------|-----------------|--------|
18
+ | AC-F004-001 | Component | TC-001 | draft |
19
+ | AC-F004-002 | Component | TC-002 | draft |
20
+ | ... |
21
+
22
+ ## Verification Rules Coverage
23
+
24
+ Mỗi rule trong `frd.md > Verification Rules` của feature phải có **≥ 1 positive test case** (input hợp lệ pass) và **≥ 1 negative test case** (input vi phạm rule bị reject). Nếu rule áp dụng ở nhiều layer (client + gateway, xem `fdd.md > Verification Implementation`) → cover từng layer.
25
+
26
+ | Rule ID | Layer (từ FDD) | Test Case ID(s) | Status |
27
+ |---------|----------------|-----------------|--------|
28
+ | VR-F004-001 | service | TC-010 (pos), TC-011 (neg) | draft |
29
+ | VR-F004-002 | gateway | TC-012 (neg) | draft |
30
+ | ... |
31
+
32
+ ## Test Cases
33
+
34
+ ### TC-001: Non-owner không thấy assign control
35
+ **Verifies:** AC-F004-001 (primary)
36
+ **Level:** Component test (TodoModal)
37
+ **Setup:**
38
+ - Render TodoModal với prop `isOwner: false`
39
+ **Steps:**
40
+ 1. Mở modal
41
+ 2. Query assign control
42
+ **Expected:**
43
+ - Assign control không có trong DOM
44
+ **Negative variants:** —
45
+
46
+ ### TC-006: Happy path assign
47
+ **Verifies:** AC-F004-006, AC-F004-007 (primary), AC-F004-008 (supporting)
48
+ **Level:** E2E (Playwright)
49
+ **Setup:**
50
+ - Seed: userA, userB
51
+ - userA login, tạo todo
52
+ **Steps:**
53
+ 1. userA mở task detail
54
+ 2. Click assign, chọn userB
55
+ 3. Confirm
56
+ **Expected:**
57
+ - Modal hiển thị owner = userB
58
+ - Refresh dashboard → card hiển thị userB
59
+ **Verification queries:**
60
+ - DB: `SELECT * FROM todo_history WHERE todo_id=? AND event_type='ASSIGNED'` → 1 row, payload đúng