KB-3D7F rev 5

Chuẩn format báo cáo sau khi quét UI — v0.2 (bản thử)

11 min read Revision 5
quy-trinhsua-rescannerformatquet-uiv0.2

Chuẩn format báo cáo sau khi quét UI — v0.2 (bản thử)

Trạng thái: v0.2 — BẢN THỬ, chưa chốt. Khuôn này rút từ dữ liệu thật của UI master-list-quy-trinh-v1 (config-driven) và đối chiếu 30 slot đang có trong PG. Nó sẽ được chỉnh sau đợt quét thử 2 UI (master-list, mow-unified-canvas) vì canvas có thể thuộc dạng UI khác. Đừng coi đây là chuẩn cuối cho tới khi có ghi "v1.0 — CHỐT". v0.2 bổ sung so với v0.1: file evidence riêng (mục 8); chuẩn hóa raw_label/purpose (mục 2b); luật anchor_diff không tự sửa (mục 6); quy tắc quét độc lập (mục 1b).

0. Mục đích

Một khuôn chung để mọi bên quét UI đều xuất ra cùng một dạng, nhờ đó máy so sánh (diff) chỉ báo khác biệt thật, không báo nhiễu hình thức.

Các bên phải theo khuôn này:

  • Agent quét (Claude Code, Codex…).
  • DOT scanner (khi viết ở bước sau).

Lưu ý quan trọng — đây KHÔNG phải format của PG. Format quét chỉ chứa thứ quét ra được. PG có thêm nhiều cột chỉ sinh ra sau khi ghi sổ (id, scan_id, is_current, concept_id, dedupe_key, owner_decision…). Quan hệ giữa hai bên là phép chiếu ở mục 5, không phải bắt hai bên giống hệt nhau.

1. Định dạng file

  • Một file JSONL: mỗi dòng một slot, một object JSON.
  • Encoding UTF-8, tiếng Việt có dấu để nguyên.
  • Sắp xếp: theo slot_key tăng dần (abc). Bắt buộc — để hai bản luôn cùng trật tự, diff mới sạch.
  • Tên file: <ui_id>__<who>.jsonl với who ∈ { claude, codex, dot } — giá trị cố định, viết thường, không đặt tên khác (vd master-list-quy-trinh-v1__claude.jsonl).
  • Lưu theo run_id để không ghi đè lần chạy trước. Đường dẫn: /opt/incomex/docs/mcp-writes/sua-re/scans/manual-baseline/<run_id>/<ui_id>/<ui_id>__<who>.jsonl và file evidence cùng thư mục. run_id chỉ nằm ở path, KHÔNG đưa vào từng dòng JSONL — nếu nhét vào dòng thì hai lần chạy khác run_id sẽ làm mọi dòng khác nhau, diff nhiễu toàn bộ.

1b. Quét độc lập (bắt buộc)

Mỗi bên quét (Claude Code, Codex) tự quét, KHÔNG đọc file kết quả của bên kia trước khi hoàn thành bản của mình. Mục đích của việc quét đôi là bắt "cả hai cùng thiếu"; nếu một bên nhìn bài bên kia thì mất hết tác dụng đó. Chỉ sau khi cả hai đã nộp file mới được so.

2. Các trường của một dòng (đúng thứ tự, đủ trường)

Trường Bắt buộc Giá trị
ui_id định danh UI, vd master-list-quy-trinh-v1
location_anchor địa chỉ bền của slot (mục 3)
slot_key ui_id::location_anchor (ghép đúng, không thêm bớt)
anchor_type config_key | code_marker | text_marker
source_layer config | js | dom | text
raw_label nhãn hiển thị lúc quét (chỉ để người đọc, KHÔNG phải khóa)
kind concept (vào sổ khái niệm) | flow_data (dữ liệu luồng, KHÔNG vào sổ)
purpose mô tả mục đích, hoặc "" nếu chưa rõ
purpose_status explicit | inferred | unknown

Không thêm trường tự phát. Cần trường mới → đề xuất ở report, không nhét vào dòng.

Cách viết "rỗng" — chỉ một, bắt buộc (chống nhiễu-giả):

  • Cấm null. Cấm bỏ thiếu field. Mỗi dòng luôn đủ đúng 9 field.
  • Giá trị chưa rõ → dùng chuỗi rỗng "" (với purpose thì kèm purpose_status="unknown"). Không bên nào được dùng null hay bỏ trống, vì null / "" / thiếu-field là ba dạng khác nhau, máy diff sẽ báo lệch giả.
  • Serialize 9 field đúng thứ tự bảng trên (ui_id → location_anchor → slot_key → anchor_type → source_layer → raw_label → kind → purpose → purpose_status). JSON không coi thứ tự key là nghĩa, nhưng xuất cùng thứ tự thì diff dòng-theo-dòng mới sạch.

2b. Chuẩn hóa raw_labelpurpose (chống nhiễu-giả)

  • raw_label: giữ nguyên nhãn nhìn thấy, nhưng trim khoảng trắng đầu/cuối và gộp khoảng trắng liên tiếp thành một. Không đổi chữ hoa/thường, không dịch.
  • purpose: viết ngắn, tối đa một câu. Nếu không chắc → purpose=""purpose_status="unknown". Không bịa purpose cho đẹp báo cáo — thà để unknown.
  • purpose_status: explicit nếu nguồn (config/comment/code) nói rõ; inferred nếu suy nhẹ từ tên field nhưng chưa chắc; unknown nếu không có căn cứ.

2c. Kiểm chéo anchor_type ↔ tiền tố location_anchor

Hai trường này phải nhất quán, vì location_anchor đã mang tiền tố loại:

  • location_anchor bắt đầu config_key:anchor_type phải = config_key.
  • bắt đầu code_marker:anchor_type = code_marker.
  • bắt đầu text_marker:anchor_type = text_marker.

Lệch giữa hai trường (vd anchor config_key:...anchor_type="text_marker") → file fail format, phải sửa trước khi nộp. Đây là loại sai âm thầm, không bắt bằng luật thì file "trông đúng" mà thật ra hỏng.

3. Luật dựng location_anchor (phần chống lệch-giả quan trọng nhất)

Anchor phải có tiền tố loại, theo thứ tự ưu tiên:

  1. config_key:<đường-dẫn-trong-config> — bền nhất. Vd config_key:MASTER_CONFIG.items[].maker.
  2. code_marker:<marker-ổn-định> — cho slot tính toán/hiển thị nằm trong JS. Vd code_marker:qv-tot.
  3. text_marker:<chuỗi-hiển-thị> — yếu nhất, chỉ khi không có gì bền hơn.

Quy tắc chuẩn hóa bắt buộc (hai bên sai lệch nhau ở đây là hỏng diff):

  • Mảng luôn viết [] rỗng, KHÔNG số. Đúng: items[].steps[].items[].min. Sai: items[0].steps[2].items[1].min. Một thuộc tính xuất hiện ở 1.000 phần tử vẫn là một slot.
  • Nhiều leaf luôn đi cùng nhau và cùng một điểm trên UI → gộp về object cha, KHÔNG tách leaf. Vd items[].maker (không tách .t / .n), items[].links (không tách .mot/.moit/.mout). (Đây là bài học thật từ master-list: PG neo ở object-level.)
  • KHÔNG đưa raw_label vào anchor/slot_key. Nhãn đổi được; địa chỉ thì không.
  • KHÔNG dùng chỉ số, id runtime, hay giá trị dữ liệu trong anchor.

4. Ranh giới kind: khái niệm UI vs dữ liệu luồng

  • conceptloại thuộc tính: mã quy trình, số phút, trạng thái, người thực hiện, tổng phút… Cái này vào sổ khái niệm.
  • flow_datanội dung cụ thể của một luồng: "WF-0001 gồm task nào", "task A mấy phút", input/output của một task cụ thể. Quét ra thì ghi nhận với kind=flow_data để đối chiếu, nhưng không đưa vào sổ khái niệm sua_re_*.

Phân biệt nhanh: gộp [] xong mà vẫn là một loại thông tinconcept. Nếu là một bản ghi/giá trị cụ thểflow_data.

5. Phép chiếu sang PG (để đối chiếu, KHÔNG phải để trùng khuôn)

Khi so bản quét với sổ PG (sua_re_ui_khai_niem_nhap), ghép theo:

Bản quét PG
slot_key = slot_key
location_anchor = location_anchor
raw_label ~ raw_label (chỉ tham khảo, có thể khác do đổi nhãn)
kind=concept dòng usage trong sổ
kind=flow_data KHÔNG có trong sổ (đúng)

Các cột PG không có trong bản quét (đúng, không phải lỗi): id, item_key, scan_id, is_current, concept_id, owner_concept_id, dedupe_key, match_status, owner_decision. Chúng sinh ra ở bước ghi sổ, không phải bước quét.

6. Kết quả so sánh (diff) — các nhóm

Khi so hai bản quét (hoặc quét-vs-PG), mỗi slot rơi vào đúng một nhóm:

  • match — cùng slot_key, cùng kind.
  • only_A / only_B — chỉ một bên có (bên kia sót, hoặc là slot thật mới).
  • anchor_diff — cùng chỗ trên UI nhưng hai bên đặt anchor khác cấu trúc (vd một bên gộp object, một bên tách leaf). Đây là chưa thống nhất quy tắc, không phải thiếu.
  • label_or_purpose_diff — cùng slot_key nhưng raw_label/purpose khác.
  • kind_diff — cùng slot_key nhưng một bên gọi concept, bên kia flow_data. Phải xử, vì ảnh hưởng có vào sổ hay không.

anchor_diffkind_diff KHÔNG được agent tự sửa cho khớp. Khi hai bản lệch kiểu object-level vs leaf-level (hoặc concept vs flow_data), agent không được tự chọn bên thắng rồi sửa bản kia cho bằng. Ghi nguyên trạng vào diff, nêu lý do mỗi bên, để Owner/quy tắc quyết. Chỉ khi lệch rõ ràng là một bên sót (only_A/only_B) thì mới là chuyện thiếu-đủ.

Diff chỉ được phản ánh khác biệt thật. Nếu diff báo lệch mà soi ra do format (thứ tự, tiền tố, khoảng trắng) → đó là lỗi format, sửa khuôn, không phải lệch nội dung.

7. Câu nhớ

Cùng khuôn — cùng trật tự — anchor cùng luật, thì diff mới nói thật. Quét ra concept với flow_data; sổ chỉ nhận concept. Format quét ≠ format PG; nối nhau bằng slot_key, không bắt giống hệt. Lệch anchor/kind thì hỏi Owner, không tự sửa cho bằng nhau.

8. File evidence đi kèm (bắt buộc, tách khỏi JSONL)

Mỗi UI, mỗi bên quét, ngoài file JSONL phải nộp thêm một file evidence:

<ui_id>__<who>.evidence.md

Ghi ngắn gọn (không nhét vào JSONL để diff khỏi rối):

  • Nguồn đã đọc: file HTML nào, JS nào (tự resolve từ đâu), dạng UI nhận diện được (config-driven / canvas / form / builder…).
  • Marker/config path chính: object gốc tên gì (vd MASTER_CONFIG), các nhánh chính.
  • Slot không chắc: slot nào only/uncertain, vì sao.
  • Quyết định độ hạt: chỗ nào chọn object-level, chỗ nào leaf-level, và lý do (đây là chỗ hay lệch giữa hai bên nhất).

Evidence là để người soi hiểu vì sao quét ra vậy; JSONL là để máy diff. Hai file, hai việc, không trộn.

9. Validator bắt buộc trước khi nộp

Mỗi bên quét, trước khi báo hoàn thành, phải tự chạy kiểm và chỉ nộp khi qua hết:

  • Mỗi dòng parse được JSON.
  • Mỗi dòng đủ đúng 9 field, không thiếu, không thừa field tự phát.
  • Không có null; giá trị rỗng chỉ dùng "".
  • slot_key == ui_id + :: + location_anchor (ghép đúng, không lệch).
  • anchor_type khớp tiền tố của location_anchor (mục 2c).
  • location_anchor không chứa chỉ số mảng [<số>] như [0], [1]; mảng phải là [].
  • File đã sort theo slot_key tăng dần.
  • Không trùng slot_key trong cùng file (mỗi slot đúng một dòng).
  • File nằm đúng path manual-baseline/<run_id>/<ui_id>/ và có file evidence đi kèm.

Riêng UI đã có dữ liệu trong PG (vd master-list-quy-trinh-v1, 30 slot is_current): thêm một kiểm mỏ-neo — đối chiếu bản quét với PG (SELECT read-only) và ghi trong evidence số khớp/lệch. Đây không phải để ép khớp, mà để phát hiện sớm nếu bản quét dựng lại không nổi cái đã biết đáp án — làm trước cả khi so hai agent với nhau.

Thiếu validator này thì agent dễ "tưởng đúng format" nhưng nộp file không so sánh được.