LỆNH 3B — Nút chung Đồng bộ Balo từ PG — Gate 0 HOLD
LỆNH 3B — Nút chung “Đồng bộ Balo từ PG”
Ngày thực hiện: 2026-07-16
Verdict: HOLD
Mã HOLD:SAFE_EXECUTION_BRIDGE_MISSING+AUTH_PATTERN_UNPROVEN+SINGLETON_LOCK_MISSING
Production: ZERO-TOUCH — chưa deploy, chưa chạy apply
0. Kết luận điều hành
Đã chọn đúng một vị trí chung cho nút:
SharedDirectusTable
→ title bar của bảng
→ đặt cạnh nút “Làm mới” hiện có
→ chỉ hiện khi resolvedCollection === 'balo_thuc_the'
Vị trí này tự bao phủ bốn report Balo hiện tại và mọi report Balo tương lai dùng cùng collection, không hardcode URL. Tuy nhiên Gate 0 phát hiện ba điều kiện an toàn bắt buộc chưa tồn tại:
- Nuxt không nhìn thấy
dot-balo-reconcile; execution bridge hiện hữu chỉ plan-only và từ chối REAL_RUN. - Hệ thống chưa có pattern server-side đã chứng minh cho user session + role Owner/operator + same-origin/CSRF.
dot-balo-reconcilevàdot-pg-atomic-applychưa có singleton lock.
Theo Lệnh 3B, đã không tạo endpoint, không tạo nút mock, không sửa DOT, không build một candidate nửa an toàn. Làm UI/endpoint trước ba gate này có thể tạo cảm giác “sẵn sàng deploy” sai sự thật.
DOT hiện có đã đủ preflight, atomic apply, post-verify và marker máy đọc. Không cần sửa logic reconcile hoặc output ở bước này.
1. Luật, tuyên ngôn và Assembly Gate
Đã đọc trực tiếp:
.claude/skills/incomex-rules.md— 36 mục, 8 bước và quy trình hai mũ;- Operating Rules SSOT v7.58 rev51;
- VPS Operating Rules v1.0 rev2;
- Hiến pháp v4.6.3 rev44;
- Quy định Balo canonical v1.9 rev15;
- báo cáo Lệnh 2D rev2.
Ba câu tuyên ngôn:
- Vĩnh viễn: một vị trí theo collection
balo_thuc_the, một endpoint cố định và một DOT canonical; report Balo tương lai tự kế thừa mà không sửa URL/code từng trang. - Nhầm được không: chỉ đạt khi có auth server-side, same-origin/CSRF, bridge fixed-function và singleton lock. Hiện ba lớp này chưa đủ, nên HOLD là rào chắn đúng.
- Tự động: sau thao tác có thẩm quyền, workflow phải tự chạy dry-run → gate → apply → verify → refresh bảng; hiện chưa thể đóng kín vì thiếu bridge và auth đã chứng minh.
Assembly Gate:
PG: public.balo_thuc_the + live pg_catalog đã có
Directus: collection balo_thuc_the và table_registry đã có
DOT: dot-balo-reconcile đã PASS production
Nuxt dynamic: SharedDirectusTable đã resolve collection từ table_registry
Điều kiện thiếu: execution boundary + auth/role + singleton lock
2. Gate 0 — nền source và runtime
Nuxt source SSOT:
path=/opt/incomex/docker/nuxt-repo
HEAD=ae694716d5647ce1ed7f3118274645f5d2cf060b
branch=main
status:
? web-rp-current
Không có diff Nuxt do Lệnh 3B. web-rp-current là linked worktree có sẵn, không thuộc mission.
Production Nuxt:
name=/incomex-nuxt
image=asia-southeast1-docker.pkg.dev/github-chatgpt-ggcloud/web-test/nuxt-ssr:latest
started=2026-07-11T02:50:33.302411141Z
restart=0
running=true
mount=/opt/incomex/deploys/nuxt-output -> /app/.output rw=true
NUXT_DOT_VISIBLE=no
Nuxt không mount /opt/incomex, không có Docker socket, không có SSH key và không nhìn thấy DOT. Đây là trạng thái an toàn hiện tại.
3. Gate 0 — execution bridge
3.1 Executor hiện có không phải bridge WRITE
Service đã kiểm:
name=/incomex-agent-api-executor
image=agent-api-executor-local:v1
started=2026-06-04T08:46:26.827398196Z
restart=0
running=true
user=10001
network=docker_incomex
port=127.0.0.1:8090
mounts=(none)
DOT_VISIBLE=no
OpenAPI chỉ có:
/healthz GET
/selfcheck GET
/dispatch POST
Source contract của service ghi rõ:
agent_api plan-only executor
this service NEVER writes to PostgreSQL
REAL_RUN refused
execute_enabled=false
real_run_enabled=false
dry_run_only=true
Service chỉ có fixture dot:kg:explain; không có dot-balo-reconcile, không có Docker access và không thể gọi host DOT. Vì vậy không được tái sử dụng bằng cách đổi nghĩa service plan-only thành cổng WRITE.
3.2 Không có bridge khác đủ điều kiện
Inventory mount của các container xác nhận:
- Nuxt chỉ mount production
.output; - Agent API Executor không có mount;
- Agent Data không có DOT/Docker socket;
- Claude MCP có Docker socket và một số thư mục DOT read-only, nhưng đó là admin/MCP boundary, không phải fixed-function browser bridge và Lệnh 3B cấm browser gọi trực tiếp gateway/admin tool.
Không có container/service nào đồng thời đáp ứng:
Nuxt gọi được
+ chỉ cho đúng dot-balo-reconcile
+ không nhận command/path
+ có quyền chạy DOT host
+ có lock
+ trả output đã lọc
Kết luận:
HOLD: SAFE_EXECUTION_BRIDGE_MISSING
4. Gate 0 — auth và same-origin/CSRF
Các auth pattern hiện có:
- Directus auth middleware đọc
useDirectusAuth().user, nhưng plugin có:
if (import.meta.server) return
enableGlobalAuthMiddleware=false
- Firebase middleware có:
if (process.server) return
- Directus proxy hiện có khả năng chuyển tiếp cookie
directus_session_token/__session, nhưng không kiểm role cho server action. - Các POST/PATCH action hiện có không cung cấp một pattern hoàn chỉnh user-session + role + same-origin/CSRF để tái sử dụng. Bearer token ở AI search là service token riêng, không phải user Owner/operator session.
Role Directus đọc-live hiện có gồm Administrator, tac-admin, Editor, AI Agent, Agent, tac-agent và các role Public. Không có role tên rõ ràng Owner hoặc Balo operator; chưa có quyết định role nào được phép vận hành Balo.
Do ẩn nút client không thay thế authorization server-side, không được tự suy luận Administrator hay tac-admin là quyền Owner.
Kết luận:
HOLD: AUTH_PATTERN_UNPROVEN
5. Gate 0 — singleton lock
Đã đọc toàn bộ hai file:
/opt/incomex/dot/bin/dot-balo-reconcile
/opt/incomex/dot/bin/dot-pg-atomic-apply
Không tìm thấy:
flock
lockfile
pg_try_advisory_lock
ALREADY_RUNNING
singleton guard
UI disabled chỉ chặn một browser; hai browser vẫn có thể đi vào apply đồng thời. Atomic transaction không thay thế singleton workflow lock.
Kết luận:
HOLD: SINGLETON_LOCK_MISSING
6. DOT contract đã đủ — read-only evidence
DOT canonical:
path=/opt/incomex/dot/bin/dot-balo-reconcile
sha256=7e657b96cc27e0f396e2485a67d6296c4de10aabd2f68cf4aaf367cb7b9bdfe5
DOT_HEAD=bddfdcc5a504ff6f4c1bc4fdf075dc8dde209402
Dry-run production chỉ đọc:
SQL_LOCK|manifest=PASS|sha256=c018f12ce86918c2a6199b70c4626c981deb2632edc686c1544504a3cf387369|atomic_guard=PASS_NONEXECUTING
table|385|385|0|0|0|0|0
view|699|699|0|0|0|0|0
function|654|654|0|0|0|0|0
trigger|410|410|0|0|0|0|0
TOTAL|2148|2148|0|0|0|0|0
BALO_RESULT|add=0|revive=0|metadata_update=0|mark_missing=0|conflict=0|sequence_safe=t
FINGERPRINT|unchanged=PASS
DRY_RUN|PASS|delta=0|write=0
Verify production chỉ đọc:
BALO_RESULT|add=0|revive=0|metadata_update=0|mark_missing=0|conflict=0|sequence_safe=t
FINGERPRINT|unchanged=PASS
VERIFY|PASS_RECONCILED|delta=0|write=0
Các marker đã đủ để parser fail-closed. Với delta hiện tại, contract tương lai phải trả ALREADY_UP_TO_DATE và tuyệt đối không gọi --apply.
7. Vị trí nút đã chọn và cơ chế tự kế thừa
Vị trí duy nhất:
web/components/shared/DirectusTable.vue
title bar, cạnh nút “Làm mới” hiện có
Lý do:
- dynamic report host chỉ truyền
tableId; SharedDirectusTableđọctable_registryrồi tínhresolvedCollection;handleRefresh()đã refresh đúng bảng hiện tại và emitrefreshed;- không cần sửa từng URL/report.
Điều kiện hiển thị thiết kế:
isBaloCollection = resolvedCollection === 'balo_thuc_the'
show = isBaloCollection && currentUserHasApprovedBaloOperatorRole
Role check ở client chỉ phục vụ UX; endpoint vẫn bắt buộc kiểm server-side.
Registry live chứng minh bốn report hiện tại cùng collection:
tbl_balo_function|/reports/report-balo-function|balo_thuc_the|active|view_sql=null
tbl_balo_table|/reports/report-balo-table|balo_thuc_the|active|view_sql=null
tbl_balo_trigger|/reports/report-balo-trigger|balo_thuc_the|active|view_sql=null
tbl_balo_view|/reports/report-balo-view|balo_thuc_the|active|view_sql=null
Vì nhận diện theo collection:
- bốn report trên tự có nút;
- report Balo tương lai dùng
balo_thuc_thetự có nút; - PG Census có
view_sqlvà collection khác nên không có nút; - report không phải Balo không có nút.
Không cần cột/flag DB mới và không hardcode bốn URL.
8. Đề xuất đúng một bridge tối thiểu cho mission riêng
Đề xuất duy nhất:
Fixed-purpose host bridge qua Unix socket riêng
/run/incomex/balo-reconcile.sock
Contract bắt buộc của bridge:
- Chỉ một action
POST /reconcile, body rỗng; không nhận command/path/mode/SQL/container/user/database. - Chạy trên host, gọi đúng
/opt/incomex/dot/bin/dot-balo-reconcile; không mount Docker socket, SSH key hoặc/opt/incomexvào Nuxt. - Unix socket chỉ bind-mount đúng socket vào Nuxt; không mở TCP/public port.
- Khóa tại execution boundary bằng:
flock -n /run/lock/incomex-balo-reconcile.lock
Phiên thứ hai trả 409 ALREADY_RUNNING.
5. Workflow cố định: dry-run → parse gate → delta=0 thì dừng → apply khi an toàn → verify delta=0.
6. Parser chỉ chấp nhận marker/field whitelist; output sai contract trả HOLD/FAILED; không trả raw shell, env, command hay stack trace.
7. Service/unit được harden và không cung cấp shell/API tổng quát.
Đây là đề xuất kiến trúc, chưa tạo và chưa deploy. Không mở rộng executor plan-only hiện tại.
9. Endpoint/UI contract sau khi bridge và auth PASS
Sau gate riêng, candidate mới được phép tạo:
POST /api/balo/reconcile
BaloSyncButton.vue
Endpoint:
- xác thực Directus session server-side;
- kiểm role ID đã được Owner phê duyệt;
- kiểm same-origin/CSRF theo pattern đã duyệt;
- gọi duy nhất Unix-socket bridge;
- request body phải rỗng;
- response chỉ chứa schema đã cho trong Lệnh 3B.
UI:
- component nhỏ cạnh
Làm mới; - disabled khi đang chạy, không retry WRITE;
- chỉ
UPDATEDvàverified=truemới gọihandleRefresh(); ALREADY_UP_TO_DATEkhông apply và có thể refresh read-only;- HOLD/FAILED không refresh giả thành công.
10. Test/build/Git
Không có code candidate vì Gate 0 HOLD trước Bước 3. Do đó:
ENDPOINT_CREATED=NO
UI_COMPONENT_CREATED=NO
TYPECHECK=N/A_NO_CODE_AFTER_HOLD
BUILD=N/A_NO_CODE_AFTER_HOLD
MOCK_TEST=N/A_NO_ENDPOINT_AFTER_HOLD
NUXT_COMMIT=NONE
DOT_COMMIT=NONE
Nuxt mission diff rỗng; HEAD giữ nguyên:
ae694716d5647ce1ed7f3118274645f5d2cf060b
Không tạo commit no-op.
11. Zero-touch production
NUXT_DEPLOY=NOT_RUN
BALO_APPLY_FROM_UI=NOT_RUN
DOT_APPLY=NOT_RUN
DB_WRITE=NOT_RUN
CONTAINER_RESTART_RELOAD_RECREATE=NOT_RUN
NGINX_CHANGE=NOT_RUN
COMPOSE_CHANGE=NOT_RUN
CRON_CHANGE=NOT_RUN
GITHUB_PUSH=NOT_RUN
WORKFLOW_TRIGGER=NOT_RUN
Container cuối mission:
nuxt_started=2026-07-11T02:50:33.302411141Z restart=0 running=true
executor_started=2026-06-04T08:46:26.827398196Z restart=0 running=true
Hai lệnh đã chạy với DOT đều là --dry-run và --verify; output xác nhận write=0 và fingerprint không đổi.
12. Nợ/gate cần Owner quyết
TD-3B-01: phê duyệt mission riêng xây fixed-purpose Unix-socket bridge.
TD-3B-02: chỉ định chính xác Directus role ID được phép vận hành Balo
(không tự suy luận Administrator hay tac-admin).
TD-3B-03: trong mission bridge, chứng minh singleton 409 và auth/CSRF trước khi quay lại code UI.
OR v7.58 không cần cập nhật: không phát hiện nguyên tắc vận hành tổng quát mới; ba blocker là áp dụng trực tiếp fail-closed/security gate hiện hành. Canonical Balo chưa sửa vì Lệnh 3B dừng trước thay đổi sản phẩm/runtime.
13. Evidence paths
Nuxt source: /opt/incomex/docker/nuxt-repo
DOT source: /opt/incomex/dot/bin/dot-balo-reconcile
Atomic gate: /opt/incomex/dot/bin/dot-pg-atomic-apply
Executor runtime source: /app/main.py trong incomex-agent-api-executor
Report: knowledge/current-state/reports/codex-lenh-3b-balo-sync-button-2026-07-16.md
CHỐT: HOLD. Đã chọn đúng vị trí nút, nhưng không được code/deploy cho tới khi bridge, auth và singleton lock PASS trong gate riêng.