Điều 28: Luật Kỹ thuật Hiển thị — v2.0 BAN HÀNH (NT rà soát)
ĐIỀU 28: LUẬT KỸ THUẬT HIỂN THỊ — v2.0 BAN HÀNH
v2.0 BAN HÀNH | S150 (2026-04-01) | Huyên đề xuất + Claude soạn Đổi tên: "Luật Khuôn Mẫu Chuẩn" → "LUẬT KỸ THUẬT HIỂN THỊ" Kế thừa: v1.0 (S157). Mở rộng: +Collection PG, +Nuxt whitelist, +Checklist, +Quy trình test, +Chuyển giao, +Coverage scanner. Hội đồng: GPT 8.4/10 + Gemini 9.5/10. 2 vòng review. Đồng thuận ban hành. Rà soát: 13/13 NT — 0 vi phạm (S165-KB rà soát).
I. TUYÊN BỐ CỐT LÕI
Nuxt CHỈ render từ khuôn đã đăng ký trong PG. Không có khuôn = không có giao diện. Không có ngoại lệ.
Mọi giao diện hiển thị business data trong Incomex = 1 instance của 1 khuôn mẫu chuẩn (template). Khuôn được code 1 LẦN, đăng ký trong PG, kiểm soát bởi DOT. Instance = config data trong PG → khuôn render. Thêm giao diện mới = INSERT config, KHÔNG code.
Phạm vi luật — 3 lớp rõ ràng
| Lớp | Ví dụ | Thuộc Điều 28? |
|---|---|---|
| Display template | DirectusTable, DirectusMatrix, TabPivot, DynamicEntityList | ✅ CÓ — phải đăng ký |
| Hạ tầng UI | Layout shell, NavBar, ErrorPage, LoadingSpinner | ❌ KHÔNG — hạ tầng cố định |
| PG trigger/function | BirthTrigger, pivot_matrix() | ❌ KHÔNG — backend |
II. 5 NGUYÊN TẮC
NT-D1: NUXT = MÀN HÌNH, CHỈ RENDER TỪ KHUÔN
Nuxt ĐƯỢC: đọc Directus API, render từ khuôn, phát event/submit payload. Nuxt KHÔNG ĐƯỢC: business logic, query DB, tạo component ngoài khuôn, hardcode, điều phối logic submit.
NT-D2: KHUÔN = SSOT, CODE 1 LẦN
1 loại giao diện = 1 khuôn. Instance = config. Sửa khuôn = tất cả instances cập nhật.
NT-D3: CONFIG TRONG PG, KHÔNG TRONG TEXT
Config trong PG (JSONB) để validate, query, DOT thao tác. Text = documentation.
NT-D4: KHUÔN LÀ THỰC THỂ QUẢN TRỊ
Đăng ký design_templates, birth record, species SPE-TPL, DOT-health, PASS test.
NT-D5: MÁY ĐÚC KHUÔN — CHECKLIST 8 BỘ PHẬN
| # | Bộ phận | Quên thì sao |
|---|---|---|
| 1 | config_schema (JSONB) | Lỗi âm thầm |
| 2 | PG validation (CHECK) | INSERT bậy |
| 3 | Error boundary | Trang trắng |
| 4 | Loading + Empty state | UI treo |
| 5 | DOT-health | Lỗi không ai biết |
| 6 | Test 5/5 PASS | Khuôn lỗi lan |
| 7 | Documentation | Không dùng được |
| 8 | Birth record (auto) | Không biết tồn tại |
Thiếu 1 = KHÔNG active. PG trigger enforce (§III).
III. COLLECTION design_templates
Schema
| Field | Kiểu | Constraint | Mục đích |
|---|---|---|---|
| id | SERIAL | PK | |
| code | TEXT | UNIQUE NOT NULL | TPL-xxx |
| name | TEXT | NOT NULL | Tên hiển thị |
| description | TEXT | nullable | Mô tả + cách dùng |
| version | INT | DEFAULT 1 | Version hiện tại |
| config_schema | JSONB | NOT NULL | Quy tắc validate config |
| component_path | TEXT | nullable | Nuxt component |
| instance_collection | TEXT | FK → collection_registry.collection_name | Collection chứa instances — FK enforce |
| instance_count | INT | DEFAULT 0 | AUTO cập nhật bởi PG trigger |
| checklist_status | JSONB | DEFAULT '{}' | 8 bộ phận — DOT validate đủ 8 key |
| test_results | JSONB | DEFAULT '{}' | 5 loại test — DOT verify |
| migration_source | TEXT | nullable | Component gốc (chuyển giao) |
| status | TEXT | FK → template_statuses.code, DEFAULT 'draft' | Lifecycle — FK enforce |
| species_code | TEXT | DEFAULT 'template' | SPE-TPL |
| composition_level | TEXT | DEFAULT 'material' | Lớp 4 |
★ REFERENCE TABLE cho status (NT4+NT10)
CREATE TABLE template_statuses (code TEXT PRIMARY KEY, name TEXT NOT NULL, sort_order INT);
INSERT INTO template_statuses VALUES
('draft', 'Nháp', 1),
('testing', 'Đang test', 2),
('active', 'Hoạt động', 3),
('deprecated', 'Lỗi thời', 4),
('retired', 'Ngừng', 5);
ALTER TABLE design_templates ADD CONSTRAINT fk_tpl_status
FOREIGN KEY (status) REFERENCES template_statuses(code);
Thêm trạng thái mới = INSERT 1 row. KHÔNG ALTER TABLE.
★ PG TRIGGER enforce lifecycle (Tuyên ngôn ②)
CREATE FUNCTION fn_template_lifecycle_guard() RETURNS TRIGGER AS $$
BEGIN
-- CẤM nhảy cóc: draft→active (bỏ qua testing)
IF OLD.status = 'draft' AND NEW.status = 'active' THEN
RAISE EXCEPTION 'CẤM draft→active. PHẢI qua testing (5/5 PASS).';
END IF;
-- CẤM active khi checklist chưa đủ 8 bộ phận
IF NEW.status = 'active' AND (
SELECT COUNT(*) FROM jsonb_object_keys(NEW.checklist_status)
) < 8 THEN
RAISE EXCEPTION 'CẤM active khi checklist < 8. Hiện: %',
(SELECT COUNT(*) FROM jsonb_object_keys(NEW.checklist_status));
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_template_lifecycle
BEFORE UPDATE ON design_templates
FOR EACH ROW WHEN (OLD.status IS DISTINCT FROM NEW.status)
EXECUTE FUNCTION fn_template_lifecycle_guard();
→ Agent KHÔNG THỂ active khuôn chưa test hoặc checklist thiếu.
Quy tắc versioning
- Không breaking: KHÔNG tăng version. Instance tự hưởng.
- Breaking: TĂNG version. Backward compatibility nội bộ.
- CẤM: Sửa breaking mà không tăng version.
Quy tắc instance
- Instance PHẢI có
template_code(FK) +template_version(INT). - Khuôn upgrade → instances cũ giữ version cũ → render theo version đó.
IV. QUY TRÌNH TEST KHUÔN — 5/5 PASS = ACTIVE
| Test | Tên | Tiêu chí PASS |
|---|---|---|
| 1 | Validation Gate | 5/5 config sai bị reject |
| 2 | Multi-Instance | 3/3 configs render đúng, Nuxt = PG |
| 3 | Resilience | 5/5 edge cases không crash |
| 4 | Truth Check | 100% ô Nuxt = PG, 0 sai |
| 5 | Live Config | Sửa config → giao diện đổi ngay, không deploy |
Lifecycle: draft → testing (5/5 PASS) → active. PG trigger enforce (§III).
V. DOT QUẢN TRỊ — PAIRED (NT12)
| DOT | Cấp | Chức năng | Paired với | Trigger |
|---|---|---|---|---|
| DOT-template-create | B | Tạo + đăng ký khuôn mới | DOT-template-health | on-demand |
| DOT-template-health | A | Kiểm tra checklist, test re-run, coverage | — (self-monitoring) | cron (đọc từ dot_config) + manual |
| DOT-template-coverage | A | Quét route/component ngoài registry | — | cron weekly + manual |
Dual-trigger (NT7): Cron (đọc schedule từ dot_config) + on-demand manual.
VI. QUY TRÌNH TẠO KHUÔN MỚI
- Kiểm tra khuôn phù hợp đã có? → CÓ → INSERT config. XONG.
- Thiết kế (4 bước HP) → 3. Đăng ký (DOT-template-create, status='draft') → 4. Implement (code 1 lần) → 5. Test (5/5) → 6. UPDATE status='testing' → 7. UPDATE status='active' → PG trigger verify checklist 8/8.
VII. CHUYỂN GIAO COMPONENT HIỆN CÓ
4 CÓ = đưa vào: Tái sử dụng + Config-driven + Độc lập + Ổn định.
KHÔNG đưa vào: Hạ tầng UI, backend, dùng 1 lần, chưa ổn định.
Quy trình: Kiểm kê → Đăng ký (draft) → Bổ sung thiếu → Test 5/5 → Activate.
| Component | Ứng viên |
|---|---|
| DynamicEntityList/Detail | ✅ TPL-DYNAMIC-LIST/DETAIL |
| TabPivotView | ✅ TPL-TAB-PIVOT |
| CommentModule | ✅ TPL-003 |
| WorkflowModule | ✅ TPL-004 |
| TaskModule | ✅ TPL-005 |
| InspectorDOT | 🔄 TPL-006 |
| Layout/NavBar/Error | ❌ Hạ tầng |
| BirthTrigger | ❌ Backend |
VIII. NUXT WHITELIST + COVERAGE SCANNER
Whitelist
Nuxt load → đọc template_code → SELECT design_templates WHERE status='active' → CÓ → render / KHÔNG → "Not found".
Coverage Scanner (DOT-template-coverage)
3 câu: (1) Route nào do template nào render? (2) Route ngoài registry? (3) Component ngoài whitelist? Mục tiêu: 0 ngoài registry = coverage 100%.
IX. QUAN HỆ VỚI LUẬT KHÁC
| Luật | Quan hệ |
|---|---|
| Điều 0-G | Khuôn → birth_registry auto |
| NT1 (SSOT) | design_templates = SSOT |
| NT4 (Sẵn sàng) | Instance = config, ref table status |
| NT10 (PG quản lý) | Config PG, status FK, lifecycle trigger |
| NT12 (DOT cặp) | create(B)↔health(A) |
| NT13 (Ưu tiên PG) | PG trigger lifecycle guard |
| Điều 35 (DOT) | DOT lifecycle, dùng chung dot_config |
| Điều 36 (Collection) | instance_collection FK, template_code FK enforce |
X. NỢ KỸ THUẬT
| TD | Nội dung | Khi nào |
|---|---|---|
| TD-A | CI scanner cấm import ngoài whitelist | Sau ban hành |
| TD-B | Runtime manifest từ PG | Sau ban hành |
| TD-C | Permissions/scope per template | Multi-role |
| TD-D | Performance budget | TPL-002 active |
| TD-E | Tách _tests, _health tables | >50 khuôn |
| TD-F | Semver, compatibility matrix | Khuôn upgrade |
Điều 28 v2.0 BAN HÀNH | 13/13 NT ✅ | 6/6 Q ✅ | Ref table status | PG trigger lifecycle+checklist guard | DOT paired rõ | instance_collection FK | Dùng chung dot_config