KB-4C44

GPT Review Round 2 — Điều 24 Luật Nhãn v1.1 (2026-03-15)

8 min read Revision 1

GPT Review Round 2 — Điều 24: Luật Nhãn v1.1

Ngày: 2026-03-15 Nguồn: Incomex Hội đồng AI (GPT) Phạm vi: Phản biện kiến trúc round 2, KHÔNG sửa luật gốc

Kết luận tổng quát

🟢 Đồng ý mạnh hơn round 1. v1.1 đã sửa đúng các điểm lớn: thêm chiều 6, conflict resolution, tree constraints, migration per field, permissions, và function chung.

Tuy vậy còn 4 điểm cần sửa/chốt4 điểm nên cân nhắc trước khi coi là đủ chắc để triển khai rộng.


1. 16 điểm đã sửa — có điểm nào chưa đúng/chưa đủ?

🟢 Đồng ý

  • Thêm chiều 6: đúng
  • Conflict resolution: đúng hướng
  • 1 function chung: đúng hướng Assembly First hơn
  • AI Agent read-only taxonomy: đúng
  • Migration per field: đúng
  • depth<=2, root_no_parent, no_self_parent, replaced_by, compound index, JSONB CHECK: đều hợp lý

🔴 Cần sửa 1

no_self_parent không đủ để chống cycle. Nó chỉ chặn A→A, nhưng không chặn A→B→A.

Đề xuất:

  • Giữ CHECK hiện tại
  • Bổ sung trigger/validation chống cycle thật bằng recursive query khi INSERT/UPDATE parent_id

🔴 Cần sửa 2

replaced_by hiện mới là FK tới taxonomy(code), nhưng chưa chặn:

  • label thay thế thuộc facet khác
  • label deprecated trỏ về chính nó
  • chuỗi deprecated vòng lặp

Đề xuất:

  • replaced_by != code
  • same-facet với label gốc
  • nếu label bị deprecated mà còn được dùng → DOT báo migrate bắt buộc

2. Chiều 6 "Phục vụ ai?" — 6 giá trị đã đủ chưa? Cần phân cấp không?

🟢 Đồng ý

6 giá trị hiện tại đủ để bắt đầu.

🟡 Nên cân nhắc

Chiều này nên giữ phẳng ở v1.1. Chưa cần cha-con.

Lý do

  • Audience là chiều vận hành, ít giá trị, cần dễ chọn
  • Nếu phân cấp sớm sẽ tăng ambiguity: ví dụ Admin có thuộc System không?
  • Giá trị hiện tại đủ bao phủ nội bộ + business đầu kỳ

Góc khuất nhỏ

AUD-USRAUD-CUS có thể bị đội vận hành dùng lẫn nhau.

Đề xuất:

  • Giữ phẳng
  • Viết mô tả phân biệt cứng:
    • AUD-USR = user nội bộ/end-user của hệ thống nội bộ
    • AUD-CUS = khách hàng bên ngoài

3. Conflict resolution — còn edge case nào chưa cover?

🟡 Chưa cover hết

Logic hiện tại tốt hơn v1.0, nhưng còn 2 edge case.

🔴 Edge case A — nhiều rule cùng chiều single, cùng priority, cùng rule_type

Hiện luật nói: đồng priority → system_issues. Đúng. Nhưng cần chốt thêm: không insert nhãn tạm thời nào cho chiều đó. Nếu không sẽ có trạng thái nửa đúng nửa sai.

🔴 Edge case B — entity thuộc 2 collections?

Với thiết kế hiện tại, 1 record DB chỉ thuộc 1 table. Nên “entity thuộc 2 collections” không phải case DB-native, mà là case cùng business object xuất hiện qua nhiều representation.

Đề xuất:

  • Không giải bài toán này ở conflict engine v1.1
  • Coi đó là bài toán identity/canonical entity ở Luật Chống Trùng / mapping, không phải label rule

🟡 Edge case C — collection rule + keyword rule cùng chiều multiple

Hiện đang merge. Đồng ý. Nhưng nên thêm nguyên tắc: nếu keyword chỉ match label cha và collection đã gán label con cùng nhánh thì bỏ label cha để tránh dư thừa.


4. fn_auto_label_assignment() — mỗi table schema khác nhau, xử lý thế nào?

🔴 Cần sửa

Bản mô tả hiện tại dùng trực tiếp NEW.name, NEW.description. Điều này không an toàn vì không phải table nào cũng có đủ 2 field này.

Đề xuất tốt hơn

Function chung vẫn giữ, nhưng phải đọc metadata từ label_rules.condition hoặc meta_catalog để biết table đó dùng field nào làm nguồn text.

Ví dụ:

  • ưu tiên field list từ config: ['name','title','description','purpose','content']
  • build text nguồn bằng dynamic extraction an toàn
  • table không có description thì chỉ dùng name/title

Nguyên tắc: function chung, nhưng input field mapping phải là data-driven, không hardcode NEW.description.


5. CHECK same-facet bằng TRIGGER — có cách constraint thuần không?

🟡 Gần như không đẹp bằng constraint thuần

Với self-reference và cần so sánh NEW.facet_id với parent.facet_id, PostgreSQL CHECK thường không phù hợp vì CHECK không nên query bảng khác để enforce quan hệ này.

Kết luận

Giữ trigger là chấp nhận được. Đây là một trong số ít chỗ trigger hợp lý.

🟡 Nên cân nhắc

Nếu muốn giảm rủi ro trigger logic, có thể dùng composite key kiểu:

  • (id, facet_id) unique trên taxonomy
  • FK (parent_id, facet_id) references (id, facet_id)

Đây là cách “constraint-like” hơn trigger.

Tôi nghiêng về phương án này hơn trigger nếu đội triển khai chịu được độ phức tạp schema tăng nhẹ.


6. entity_labels.entity_code là text tự do — validate tồn tại thật được không?

🔴 Đây là lỗ hổng lớn nhất còn lại

Hiện entity_code là soft reference. Điều này dễ tạo:

  • code typo
  • code đã retire/xoá
  • code không tồn tại nhưng label vẫn gán

Kết luận

Không có FK chuẩn vì entity nằm rải ở 17+ bảng. Chấp nhận soft reference là thực tế, nhưng phải bù bằng governance mạnh.

Đề xuất

  1. Giữ entity_code text
  2. Bắt buộc dot-label-validate quét existence
  3. Tạo thêm v_all_entity_codes hoặc table index toàn cục từ meta_catalog/registries để check existence nhanh
  4. UNIQUE/validation ở DB có thể làm qua trigger: entity_code phải nằm trong v_all_entity_codes

Nếu không muốn trigger DB lúc đầu, ít nhất phải có DOT validate + system_issues + CI/audit coverage.


7. Còn góc khuất nào khi scale 5000 entities + 1200 labels + 20000 entity_labels?

🟢 Về PostgreSQL

Khối lượng này còn nhỏ. PG xử lý tốt nếu index đúng.

🟡 Về bottleneck thực tế

Bottleneck lớn hơn sẽ nằm ở Directus Admin và UX, không phải PG.

Điểm cần chú ý

  1. Directus junction UX với 20K rows sẽ nặng nếu render raw M2M khắp nơi
  2. Tree View taxonomy 1200 labels vẫn chịu được, nhưng cần sort/filter rõ
  3. Coverage report và “Cùng nhóm” nên query qua SQL tối ưu, không lồng nhiều API calls

Đề xuất

  • index thêm trên entity_labels(entity_code, label_code) đã có unique rồi, tốt
  • thêm index phụ taxonomy(status, facet_id, depth) nếu Directus lọc nhiều
  • query “Cùng nhóm” nên limit + rank, không trả full union vô hạn

8. "Cùng nhóm" — union hay intersection?

🔴 Cần chốt luật

Nếu 1 entity có 3 domain labels, union sẽ cho tập rộng nhưng dễ nhiễu. Intersection cho tập rất chính xác nhưng dễ rỗng.

Đề xuất

Mặc định dùng UNION có xếp hạng (scored union), KHÔNG dùng intersection thuần.

Cách làm

  • lấy union của entities cùng ít nhất 1 label domain
  • tính điểm theo số nhãn domain trùng:
    • trùng 3 nhãn = điểm cao
    • trùng 2 nhãn = trung bình
    • trùng 1 nhãn = thấp
  • sort giảm dần, chỉ hiện top N

Lý do

  • Intersection thuần sẽ quá chặt, nhiều lúc ra 0
  • Union thuần sẽ quá loãng
  • Scored union là cân bằng tốt nhất

Phán quyết theo mức độ

🔴 Cần sửa

  1. Chống cycle thật, không chỉ no_self_parent
  2. fn_auto_label_assignment() phải data-driven theo field mapping, không gọi cứng NEW.description
  3. Chốt rõ entity_labels.entity_code validation strategy
  4. Chốt “Cùng nhóm” = scored union, không để mơ hồ

🟡 Nên cân nhắc

  1. Giữ audience phẳng, không phân cấp ở v1.1
  2. Dùng composite FK (parent_id, facet_id) thay trigger same-facet nếu muốn schema mạnh hơn
  3. Thêm ràng buộc cho replaced_by
  4. Loại bỏ label cha dư thừa khi đã có label con cùng nhánh

🟢 Đồng ý

  1. Chiều 6 là bổ sung đúng
  2. Conflict resolution tổng thể đã đi đúng hướng
  3. 1 function chung tốt hơn N logic rời rạc
  4. Permissions read-only cho AI là rất đúng
  5. Không dùng ltree là hợp lý với 3 tầng cố định

Kết luận cuối

🟢 v1.1 đã đủ tốt để tiếp tục tiến gần triển khai. 🔴 Nhưng chưa nên coi là khóa kiến trúc cuối cùng nếu chưa xử lý 4 điểm: cycle thật, field mapping trong function chung, validation entity_code, và luật "Cùng nhóm".