Quy định Balo Thông Tin Thực Thể — canonical vận hành reconcile 4 loại
Quy định Balo Thông Tin Thực Thể (v1.9 — CHÍNH THỨC; Pha A + Pha B PASS)
Census = lớp ĐỌC (read-only). Balo = lớp GHI CHÚ (annotation) TRÊN kết quả đọc — có WRITE. Balo = MỘT bảng, mỗi thực thể MỘT DÒNG (thêm thực thể = thêm dòng, KHÔNG thêm cột). v1.6 (sau lần chạy HOLD đầu tiên): (a) live pg_catalog = nguồn thật cho Core, report-pg chỉ đối chiếu (snapshot cũ lệch → cảnh báo, không HOLD); (b) đường ghi atomic PG-native đi qua cổng
dot-pg-atomic-apply(hệ trước đó thiếu đường này — chính là nguyên nhân HOLD lần đầu).
0. Đường ghi atomic PG-native — dot-pg-atomic-apply (cổng dùng chung)
Hệ trước đây không có DOT nào chạy được "một transaction CREATE+INSERT+VERIFY+COMMIT/ROLLBACK" thuần PG (mọi DOT đều đi qua Directus API, không atomic, kèm ghi bảng cấm). Balo Core (và mọi DDL atomic sau) đi qua cổng mới:
- Input: một file
.sqlnằm trong allowlist repo (vd/opt/incomex/dot/sql/) — KHÔNG nhận SQL gõ tay lúc chạy. - Bốn khóa an toàn: (1) allowlist — file trong
/opt/incomex/dot/sql/, realpath, không symlink, đuôi.sql; (2) manifest hash-lock — file phải có trongapproved-sql-manifest.tsvvà sha256 hiện tại khớp tuyệt đối (chống file lạ/sửa lén); (3) guardrail tĩnh chặnDROP/DELETE/TRUNCATE+ transaction control (BEGIN/COMMIT/ROLLBACK/SAVEPOINT) + mọi dòng bắt đầu\(psql meta-command như\!,\copy) +COPY PROGRAM/ALTER SYSTEM/CREATE EXTENSION/CREATE EVENT TRIGGER+ tên bảng cấm; (4) atomic bắt buộcpsql -v ON_ERROR_STOP=1 -1 -f— lỗi bất kỳ → rollback toàn bộ. - File SQL tự chứa phần VERIFY trong transaction (DO block, lệch →
RAISE EXCEPTION→ rollback). Không verify-sau-commit. - Tái dùng cho reconcile + mọi DDL atomic về sau. KHÔNG phải runner SQL tự do.
1. Hai PHA — không làm chung
- Pha A — Balo Core (PG thuần, first-run only, ATOMIC qua
dot-pg-atomic-apply): tạo bảng + sequence + phát balo rỗng + verify, trong một transaction. KHÔNG UI, KHÔNG reconcile. - Pha B — Balo UI (sau): drill-down report, MOUT, Nuxt, deploy.
- Reconcile (còn/mới/mất, da_mat) = macro riêng, sau Core.
2. Hai loại ngăn
- IN-SẴN (máy điền, không đoán): STT, Tên, Bảng/chỗ ở, khóa nhận diện.
- PHẢI-GẬT (máy đoán = rác, để trống v0): Nhóm, Loài, Chuyên môn, Lớp, Ghi chú, Kiểm soát, Active.
Tự cấp ô trống OK; tự điền ngăn phải-gật CẤM.
3. Cấu trúc bảng balo_thuc_the (chốt)
| # | Cột | Kiểu | Ràng buộc |
|---|---|---|---|
| 1 | stt |
bigint | PK DEFAULT nextval('balo_stt_seq'); bất biến, không tái dùng |
| 2 | nhom |
varchar | NULL (không CHECK) |
| 3 | ten |
text | NULL (in-sẵn) |
| 4 | noi_luu |
text | NULL (in-sẵn) |
| 5 | loai |
varchar | NULL; tham chiếu mềm entity_species.species_code (người điền tay) |
| 6 | chuyen_mon |
varchar | NULL (anchor cây 7 tầng MOW, chưa có → trống) |
| 7 | lop |
varchar | NULL; CHECK lop IS NULL OR lop IN ('nguyen_tu','phan_tu','hop_chat','vat_lieu','san_pham','cong_trinh') |
| 8 | ghi_chu |
text | NULL |
| 9 | kiem_soat |
boolean | NOT NULL DEFAULT false (v0 false hết) |
| 10 | active |
varchar | NULL (không CHECK v0) |
| 11 | khoa_nhan_dien |
text | NOT NULL UNIQUE — "ID đối tượng" truy ngược + đối chiếu |
| cờ | da_mat |
boolean | NOT NULL DEFAULT false (tạo sẵn, v0 không set) |
| tự-suy | loai_census |
varchar | GENERATED ALWAYS AS (split_part(khoa_nhan_dien,':',1)) STORED — KHÔNG insert tay |
| audit | date_created |
timestamptz | NOT NULL DEFAULT now() |
| audit | date_updated |
timestamptz | NOT NULL DEFAULT now() (không trigger v0) |
CREATE SEQUENCE balo_stt_seq+ALTER SEQUENCE balo_stt_seq OWNED BY balo_thuc_the.stt. Index:khoa_nhan_dien(unique),loai_census.- FK loài:
species_codeUNIQUE → FK nullable; nếu không → validate mềm. Đừng để FK chặn phát balo.
4. Khóa nhận diện chuẩn theo loại (từ pg_catalog LIVE, KHÔNG information_schema)
| Loại | Nguồn | khoa_nhan_dien |
|---|---|---|
| table | pg_class relkind='r' + pg_namespace |
table:schema.relname |
| view | pg_class relkind='v' + pg_namespace |
view:schema.relname |
| function | pg_proc prokind='f' + pg_namespace |
function:schema.name(+pg_get_function_identity_arguments(oid)+) |
| trigger | pg_trigger tgisinternal=false + pg_class + pg_namespace |
trigger:schema.relname.tgname |
stt= ID hồ sơ (bất biến).khoa_nhan_dien= khóa nhận diện. Identifier có dấu chấm/ký tự bất thường gây mơ hồ → HOLD.- Bộ lọc schema CHUẨN (dùng NHẤT QUÁN cho Gate + INSERT + VERIFY + reconcile sau):
n.nspname NOT IN ('pg_catalog','information_schema') AND n.nspname NOT LIKE 'pg_toast%'. Cùng một bộ lọc cho cả đếm và phát balo → count nội bộ luôn khớp. (Định nghĩa loại: tablerelkind='r', viewrelkind='v', functionprokind='f', triggerNOT tgisinternal.)
5. Phạm vi v0 = 4 LOẠI: table, view, function, trigger
- Bỏ
column+ 7 loại census còn lại → làm sau, có chọn lọc. Chưa làm ≠ thiếu/sai.
6. Chuyên môn = anchor cây 7 tầng MOW (1 cột, chưa có PG → trống). ## 7. Lớp = 6 lớp tiếng Việt cố định (CHECK), không kế thừa composition_level bẩn. ## 8. Loài = tái dùng entity_species (parent_id+depth gom chi/họ).
9. Kho lưu (Core) — 2 object quản trị chính + phụ thuộc
2 object quản trị chính: balo_thuc_the + balo_stt_seq (MỚI). Được phép có object phụ thuộc do constraint/index đã khai trong SQL duyệt (pkey, unique key trên khoa_nhan_dien, index loai_census) — đây là tất yếu của PG, KHÔNG phải mở scope. entity_species (tái dùng). Đường ghi: cổng dot-pg-atomic-apply. KHÔNG đụng table_registry/MOUT/meta_catalog/birth_registry/qt001_/v_registry_.
10. Cách chạy Core (atomic, first-run only, qua cổng)
- Gate khảo sát (read-only): entity_species khớp? cây MOW chưa có? balo/sequence CHƯA tồn tại? identifier không mơ hồ? cổng
dot-pg-atomic-applyđã sẵn sàng? Lệch → HOLD. - Một file SQL balo chạy qua cổng: CREATE sequence → CREATE table → ALTER OWNED BY → CREATE INDEX → INSERT 4 loại (từ live pg_catalog, ORDER BY cố định sort_kind 1–4, insert
ten/noi_luu/khoa_nhan_dien) → VERIFY (DO block) trước COMMIT. - VERIFY trong transaction:
- Nguồn thật = live pg_catalog. Đếm 4 loại từ live; nếu query không đọc được hoặc 4 loại tự mâu thuẫn → RAISE (rollback).
- report-pg chỉ đối chiếu tham khảo: lệch do snapshot cũ → ghi cảnh báo, KHÔNG rollback/HOLD.
loai_censusdistinct = đúng 4;khoa_nhan_dienUNIQUE; scope đúng (chỉ 2 object); không đụng bảng cấm. Lệch → RAISE (rollback).- Tất cả PASS → COMMIT.
11. Cơ chế duy trì canonical sau first-run
balo-core-first-run.sqlchỉ dùng để đúc lần đầu. Không chạy lại file first-run để bổ sung object mới.- Cơ chế duy trì canonical là
/opt/incomex/dot/bin/dot-balo-reconcile, dùng nguyên ba chế độ:
dot-balo-reconcile --dry-run
dot-balo-reconcile --apply
dot-balo-reconcile --verify
- Scope hiện hành chỉ gồm
table | view | function | trigger; chưa mở 8 loại Census còn lại. - Nguồn phát hiện là live
pg_catalog, không hardcode số lượng hay danh sách object. - Dòng còn tồn tại và đúng metadata là KEEP: không UPDATE, không đổi
date_updated, giữ nguyên STT. - Object mới: ADD hàng tối thiểu
ten | noi_luu | khoa_nhan_dien; sequence cấp STT mới; PG tự sinh/default các trường còn lại. - Object mất:
da_mat=true; không DELETE hồ sơ, không tái cấp STT. - Object trở lại với cùng
khoa_nhan_dien: REVIVE và giữ nguyên STT. - Rename hoặc chuyển schema thông thường được biểu diễn thành khóa mới ADD + khóa cũ MARK_MISSING; không tự ghép hai khóa.
- Các trường phải-gật
nhom | loai | chuyen_mon | lop | ghi_chu | kiem_soat | activekhông được reconcile tự động. - UI Làm mới chỉ đọc/re-fetch; không gọi WRITE hoặc reconcile.
- Hiện không có cron, daemon, event listener hay máy chạy ngầm cho Balo.
12. Quy tắc kết thúc mission có thay đổi PG object
Sau một mission đã thực hiện thành công việc tạo, xóa, đổi tên hoặc chuyển schema đối với table | view | function | trigger, Agent phải chạy:
dot-balo-reconcile --dry-run
Được chạy --apply ngay trong cùng mission chỉ khi đồng thời:
- mission ban đầu đã được phép WRITE PG;
- delta chỉ gồm ADD của đúng các object do mission vừa tạo;
CONFLICT=0;sequence_safe=true;- không có REVIVE;
- không có MARK_MISSING;
- không có METADATA_UPDATE ngoài phạm vi mission.
Sau apply bắt buộc chạy:
dot-balo-reconcile --verify
dot-balo-reconcile --dry-run
và delta phải bằng 0.
Phải HOLD chờ Owner nếu có ADD ngoài danh sách object của mission, MARK_MISSING, REVIVE, METADATA_UPDATE không giải thích được, CONFLICT, sequence không an toàn hoặc schema contract khác chuẩn. Không tự sửa nhãn Balo, không setval, không WRITE bằng đường khác.
Mission không đổi bốn loại PG object trên — ví dụ chỉ sửa bản ghi nghiệp vụ, UI, tài liệu, kiểm tra read-only hoặc object ngoài scope — không bắt buộc chạy reconcile.
13. Nguyên tắc bảo toàn
- STT hồ sơ bất biến, không tái sử dụng; dòng cũ giữ STT, dòng mới do sequence cấp.
- Không DELETE hồ sơ Balo; object mất chỉ đánh dấu và object trở lại giữ hồ sơ cũ.
- DOT 100% cho mọi WRITE; đường ghi atomic dùng cổng
dot-pg-atomic-apply; không psql direct WRITE. - Atomic: xong sạch hoặc rollback. Không tự DROP/DELETE/TRUNCATE để chữa lỗi.
- Nguồn thật là live
pg_catalog; report/UI là mặt đọc và đối chiếu. - Census = đọc; Balo = ghi chú; nhãn thủ công và
loai_censuslà hai trục khác nhau.
14. Trạng thái triển khai đã xác minh
- Pha A first-run PASS ngày 2026-06-26; file canonical
/opt/incomex/dot/sql/balo-core-first-run.sql. - Pha B visibility PASS; Directus/Nuxt đọc live, UI không có quyền reconcile.
- DOT reconcile bốn loại được triển khai tại commit
65a2d732dd31459ce9d9312fce5b36c9ac9627a3. - Manifest bảo tồn các dòng đã có tại commit
bddfdcc5a504ff6f4c1bc4fdf075dc8dde209402. - LỆNH 2C ngày 2026-07-16 đã atomic apply và verify production PASS. Các số
total=2148,table=385,view=699,function=654,trigger=410, sequence-next2149chỉ là evidence tại thời điểm triển khai, không phải quy tắc hoặc giá trị hardcode. - Sau LỆNH 2C:
--verifyvà--dry-runđều delta=0; hiện không có cron.
15. Không tạo hệ thống mới
Dùng nguyên DOT đã PASS. Không tạo wrapper orchestration, event listener, DDL tracking table, trigger theo dõi, cron, queue hoặc verifier framework riêng.
SỬA ĐỔI 2026-07-17 (§16 thay thế một phần §15). Bản v1.9 của §15 có ghi cấm "nút UI đồng bộ Balo" và "daemon". Owner đã chủ động bãi bỏ đúng hai mục đó qua chuỗi lệnh 3A→3D.3 và lệnh triển khai 2026-07-17, sau khi thiết kế được khoá lại thành fixed-purpose bridge (không phải orchestration tự do). Mọi lệnh cấm còn lại của §15 vẫn còn hiệu lực. Không suy rộng miễn trừ này sang cron, queue, wrapper hay verifier framework.
16. Nút "Đồng bộ Balo từ PG" — Administrator-only WRITE workflow (PASS 2026-07-17)
16.1 Hai nút, hai bản chất — không được nhầm
| Nút | Bản chất | Quyền |
|---|---|---|
| Làm mới | CHỈ ĐỌC — đọc lại Directus, không chạm pg_catalog, không WRITE | mọi người xem |
| Đồng bộ Balo từ PG | WRITE — reconcile Balo theo live pg_catalog |
CHỈ Administrator |
Không đổi tên "Đồng bộ Balo từ PG" thành "Làm mới" hay bất kỳ tên nào gợi ý chỉ-đọc. Tên nút phải giữ đúng bản chất WRITE.
16.2 Một lần bấm = một chuỗi khoá sẵn
Administrator bấm nút
→ Nuxt requestGate (POST + body rỗng + Origin + Sec-Fetch-Site + X-Incomex-Action)
→ Directus session → authorizationGate (allowlist role ID)
→ Unix socket /run/incomex/balo-reconcile.sock (fixed-purpose bridge)
→ flock singleton → dot-balo-reconcile --sync
→ dry-run → apply atomic qua dot-pg-atomic-apply NẾU có chênh lệch an toàn → verify
→ refresh bảng hiện tại (không reload toàn app)
Đường ghi vẫn là §0 (dot-pg-atomic-apply, SQL hash-lock). Nút không tạo đường ghi mới; nó chỉ là mặt bấm của DOT đã PASS. Directus/Nuxt không bao giờ WRITE PG trực tiếp.
16.3 Tự kế thừa — không hardcode
Cổng duy nhất trong code: <BaloSyncButton v-if="resolvedCollection === 'balo_thuc_the'" /> tại components/shared/DirectusTable.vue.
resolvedCollection lấy từ table_registry lúc chạy; route là động (pages/reports/[report]/index.vue). Vì vậy mọi report hiện tại và tương lai dùng collection='balo_thuc_the' tự nhận nút — chỉ cần một dòng registry, không sửa code, không thêm URL, không danh sách report ID.
16.4 Điều kiện vận hành bắt buộc (thiếu một là hỏng)
volumes: /run/incomex:/run/incomex:ro— thiếu ⇒500 BALO_BRIDGE_FAILED_CLOSED.group_add: ["987"]— GID live củaincomex-balo-sync; thiếu ⇒EACCES. GID do OS cấp ⇒ preflightgetent group incomex-balo-syncbắt buộc.- CẢ HAI biến:
NUXT_BALO_OPERATOR_ROLE_IDS(endpoint) vàNUXT_PUBLIC_BALO_OPERATOR_ROLE_IDS(hiện nút). Chỉ set một ⇒ endpoint chạy nhưng nút vô hình. Allowlist rỗng ⇒ fail-closed503. - Nginx:
location = /api/balo/reconcile→nuxt_backend(exact). Thiếu ⇒/api/catch-all nuốt về Agent Data (404 {"detail":"Not Found"}). - Deploy: chỉ
docker compose up -d nuxt. Cấmdocker compose up -dtrần (recreate agent-data, mất 10 env, hạ mem 2560→1536 MiB).
16.5 Không cron
Nút là on-demand. Không cron, không queue, không tự chạy. Lock flock chống chạy đồng thời: phiên thứ hai nhận 409 ALREADY_RUNNING, apply không được gọi.
16.6 Bridge fixed-purpose
Bridge không nhận command, không nhận path, không nhận tham số từ request. Protocol đúng một dòng BALO_SYNC_V1\n; argv cố định [dot-balo-reconcile, --sync], shell=false. Socket AF_UNIX (RestrictAddressFamilies=AF_UNIX), không TCP, không public. Socket path đọc từ runtime config, không bao giờ từ request.
16.7 Evidence tại thời điểm triển khai (KHÔNG phải quy tắc)
total=2148 (table=385, view=699, function=654, trigger=410), fingerprint 8ed1cd5d2f93f5f8a970497bd5e4ec87, date_updated hash ae41989817f47ca97cf4a07b3cb89c5d. Một lần bấm production trả ALREADY_UP_TO_DATE, verified=true, delta=0, write=0, dữ liệu trước=sau. Đây là số đo tại 2026-07-17, không hardcode. Nhánh UPDATED đã có fixture + atomic apply production trước đó; mission này không tạo object PG thử để ép nhánh đó.
17. BẢO VỆ CƠ CHẾ — bắt buộc với MỌI agent về sau (2026-07-17, sau Owner acceptance)
Nút đã được Administrator thật nghiệm thu trên production (2026-07-17): 4/4 report Balo hiện nút, PG Census không có nút, một lần bấm →
ALREADY_UP_TO_DATEverified=true, bảng tự refresh tại chỗ,write=0, fingerprint trước=sau; singleton trả409 ALREADY_RUNNING. §17 tồn tại để không agent nào phá lại chuỗi này.
17.1 Verifier bắt buộc — dot-balo-one-click-guard
/opt/incomex/dot/bin/dot-balo-one-click-guard # READ-ONLY, 37 checks
BALO_ONE_CLICK_GUARD|PASS → được đi tiếp
BALO_ONE_CLICK_GUARD|HOLD|reason=... → DỪNG, không tự sửa
Mọi mission đụng Nginx · Nuxt · docker-compose · Directus auth/role · DOT Balo · systemd bridge · reports/table_registry PHẢI chạy guard trước và sau mutation. Sau mutation chỉ PASS nếu guard trở lại PASS.
Guard chỉ đọc: không auto-fix, không apply, không gửi BALO_SYNC_V1, không ghi DB, không reload/restart. Guard HOLD = tín hiệu gọi người, không phải việc để agent tự vá.
17.2 Mười ba điều không được phá
- "Làm mới" chỉ ĐỌC. "Đồng bộ Balo từ PG" là WRITE, Administrator-only. Không đổi tên, không gộp hai nút.
- Không xoá/đổi
location = /api/balo/reconciletrong Nginx. Thiếu ⇒ catch-all/api/nuốt về Agent Data (404 {"detail":"Not Found"}) và nút chết câm. - Nuxt luôn phải có đủ: exact socket mount
/run/incomex:/run/incomex:ro·group_addGID live củaincomex-balo-sync·NUXT_BALO_OPERATOR_ROLE_IDS(private) ·NUXT_PUBLIC_BALO_OPERATOR_ROLE_IDS(public) · socket path · production origin. - Deploy chỉ
docker compose up -d nuxt. - CẤM
docker compose up -dtrần. Đo tại 2026-07-17: agent-data đang chạy có 10 envPG_*/DIRECTUS_DB_*và mem 2560 MiB, nhưng compose file có 0 env đó vàmem_limit: 1536m⇒ recreate trần sẽ mất 10 env + hạ mem. Compose và runtime lệch có thật — phải reconcile trước, không phải sau. - Browser/Nuxt/Directus không bao giờ WRITE PG trực tiếp. Đường ghi duy nhất vẫn là §0
dot-pg-atomic-apply. - Bridge không nhận command/path/SQL/tham số từ request. Protocol đúng một dòng
BALO_SYNC_V1. - Không bỏ
--syncvà không bỏ singletonflock. - Report Balo tương lai tự kế thừa nút qua
collection='balo_thuc_the'— thêm một dòngtable_registry, không sửa code, không hardcode report URL/ID. - VPS là SSOT của code và cấu hình. Không
git pull/reset --hard/clean/checkout -- ., không coi GitHub là bản đúng hơn, không push. - Google Secret Manager (
github-chatgpt-ggcloud) là SSOT cho credential. Ngoại lệ đã đo: login Directus Administrator (DIRECTUS_ADMIN_EMAIL/DIRECTUS_ADMIN_PASSWORD) KHÔNG nằm trong Secret Manager mà ở/opt/incomex/docker/.env(0600 root). Secret Manager chỉ cóDIRECTUS_ADMIN_TOKEN(token, không dùng để login UI được). - Mọi mutation phải qua Git Safety Gate: commit local (đường dẫn tường minh, không
git add -A/.), bundle +git bundle verify, backup byte-exact file runtime/config, rollback dựng sẵn — trước khi sửa. disk-monitor.shlà report-only. Không phục hồidocker system prune -ftheo giờ: đo thật nhiều lần choTotal reclaimed space: 0Bvà91% → 91%trong khi xoá sạch container stopped ⇒ thu hồi bằng 0, phá bằng chứng runtime và mọi canary/blue-green. Dọn đĩa là hành động có review, không phải cron.
17.3 Hai bẫy đo đã trả giá — đừng vấp lại
curlKHÔNG kiểm được nút.modules/directus/runtime/plugins/auth.tscóif (import.meta.server) return⇒ SSR không render nút; chỉcurlsẽ kết luận sai là FAIL. Phải browser thật có phiên Administrator.- So compose PHẢI
--no-interpolate. Không có nó, compose render secret plaintext vào output (đã từng rò rỉ ra evidence 2026-07-17). Và compose chuẩn hoá:rothành{source,target,read_only}⇒ grep chuỗi ngắn"/run/incomex:/run/incomex:ro"không bao giờ khớp; phải đọc--format json.
17.4 Workflow GitHub ghi đè Nginx
Đo 2026-07-17 bằng PAT (Secret Manager gh_pat_sync_secrets), quét toàn bộ workflow của Huyen1974/web-test và Huyen1974/agent-data:
web-test "Deploy to VPS" id=233946923 .github/workflows/deploy-vps.yml = disabled_manually
→ workflow DUY NHẤT có khả năng ghi đè nginx (scp infra/nginx/conf.d/default.conf)
→ lần chạy thành công cuối 2026-07-11T02:47:26Z = đúng sự cố 11/07
web-test "Điều 31 VPS Setup" id=250152096 = disabled_manually
web-test "Post-Deploy E2E" id=249989926 = disabled_manually
web-test "Health Check" id=227044705 = active → CHỈ curl, permissions: contents:read, không ssh/scp/rsync ⇒ an toàn
agent-data → 11 workflow, KHÔNG cái nào chạm VPS/nginx
Đính chính KB: workflow agent-data Vector-Audit 244569412 từng ghi trong KB không tồn tại (GitHub trả 404). "Hai workflow nguy hiểm" thực chất là một (233946923), đã disabled. Không bật lại Deploy to VPS cho tới khi deploy hardening PASS — nó sẽ đắp nginx tháng 3 (nuxt-repo/infra/nginx) đè production và xoá route balo.