Điều 24: Luật Nhãn — Label Law v1.3
ĐIỀU 24: LUẬT NHÃN — LABEL LAW v1.3
(Bổ sung Hiến pháp Kiến trúc — Quản lý phân loại đa chiều cho toàn hệ thống)
LUẬT v1.3 CHÍNH THỨC | S122 (2026-03-15) | ĐỒNG THUẬN: Huyen + Claude + GPT (4 rounds) + Gemini (4 rounds). ĐÓNG BĂNG. v1.3 thay đổi từ v1.2: +Khóa NULL bypass composite FK (GPT R3🔴). +Trigger DDL per-table theo cột thực có (GPT R3🔴). +BEFORE INSERT auto-fill parent_facet (Gemini R3🟡). +Max 3 nhãn domain/entity chống label farming (Gemini R3🟡). +"Cùng nhóm" giữ per-facet domain (GPT R3 vs Gemini R3 — Claude quyết: per-facet đúng hơn). Đọc:
search_knowledge("luật nhãn label law taxonomy")
I–IV. (Giữ nguyên từ v1.2)
Vấn đề (7 hệ thống rời rạc) → Nguyên tắc (Faceted, SSOT, Tự động) → Ma trận 6×6 → 6 chiều chi tiết.
V. CẤU TRÚC PG + DIRECTUS — ASSEMBLY FIRST
5.1 Schema — 5 collections (v1.3)
-- 1. CHIỀU PHÂN LOẠI (6 records)
CREATE TABLE taxonomy_facets (
id serial PRIMARY KEY,
code text UNIQUE NOT NULL,
name text NOT NULL,
description text,
cardinality text NOT NULL DEFAULT 'single'
CHECK (cardinality IN ('single','multiple')),
max_labels_per_entity smallint DEFAULT 0,
sort integer DEFAULT 0,
status text DEFAULT 'active'
);
-- 2. GIÁ TRỊ NHÃN (phân cấp 3 tầng)
CREATE TABLE taxonomy (
id serial PRIMARY KEY,
code text UNIQUE NOT NULL,
name text NOT NULL,
name_en text,
facet_id integer NOT NULL REFERENCES taxonomy_facets(id),
parent_id integer,
parent_facet integer,
depth smallint NOT NULL DEFAULT 0
CHECK (depth >= 0 AND depth <= 2),
description text,
scope text[],
status text DEFAULT 'active'
CHECK (status IN ('active','deprecated')),
replaced_by text,
sort integer DEFAULT 0,
date_created timestamp DEFAULT now(),
CONSTRAINT chk_root_no_parent CHECK (
(depth = 0 AND parent_id IS NULL AND parent_facet IS NULL)
OR (depth > 0 AND parent_id IS NOT NULL AND parent_facet IS NOT NULL)
),
CONSTRAINT uq_taxonomy_id_facet UNIQUE (id, facet_id),
CONSTRAINT fk_taxonomy_parent_same_facet
FOREIGN KEY (parent_id, parent_facet) REFERENCES taxonomy(id, facet_id),
CONSTRAINT chk_parent_facet_match CHECK (parent_facet = facet_id),
CONSTRAINT chk_replaced_by_not_self CHECK (replaced_by != code),
CONSTRAINT fk_replaced_by FOREIGN KEY (replaced_by) REFERENCES taxonomy(code)
);
CREATE OR REPLACE FUNCTION fn_taxonomy_auto_fill_parent_facet()
RETURNS trigger AS $$
BEGIN
IF NEW.parent_id IS NOT NULL THEN
NEW.parent_facet := NEW.facet_id;
ELSE
NEW.parent_facet := NULL;
END IF;
RETURN NEW;
END; $$ LANGUAGE plpgsql;
CREATE TRIGGER trg_taxonomy_fill_parent_facet
BEFORE INSERT OR UPDATE ON taxonomy FOR EACH ROW
EXECUTE FUNCTION fn_taxonomy_auto_fill_parent_facet();
CREATE OR REPLACE FUNCTION chk_no_cycle() RETURNS trigger AS $$
DECLARE v_current integer; v_depth integer := 0;
BEGIN
IF NEW.parent_id IS NULL THEN RETURN NEW; END IF;
v_current := NEW.parent_id;
WHILE v_current IS NOT NULL AND v_depth < 5 LOOP
IF v_current = NEW.id THEN
RAISE EXCEPTION 'Cycle detected in taxonomy tree';
END IF;
SELECT parent_id INTO v_current FROM taxonomy WHERE id = v_current;
v_depth := v_depth + 1;
END LOOP;
RETURN NEW;
END; $$ LANGUAGE plpgsql;
CREATE TRIGGER trg_taxonomy_no_cycle
BEFORE INSERT OR UPDATE ON taxonomy FOR EACH ROW
EXECUTE FUNCTION chk_no_cycle();
CREATE OR REPLACE FUNCTION chk_replaced_by_valid() RETURNS trigger AS $$
DECLARE v_target_facet integer; v_chain text; v_depth integer := 0;
BEGIN
IF NEW.replaced_by IS NULL THEN RETURN NEW; END IF;
SELECT facet_id INTO v_target_facet FROM taxonomy WHERE code = NEW.replaced_by;
IF v_target_facet != NEW.facet_id THEN
RAISE EXCEPTION 'replaced_by must point to same facet';
END IF;
v_chain := NEW.replaced_by;
WHILE v_chain IS NOT NULL AND v_depth < 5 LOOP
IF v_chain = NEW.code THEN RAISE EXCEPTION 'Replacement cycle detected'; END IF;
SELECT replaced_by INTO v_chain FROM taxonomy WHERE code = v_chain;
v_depth := v_depth + 1;
END LOOP;
RETURN NEW;
END; $$ LANGUAGE plpgsql;
CREATE TRIGGER trg_taxonomy_replaced_by
BEFORE INSERT OR UPDATE ON taxonomy FOR EACH ROW
EXECUTE FUNCTION chk_replaced_by_valid();
CREATE INDEX idx_taxonomy_facet_parent_sort ON taxonomy(facet_id, parent_id, sort);
CREATE INDEX idx_taxonomy_depth ON taxonomy(depth);
-- 3. MA TRẬN CHIỀU × LỚP (36 records)
CREATE TABLE taxonomy_matrix (
id serial PRIMARY KEY,
facet_id integer NOT NULL REFERENCES taxonomy_facets(id),
composition_level text NOT NULL,
requirement text NOT NULL DEFAULT 'not_applicable'
CHECK (requirement IN ('mandatory','optional','not_applicable')),
UNIQUE(facet_id, composition_level)
);
-- 4. GÁN NHÃN CHO ENTITY (junction)
CREATE TABLE entity_labels (
id serial PRIMARY KEY,
entity_code text NOT NULL,
label_code text NOT NULL REFERENCES taxonomy(code),
assigned_by text DEFAULT 'auto'
CHECK (assigned_by IN ('auto','rule','user','ai','dot')),
rule_id integer,
assigned_at timestamp DEFAULT now(),
UNIQUE(entity_code, label_code)
);
CREATE INDEX idx_el_entity ON entity_labels(entity_code);
CREATE INDEX idx_el_label ON entity_labels(label_code);
CREATE OR REPLACE VIEW v_all_entity_codes AS
SELECT code FROM dot_tools WHERE code IS NOT NULL
UNION ALL SELECT code FROM workflows WHERE code IS NOT NULL
UNION ALL SELECT code FROM checkpoint_types WHERE code IS NOT NULL
UNION ALL SELECT code FROM modules WHERE code IS NOT NULL
UNION ALL SELECT code FROM tasks WHERE code IS NOT NULL
UNION ALL SELECT code FROM agents WHERE code IS NOT NULL
UNION ALL SELECT code FROM ui_pages WHERE code IS NOT NULL
UNION ALL SELECT code FROM collection_registry WHERE code IS NOT NULL
UNION ALL SELECT code FROM table_registry WHERE code IS NOT NULL
UNION ALL SELECT code FROM entity_dependencies WHERE code IS NOT NULL
UNION ALL SELECT code FROM workflow_steps WHERE code IS NOT NULL
UNION ALL SELECT code FROM checkpoint_sets WHERE code IS NOT NULL
UNION ALL SELECT code FROM workflow_change_requests WHERE code IS NOT NULL
UNION ALL SELECT code FROM system_issues WHERE code IS NOT NULL
UNION ALL SELECT code FROM meta_catalog WHERE code IS NOT NULL
UNION ALL SELECT code FROM taxonomy WHERE code IS NOT NULL;
-- 5. QUY TẮC GÁN TỰ ĐỘNG
CREATE TABLE label_rules (
id serial PRIMARY KEY,
name text NOT NULL,
facet_id integer NOT NULL REFERENCES taxonomy_facets(id),
rule_type text NOT NULL
CHECK (rule_type IN ('collection','keyword','relation')),
condition jsonb NOT NULL
CHECK (jsonb_typeof(condition) = 'object')
CHECK (condition ? 'collection' OR condition ? 'name_pattern' OR condition ? 'relation'),
result_label text NOT NULL REFERENCES taxonomy(code),
priority integer DEFAULT 100,
skip_wide_warning boolean DEFAULT false,
status text DEFAULT 'active'
);
5.2 Conflict Resolution (giữ nguyên v1.2 + bổ sung v1.3)
(Giữ nguyên bảng conflict v1.2)
v1.3 Gemini R3: Chống label farming. Chiều NHIỀU có max_labels_per_entity trong taxonomy_facets (ví dụ: domain=3). Nếu auto-assign vượt threshold → KHÔNG gán thêm, ghi system_issues.
5.3 PG Function gán tự động (v1.3 — per-table DDL)
CREATE OR REPLACE FUNCTION fn_auto_label_assignment()
RETURNS trigger AS $$
DECLARE
v_entity_code text;
v_collection text;
v_row jsonb;
v_name text;
v_desc text;
v_facet_max smallint;
v_current_count integer;
r label_rules%ROWTYPE;
BEGIN
IF TG_OP = 'UPDATE' THEN
IF (to_jsonb(NEW)->>'name') IS NOT DISTINCT FROM (to_jsonb(OLD)->>'name')
AND (to_jsonb(NEW)->>'description') IS NOT DISTINCT FROM (to_jsonb(OLD)->>'description') THEN
RETURN NEW;
END IF;
END IF;
v_collection := TG_TABLE_NAME;
v_row := to_jsonb(NEW);
v_entity_code := v_row->>'code';
IF v_entity_code IS NULL THEN RETURN NEW; END IF;
v_name := COALESCE(v_row->>'name', '');
v_desc := COALESCE(v_row->>'description', '');
FOR r IN
SELECT * FROM label_rules WHERE status = 'active' ORDER BY priority ASC
LOOP
SELECT max_labels_per_entity INTO v_facet_max
FROM taxonomy_facets WHERE id = r.facet_id;
IF v_facet_max > 0 THEN
SELECT COUNT(*) INTO v_current_count
FROM entity_labels el JOIN taxonomy t ON el.label_code = t.code
WHERE el.entity_code = v_entity_code AND t.facet_id = r.facet_id;
IF v_current_count >= v_facet_max THEN
CONTINUE;
END IF;
END IF;
IF r.rule_type = 'collection'
AND r.condition->>'collection' = v_collection THEN
INSERT INTO entity_labels(entity_code, label_code, assigned_by, rule_id)
VALUES (v_entity_code, r.result_label, 'rule', r.id)
ON CONFLICT (entity_code, label_code) DO NOTHING;
END IF;
IF r.rule_type = 'keyword'
AND (v_name ~* (r.condition->>'name_pattern')
OR v_desc ~* (r.condition->>'name_pattern')) THEN
INSERT INTO entity_labels(entity_code, label_code, assigned_by, rule_id)
VALUES (v_entity_code, r.result_label, 'rule', r.id)
ON CONFLICT (entity_code, label_code) DO NOTHING;
END IF;
END LOOP;
RETURN NEW;
END; $$ LANGUAGE plpgsql;
v1.3 GPT R3🔴: Trigger DDL per-table theo cột thực có.
CREATE TRIGGER trg_label_assign_dot_tools
AFTER INSERT OR UPDATE OF name, description ON dot_tools
FOR EACH ROW EXECUTE FUNCTION fn_auto_label_assignment();
CREATE TRIGGER trg_label_assign_agents
AFTER INSERT OR UPDATE OF name ON agents
FOR EACH ROW EXECUTE FUNCTION fn_auto_label_assignment();
CREATE TRIGGER trg_label_assign_deps
AFTER INSERT ON entity_dependencies
FOR EACH ROW EXECUTE FUNCTION fn_auto_label_assignment();
Quy tắc generate trigger: Kiểm tra information_schema.columns. Có name+description → UPDATE OF name, description. Có name only → UPDATE OF name. Không name → AFTER INSERT only. DOT dot-label-trigger-setup tự generate.
5.4 "Cùng nhóm" = Scored Union Top N — PER-FACET (v1.3)
SELECT el2.entity_code,
COUNT(*) AS shared_labels,
array_agg(t.name) AS shared_label_names
FROM entity_labels el1
JOIN entity_labels el2 ON el1.label_code = el2.label_code
JOIN taxonomy t ON el1.label_code = t.code
WHERE el1.entity_code = :source_entity
AND el2.entity_code != :source_entity
AND t.facet_id = (SELECT id FROM taxonomy_facets WHERE code = 'FAC-01')
GROUP BY el2.entity_code
ORDER BY shared_labels DESC
LIMIT 20;
5.5 Directus (giữ nguyên v1.2)
AI Agent READ-only. Tắt Count junction. parent_facet ẩn, auto-fill trigger.
VI–IX. (Giữ nguyên từ v1.2)
Quy tắc gán 3 tầng + Registries UI 6×6 + DOT kiểm tra + Migration.
v1.3 bổ sung DOT: dot-label-trigger-setup — generate DDL trigger đúng per table dựa trên information_schema.columns.
X. PROVENANCE — NGUỒN GỐC DESCRIPTION/METADATA (bổ sung S178 Fix 23, Đ43 Phase C Track A)
10.1 Facet FAC-PROV — Description Provenance
Facet mới theo dõi nguồn gốc sinh description/metadata — ai viết, bằng phương thức nào. Scope: provenance của description/metadata, không phải provenance toàn entity.
INSERT INTO taxonomy_facets(code, name, description, cardinality, max_labels_per_entity)
VALUES ('FAC-PROV', 'Description Provenance',
'Nguồn gốc sinh description/metadata — ai viết, bằng phương thức nào.',
'single', 1)
ON CONFLICT (code) DO NOTHING;
INSERT INTO taxonomy(code, name, facet_id, depth, description, status)
VALUES
('PROV-AI', 'AI-generated',
(SELECT id FROM taxonomy_facets WHERE code='FAC-PROV'),
0, 'Description được AI agent sinh tự động — chưa human review', 'active'),
('PROV-HUMAN', 'Human-reviewed',
(SELECT id FROM taxonomy_facets WHERE code='FAC-PROV'),
0, 'Description do con người viết trực tiếp hoặc đã review và approve output AI', 'active'),
('PROV-DOT', 'DOT-generated',
(SELECT id FROM taxonomy_facets WHERE code='FAC-PROV'),
0, 'Description được DOT sinh từ template/metadata', 'active')
ON CONFLICT (code) DO NOTHING;
10.2 Cách dùng
| Tình huống | Label | Gán bởi |
|---|---|---|
| AI agent sinh description (backfill hoặc khai sinh) | PROV-AI | agent (assigned_by='ai') |
| Con người viết/xác nhận (spot-check OK) | PROV-HUMAN | user (assigned_by='user') |
| DOT script tự sinh từ template | PROV-DOT | dot (assigned_by='dot') |
-- Gán khi AI backfill:
INSERT INTO entity_labels(entity_code, label_code, assigned_by)
VALUES ('DOT-042', 'PROV-AI', 'ai')
ON CONFLICT (entity_code, label_code) DO NOTHING;
-- Chuyển sau human review:
UPDATE entity_labels SET label_code='PROV-HUMAN', assigned_by='user'
WHERE entity_code='DOT-042' AND label_code='PROV-AI';
10.3 Liên kết
- Đ3 §2.4: Quy trình backfill description yêu cầu gán provenance label
- Đ4 §2.1: Birth guard kiểm tra description tại INSERT — provenance gán riêng bởi agent
- Đ43: Context pack hiển thị tỷ lệ AI-generated vs Human-reviewed
Điều 24 v1.3 | S122 (2026-03-15) | Huyen + Claude + GPT R1+R2+R3 + Gemini R1+R2+R3. 6 rounds review. NULL bypass sealed. Per-table trigger DDL. Label farming threshold. parent_facet auto-fill.
CHANGELOG (inline)
| Ngày | Nội dung |
|---|---|
| S178 Fix 23 (2026-04-20) | +§X (Section 10): FAC-PROV facet + 3 label values (PROV-AI, PROV-HUMAN, PROV-DOT). Provenance cho description/metadata — theo dõi nguồn gốc sinh. Liên kết Đ3 §2.4 + Đ4 §2.1 + Đ43. Hội đồng 2 vòng APPROVE FINAL (Gemini 10 + GPT 9.4). |