KB-5792 rev 5

Điều 33: Luật PostgreSQL v2.1 BAN HÀNH

29 min read Revision 5
dieu33lawpostgresqlv2.1ban-hanhs176s178-fix15kien-truc4-database3-lopngoai-leaccess-control_dot_origin

Đ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_origin cho 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.md v1.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_metadata riêng để Agent Data có thể vận hành độc lập, NoSQL-style upsert mà không vướng Directus schema constraints.
  • workflow riêng để workflow engine tương lai có không gian riêng, không trộn với business data.
  • directus riê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ị).

  • 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 directus DB, á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)

  1. 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.
  2. Mỗi DOT trong dot_tools PHẢI khai báo: ghi vào DB nào, thuộc lớp nào, gateway nào.
  3. 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.
  4. data-connection-law.md v1.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Ễ SAI
  • GROUP BY type ở A vs GROUP 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_tools với domain='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_log sau 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ào directus)
  • Phải dùng pattern ON CONFLICT DO UPDATE cho mọi upsert
  • Phải đăng ký vào dot_tools như "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_metadata ngoà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_tools registry (§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_306ac539ad365fce rò rỉ git history)
  • CẤM commit .env và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 .env nằm sai chỗ, password rò rỉ trong git log
  • dot-pg-connection-monitor (Cấp A, daily): query pg_stat_activity xem 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:

  1. Auto-apply 36 SQL file qua marker .done (không qua DOT)
  2. 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.ts comment "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 .done filesystem)
  • 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ào sql/archive/

Chạy:

  • Trong CI deploy-vps.yml: dot-migration-apply thay cho for 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-refresh thay 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') trong fn_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.crontab PHẢ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ánh crontab vs dot_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 psql từ 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 .yml chỉ 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.