@educa-corp/sdd-framework 0.7.0 → 0.7.2
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/bin/lint-trace.js +64 -3
- package/bin/self-check.js +8 -5
- package/bin/trace-schema.json +44 -4
- package/core/FRAMEWORK_VERSION +1 -1
- package/core/commands/amend-prd.md +3 -2
- package/core/commands/dev-run-test.md +1 -1
- package/core/commands/generate-bdd.md +9 -6
- package/core/commands/generate-code.md +2 -2
- package/core/commands/generate-tech-docs.md +2 -2
- package/core/commands/map-testids.md +1 -1
- package/core/commands/validate-traces.md +6 -6
- package/core/steps/context-loader.md +1 -1
- package/core/templates/ci/trace-gate.gitlab-ci.yml +169 -0
- package/core/templates/project-context.yaml +3 -3
- package/core/templates/tech-design.template.md +2 -2
- package/docs/02-concepts/architecture.md +1 -1
- package/docs/02-concepts/overview.md +1 -1
- package/docs/02-concepts/pipeline-steps/02-specification.md +13 -7
- package/docs/02-concepts/pipeline-steps/07-dev-selftest.md +2 -0
- package/docs/02-concepts/pipeline-steps/08-qc-automation.md +1 -0
- package/docs/02-concepts/pipeline-steps/09-validate-traces.md +34 -3
- package/docs/02-concepts/pipeline-steps/10-feedback-loop.md +10 -1
- package/docs/02-concepts/traceability.md +187 -183
- package/docs/03-guides/architect.md +15 -5
- package/docs/03-guides/developer.md +1 -0
- package/docs/03-guides/product-owner.md +89 -72
- package/docs/03-guides/tester-qa.md +81 -81
- package/docs/04-reference/commands.md +1 -1
- package/docs/04-reference/trace-schema.md +8 -1
- package/docs/explain/02c-amend-prd.md +1 -1
- package/docs/explain/06-generate-bdd.md +1 -1
- package/docs/explain/13-dev-run-test.md +15 -1
- package/docs/explain/19-qc-run-test.md +91 -87
- package/docs/explain/21-validate-traces.md +79 -75
- package/package.json +1 -1
|
@@ -3,15 +3,20 @@
|
|
|
3
3
|
# Bước 2 · Specification — Hình thành đặc tả (PRD)
|
|
4
4
|
|
|
5
5
|
> **Tóm tắt.** Biến khung intent thành **PRD** chuẩn nghiệp vụ, tinh chỉnh qua 3 lăng kính, rồi qua **gate chất lượng** để PO đóng dấu `approved`.
|
|
6
|
-
> **Commands:** `/generate-prd` (lần đầu) · `/extend-prd` (thêm vào PRD đã có) → `/refine-prd` → `/review-context`
|
|
6
|
+
> **Commands:** `/generate-prd` (lần đầu) · `/extend-prd` (**thêm** vào PRD đã có) · `/amend-prd` (**đổi** yêu cầu đã có) → `/refine-prd` → `/review-context`
|
|
7
7
|
|
|
8
|
-
> **Chọn lệnh nào —
|
|
8
|
+
> **Chọn lệnh nào — bốn nhánh, phân biệt bằng THAO TÁC GHI:**
|
|
9
9
|
>
|
|
10
|
-
> | Tình huống | Lệnh | Vì sao không dùng cái kia |
|
|
11
|
-
>
|
|
12
|
-
> | PRD **chưa tồn tại** | `/generate-prd` | — |
|
|
13
|
-
> | PRD đã có, **
|
|
14
|
-
> | PRD đã có, **
|
|
10
|
+
> | Tình huống | Lệnh | Ghi kiểu gì | Vì sao không dùng cái kia |
|
|
11
|
+
> |---|---|---|---|
|
|
12
|
+
> | PRD **chưa tồn tại** | `/generate-prd` | **Write** cả file | — |
|
|
13
|
+
> | PRD đã có, **THÊM** UC/AC/BR mới | **`/extend-prd`** | Edit **add-only** — output là **superset chặt** | `/generate-prd` **từ chối chạy** trên file đã có |
|
|
14
|
+
> | PRD đã có, **ĐỔI** một yêu cầu đang đúng cú pháp | **`/amend-prd`** | Edit **tại chỗ** — output **KHÔNG** phải superset | `/extend-prd` chỉ add-only; `/refine-prd` chỉ áp finding của chính nó |
|
|
15
|
+
> | PRD đã có, sửa **vấn đề review đã soi ra** | `/refine-prd` → Review Board → `--resume` | Edit trong phạm vi finding | `/refine-prd` **không thêm/đổi được** theo ý định mới — nó tự cấm đụng section ngoài findings |
|
|
16
|
+
>
|
|
17
|
+
> **Xoá hẳn một BR/AC → không có lệnh, và có chủ ý:** xoá row làm `@trace.business_rules` trong `.feature` trỏ vào ID không còn ⇒ `TRACE_ORPHAN` 🔴. Dùng `/amend-prd --retire {ID}` — khai tử **tại chỗ**, giữ nguyên row + ID.
|
|
18
|
+
>
|
|
19
|
+
> ⚠️ **Sửa tay file `.md` là điểm mù (GAPS-v4 G54).** Mọi drift detector so **nhãn version**, không so nội dung (0 content hash trong codebase) — sửa mà không bump version ⇒ **0 cờ**. `/validate-traces` Step 3.9 canh cửa sau bằng cờ 🔴 `PRD_UNTRACKED_EDIT`.
|
|
15
20
|
>
|
|
16
21
|
> **`/generate-prd` dừng hẳn (không hỏi Y/N) nếu file đã tồn tại.** Ghi đè sẽ mất `# Change Log` + rollover, Version/Status thật, và **đánh số lại BR từ đầu** — cái cuối lan **ra ngoài file**, phá mọi `@trace.business_rules` trong `bdd/` đã sinh. Ba mất mát đều không hoàn tác được từ trong lệnh, nên không đặt sau một phím bấm.
|
|
17
22
|
|
|
@@ -41,6 +46,7 @@ PRD là **hợp đồng nghiệp vụ** giữa PO ↔ Dev ↔ AI. Đây là **c
|
|
|
41
46
|
|------|---------|---------|
|
|
42
47
|
| `/generate-prd` | **Sinh** PRD draft từ product-definition. Từ chối chạy nếu PRD đã tồn tại | PRD `Status: draft` |
|
|
43
48
|
| `/extend-prd` | **Thêm** UC/AC/BR vào PRD đã duyệt — đánh số **nối tiếp**, ghi **add-only** + guard sau-ghi, drain `feedback/prd-change-requests/` | PRD v+1, `Status → draft` |
|
|
49
|
+
| `/amend-prd` | **Đổi tại chỗ** một AC/BR/UC đã duyệt — PO khai tường minh `amend_targets`, kiểm va chạm, guard sau-ghi **HAI CHIỀU** (Bảo toàn + Giới hạn). `--retire {ID}` khai tử tại chỗ | PRD v+1, `Status → draft` |
|
|
44
50
|
| `/refine-prd` | **Tinh chỉnh** qua 3 lăng kính DEV/SA/PO (fan-out per-UC) | Findings để PO accept/reject |
|
|
45
51
|
| `/review-context` | **Gate chất lượng** — findings P0–P5, phải sạch critical | PO đặt `Status: approved` |
|
|
46
52
|
|
|
@@ -24,6 +24,8 @@ Trước khi đẩy sang QC chính thức, Dev cần một vòng **kiểm nhanh
|
|
|
24
24
|
- Cho phép **thử tại chỗ** trên service/app đang chạy (`/dev-smoke-test`).
|
|
25
25
|
|
|
26
26
|
> **`dev_selftest` ≠ `qc_status`.** Hai trục **độc lập**: dev smoke (nhanh, tự kiểm) vs QC chính thức (Playwright, evidence). Không lấn quyền nhau.
|
|
27
|
+
>
|
|
28
|
+
> ⚠️ **Nhưng "độc lập" chỉ đúng với `status` về KẾT QUẢ CHẠY, không đúng về QUYỀN KHẲNG ĐỊNH** *(GAPS-v4 G55)*. `pass` mang nghĩa *"scenario này đã được nghiệm thu theo spec **hiện tại**"* — nên trên row `DRIFT`/`ORPHANED`, `/dev-run-test` và `/qc-run-test` **không được** ghi `pass`; chúng hạ về `not_run`. `fail`/`skip` thì ghi bình thường. `lint-trace` **T12** bắt trạng thái `DRIFT + pass` ở sổ thật, bất kể ai ghi ra.
|
|
27
29
|
|
|
28
30
|
---
|
|
29
31
|
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
- Phân rã yêu cầu thành test case bám scenario, phát hiện **gap tài liệu**.
|
|
23
23
|
- Chạy test thật, ghi **`qc_status` chính thức** + **evidence**.
|
|
24
|
+
⚠️ Nhưng `/qc-run-test` **đọc cột `status` trước khi ghi `pass`** *(GAPS-v4 G55)*: row `DRIFT`/`ORPHANED` + test xanh → hạ về `not_run`, và **không** đóng bug nào ở lần chạy đó. `fail`/`skip` ghi bình thường.
|
|
24
25
|
- Phân loại FAIL: **script-bug** (sửa script) vs **product-gap** (giữ FAIL + evidence, **không bao giờ fake-pass**).
|
|
25
26
|
- Đẩy **product-gap** ngược về PO/Dev.
|
|
26
27
|
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
# Bước 9 · Validate Traces — Ma trận độ phủ (Coverage Matrix)
|
|
4
4
|
|
|
5
|
-
> **Tóm tắt.** Check độ phủ giữa **spec ↔ code ↔ test** — 2 chiều quét, **6 tầng drift**, 4 cờ 🔴 chặn PR, 2 cờ ⓘ. Chỉ ra chỗ chưa phủ — mặc định **không sửa gì**.
|
|
5
|
+
> **Tóm tắt.** Check độ phủ giữa **spec ↔ code ↔ test** — 2 chiều quét, **6 tầng drift**, 4 cờ 🔴 chặn PR, 1 cờ 🔴 không-chặn (`PRD_UNTRACKED_EDIT`), 2 cờ ⓘ. Chỉ ra chỗ chưa phủ — mặc định **không sửa gì**.
|
|
6
|
+
> **Có phạm vi:** `--domain {d}` · `--prd {TICKET-ID}` · `--uc {UC-ID}`; không cờ nào = toàn bộ.
|
|
6
7
|
> **Command:** `/validate-traces` · `--realign-prd-version {UC-ID}` · `--realign-techdoc-revision {UC-ID}`
|
|
7
8
|
>
|
|
8
9
|
> *Lệnh **read-only** ở chế độ thường. Hai flag `--realign-*` là ngoại lệ có kiểm soát: chúng sửa **đúng dòng `@trace.*`** trong code, không đụng logic — xem [Realign](#realign--đường-ra-cho-cờ-ⓘ).*
|
|
@@ -41,7 +42,7 @@ Traceability chỉ có giá trị khi **kiểm được**. Bước này cho mộ
|
|
|
41
42
|
| Ma trận coverage spec ↔ code ↔ test | Trạng thái từng SC + `code_coverage` tổng |
|
|
42
43
|
| `{trace_dir}/trace-report.json` | Bản máy đọc cho **panel VS Code** ("Spec Driven Docs Tools") — bị **ghi đè** mỗi lần chạy |
|
|
43
44
|
| `{trace_dir}/trace-history.jsonl` | **Nhật ký append-only** — mỗi lần chạy ghi thêm 1 dòng *delta*. Đây là **dữ liệu**, không phải mirror: **phải commit**, mất là mất vĩnh viễn |
|
|
44
|
-
| Cờ audit | 6 cờ drift + 4 cờ 🔴 chặn PR + 2 cờ ⓘ (bảng dưới) |
|
|
45
|
+
| Cờ audit | 6 cờ drift + 4 cờ 🔴 chặn PR + `PRD_UNTRACKED_EDIT` 🔴 (không chặn) + 2 cờ ⓘ (bảng dưới) |
|
|
45
46
|
| Hàng đợi | Đếm PRD change request còn `Open` kèm **số ngày chờ** (Step 7b) |
|
|
46
47
|
|
|
47
48
|
---
|
|
@@ -68,6 +69,25 @@ Traceability chỉ có giá trị khi **kiểm được**. Bước này cho mộ
|
|
|
68
69
|
|
|
69
70
|
## Framework xử lý thế nào (Mechanics)
|
|
70
71
|
|
|
72
|
+
### Phạm vi audit — `--domain` / `--prd` / `--uc`
|
|
73
|
+
|
|
74
|
+
Đây là **lệnh đắt nhất** trong framework: ~24k token chỉ dẫn + ~9k `context-loader`, rồi đọc **mọi** PRD · `.feature` · tech-doc · design-spec · file source có tag · `.tsv`. Chi phí tăng **tuyến tính theo cả repo**, không theo phần việc đang làm.
|
|
75
|
+
|
|
76
|
+
| Cờ | `scope.kind` | Phạm vi |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| *(không có)* | `all` | Toàn bộ |
|
|
79
|
+
| `--domain {d}` | `domain` | Một domain |
|
|
80
|
+
| `--prd {TICKET-ID}` | `prd` | Một feature-package |
|
|
81
|
+
| `--uc {UC-ID}` | `uc` | Một UC — **mọi platform của nó** |
|
|
82
|
+
|
|
83
|
+
**`/sync` Step 1e nói cho bạn biết scope là gì**: nó liệt kê PRD nào vừa đổi *(so với lần pull)* và PRD nào đã đổi *kể từ lần audit gần nhất* — con số thứ hai tích luỹ đúng qua nhiều lần pull. Hai lệnh khớp nhau thành một vòng: `/sync` chỉ chỗ → audit scoped rẻ → sửa → audit **toàn bộ** một lần trước khi tạo PR.
|
|
84
|
+
|
|
85
|
+
⚠️ **Biên bản có scope KHÔNG BAO GIỜ được coi là đầy đủ.** Report mang field `scope`, và `gate-trace` **G2 fail** nếu `scope.kind !== "all"` — **không ngoại lệ**, không đếm xem trên đĩa có bao nhiêu domain. Muốn tạo PR thì phải có một lần audit **toàn bộ** đã commit.
|
|
86
|
+
|
|
87
|
+
> **Vì sao điều kiện phải tuyệt đối (GAPS-v4 G57):** bản cũ hỏi *"còn domain **nào khác** không"*, nên trong repo **một domain** thì không còn domain nào khác ⇒ **không fail** ⇒ một biên bản hẹp-theo-PRD được nhận là *"toàn bộ"*. Thêm cờ scope mà không siết G2 là **tự tay mở** đúng cái *"cấp giấy xanh cho thứ chưa ai xem"* mà chú thích của gate cảnh báo.
|
|
88
|
+
|
|
89
|
+
**Ba chỗ cố ý KHÔNG âm thầm:** `--prd`/`--uc` không phân giải được → **DỪNG** *(không rơi về `all` — chạy toàn bộ khi người ta xin một phần là đốt 30 phút; và không audit rỗng rồi báo "sạch" trên 0 row)* · nhiều cờ scope cùng lúc → **DỪNG** *(không tự ưu tiên)* · **lint vẫn chạy toàn bộ** dù audit có scope *(sổ hỏng ở domain khác vẫn là sổ hỏng, và lint rẻ vì không cần LLM)*.
|
|
90
|
+
|
|
71
91
|
### Phân loại `status` từng SC (thứ tự ưu tiên, rule sớm thắng)
|
|
72
92
|
|
|
73
93
|
| # | Trạng thái | Điều kiện |
|
|
@@ -97,6 +117,7 @@ Chiều ngược là cần thiết vì gen lại BDD có thể làm một SC bi
|
|
|
97
117
|
|
|
98
118
|
| Cờ | So cái gì | Step |
|
|
99
119
|
|---|---|:---:|
|
|
120
|
+
| `PRD_UNTRACKED_EDIT` 🔴 | **Nội dung PRD đổi mà nhãn `Version` KHÔNG đổi** — `git diff` **và** `git status` so với mốc `spec_baseline` của lần audit trước. Có người sửa ngoài đường chính thức | **3.9** |
|
|
100
121
|
| `PRD_DRIFT` | Version PRD vs cột `prd_version` vs `@trace.prd_version` trong code — **và** changelog **có** nêu UC này | 4 |
|
|
101
122
|
| `TECHDOC_DRIFT` · `FE_TECHDOC_DRIFT` | `@trace.revision` tech-doc vs cột đã lưu — **và** changelog nêu UC này | 5 |
|
|
102
123
|
| `BDD_DRIFT` | `@trace.bdd_version` trong code vs `.feature` hiện tại | 5c |
|
|
@@ -133,7 +154,17 @@ Chiều ngược là cần thiết vì gen lại BDD có thể làm một SC bi
|
|
|
133
154
|
|
|
134
155
|
Tệ hơn: **làm theo hướng dẫn cũng không tắt được.** `/generate-bdd` sạch được cột TSV, nhưng tag trong code chỉ `/generate-code` ghi — mà nó thấy row đang `OK` là **skip**. Vòng lặp đóng, và lối ra duy nhất là ép sinh lại code cho hàng loạt UC không hề thay đổi.
|
|
135
156
|
|
|
136
|
-
Bộ lọc đọc
|
|
157
|
+
Bộ lọc đọc **`{changelog_scope}` của row changelog** — một **contract máy đọc** (`bin/trace-schema.json` → `changelog_row_contract`) với **bốn** producer: `/refine-prd` Phase 3 · `/extend-prd` Bước 6 · `/amend-prd` Bước 5 · `/review-context` Fix/Resume Phase 3. `self-check` **R12** fail build nếu producer nào không có dòng template mang token.
|
|
158
|
+
|
|
159
|
+
Ba bước dựng tập bị ảnh hưởng:
|
|
160
|
+
|
|
161
|
+
1. **Tách mệnh đề** — ngăn bằng `;`, mỗi mệnh đề mở đầu bằng đơn vị sở hữu (`{UC-ID}:` hoặc `PRD-global:`).
|
|
162
|
+
2. **Chuẩn hoá về UC** — phép phân giải **`BR/AC → UC sở hữu`**: `BR{n}` → UC có BR đó trong bảng Business Rule (PRD §3) · `AC{n}` → UC có AC đó ở dòng `**AC liên quan:**`. **Không bỏ bước này**: phép thử là *"**UC** này có trong tập?"*, nên một row chỉ nêu `sửa BR8` làm UC sở hữu BR8 rơi vào ⓘ trong khi nội dung của nó vừa đổi — và ⓘ **mở cửa** cho `--realign-prd-version` dán nhãn lại (GAPS-v4 G53).
|
|
163
|
+
3. **Phân loại** *(first-match-wins)* — **row mơ hồ xét TRƯỚC**: nó là điều kiện **cấp row**, nên xét sau thì một UC có thể được xếp ⓘ trước khi ta biết là không suy đoán được gì.
|
|
164
|
+
|
|
165
|
+
**Row nào mơ hồ → 🟠 cho MỌI UC** — lưới an toàn: mất tính năng *lọc*, không mất tính năng *cảnh báo*. ⚠️ Nhưng nó **không phải cái cớ để producer ghi bừa**: đúng khi **thiếu** thông tin, sai khi producer **có** thông tin mà không ghi.
|
|
166
|
+
|
|
167
|
+
Mệnh đề mang hậu tố **`[no-behavior]`** → UC nêu trong đó ở lại ⓘ. Chỉ producer **chứng minh được** tính trung tính mới được dùng (`changelog_row_contract.neutral_checks`) — hiện chỉ `/review-context --fix` cho fix thuần cấu trúc (P4).
|
|
137
168
|
|
|
138
169
|
> Đây là bài mà framework **đã giải đúng ở cấp scenario**: `sc_version` chỉ bump khi thân scenario thực sự đổi, vì *"bump vô cớ sẽ tạo DRIFT giả, làm cờ mất giá trị"*. Hai cờ ⓘ là bản tương ứng ở cấp tài liệu.
|
|
139
170
|
|
|
@@ -37,7 +37,7 @@ Framework là pipeline **một chiều** — nhưng vẫn cần đường **ph
|
|
|
37
37
|
| `/learn` | Tất cả | Guardrail lesson | `project-lessons.md` (qua step `capture-lesson`) |
|
|
38
38
|
| `/fix-bug` | Dev | Sửa lỗi có root-cause + regression test | Code + `@trace.fixes/root_cause/regression` |
|
|
39
39
|
| `/extend-prd` | PO | **Drain** PRD change request → UC/AC/BR mới trong PRD | PRD v+1 · request → `archived/` |
|
|
40
|
-
| `/sync` | Lead (umbrella) | Pull + submodule + **nổi feedback** + làm mới Living Docs | Chạy hằng ngày |
|
|
40
|
+
| `/sync` | Lead (umbrella) | Pull + submodule + **nổi feedback** + **nổi spec delta** (Step 1e) + làm mới Living Docs | Chạy hằng ngày |
|
|
41
41
|
|
|
42
42
|
### Ba hàng đợi — mỗi cái phải có người lấy ra
|
|
43
43
|
|
|
@@ -48,6 +48,15 @@ Framework là pipeline **một chiều** — nhưng vẫn cần đường **ph
|
|
|
48
48
|
| `prd-change-requests/` | `/propose-scenario` B | **`/extend-prd`** | `/validate-traces` Step 7b — đếm `Status: Open` kèm **số ngày chờ** |
|
|
49
49
|
|
|
50
50
|
> **Vì sao cột "ai nhắc lại" quan trọng.** `/sync` chỉ hiện những gì về **trong đúng lần pull đó** (`git diff old..new`) — nó là **chuông cửa, không phải tồn kho**. Bỏ lỡ một lần là mất khỏi màn hình vĩnh viễn. Hai hàng đợi đầu không sao vì có lệnh **quét lại thư mục mỗi lần chạy**; riêng `prd-change-requests/` thì không — nên `/validate-traces` phải nhắc thay.
|
|
51
|
+
|
|
52
|
+
> **Cùng nguyên tắc đó áp cho SPEC — và đó là Step 1e (GAPS-v4 G56).** Trước đó `/sync` diff đúng ba đường dẫn `feedback/*` và **bỏ qua `specs/`**, tức hỏi *"có góp ý gì mới"* rồi bỏ qua chính tài liệu mà mọi lệnh downstream đọc. Giờ nó trả lời **hai** câu bằng **hai** mốc:
|
|
53
|
+
>
|
|
54
|
+
> | | Mốc | Trả lời | Vấn đề nếu chỉ có nó |
|
|
55
|
+
> |---|---|---|---|
|
|
56
|
+
> | **1e-A** | `{old_sha}..{new_sha}` | *"đổi gì kể từ lần **PULL**"* | **chuông cửa** — reset mỗi lần pull; pull 4 ngày liền không audit thì ngày thứ 5 chỉ thấy delta của **một** ngày |
|
|
57
|
+
> | **1e-B** | `spec_baseline.sha_at_audit` | *"đổi gì kể từ lần **AUDIT**"* | **tồn kho** — tích luỹ đúng |
|
|
58
|
+
>
|
|
59
|
+
> 1e-B đọc mốc mà `/validate-traces` Step 6b ghi (cùng khối dùng cho cờ `PRD_UNTRACKED_EDIT`). `/sync` **chỉ đọc, không bao giờ ghi** — nếu nó cũng ghi thì mốc audit trượt theo mỗi lần pull, tức 1e-B thoái hoá thành 1e-A. Và dòng `Next` giờ **rẽ nhánh theo dữ liệu**, không còn in một hằng số.
|
|
51
60
|
>
|
|
52
61
|
> Trước v0.4.3, hàng đợi thứ ba **không có người lấy ra**: có producer, có storage, có commit, có mặt trong `/sync` — nhưng 0 consumer, và **không gì báo**. Yêu cầu nghiệp vụ thật do tester phát hiện từ sản phẩm chạy thật rơi vào im lặng hoàn toàn. Từ v0.4.3, cả ba hàng đợi được khai vào `bin/trace-schema.json` §`queues` nên **self-check chặn build** nếu một hàng đợi mất consumer.
|
|
53
62
|
|
|
@@ -1,183 +1,187 @@
|
|
|
1
|
-
[← Roles & HITL](roles-and-hitl.md) · [Concepts](./) · [Architecture →](architecture.md)
|
|
2
|
-
|
|
3
|
-
# Traceability — Truy vết đầu-cuối (End-to-end Trace)
|
|
4
|
-
|
|
5
|
-
> Từ scenario nhìn xuống biết code nào hiện thực; từ code nhìn lên biết scenario nào yêu cầu. Đây là một trong ba "trái tim" của framework.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Trace tags theo artifact
|
|
10
|
-
|
|
11
|
-
| Artifact | Trace tag |
|
|
12
|
-
|----------|-----------|
|
|
13
|
-
| **BDD file** (header) | `@trace.id` · **`@trace.platform`** · **`@trace.service`** · `@trace.module` · `@trace.domain` · `@trace.prd` · `@trace.prd_version` · `@trace.bdd_version` · `@trace.status` · `@trace.dataset` |
|
|
14
|
-
| **BDD scenario** (mỗi SC) | `@trace.scenario` · **`@trace.sc_version`** · `@trace.business_rules` |
|
|
15
|
-
| **Code** (boundary only) | `@trace.implements` · `@trace.source` · **`@trace.prd_version` · `@trace.bdd_version` · `@trace.tech_doc_revision`** · **`@trace.design_spec_version`** *(chỉ FE/App)* — lặp cả block theo **từng UC** trong file đa-UC |
|
|
16
|
-
| **Code** (chỗ chưa implement) | `@trace.stub` · `@trace.stub_owner` · `@trace.stub_for` · `@trace.seam_pending` · `@trace.seam_port` |
|
|
17
|
-
| **Test** | `@trace.verifies` |
|
|
18
|
-
| **Bug fix** | `@trace.fixes` · `@trace.root_cause` · `@trace.regression` |
|
|
19
|
-
|
|
20
|
-
→ Đầy đủ field & format: [Reference › Trace Schema](../04-reference/trace-schema.md).
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
## Boundary-only tagging
|
|
25
|
-
|
|
26
|
-
Chỉ tag `@trace` ở **boundary**, không tag mọi file → tránh **tag explosion**:
|
|
27
|
-
|
|
28
|
-
| ✅ Tag | ❌ Không tag |
|
|
29
|
-
|--------|-------------|
|
|
30
|
-
| Controller / Handler / Middleware / Steps file | Entity / Repository / DTO / Interface / Base class |
|
|
31
|
-
|
|
32
|
-
> Shared code (entity, repo) được dò qua **import chain** từ boundary — không cần tag riêng.
|
|
33
|
-
|
|
34
|
-
---
|
|
35
|
-
|
|
36
|
-
## Trace state — file `.tsv`
|
|
37
|
-
|
|
38
|
-
`.trace/{domain}/{prd-slug}/{UC-ID}-{platform}.tsv` — mỗi UC × platform một sổ. Trong umbrella, nằm ở **spec repo dùng chung** (một nơi authoritative để PM/PO quản lý).
|
|
39
|
-
|
|
40
|
-
| Cột | Chủ sở hữu | Ý nghĩa |
|
|
41
|
-
|-----|-----------|---------|
|
|
42
|
-
| `status` | `/generate-code`, `/validate-traces` | OK / GAP / DRIFT / UNTRACKED |
|
|
43
|
-
| `implemented_by` | `/generate-code` | File code hiện thực SC |
|
|
44
|
-
| `dev_selftest` | `/dev-run-test` | Smoke của **dev** |
|
|
45
|
-
| `qc_status` | `/qc-run-test`, `/report-bug` | Trạng thái QC **chính thức** (Playwright) |
|
|
46
|
-
| `bdd_version` / `spec_ver` | spec | Version để phát hiện drift |
|
|
47
|
-
| `service` *(cột 23)* | `/generate-bdd` | Đội/submodule sở hữu SC — nguồn của `by_service` trên dashboard |
|
|
48
|
-
| `design_spec_version` *(cột 24)* | `/generate-bdd` | Version design-spec lúc sinh BDD *(FE/App; `—` cho backend)* |
|
|
49
|
-
|
|
50
|
-
**24 cột.** TSV cũ thiếu cột mới → đọc thành giá trị rỗng, **không báo lỗi**; header tự nâng ở lần `/generate-bdd` gen lại kế tiếp. Đọc theo **tên cột ở header row**, không theo vị trí.
|
|
51
|
-
|
|
52
|
-
> **Làm mất hiệu lực ≠ ghi đè.** Chủ sở hữu là người **duy nhất** ghi giá trị **khẳng định** (`pass`/`fail`/số lượng). Nhưng lệnh nào làm giá trị đó **hết đúng** (spec đổi, code đổi) **bắt buộc** hạ nó về `not_run`/`—`. Giữ một `pass` sinh ra từ spec đã bị sửa là **báo cáo sai**, không phải tôn trọng quyền sở hữu cột. Ngoại lệ có chủ ý: `qc_owner`/`qc_blocked_by` (con trỏ bug vẫn còn giá trị) và `test_count`/`test_classes` (test vẫn trên đĩa — **cảnh báo**, không hạ số, để tỷ lệ coverage không nhảy loạn).
|
|
53
|
-
| `gen_ver` | `/generate-code` | Version lúc sinh code (so với `spec_ver`) |
|
|
54
|
-
| `test_count` | test | Số test phủ SC |
|
|
55
|
-
| `last_updated` | nhiều | Mốc cập nhật |
|
|
56
|
-
|
|
57
|
-
> **`dev_selftest` ≠ `qc_status`.** Dev smoke (nhanh, tự kiểm) và QC chính thức (Playwright, evidence) là **hai trục độc lập** — không lấn quyền nhau.
|
|
58
|
-
|
|
59
|
-
---
|
|
60
|
-
|
|
61
|
-
## Phân loại coverage (Coverage Status)
|
|
62
|
-
|
|
63
|
-
`/validate-traces` phân loại mỗi SC theo **thứ tự ưu tiên** (rule sớm thắng):
|
|
64
|
-
|
|
65
|
-
| # | Trạng thái | Điều kiện | Hành động |
|
|
66
|
-
|---|-----------|-----------|-----------|
|
|
67
|
-
| 1 | **UNTRACKED** | `gen_ver == —` | Chưa sinh code → `/generate-code` |
|
|
68
|
-
| 2 | **DRIFT** | có code **và** `spec_ver != gen_ver` | Spec đổi → **regen trước khi test** |
|
|
69
|
-
| 3 | **GAP** | có code **và** `test_count == — / 0` | Có code, chưa test → bù test |
|
|
70
|
-
| 4 | **OK** | version khớp, có code, có test | Đủ phủ |
|
|
71
|
-
|
|
72
|
-
> **DRIFT xét trước GAP:** SC có code + chưa test + spec vừa drift phải hiện `DRIFT` (không phải `GAP`) — vì `/generate-code` xử GAP = "skip codegen" còn DRIFT = "regenerate". Nếu GAP thắng, code lỗi thời bị bỏ qua.
|
|
73
|
-
|
|
74
|
-
---
|
|
75
|
-
|
|
76
|
-
## Version sống ở đâu — và vì sao code CŨNG mang version
|
|
77
|
-
|
|
78
|
-
**Spec là SSOT của "version hiện tại". Code mang version của "lúc tôi được sinh ra".** Hai thứ khác nhau, nên không phải dual SSOT.
|
|
79
|
-
|
|
80
|
-
| Nơi | Ghi cái gì | Ai ghi |
|
|
81
|
-
|---|---|---|
|
|
82
|
-
| `.feature` / PRD / tech-doc | version **hiện tại** của spec — SSOT | tác giả spec |
|
|
83
|
-
| `.tsv` `spec_ver` | gương của `@trace.sc_version` hiện tại | `/generate-bdd`, `/validate-traces` |
|
|
84
|
-
| `.tsv` `gen_ver` | version scenario **tại thời điểm codegen**, theo từng SC | `/generate-code` |
|
|
85
|
-
| **Code** `@trace.prd_version` · `@trace.bdd_version` · `@trace.tech_doc_revision` · `@trace.design_spec_version` | version của **từng artifact upstream** tại thời điểm codegen, theo từng **method** | `/generate-code` |
|
|
86
|
-
|
|
87
|
-
Drift = **so các mốc này với nhau**; sự lệch nhau chính là tín hiệu, không phải lỗi dữ liệu:
|
|
88
|
-
|
|
89
|
-
- `spec_ver != gen_ver` → `DRIFT` (scenario đổi sau khi sinh code)
|
|
90
|
-
- `@trace.prd_version` trong code < Version PRD hiện tại → `PRD_DRIFT`
|
|
91
|
-
- `@trace.bdd_version` trong code < `.feature` hiện tại → `BDD_DRIFT`
|
|
92
|
-
- `@trace.tech_doc_revision` trong code < `@trace.revision` của tech-doc → `TECHDOC_DRIFT`
|
|
93
|
-
- `@trace.design_spec_version` trong code FE < Version design-spec → `DESIGNSPEC_DRIFT`
|
|
94
|
-
|
|
95
|
-
> **Lệch version KHÔNG luôn là drift.** PRD và tech-doc là tài liệu **gộp** phủ nhiều UC nhưng chỉ có **một** số version. Thêm UC7 làm mọi UC cũ lệch số dù không đổi một chữ. Nên `/validate-traces` **lọc theo
|
|
96
|
-
|
|
97
|
-
**
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
1
|
+
[← Roles & HITL](roles-and-hitl.md) · [Concepts](./) · [Architecture →](architecture.md)
|
|
2
|
+
|
|
3
|
+
# Traceability — Truy vết đầu-cuối (End-to-end Trace)
|
|
4
|
+
|
|
5
|
+
> Từ scenario nhìn xuống biết code nào hiện thực; từ code nhìn lên biết scenario nào yêu cầu. Đây là một trong ba "trái tim" của framework.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Trace tags theo artifact
|
|
10
|
+
|
|
11
|
+
| Artifact | Trace tag |
|
|
12
|
+
|----------|-----------|
|
|
13
|
+
| **BDD file** (header) | `@trace.id` · **`@trace.platform`** · **`@trace.service`** · `@trace.module` · `@trace.domain` · `@trace.prd` · `@trace.prd_version` · `@trace.bdd_version` · `@trace.status` · `@trace.dataset` |
|
|
14
|
+
| **BDD scenario** (mỗi SC) | `@trace.scenario` · **`@trace.sc_version`** · `@trace.business_rules` |
|
|
15
|
+
| **Code** (boundary only) | `@trace.implements` · `@trace.source` · **`@trace.prd_version` · `@trace.bdd_version` · `@trace.tech_doc_revision`** · **`@trace.design_spec_version`** *(chỉ FE/App)* — lặp cả block theo **từng UC** trong file đa-UC |
|
|
16
|
+
| **Code** (chỗ chưa implement) | `@trace.stub` · `@trace.stub_owner` · `@trace.stub_for` · `@trace.seam_pending` · `@trace.seam_port` |
|
|
17
|
+
| **Test** | `@trace.verifies` |
|
|
18
|
+
| **Bug fix** | `@trace.fixes` · `@trace.root_cause` · `@trace.regression` |
|
|
19
|
+
|
|
20
|
+
→ Đầy đủ field & format: [Reference › Trace Schema](../04-reference/trace-schema.md).
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Boundary-only tagging
|
|
25
|
+
|
|
26
|
+
Chỉ tag `@trace` ở **boundary**, không tag mọi file → tránh **tag explosion**:
|
|
27
|
+
|
|
28
|
+
| ✅ Tag | ❌ Không tag |
|
|
29
|
+
|--------|-------------|
|
|
30
|
+
| Controller / Handler / Middleware / Steps file | Entity / Repository / DTO / Interface / Base class |
|
|
31
|
+
|
|
32
|
+
> Shared code (entity, repo) được dò qua **import chain** từ boundary — không cần tag riêng.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Trace state — file `.tsv`
|
|
37
|
+
|
|
38
|
+
`.trace/{domain}/{prd-slug}/{UC-ID}-{platform}.tsv` — mỗi UC × platform một sổ. Trong umbrella, nằm ở **spec repo dùng chung** (một nơi authoritative để PM/PO quản lý).
|
|
39
|
+
|
|
40
|
+
| Cột | Chủ sở hữu | Ý nghĩa |
|
|
41
|
+
|-----|-----------|---------|
|
|
42
|
+
| `status` | `/generate-code`, `/validate-traces` | OK / GAP / DRIFT / UNTRACKED |
|
|
43
|
+
| `implemented_by` | `/generate-code` | File code hiện thực SC |
|
|
44
|
+
| `dev_selftest` | `/dev-run-test` | Smoke của **dev** |
|
|
45
|
+
| `qc_status` | `/qc-run-test`, `/report-bug` | Trạng thái QC **chính thức** (Playwright) |
|
|
46
|
+
| `bdd_version` / `spec_ver` | spec | Version để phát hiện drift |
|
|
47
|
+
| `service` *(cột 23)* | `/generate-bdd` | Đội/submodule sở hữu SC — nguồn của `by_service` trên dashboard |
|
|
48
|
+
| `design_spec_version` *(cột 24)* | `/generate-bdd` | Version design-spec lúc sinh BDD *(FE/App; `—` cho backend)* |
|
|
49
|
+
|
|
50
|
+
**24 cột.** TSV cũ thiếu cột mới → đọc thành giá trị rỗng, **không báo lỗi**; header tự nâng ở lần `/generate-bdd` gen lại kế tiếp. Đọc theo **tên cột ở header row**, không theo vị trí.
|
|
51
|
+
|
|
52
|
+
> **Làm mất hiệu lực ≠ ghi đè.** Chủ sở hữu là người **duy nhất** ghi giá trị **khẳng định** (`pass`/`fail`/số lượng). Nhưng lệnh nào làm giá trị đó **hết đúng** (spec đổi, code đổi) **bắt buộc** hạ nó về `not_run`/`—`. Giữ một `pass` sinh ra từ spec đã bị sửa là **báo cáo sai**, không phải tôn trọng quyền sở hữu cột. Ngoại lệ có chủ ý: `qc_owner`/`qc_blocked_by` (con trỏ bug vẫn còn giá trị) và `test_count`/`test_classes` (test vẫn trên đĩa — **cảnh báo**, không hạ số, để tỷ lệ coverage không nhảy loạn).
|
|
53
|
+
| `gen_ver` | `/generate-code` | Version lúc sinh code (so với `spec_ver`) |
|
|
54
|
+
| `test_count` | test | Số test phủ SC |
|
|
55
|
+
| `last_updated` | nhiều | Mốc cập nhật |
|
|
56
|
+
|
|
57
|
+
> **`dev_selftest` ≠ `qc_status`.** Dev smoke (nhanh, tự kiểm) và QC chính thức (Playwright, evidence) là **hai trục độc lập** — không lấn quyền nhau.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Phân loại coverage (Coverage Status)
|
|
62
|
+
|
|
63
|
+
`/validate-traces` phân loại mỗi SC theo **thứ tự ưu tiên** (rule sớm thắng):
|
|
64
|
+
|
|
65
|
+
| # | Trạng thái | Điều kiện | Hành động |
|
|
66
|
+
|---|-----------|-----------|-----------|
|
|
67
|
+
| 1 | **UNTRACKED** | `gen_ver == —` | Chưa sinh code → `/generate-code` |
|
|
68
|
+
| 2 | **DRIFT** | có code **và** `spec_ver != gen_ver` | Spec đổi → **regen trước khi test** |
|
|
69
|
+
| 3 | **GAP** | có code **và** `test_count == — / 0` | Có code, chưa test → bù test |
|
|
70
|
+
| 4 | **OK** | version khớp, có code, có test | Đủ phủ |
|
|
71
|
+
|
|
72
|
+
> **DRIFT xét trước GAP:** SC có code + chưa test + spec vừa drift phải hiện `DRIFT` (không phải `GAP`) — vì `/generate-code` xử GAP = "skip codegen" còn DRIFT = "regenerate". Nếu GAP thắng, code lỗi thời bị bỏ qua.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Version sống ở đâu — và vì sao code CŨNG mang version
|
|
77
|
+
|
|
78
|
+
**Spec là SSOT của "version hiện tại". Code mang version của "lúc tôi được sinh ra".** Hai thứ khác nhau, nên không phải dual SSOT.
|
|
79
|
+
|
|
80
|
+
| Nơi | Ghi cái gì | Ai ghi |
|
|
81
|
+
|---|---|---|
|
|
82
|
+
| `.feature` / PRD / tech-doc | version **hiện tại** của spec — SSOT | tác giả spec |
|
|
83
|
+
| `.tsv` `spec_ver` | gương của `@trace.sc_version` hiện tại | `/generate-bdd`, `/validate-traces` |
|
|
84
|
+
| `.tsv` `gen_ver` | version scenario **tại thời điểm codegen**, theo từng SC | `/generate-code` |
|
|
85
|
+
| **Code** `@trace.prd_version` · `@trace.bdd_version` · `@trace.tech_doc_revision` · `@trace.design_spec_version` | version của **từng artifact upstream** tại thời điểm codegen, theo từng **method** | `/generate-code` |
|
|
86
|
+
|
|
87
|
+
Drift = **so các mốc này với nhau**; sự lệch nhau chính là tín hiệu, không phải lỗi dữ liệu:
|
|
88
|
+
|
|
89
|
+
- `spec_ver != gen_ver` → `DRIFT` (scenario đổi sau khi sinh code)
|
|
90
|
+
- `@trace.prd_version` trong code < Version PRD hiện tại → `PRD_DRIFT`
|
|
91
|
+
- `@trace.bdd_version` trong code < `.feature` hiện tại → `BDD_DRIFT`
|
|
92
|
+
- `@trace.tech_doc_revision` trong code < `@trace.revision` của tech-doc → `TECHDOC_DRIFT`
|
|
93
|
+
- `@trace.design_spec_version` trong code FE < Version design-spec → `DESIGNSPEC_DRIFT`
|
|
94
|
+
|
|
95
|
+
> **Lệch version KHÔNG luôn là drift.** PRD và tech-doc là tài liệu **gộp** phủ nhiều UC nhưng chỉ có **một** số version. Thêm UC7 làm mọi UC cũ lệch số dù không đổi một chữ. Nên `/validate-traces` **lọc theo `{changelog_scope}` của row changelog**: UC có trong tập bị ảnh hưởng → `PRD_DRIFT` 🟠; không có → `PRD_STALE_REF` ⓘ (chỉ con trỏ cũ, sạch bằng `--realign-prd-version`); row **mơ hồ** → 🟠 cho mọi UC (lưới an toàn).
|
|
96
|
+
>
|
|
97
|
+
> Dòng changelog là **contract máy đọc** (`changelog_row_contract`), không phải ghi chú cho người đọc: mỗi mệnh đề mở đầu bằng **đơn vị sở hữu** (`{UC-ID}:` hoặc `PRD-global:`), và **BR/AC luôn đi kèm UC sở hữu** — `sửa BR8` trơ trọi nêu đủ ID để **không** bị coi là mơ hồ, nhưng phép thử là *"**UC** này có trong tập?"*, nên UC sở hữu BR8 rơi vào ⓘ trong khi nội dung của nó vừa đổi, và `--realign` dán nhãn lại (GAPS-v4 G53). Consumer vì thế **chuẩn hoá BR/AC về UC sở hữu** trước khi so. `self-check` **R12** canh cả bốn producer.
|
|
98
|
+
|
|
99
|
+
> **Và một điểm mù mà so-version KHÔNG bắt được: sửa tay.** Mọi cờ ở trên so **nhãn**, không so **nội dung** — không có content hash nào trong framework. PRD bị sửa mà `Version` không đổi ⇒ **0 cờ**. `/validate-traces` **Step 3.9** bịt bằng cờ 🔴 `PRD_UNTRACKED_EDIT`: so `git diff` **và** `git status` với mốc `spec_baseline` của lần audit trước. Cửa chính để đổi một yêu cầu đã duyệt là **`/amend-prd`**.
|
|
100
|
+
|
|
101
|
+
**Vì sao không thể bỏ tag version trong code và chỉ dựa vào `.tsv`:**
|
|
102
|
+
|
|
103
|
+
1. **Độ phân giải khác nhau.** `.tsv` là một sổ cho mỗi UC × platform. Một **file code** có thể phục vụ nhiều UC, mỗi UC ở một version khác nhau — chỉ tag đặt cạnh từng method mới diễn đạt được "UC1 ở bdd v1.4, UC3 ở v2.1".
|
|
104
|
+
2. **Vòng đời khác nhau.** `.tsv` là artifact **sinh ra**, có thể regen/xoá/mirror; ở chế độ umbrella nó còn nằm ở **repo khác** (spec submodule) với code. Tag trong code là bản ghi duy nhất **đi cùng** code qua mọi lần copy/move/merge.
|
|
105
|
+
3. **Sự lệch nhau là thứ ta muốn đo.** Nếu chỉ có một bản ghi thì không có gì để so — đó mới là lúc drift trở nên không phát hiện được.
|
|
106
|
+
|
|
107
|
+
> ⚠️ **Đừng "tối ưu" bằng cách gỡ tag version khỏi code.** `/validate-traces` Step 4/5/5c **đọc chính các tag đó**; gỡ đi là làm drift detection mù **im lặng** — build vẫn xanh, dashboard vẫn đẹp. `/review-code` lăng kính 1 gắn cờ **major** cho mỗi block `@trace.implements` thiếu tag version đi kèm.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Luồng truy vết (Trace Flow)
|
|
112
|
+
|
|
113
|
+
```mermaid
|
|
114
|
+
flowchart LR
|
|
115
|
+
PRD["PRD<br/>prd_version"] --> BDD["Scenario<br/>@trace.id + bdd_version"]
|
|
116
|
+
BDD --> CODE["Code boundary<br/>@trace.implements/source"]
|
|
117
|
+
CODE --> TEST["Test<br/>@trace.verifies"]
|
|
118
|
+
BDD -.-> TSV[".tsv state<br/>status/gen_ver/qc_status"]
|
|
119
|
+
CODE -.-> TSV
|
|
120
|
+
TEST -.-> TSV
|
|
121
|
+
TSV --> VM["/validate-traces<br/>Coverage Matrix"]
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Sổ trace trong git — và khi nó conflict
|
|
127
|
+
|
|
128
|
+
Sổ trace là **dữ liệu không dựng lại được**, nằm trong git, và **được nhiều người ghi trên
|
|
129
|
+
nhiều nhánh song song**. Ba thứ đó cộng lại nghĩa là git phải được dạy cách merge nó.
|
|
130
|
+
|
|
131
|
+
### Luật đã cài sẵn
|
|
132
|
+
|
|
133
|
+
`/setup-ai-first` tạo `{trace_dir}/.gitattributes`; `/sync` Step 4c kiểm và tạo hộ nếu thiếu:
|
|
134
|
+
|
|
135
|
+
```gitattributes
|
|
136
|
+
*.tsv text eol=lf merge=union
|
|
137
|
+
*.jsonl text eol=lf merge=union
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
| Luật | Không có nó thì sao |
|
|
141
|
+
|---|---|
|
|
142
|
+
| `merge=union` | Hai nhánh cùng ghi một sổ → **conflict**. Giải bằng "take mine" = **mất row của người kia, im lặng**. Với `trace-history.jsonl` (append cuối file) thì **mọi** cặp nhánh song song đều conflict. |
|
|
143
|
+
| `text eol=lf` | Một máy ghi CRLF → git thấy **mọi dòng** đã đổi → union giữ cả hai bản → **nhân đôi cả file**, gồm cả dòng header. Team mixed Windows/macOS gặp ca này mà không ai làm gì sai. |
|
|
144
|
+
|
|
145
|
+
`union` là driver **built-in** của git — không ai cần chạy `git config` gì thêm.
|
|
146
|
+
|
|
147
|
+
> **`*.json` cố ý KHÔNG có trong danh sách.** `trace-report.json` nằm cùng thư mục và union
|
|
148
|
+
> trên JSON tạo ra **JSON không hợp lệ** → panel VS Code parse lỗi. Nó **sinh lại được**:
|
|
149
|
+
> conflict ở đó thì chạy lại `/validate-traces`, đừng merge tay.
|
|
150
|
+
|
|
151
|
+
### Đánh đổi có chủ ý: trùng row thay vì mất row
|
|
152
|
+
|
|
153
|
+
`merge=union` **có thể** tạo hai row cùng `sc_id`. Đó là **thiết kế**, không phải lỗi:
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
trùng row → --lint-trace T4 bắt được → /validate-traces reconcile → sạch
|
|
157
|
+
mất row → KHÔNG có gì bắt được → phát hiện sau 3 tuần → khôi phục bằng tay
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Đây là lý do luật merge và `--lint-trace` là **một cặp**: union chuyển lỗi từ dạng *im lặng*
|
|
161
|
+
sang dạng *bắt được*, và cái bắt nó phải tồn tại. Có union mà không có lint thì chỉ là đổi chỗ lỗi.
|
|
162
|
+
|
|
163
|
+
### Playbook — 4 bước
|
|
164
|
+
|
|
165
|
+
Vẫn thấy conflict trong `{trace_dir}/` (nhánh phân kỳ quá xa, hoặc luật vừa mới được thêm):
|
|
166
|
+
|
|
167
|
+
1. **GIỮ CẢ HAI BÊN.** Xoá ba dòng marker (`<<<<<<<`, `=======`, `>>>>>>>`), giữ toàn bộ row
|
|
168
|
+
của cả hai phía. **Đừng chọn một bên.**
|
|
169
|
+
2. `npx @educa-corp/sdd-framework --lint-trace` — T7 xác nhận không còn marker, T4 chỉ ra row nào trùng.
|
|
170
|
+
3. `/validate-traces` — reconcile row trùng về một row đúng, tính lại `status`.
|
|
171
|
+
4. Commit.
|
|
172
|
+
|
|
173
|
+
**Ba điều không bao giờ làm:**
|
|
174
|
+
|
|
175
|
+
| ❌ | Vì sao |
|
|
176
|
+
|---|---|
|
|
177
|
+
| `git checkout --ours/--theirs` trên file trace | Xoá sạch row của một phía. Không có gì báo. |
|
|
178
|
+
| Xoá file trace để "cho sạch" rồi chạy lại pipeline | Sổ **không regenerate được** — `trace-history.jsonl` mất là mất vĩnh viễn. |
|
|
179
|
+
| Commit khi còn marker | Marker thành một "row" TSV. `--lint-trace` T7 bắt, nhưng nếu chưa cắm vào CI thì nó vào nhánh chung. |
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Đọc tiếp (Next)
|
|
184
|
+
|
|
185
|
+
- [Architecture](architecture.md) — 3 lớp & context-loader
|
|
186
|
+
- [Pipeline › Validate Traces](pipeline-steps/09-validate-traces.md) — dùng ma trận coverage
|
|
187
|
+
- [Reference › Trace Schema](../04-reference/trace-schema.md) — field đầy đủ
|
|
@@ -98,7 +98,7 @@ sprint thứ ba không ai làm.** Framework có hai lệnh CLI trả exit code
|
|
|
98
98
|
|
|
99
99
|
| Lệnh | Chặn gì | Đặt ở đâu |
|
|
100
100
|
|---|---|---|
|
|
101
|
-
| `--lint-trace` | **Cấu trúc s
|
|
101
|
+
| `--lint-trace` | **Cấu trúc sổ** (13 rule): header lệch · row sai số ô · enum sai · `sc_id` trùng · marker conflict git · `.jsonl` hỏng. Cộng **T12 — nhất quán GIỮA các ô**: row vừa `status ∈ {DRIFT, ORPHANED}` vừa mang `dev_selftest`/`qc_status = pass`. Cộng hai điều kiện **cấu hình** ở mức ⚠️: thiếu luật merge · sổ bị gitignore | pre-push **và** CI |
|
|
102
102
|
| `--gate-trace` | **Cấu hình** (nâng hai ⚠️ trên thành chặn) + **cờ 🔴**: `ORPHANED` · `TRACE_ORPHAN` · `SEAM_UNWIRED` · `STUB_UNRESOLVED` | CI (cần report tươi) |
|
|
103
103
|
|
|
104
104
|
```bash
|
|
@@ -106,20 +106,30 @@ npx @educa-corp/sdd-framework --lint-trace # exit 1 nếu sổ hỏng
|
|
|
106
106
|
npx @educa-corp/sdd-framework --gate-trace # exit 1 nếu có cờ 🔴
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
-
Mẫu sẵn dùng: `.agent/templates/ci/trace-gate.yml` (GitHub Actions) · `.agent/templates/hooks/pre-push`.
|
|
109
|
+
Mẫu sẵn dùng: `.agent/templates/ci/trace-gate.yml` (GitHub Actions) · `.agent/templates/ci/trace-gate.gitlab-ci.yml` (GitLab CI) · `.agent/templates/hooks/pre-push`.
|
|
110
110
|
**Copy ra khỏi `.agent/`** trước khi dùng — mọi thứ trong đó bị ghi đè mỗi lần `/update-framework`.
|
|
111
111
|
|
|
112
112
|
### Bốn tầng của `--gate-trace`, và vì sao thứ tự đó
|
|
113
113
|
|
|
114
114
|
0. **Sổ được bảo vệ?** — sổ có **nằm trong git**? git có **biết cách gộp** sổ? Kiểm cấu trúc của một quyển sổ sắp mất thì vô nghĩa. Hai điều kiện này `lint-trace` đã phát hiện dưới dạng ⚠️; gate **nâng** chúng thành lỗi chặn — *pre-push nhắc, CI chặn*.
|
|
115
|
+
**Và một điều kiện thứ ba: có kiểm được không?** (`T13`) — thiếu binary `git`, hoặc sổ ngoài mọi repo. Trước v0.7.2, ca này làm G0 in `✅ sổ được bảo vệ — 2/2 điều kiện` trên bằng chứng chưa bao giờ thu, vì G0 lọc warns theo rule id và không thấy gì thì kết luận *"sạch"* thay vì *"không biết"*. Ca thường gặp nhất là **image CI tối giản** — `node:*-alpine` và `node:*-slim` đều không có git, tức chính môi trường mà cổng này tồn tại để chạy.
|
|
115
116
|
1. **Sổ đúng hình dạng?** (gọi `--lint-trace`) — phán trạng thái trên sổ lệch cột là phán trên dữ liệu rác.
|
|
116
|
-
2. **Report còn tươi
|
|
117
|
-
Không có tầng này thì cổng là **sân khấu**: chạy `/validate-traces` một lần,
|
|
118
|
-
rồi sửa gì cũng được — CI đọc report cũ và cho qua mãi.
|
|
117
|
+
2. **Report còn tươi VÀ phủ toàn bộ?** — đối chiếu `trace-report.json` với chính sổ TSV nó khai
|
|
118
|
+
là đang mô tả. Không có tầng này thì cổng là **sân khấu**: chạy `/validate-traces` một lần,
|
|
119
|
+
commit report, rồi sửa gì cũng được — CI đọc report cũ và cho qua mãi.
|
|
120
|
+
**Cộng thêm (G57):** report mang field `scope`, và G2 **fail** nếu `scope.kind !== "all"` —
|
|
121
|
+
**không ngoại lệ**, không đếm xem trên đĩa có bao nhiêu domain. `/validate-traces --domain/--prd/--uc`
|
|
122
|
+
là audit **một phần**; muốn tạo PR thì phải có một lần audit **toàn bộ** đã commit.
|
|
123
|
+
*(Report sinh trước G57 không có `scope` → gate ⚠️ "kiểm độ phủ ở mức YẾU", không fail.)*
|
|
119
124
|
3. **Có cờ 🔴?** — đếm counter, nêu tên thủ phạm kèm câu `fix` của chính framework.
|
|
120
125
|
|
|
121
126
|
### Giới hạn — nói rõ với team, đừng để họ tin quá
|
|
122
127
|
|
|
128
|
+
> **Hai thứ `--lint-trace` bắt được mà không cần LLM** *(nên cứ để nó chạy ở CI)*:
|
|
129
|
+
> **T12** — `status = DRIFT` mà `dev_selftest = pass` là một sổ **hợp lệ** với 11 rule cũ (T1–T8
|
|
130
|
+
> chỉ kiểm hình dạng *từng ô*), nhưng nó là một lời khẳng định **sai**. T12 là rule đầu tiên nhìn
|
|
131
|
+
> **nhiều ô cùng lúc**. **T10/T11** — luật merge + gitignore, gate nâng thành chặn.
|
|
132
|
+
>
|
|
123
133
|
> `--gate-trace` chứng minh **"report khớp SỔ"**.
|
|
124
134
|
> Chỉ `/validate-traces` chứng minh được **"sổ khớp CODE"** (nó phải quét tag trong source,
|
|
125
135
|
> đọc `.feature`, so version — việc của LLM).
|
|
@@ -81,6 +81,7 @@ public TokenDto login(...) { }
|
|
|
81
81
|
- ❌ Gộp tag của nhiều UC về một header file, hoặc trỏ `@trace.source` vào thư mục → drift báo oan hoặc mù; UC rơi về `UNTRACKED` dù đã có code.
|
|
82
82
|
- ❌ Gỡ tag version khỏi code cho "gọn" → `/validate-traces` mù, **im lặng**.
|
|
83
83
|
- ❌ Coi `dev_selftest` thay QC chính thức.
|
|
84
|
+
- ❌ Chạy `/dev-run-test` khi row đang `DRIFT` rồi tin dấu xanh. Từ v0.7.0 lệnh **tự chặn**: test pass trên row `DRIFT` → nó ghi `not_run`, **không** ghi `pass` (`fail` thì vẫn ghi `fail`). Lý do: `pass` nghĩa *"đã nghiệm thu theo spec **hiện tại**"*, và test cũ đang nghiệm thu một hành vi không còn tồn tại. Đường đúng: `/generate-code {UC-ID}` → `/dev-gen-test {UC-ID}` → chạy lại.
|
|
84
85
|
- ❌ Để AI tự review code nó vừa sinh.
|
|
85
86
|
- ❌ Tạo PR khi `/validate-traces` còn cờ 🔴 (`SEAM_UNWIRED` · `STUB_UNRESOLVED` · `ORPHANED` · `TRACE_ORPHAN`) — build xanh không chứng minh luồng ghép chạy đúng.
|
|
86
87
|
- ❌ Coi FE `fe_phase = ui` là xong vì status đã `OK` — test đang chạy trên **mock**.
|