Điều 33: Luật PostgreSQL v2.1 BAN HÀNH
ĐIỀU 33: LUẬT POSTGRESQL — v2.1 BAN HÀNH
v2.1 BAN HÀNH | Base v2.0 (S176) + amend S178 Fix 15 | 2026-04-18 — giữ nguyên toàn bộ nền tảng v2.0 và bổ sung chuẩn
_dot_origincho các bảng governance mới sinh bởi gói DOT Repair Governance. PG là nền tảng DUY NHẤT. Mọi thứ đứng trên PG. PG sai = tất cả sai. Ban hành theo Hiến pháp v4.6.3. Thay thế hiệu lực v2.0 tại cùng path.
§0. KIẾN TRÚC HẠ TẦNG DỮ LIỆU — 4 DATABASE + 3 LỚP NÃO-KHO-CỔNG
Mọi DOT, mọi script, mọi báo cáo, mọi thiết kế PHẢI biết rõ kiến trúc này TRƯỚC khi viết. Không hiểu kiến trúc = không được triển khai. Bài học S176: HP v4.4.0 đã quên kiến trúc cũ (
data-connection-law.mdv1.1) → cả buổi chiều rà soát mới hiểu cấu trúc thực tế. Lỗ hổng nghiêm trọng. v2.0 ghi rõ trong luật PG.
§0.1 Cụm PostgreSQL — 4 Database trong 1 Cluster
PostgreSQL 16 chạy trên VPS Contabo EU. Cluster postgres (Docker container postgres, port 5432) chứa 4 database có vai trò riêng biệt:
| # | Database | Owner | Tables (active) | Vai trò | Lớp | Gateway hợp pháp |
|---|---|---|---|---|---|---|
| 1 | directus |
workflow_admin | 138+ collections | Business warehouse — Directus CMS, mọi bảng nghiệp vụ user nhập, mọi managed collections (workflows, tasks, modules, system_issues, meta_catalog, knowledge_documents, registry tables, governance, KG, normative…), mọi PG function/trigger phục vụ Directus | Lớp KHO | Directus REST API + DOT chính thức + PG function fn_* |
| 2 | incomex_metadata |
workflow_admin | 9 tables | Agent Data store — kb_documents, kb_documents_history, kb_audit_log, metadata_store, chat_messages, cron_heartbeat, kb_config, trigger_guard_alerts, trigger_guard_exceptions. Bản chất NoSQL-style, tương thích Qdrant vector | Lớp NÃO | Agent Data FastAPI (pg_store.py) — cổng riêng, KHÔNG qua Directus |
| 3 | workflow |
workflow_admin | 0 (rỗng) | Placeholder workflow engine — đang RỖNG có chủ đích. Để dành cho Kestra/Temporal/workflow engine giai đoạn sau (theo data-connection-law.md v1.1 Điều 4) |
Lớp ENGINE (chưa active) | Sẽ định khi engine kích hoạt |
| 4 | postgres |
workflow_admin | system | Default cluster admin DB — không chứa dữ liệu nghiệp vụ. Chỉ dùng admin cluster | — | Không truy cập trực tiếp |
Quan trọng — KHÔNG ĐƯỢC GOM 4 DB:
incomex_metadatariêng để Agent Data có thể vận hành độc lập, NoSQL-style upsert mà không vướng Directus schema constraints.workflowriêng để workflow engine tương lai có không gian riêng, không trộn với business data.directusriêng để Directus CMS làm SSOT business data sạch.- Gom = vi phạm "tách dữ liệu gốc khỏi hiển thị" + "không trộn vai trò 3 lớp" + phá kiến trúc đã được Owner phê duyệt từ S100.
§0.2 Ba Lớp Kiến trúc — Não, Kho, Cổng
Nguyên tắc: Không trộn vai trò. Mỗi thành tố nằm rõ ở 1 lớp chính. Luồng dữ liệu: Cổng → Não → Kho (hoặc ngược lại khi hiển thị).
Lớp NÃO (Brain) — Suy luận, AI, vector search
- Agent Data FastAPI (Docker container, VPS) — orchestrator AI, gọi Qdrant + đọc/ghi
incomex_metadata - Qdrant (Docker container, VPS) — vector store cho embedding tri thức
- AI agents IDE (Cursor, Codex, Gemini CLI, Claude Code, Claude Desktop) — gọi API, không lưu trữ lâu dài
- Vai trò: đọc tri thức, sinh phản hồi, điều khiển CI/CD. KHÔNG là SSOT cho dữ liệu lâu dài
Lớp KHO (Warehouse) — Bản ghi chính thức, SSOT
- PG DB
directus— SSOT cho mọi business records - PG DB
incomex_metadata— SSOT cho Agent Data document store - Directus CMS (port 8055) — gateway chính thức cho
directusDB, áp 3 vùng schema (Core/Migration/Growth) - GitHub — SSOT cho mã nguồn (Nuxt, DOT scripts, infra YAML); GitHub là đường ống push→VPS, không phải runtime SSOT
- GCP Secret Manager — SSOT cho mọi secret (PG password, API token, service account key)
- Vai trò: lưu trữ chính thức, schema enforce, version, audit, phân quyền
Lớp CỔNG (Gate) — Giao diện người, hệ thống ngoài
- Nuxt Web (
https://vps.incomexsaigoncorp.vn) — portal, đọc Directus REST/GraphQL qua nuxt-directus, KHÔNG truy cập DB trực tiếp - Directus Admin UI (port 8055) — admin nội bộ
- Lark Suite (Base, Docs, Sheets, Messenger) — nguồn dữ liệu nghiệp vụ thực
- Chatwoot (planned) — CSKH đa kênh, sẽ tích hợp khi active
- GitHub Actions — CI/CD push qua SSH→VPS deploy
- Vai trò: thu nhận input người, hiển thị output. KHÔNG là SSOT, KHÔNG truy cập DB trực tiếp (trừ deploy hook hợp pháp ghi §15)
§0.3 Quy tắc đọc kiến trúc (BẮT BUỘC)
- Mọi tài liệu phân tích về PG PHẢI bắt đầu bằng câu hỏi: "DB nào? Lớp nào?" Không trả lời được = không được phân tích tiếp.
- Mỗi DOT trong
dot_toolsPHẢI khai báo: ghi vào DB nào, thuộc lớp nào, gateway nào. - Mọi writer mới phải đối chiếu §0.1+§0.2 TRƯỚC khi viết code. Không đối chiếu = vi phạm §0.
data-connection-law.mdv1.1 SUPERSEDED — nội dung kiến trúc 3 lớp đã đưa lên đây. Không viện dẫn file cũ.
§1. NGUYÊN TẮC TỐI THƯỢNG
PostgreSQL 16 = NỀN TẢNG DUY NHẤT của toàn bộ hệ thống Incomex.
PG = NƠI ENFORCE TẤT CẢ LUẬT. Application logic chỉ ĐỌC kết quả PG đã enforce.
PIVOT + AUTO = LINH HỒN HỆ THỐNG. Tất cả thiết kế PG phục vụ 2 mục tiêu: (1) Pivot đúng — mọi data có thể truy vấn đa chiều tức thì. (2) Auto 100% — mọi quy trình tự chạy, không ai nhớ gọi tay.
Thiết kế cho: hàng trăm triệu bản ghi, hàng ngàn collections, ~20.000 báo cáo pivot, tự động 100%, pivot-ready by default.
Bổ sung S176: PG = NỀN TẢNG DUY NHẤT, nhưng cluster có 4 DB với vai trò khác nhau (§0). "PG nền tảng" KHÔNG có nghĩa "1 DB duy nhất".
§2. CHUẨN ĐẶT TÊN (NAMING CONVENTIONS)
§2.1 Collections: snake_case, số nhiều, tiếng Anh
| Loại | Pattern | Ví dụ |
|---|---|---|
| Entity tables | {noun_plural} |
tasks, products |
| Junction (M2M) | {entity1}_{entity2} |
task_comments, entity_labels |
| Registry/catalog | {domain}_registry / _catalog |
collection_registry, meta_catalog |
| System tables | system_{function} |
system_issues |
| Views | v_{mô_tả} |
v_pivot_by_level |
| Materialized views | mv_{mô_tả} |
mv_dashboard_daily |
§2.2 Fields — CÙNG Ý NGHĨA = CÙNG TÊN ở MỌI bảng
| Field | Kiểu | Bắt buộc ở |
|---|---|---|
id |
serial/uuid PK | mọi bảng |
code |
text UNIQUE | mọi governed entity |
name |
text | mọi entity |
status |
text | mọi governed entity |
entity_type |
text | khi tham chiếu cross-table |
entity_code |
text | khi tham chiếu cross-table |
composition_level |
text | mọi governed entity |
species_code |
text | mọi governed entity |
is_active |
boolean | khi cần soft-delete |
date_created |
timestamp | mọi bảng |
date_updated |
timestamp | mọi bảng |
CẤM: cùng nghĩa khác tên, viết tắt không rõ, boolean thiếu prefix is_.
§2.3-2.6 Functions / Triggers / Indexes / Code Prefixes
(Giữ nguyên v1.2)
§3. PIVOT-READY BY DEFAULT
(Giữ nguyên v1.2)
§4. TỰ ĐỘNG 100% (AUTOMATION)
(Giữ nguyên v1.2)
§5. SCALE — HÀNG TRĂM TRIỆU
(Giữ nguyên v1.2)
§6. CONSTRAINT & GUARD
(Giữ nguyên v1.2)
§7. MIGRATION & CHANGE MANAGEMENT
(Giữ nguyên v1.2)
§8. TOÀN VẸN THỰC THỂ — PG LÀ PHÁP LUẬT
(Giữ nguyên v1.2 — 8 loại vi phạm, auto-birth, scanners, vòng lặp, checklist 10 mục)
§9. VÒNG ĐỜI THỰC THỂ — SINH LÃO BỆNH TỬ
(Giữ nguyên v1.2)
§10. TỰ PHÁT HIỆN THIẾU TỰ ĐỘNG — PG LÀ THANH TRA
(Giữ nguyên v1.3 — §10.1-10.8)
§11. ĐỒNG BỘ SCHEMA — ĐIỀU KIỆN CỐT TỬ CHO PIVOT
Pivot đúng KHI VÀ CHỈ KHI schema đồng bộ. 1 field sai tên = JOIN sai = số sai = mất tin cậy. PG có
information_schema— đủ sức tự quét chính mình.
§11.1 Vấn đề
Pivot cần GROUP BY, JOIN cross-table. Nếu bảng A gọi type, bảng B gọi category, bảng C gọi kind — cùng nghĩa "phân loại" — thì:
JOIN ON a.type = b.category→ phải nhớ mapping → DỄ SAIGROUP BY typeở A vsGROUP BY categoryở B → 2 pivot khác nhau cho cùng 1 câu hỏi → LOẠN- Thêm bảng D → phải tra mapping → KHÔNG scale được
Schema đồng bộ = Pivot tự đúng. Schema lệch = Pivot sai từ gốc.
§11.2 Bảng field chuẩn — CANONICAL FIELDS
Mở rộng §2.2 thành canonical field registry — PG table lưu danh sách field chuẩn:
CREATE TABLE IF NOT EXISTS canonical_fields (
id serial PRIMARY KEY,
field_name text UNIQUE NOT NULL, -- tên chuẩn: entity_type, status, composition_level...
field_type text NOT NULL, -- text, integer, boolean, timestamp, jsonb
description text, -- ý nghĩa
is_pivotable boolean DEFAULT true, -- có dùng cho pivot không
aliases text[], -- các tên SAI thường gặp: ['type','category','kind']
required_in text, -- 'all_governed', 'cross_ref', 'optional'
date_created timestamp DEFAULT now()
);
Seed dữ liệu ban đầu (từ §2.2):
INSERT INTO canonical_fields (field_name, field_type, is_pivotable, aliases, required_in) VALUES
('code', 'text', true, '{"code_id","entity_code_id"}', 'all_governed'),
('name', 'text', true, '{"title","label","display_name"}', 'all_governed'),
('status', 'text', true, '{"state","lifecycle_status"}', 'all_governed'),
('entity_type', 'text', true, '{"type","table_name","collection"}', 'cross_ref'),
('entity_code', 'text', true, '{"ref_code","target_code"}', 'cross_ref'),
('composition_level', 'text', true, '{"level","comp_level","layer"}','all_governed'),
('species_code', 'text', true, '{"species","species_id"}', 'all_governed'),
('is_active', 'boolean', true, '{"active","enabled","is_enabled"}', 'optional'),
('date_created', 'timestamp', false, '{"created_at","created"}', 'all_governed'),
('date_updated', 'timestamp', false, '{"updated_at","updated","modified"}', 'all_governed'),
('priority', 'text', true, '{"prio","importance"}', 'optional'),
('sort', 'integer', false, '{"order","position","display_order"}', 'optional');
§11.3 DOT-SCHEMA-SCANNER — PG tự quét schema
PG function fn_scan_schema_violations() dùng information_schema.columns để:
-- Tìm fields dùng TÊN SAI (alias thay vì canonical):
SELECT
c.table_name,
c.column_name AS wrong_name,
cf.field_name AS canonical_name,
cf.aliases
FROM information_schema.columns c
JOIN canonical_fields cf ON c.column_name = ANY(cf.aliases)
WHERE c.table_schema = 'public'
AND c.table_name NOT LIKE 'directus_%' -- skip Directus internal
ORDER BY c.table_name;
-- Tìm governed tables THIẾU field bắt buộc:
SELECT
mc.entity_type AS table_name,
cf.field_name AS missing_field
FROM meta_catalog mc
CROSS JOIN canonical_fields cf
WHERE cf.required_in = 'all_governed'
AND mc.governed = true
AND NOT EXISTS (
SELECT 1 FROM information_schema.columns c
WHERE c.table_name = mc.entity_type
AND c.column_name = cf.field_name
AND c.table_schema = 'public'
);
-- Tìm fields CÙNG Ý NGHĨA KHÁC TYPE giữa các bảng:
SELECT
c1.table_name AS table_a, c1.column_name, c1.data_type AS type_a,
c2.table_name AS table_b, c2.data_type AS type_b
FROM information_schema.columns c1
JOIN information_schema.columns c2
ON c1.column_name = c2.column_name
AND c1.table_name < c2.table_name
WHERE c1.data_type != c2.data_type
AND c1.table_schema = 'public' AND c2.table_schema = 'public'
AND c1.column_name IN (SELECT field_name FROM canonical_fields);
DOT-SCHEMA-SCANNER bọc function này trong DOT tool:
| Kiểm tra | Phát hiện | Hành động |
|---|---|---|
| Field dùng alias thay canonical | category thay vì entity_type |
→ approval (type=schema_rename): đề xuất rename |
| Governed table thiếu field bắt buộc | Table X thiếu species_code |
→ approval (type=schema_add_field): đề xuất ALTER TABLE ADD |
| Cùng field khác data type | status = text ở A, integer ở B |
→ approval (type=schema_type_fix): đề xuất ALTER COLUMN TYPE |
| Field pivot-critical nằm trong JSONB | composition_level trong metadata_json thay vì column |
→ approval (type=schema_extract): đề xuất tách ra column riêng |
Cron: Weekly Chủ nhật 1:30AM (trước orphan scanner).
§11.4 Xử lý khi phát hiện schema lệch
Loại 1 — Rõ ràng, tự xử được (DOT Loại 2 — §12):
- Thiếu field bắt buộc →
ALTER TABLE ADD COLUMN {field} {type} DEFAULT {value}→ tự chạy → báo cáo - Field kiểu sai (text vs varchar) →
ALTER COLUMN TYPE→ tự chạy → báo cáo
Loại 2 — Phức tạp, cần phê duyệt (DOT Loại 1 — §12):
- Rename field (có data cũ, có code đang đọc) → approval → dependency audit → migration script
- Tách JSONB ra column → approval → backfill data → verify
§11.5 canonical_fields = PIVOT DICTIONARY
canonical_fields không chỉ để quét — nó là từ điển cho toàn bộ hệ thống:
pivot_query()dùng để validate: field này có tồn tại trên table không?- AI dùng để hiểu: "entity_type" ở mọi bảng nghĩa là gì?
- DOT tools dùng để auto-generate: tạo table mới → đọc canonical_fields → tạo đủ fields chuẩn
- Scale: thêm 1000 collections → tất cả cùng schema → Pivot JOIN không cần mapping
Register Directus + khai sinh đầy đủ (meta_catalog, birth, species, collection_registry).
§11.6 Cập nhật §10.4 — 5 DOT Scanners
| DOT | Quét gì | Cron |
|---|---|---|
| DOT-SCHEMA-SCANNER | Schema lệch (§11.3) | Weekly CN 1:30AM |
| DOT-ORPHAN-SCANNER | Mồ côi (§8.1) | Daily 2AM |
| DOT-LIFECYCLE-SCANNER | Bất thường vòng đời (§9.3) | Weekly CN 2:30AM |
| DOT-AUTOMATION-SCANNER | Thiếu tự động (§10.2) | Weekly CN 3AM |
| DOT-MISCLASS-SCANNER | Nhầm chuồng (§8.1 loại 6) | Weekly CN 3:30AM |
§12. HAI LOẠI DOT — CHUẨN HOÁ VÒNG ĐỜI
Mọi DOT cuối cùng chỉ có 2 loại. Không có loại 3. Đích cuối của DOT = khép kín 1 vòng.
§12.1 LOẠI 1: XIN PHÉP (cần phê duyệt)
PHÁT HIỆN → ĐỀ XUẤT (approval_requests, pending)
→ PHÊ DUYỆT (User/AI approve/reject)
→ THỰC HIỆN (DOT/Agent apply)
→ KIỂM TRA + XÁC NHẬN (scanner quét lại)
→ (Không OK) → TẠO YÊU CẦU TIẾP THEO (vòng mới)
Khi nào: Thay đổi có tác động lớn, phức tạp, có thể sai, cần judgment. Rename field, thay species, retire entity, promote pivot tier, sửa quy tắc.
Đặc điểm:
- proposed_action PHẢI sẵn (User chỉ approve, không phải nghĩ)
- alternative_actions nếu có nhiều phương án
- Pending cho đến khi có quyết định — KHÔNG tự ý làm
- Sau apply → scanner quét lại verify
DOT loại 1: DOT-MISCLASS-SCANNER, DOT-SCHEMA-SCANNER (phần rename/migrate).
§12.2 LOẠI 2: TỰ XỬ (đủ quy tắc rõ ràng)
PHÁT HIỆN → TỰ XỬ LÝ (apply ngay)
→ BÁO CÁO (ghi log + approval_requests status='applied')
→ KIỂM TRA + XÁC NHẬN (scanner quét lại)
→ (Không OK) → TẠO YÊU CẦU TIẾP THEO (vòng mới, có thể chuyển Loại 1)
Khi nào: Quy tắc RÕ RÀNG, 100% xác định, không cần judgment. Thiếu field bắt buộc → ADD. Thiếu birth → tạo birth. Thiếu meta_catalog entry → tạo. Entity deprecated >90 ngày → retire.
Đặc điểm:
- Tự xử NGAY, không chờ ai
- BẮT BUỘC báo cáo (ghi approval_requests status='applied' để history)
- Scanner quét lại verify — nếu tự xử SAI → chuyển Loại 1 (xin phê duyệt sửa)
- Tiêu chí "đủ rõ" phải GHI TRONG LUẬT hoặc trong canonical_fields
DOT loại 2: DOT-ORPHAN-SCANNER (khai sinh thiếu), DOT-SCHEMA-SCANNER (add missing field), DOT-LIFECYCLE-SCANNER (auto-retire deprecated >90 ngày).
§12.3 Phân loại DOT vào 2 loại — meta field
Mỗi DOT tool trong dot_tools collection PHẢI có field:
dot_type:'type_1_approval'hoặc'type_2_auto'dot_scope: những gì DOT này quét/xử lý
DOT-ORPHAN-SCANNER có thể là HYBRID: một số vi phạm tự xử (Loại 2 — thiếu birth → tạo), một số xin phép (Loại 1 — thiếu species → đề xuất vì cần judgment phân loại).
Quy tắc hybrid: trong 1 DOT, mỗi HÀNH ĐỘNG phải rõ Loại 1 hay Loại 2. Ghi rõ trong code + proposed_action.
§12.4 Đích cuối — Pivot sẽ đếm DOT lửng lơ
Khi Pivot chuẩn + schema đồng bộ:
-- Pivot đếm DOT theo loại:
SELECT dot_type, count(*) FROM dot_tools GROUP BY dot_type;
-- DOT chưa phân loại (lửng lơ):
SELECT code, name FROM dot_tools WHERE dot_type IS NULL;
-- DOT có kết quả nhưng chưa khép vòng:
SELECT dt.code, dt.name,
count(ar.id) FILTER (WHERE ar.status = 'pending') AS pending,
count(ar.id) FILTER (WHERE ar.status = 'applied') AS done
FROM dot_tools dt
LEFT JOIN approval_requests ar ON ar.source = dt.name
GROUP BY dt.code, dt.name;
Target: 0 DOT lửng lơ (tất cả có dot_type). Mọi DOT đều khép vòng (phát hiện → xử lý → verify). Pivot cho thấy rõ: bao nhiêu pending, bao nhiêu done, bao nhiêu chưa phân loại.
§13. NGOẠI LỆ DOT 100% — 5 NGOẠI LỆ HỢP PHÁP (BAN HÀNH S176)
Bài học S176: NT3 "DOT 100%" trong HP cũ áp cứng cho mọi thao tác → nhưng có 3 vùng kỹ thuật + 2 vùng kiến trúc về bản chất KHÔNG THỂ qua Directus REST. Người ta bị ép bypass để chạy được hệ thống. Luật cứng quá → người ta bypass → bệnh quay lại. Sửa luật, không sửa hành vi: thừa nhận 5 ngoại lệ này là HỢP PHÁP, có điều kiện ràng buộc rõ ràng.
§13.1 NGOẠI LỆ KỸ THUẬT (3 ngoại lệ)
Ngoại lệ E1 — Schema Bootstrap DDL
Lý do: Directus REST API KHÔNG expose CREATE TABLE, ALTER TABLE, CREATE INDEX, CREATE TRIGGER. Nếu bắt qua Directus → không thể tạo bảng mới. Vô lý.
Cho phép: DOT chuyên biệt dot-pg-*-ensure, dot-schema-*-apply đi thẳng PG qua docker exec postgres psql.
Điều kiện ràng buộc:
- DOT phải đăng ký trong
dot_toolsvớidomain='infrastructure.schema' - Phải idempotent (
CREATE IF NOT EXISTS,ALTER IF NOT EXISTS) - Phải có DOT cặp Cấp A kiểm tra (NT12) — VÍ DỤ:
dot-schema-drift-check - Phải dùng password GSM (§14)
- Phải log vào
dot_run_logsau khi chạy
Ngoại lệ E2 — PG Function Call
Lý do: PG có 79+ function fn_* (refresh, transition_lifecycle, guard, reconcile). Directus REST KHÔNG expose function call. Bắt qua Directus = không gọi được.
Cho phép: Cron job + DOT gọi SELECT fn_*() trực tiếp qua docker exec postgres psql.
Điều kiện ràng buộc:
- Function phải
SECURITY DEFINER(chạy với quyền owner, không phụ thuộc user gọi) - Caller phải đăng ký DOT installer (cron entry phải link tới DOT-NNN trong
dot_tools) - Function logic phải idempotent
- Output phải log vào
dot_run_log
Ngoại lệ E3 — Normative Law Tables (PG-only)
Lý do: Bảng nrm_*, law_* (Điều 38 Luật Văn bản Quy phạm) là PG-only — Directus không có collection tương ứng. Đi qua Directus = không có đường.
Cho phép: Cụm dot-nrm-* đi thẳng PG.
Điều kiện ràng buộc:
- DOT phải đăng ký với
domain='normative' - Phải có pattern an toàn (
ON CONFLICT,UNIQUE) - Phải có DOT cặp verify (NT12)
- Phải dùng password GSM (§14)
§13.2 NGOẠI LỆ KIẾN TRÚC (2 ngoại lệ)
Ngoại lệ E4 — Agent Data → incomex_metadata
Lý do: Agent Data có DB riêng incomex_metadata (§0.1, Lớp Não). Không thuộc scope Directus. Không có "Directus API" cho DB này. Mọi write phải trực tiếp PG.
Cho phép: pg_store.py (Agent Data FastAPI) connect trực tiếp PG incomex_metadata bằng psycopg2.
Điều kiện ràng buộc:
- Agent Data là gateway DUY NHẤT vào
incomex_metadata(giống Directus là gateway DUY NHẤT vàodirectus) - Phải dùng pattern
ON CONFLICT DO UPDATEcho mọi upsert - Phải đăng ký vào
dot_toolsnhư "service writer" (không phải "DOT script", nhưng có metadata để audit) - Phải dùng password GSM (§14)
- Direct psql vào
incomex_metadatangoài Agent Data = vi phạm (trừ admin emergency có ghi log)
Ngoại lệ E5 — Workflow Engine → workflow DB (chưa active)
Lý do: DB workflow đang rỗng có chủ đích, để dành cho workflow engine tương lai (Kestra/Temporal). Khi engine active, sẽ có gateway riêng cho DB này.
Cho phép (tương lai): Workflow engine sẽ có cổng riêng connect workflow DB.
Điều kiện ràng buộc (sẽ định khi active):
- Engine phải tự khai báo vai trò gateway
- Cập nhật §0.1 với gateway thực tế
- Cập nhật §13 với điều kiện ràng buộc cụ thể
§13.3 KHÔNG HỢP PHÁP — Phải dẹp
Mọi pattern KHÔNG nằm trong E1-E5 = vi phạm NT3, phải dẹp:
- Script ad-hoc psql tay (không trong dot_tools, không có owner)
- CI bypass session variable (
set_config('app.allow_meta_update','true')) → thay bằng DOT chuyên biệt §15 - Hardcoded password trong code (vd
Incomex2026PG_*) → thay bằng password GSM §14 - SQL file vĩnh viễn trong repo auto-apply không qua DOT → thay bằng DOT migration §15
- Writer ghi PG bằng connection lấy từ env không qua GSM
- Writer KHÔNG nằm trong
dot_toolsregistry (§0-AY)
§14. ACCESS CONTROL — 1 PASSWORD GSM (BAN HÀNH S176)
Mục tiêu: Ai có password = vào được. Ai không = PG đá ngay tầng kết nối. Đơn giản, không phức tạp role. Không cần phân nhiều role: vì DOT đã review code, không nhầm việc. Lo ngại chính là chặn ai đó (người, AI, script ngoài) cố ghi PG mà không có thẩm quyền.
§14.1 Nguyên tắc
- 1 password DUY NHẤT cho cluster PG, lưu GSM (Google Secret Manager) làm SSOT
- 1 biến .env CHUNG trên VPS (
/opt/incomex/.env, chmod 600) chứa password - Cả PG container và mọi DOT đọc cùng biến này → đổi GSM 1 lần → kéo về VPS 1 lần → cả hệ thống nhận
- PG REVOKE mọi connection trustless. Mọi connection PHẢI có password đúng.
§14.2 Cấu trúc biến môi trường
# /opt/incomex/.env (chmod 600, owner root)
PG_PASSWORD=<lấy từ GSM, không hardcode>
PG_HOST=postgres # docker network alias
PG_PORT=5432
PG_USER_DIRECTUS=workflow_admin # user cho directus DB
PG_USER_AGENT=incomex # user cho incomex_metadata DB (Lớp Não)
PG_USER_ADMIN=workflow_admin # superuser cho admin emergency
Lưu ý: Có 2 user nghiệp vụ (workflow_admin cho directus, incomex cho incomex_metadata) vì 2 lớp khác nhau. KHÔNG phải 2 password — chung 1 password, 2 user khác nhau dùng cùng 1 secret rotation cycle.
§14.3 Quy trình rotate password
# 1. Đổi password trên GSM (nguồn duy nhất)
gcloud secrets versions add pg-password --data-file=- < new_password.txt
# 2. Kéo về VPS (1 lần, manual hoặc cron)
gcloud secrets versions access latest --secret=pg-password > /opt/incomex/.env.new
chmod 600 /opt/incomex/.env.new
mv /opt/incomex/.env.new /opt/incomex/.env
# 3. Restart PG container (PG đọc .env)
docker compose restart postgres
# 4. Mọi DOT tự đọc lại .env mỗi lần chạy → tự nhận password mới
§14.4 DOT đọc password — pattern chuẩn
Bash DOT (dot/bin/dot-*):
#!/bin/bash
set -euo pipefail
source /opt/incomex/.env
export PGPASSWORD="$PG_PASSWORD"
docker exec postgres psql -U "$PG_USER_DIRECTUS" -d directus -c "..."
Python DOT (Agent Data, scripts/integrity):
import os
from pathlib import Path
from dotenv import load_dotenv
load_dotenv("/opt/incomex/.env")
import psycopg2
conn = psycopg2.connect(
host=os.environ["PG_HOST"],
port=os.environ["PG_PORT"],
user=os.environ["PG_USER_AGENT"],
password=os.environ["PG_PASSWORD"],
dbname="incomex_metadata"
)
Node.js DOT (scripts/integrity/):
require('dotenv').config({ path: '/opt/incomex/.env' });
const { Client } = require('pg');
const client = new Client({
host: process.env.PG_HOST,
port: process.env.PG_PORT,
user: process.env.PG_USER_AGENT,
password: process.env.PG_PASSWORD,
database: 'directus'
});
§14.5 Cấm tuyệt đối
- CẤM hardcode password trong code, file SQL, file YAML, comment (vi phạm S176-K2:
Incomex2026PG_306ac539ad365fcerò rỉ git history) - CẤM commit
.envvào git - CẤM gửi password qua chat (Telegram, Slack, email)
- CẤM dùng password trong URL parameter (rò rỉ qua referrer header)
- CẤM admin connect bằng tài khoản chung — phải có audit log
§14.6 DOT cảnh sát kiểm tra
dot-pg-credential-audit(Cấp A, weekly): quét repo + VPS tìm hardcoded password, file.envnằm sai chỗ, password rò rỉ trong git logdot-pg-connection-monitor(Cấp A, daily): querypg_stat_activityxem có connection lạ (user lạ, IP lạ, application_name lạ) không
§15. CI/DEPLOY HOOKS — HỢP PHÁP HOÁ (BAN HÀNH S176)
Vấn đề S176-K2: CI workflow
deploy-vps.ymlđang chạy 2 pattern bypass có chủ đích:
- Auto-apply 36 SQL file qua marker
.done(không qua DOT)- CAT-ALL UPDATE với
set_config('app.allow_meta_update','true')(backdoor) Cả 2 đều được tài liệu hoá trong code (refresh-counts.post.tscomment "known hack") → không phải shadow, là pattern thực dụng để chạy hệ thống. Sửa luật: Hợp pháp hoá pattern này thành 2 DOT chuyên biệt, có điều kiện ràng buộc rõ ràng.
§15.1 DOT-MIGRATION-APPLY (thay thế CI auto-apply SQL)
Vai trò: Apply file SQL migration trong sql/ đúng thứ tự, đăng ký vào dot_tools, log vào dot_run_log.
Đặc điểm:
- File SQL phải có header
-- DOT-MIGRATION-APPLY-OKđầu file (xác nhận đã review) - Mỗi file áp xong phải UPDATE bảng
migration_applied(thay marker.donefilesystem) - Có DOT cặp
dot-migration-verify(Cấp A) kiểm tra schema sau migration - File SQL retire phải DELETE khỏi
sql/(không để vĩnh viễn) hoặc move vàosql/archive/
Chạy:
- Trong CI deploy-vps.yml:
dot-migration-applythay chofor sql_file in *.sql; do psql < ...; done - Manual emergency:
ssh root@vps && /opt/incomex/dot/bin/dot-migration-apply
Đăng ký dot_tools:
code='DOT-MIGRATION-APPLY',tier='B',domain='infrastructure.deploy',paired_dot='DOT-MIGRATION-VERIFY'
§15.2 DOT-CAT-ALL-REFRESH (thay thế CI bypass session variable)
Vai trò: Refresh CAT-ALL + CAT-MOL/CMP/MAT/PRD/BLD sau mỗi deploy. Thay thế CAT-ALL UPDATE trong CI.
Đặc điểm:
- Gọi PG function
fn_refresh_cat_all()(đã có) — không UPDATE thẳng meta_catalog - PG function này có
SECURITY DEFINER→ chạy với quyền owner, không cần session bypass - Loại bỏ logic guard
app.allow_meta_update→ guard luôn ON, không có backdoor - Có DOT cặp
dot-cat-all-verify(Cấp A) so sánh CAT-ALL với count thực tế
Chạy:
- Trong CI deploy-vps.yml: gọi
dot-cat-all-refreshthay cho UPDATE thẳng - Cron daily: gọi để keep fresh
Đăng ký dot_tools:
code='DOT-CAT-ALL-REFRESH',tier='B',domain='monitoring.integrity',paired_dot='DOT-CAT-ALL-VERIFY'
§15.3 Loại bỏ session variable bypass
Sau khi DOT-CAT-ALL-REFRESH chạy ổn định:
- DROP guard logic check
current_setting('app.allow_meta_update')trongfn_guard_meta_catalog_update - Guard luôn ON — không có backdoor
- Bất kỳ ai cố
set_config('app.allow_meta_update','true')từ ngoài DOT = không có tác dụng
§15.4 Cron installer phải đăng ký DOT
Vấn đề K2: 6 cron PG function trong production.crontab chỉ có 1 installer DOT (dot-cron-pivot-setup DOT-308). 5 còn lại không rõ ai cài.
Quy định ban hành:
- Mọi entry trong
crontab/production.crontabPHẢI có installer DOT đăng ký - Cron entry comment phải link DOT code:
# Installed by DOT-308 dot-cron-pivot-setup - DOT cảnh sát
dot-cron-coverage-audit(Cấp A, weekly) so sánhcrontabvsdot_tools→ cron không có installer = system_issues
§15.5 GitHub Actions = lớp Cổng, KHÔNG ghi PG trực tiếp
Quy định:
- GitHub Actions chỉ làm 2 việc: (a) build/test code, (b) push qua SSH→VPS deploy
- KHÔNG được
docker exec postgres psqltừ GitHub Actions (kể cả qua SSH) - Mọi thao tác PG phải gói trong DOT, gọi DOT từ deploy script trên VPS
- Workflow
.ymlchỉ trigger DOT, không chứa SQL inline
§16. CHANGELOG
| Version | Ngày | Nội dung |
|---|---|---|
| v1.0 | S133 (2026-03-25) | Draft đầu — naming, pivot-ready, auto, scale, constraint |
| v1.1 | S138 | +Toàn vẹn thực thể (§8) |
| v1.2 | S140 | +Vòng đời (§9) |
| v1.3 | S141 | +Self-detection (§10) |
| v1.4 | S142b | +Schema sync §11 + Hai loại DOT §12 |
| v2.0 | S176 (2026-04-13) | BAN HÀNH. +§0 Kiến trúc 4 DB + 3 lớp Não-Kho-Cổng. +§13 Ngoại lệ DOT 100% (5 ngoại lệ E1-E5). +§14 Access Control 1 password GSM. +§15 CI/Deploy hooks hợp pháp (DOT-MIGRATION-APPLY, DOT-CAT-ALL-REFRESH). Council 2 vòng nội bộ Desktop. Sửa lỗi: HP v4.4.0 quên kiến trúc cũ → mất buổi chiều rà soát. data-connection-law.md retire chính thức. |
| v2.1 | S178 Fix 15 (2026-04-18) | BAN HÀNH. Giữ nguyên v2.0 và chuẩn hoá thêm nguyên tắc _dot_origin cho bảng governance mới phát sinh từ gói DOT Repair Governance v4 FINAL; dùng để truy vết công cụ/flow sinh dữ liệu, đồng bộ với Đ35 và APR type tables. |
v2.0 BAN HÀNH | S176 | +Kiến trúc 4 DB + 3 lớp + 5 ngoại lệ + Access control + CI hooks | PG = nền tảng duy nhất với cấu trúc chính xác.